How to implement distributed locks using Redis Nx?

Local locks can only control the synchronous execution of threads in the virtual machine where they are located. Now, to achieve synchronous execution of threads in all virtual machines in a distributed environment, multiple virtual machines need to share a lock. Virtual machines can be deployed in a distributed manner. It can also be deployed in a distributed manner, as shown below:

Virtual machines all seize the same lock. The lock is a separate program that provides locking and unlocking services. Whoever seizes the lock queries the database.

The lock no longer belongs to a certain virtual machine, but is deployed in a distributed manner and shared by multiple virtual machines. This lock is called a distributed lock.

Distributed lock implementation solution

There are many solutions to implement distributed locks, the commonly used ones are as follows:

1. Implement distributed locks based on database

Taking advantage of the uniqueness of the database's primary key or the unique index of the database, multiple threads can insert the same record at the same time, and whoever inserts successfully will grab the lock.

2. Implement lock based on redis

Redis provides distributed lock implementation solutions, such as: SETNX, set nx, redisson, etc.

Take SETNX as an example. The working process of the SETNX command is to set a key that does not exist. If multiple threads set the same key, only one thread will set it successfully, and the thread with successful setting will get the lock.

3. Implement using zookeeper

Zookeeper is a distributed coordination service that mainly solves the problem of synchronization between distributed programs. The structure of zookeeper is similar to a file directory. When multiple threads create a subdirectory (node) to zookeeper, only one will be successfully created. This feature can be used to implement distributed locks. Whoever successfully creates the node will obtain the lock.

Redis NX implements distributed locks

The solution for implementing distributed locks in redis can be found on the redis.cn website at http://www.redis.cn/commands/set.html

Use the command: SET resource-name anystring NX EX max-lock-time to achieve this.

NX: Indicates that the key does not exist before the setting is successful.

EX: Set expiration time

Start three ssh clients here and connect to redis: docker exec -it redis redis-cli

First authenticate: auth redis

Send test commands to three clients at the same time as follows:

Indicates setting the lock001 lock, the value is 001, and the expiration time is 30 seconds.

Plain Text
SET lock001 001 NX EX 30

The command was sent successfully. Observing the three ssh clients, it was found that only one was set successfully, and the other two settings failed. The request with successful setting means that the lock001 lock was grabbed.

How to use Set nx in code to implement distributed locks?

Set nx can be implemented using the API provided by spring-boot-starter-data-redis. Add dependencies:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.6.2</version>
</dependency>

After adding the dependency, inject restTemplate into the bean. Let’s first analyze a piece of pseudo code as follows:

if(缓存中有){

  返回缓存中的数据
}else{

  获取分布式锁
  if(获取锁成功){
       try{
         查询数据库
      }finally{
         释放锁
      }
  }
 
}

1. Obtain distributed lock

Use redisTemplate.opsForValue().setIfAbsent(key,vaue) to obtain the lock.

Consider a question here. When set nx a key/value is successful, does the key (that is, the lock) need to set an expiration time?

If you do not set the expiration time and acquire the lock but do not execute finally, the lock will always exist and other threads will not be able to acquire the lock. Therefore, when executing set nx, you need to specify the expiration time, that is, use the following command.

SET resource-name anystring NX EX max-lock-time

The specific calling method is: redisTemplate.opsForValue().setIfAbsent(K var1, V var2, long var3, TimeUnit var5)

2. How to release the lock

There are two situations for releasing locks: automatic release when the key expires, and manual deletion.

1) The method of automatically releasing the key when it expires

Because the lock has an expiration time set, the key will be automatically released when it expires. However, there is a problem that the key expires before operations such as querying the database are completed. At this time, other threads grab the lock, and finally query the database repeatedly. Repeated business operations.

how to solve this problem?

The expiration time of the key can be set long enough to complete querying the database and setting up cache and other related operations.

If this is done, the efficiency will be lower, and the time value is also difficult to control.

2) Manually delete the lock

If you use manual deletion of the lock, it may conflict with the automatic deletion of the key upon expiration, causing other people's locks to be deleted.

For example: when querying the database and other business operations have not been completed, the key expires. At this time, other threads occupy the lock. When the previous thread executes the business operation of querying the database and other business operations, manually deleting the lock will delete the locks of other threads.

To solve this problem, you can use the method to determine whether the lock is set by yourself before deleting the lock. The pseudo code is as follows:

if(缓存中有){

  返回缓存中的数据
}else{

  获取分布式锁: set lock 01 NX
  if(获取锁成功){
       try{
         查询数据库
      }finally{
         if(redis.call("get","lock")=="01"){
            释放锁: redis.call("del","lock")
         }
         
      }
  }
 
}

Lines 11 to 13 of the above code are non-atomic and will also cause the locks of other threads to be deleted. Check the instructions on the document: http://www.redis.cn/commands/set.html

The above optimization method will avoid the following scenario: the lock (key) obtained by client a has been deleted by the redis server due to the expiration time, but client a still executes the DEL command at this time. Client b has reacquired the lock of the same key after client a set the expiration time, so executing DEL on client a will release the lock added by client b.

An example of an unlocking script would look like the following:

if redis.cal1("get",KEYS[1]) == ARGV[1]
then
    return redis. call("del",KEYS[1])
else
    return 0
end

When calling the setnx command to set the key/value, each thread sets a different value, so that when the thread deletes the lock, it can first query based on the key to determine whether it is the vlaue it set at that time, and if so, delete it.

This entire operation is atomic, and the implementation method is to execute the above Lua script.

Lua is a compact scripting language. In version 2.6, redis supports the execution of Lua scripts to ensure the atomicity of multiple commands.

What is atomicity?

These instructions either all succeed or all fail.

The above is the use of Redis Nx to implement distributed locks. In order to avoid deleting locks set by other threads, redis needs to be used to execute Lua scripts. This is atomic, but there is no inaccuracy in the expiration time value setting. .

Guess you like

Origin blog.csdn.net/zy1992As/article/details/132691398