Skip to content

Distributed lock with single redis instance, simple and easy to use for NestJS

License

Notifications You must be signed in to change notification settings

huangang/nestjs-simple-redis-lock

 
 

Repository files navigation

@huangang/nestjs-simple-redis-lock

Distributed lock with single redis instance, simple and easy to use for Nestjs

Installation

npm install @huangang/nestjs-simple-redis-lock

Usage

You must install @liaoliaots/nestjs-redis, and use in Nest. This package use it to access redis:

// app.ts
import { RedisLockModule } from '@huangang/nestjs-simple-redis-lock';
import { RedisModule, RedisManager } from '@liaoliaots/nestjs-redis';

@Module({
  imports: [
    ...
    RedisModule.forRootAsync({ // import RedisModule before RedisLockModule
      imports: [ConfigModule],
      useFactory: (config: ConfigService) => ({
        host: config.get('REDIS_HOST'),
        port: config.get('REDIS_PORT'),
        db: parseInt(config.get('REDIS_DB'), 10),
        password: config.get('REDIS_PASSWORD'),
        keyPrefix: config.get('REDIS_KEY_PREFIX'),
      }),
      inject: [ConfigService],
    }),
   RedisLockModule.registerAsync({
      useFactory: async (redisManager: RedisManager) => {
        return { prefix: ':lock:', client: redisManager.getClient() }
      },
      inject: [RedisManager]
    }), // import RedisLockModule, use default configuration
  ]
})
export class AppModule {}

1. Simple example

import { RedisLockService } from '@huangang/nestjs-simple-redis-lock';

export class FooService {
  constructor(
    protected readonly lockService: RedisLockService, // inject RedisLockService 
  ) {}

  async test1() {
    try {
      /**
       * Get a lock by name
       * Automatically unlock after 1min
       * Try again after 100ms
       * The max times to retry is 36000, about 1h
       */
      await this.lockService.lock('test1');
      // Do somethings
    } finally { // use 'finally' to ensure unlocking
      this.lockService.unlock('test1'); // unlock
      // Or: await this.lockService.unlock('test1'); wait for the unlocking
    }
  }
  
  async test2() {
    /**
     * Automatically unlock after 2min
     * Try again after 50ms if failed
     * The max times to retry is 100
     */
    await this.lockService.lock('test1', 2 * 60 * 1000, 50, 100);
    // Do somethings
    await this.lockService.setTTL('test1', 60000); // Renewal the lock when the program is very time consuming, avoiding automatically unlock
    this.lockService.unlock('test1');
  }
}

2. Example by using decorator

Using @huangang/nestjs-simple-redis-lock by decorator, the locking and unlocking will be very easy. Simple example with constant lock name:

import { RedisLockService, RedisLock } from '@huangang/nestjs-simple-redis-lock';

export class FooService {
  constructor(
    protected readonly lockService: RedisLockService, // inject RedisLockService 
  ) {}

  /**
   * Wrap the method, starting with getting a lock, ending with unlocking
   * The first parameter is lock name
   * By default, automatically unlock after 1min.
   * By default, try again after 100ms if failed
   * By default, the max times to retry is 36000, about 1h
   */
  @RedisLock('test2')
  async test1() {
    // Do somethings
    return 'some values';
  }

  /**
   * Automatically unlock after 2min
   * Try again after 50ms if failed
   * The max times to retry is 100
   */ 
  @RedisLock('test2', 2 * 60 * 1000, 50, 100)
  async test2() {
    // Do somethings
    return 'some values';
  }
}

The first parameter of this decorator is a powerful function. It can use to determinate lock name by many ways. Simple example with dynamic lock name:

import { RedisLockService, RedisLock } from '@huangang/nestjs-simple-redis-lock';

export class FooService {
  lockName = 'test3';

  constructor(
    protected readonly lockService: RedisLockService, // inject RedisLockService 
  ) {}

  /**
   * Determinate lock name from 'this'
   * The first parameter is 'this', so you can access any member in 'this' for create a dynamic lock name.
   */
  @RedisLock((target) => target.lockName)
  async test1() {
    // Do somethings
    return 'some values';
  }

  /**
   * Determinate lock name from the parameters of the method
   * The original parameters also pass to the function, so you can determinate the lock name by the parameters.
   */
  @RedisLock((target, param1, param2) => param1 + param2)
  async test2(param1, param2) {
    // Do somethings
    return 'some values';
  }
}

Configuration

  • Register:*
@Module({
  imports: [
    RedisLockModule.register({
      clientName: 'client_name', // the Redis client name in nestjs-redis, to use specific Redis client. Default to use default client
      prefix: 'my_lock:', // By default, the prefix is 'lock:'
    })
  ]
})

Async register:

@Module({
  imports: [
    RedisLockModule.registerAsync({
          imports: [ConfigModule],
          useFactory: async (config: ConfigService) => ({
            clientName: config.get('REDIS_LOCK_CLIENT_NAME')
          }),
          inject: [ConfigService],
        }),
  ]
})

Debug

Add a environment variable DEBUG=nestjs-simple-redis-lock when start application to check log:

// package.json
{
  "scripts": {
    "start:dev": "DEBUG=nestjs-simple-redis-lock tsc-watch -p tsconfig.build.json --onSuccess \"node dist/main.js\"",
  }
}

About

Distributed lock with single redis instance, simple and easy to use for NestJS

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 97.8%
  • JavaScript 2.2%