How to use Redis Nx to implement distributed locks?

The local lock can only control the synchronous execution of threads in the virtual machine where it resides. Now, to realize the 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 in the following figure:

The virtual machines all grab the same lock. The lock is a separate program that provides locking and unlocking services. Whoever grabs the lock will query the database.

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

Distributed lock implementation scheme

There are many schemes for implementing distributed locks, and the commonly used ones are as follows:

1. Realize distributed lock based on database

Using the characteristics of the uniqueness of the primary key of the database, or the characteristics of the unique index of the database, multiple threads insert the same record at the same time, and whoever inserts successfully will grab the lock.

2. Implement lock based on redis

Redis provides implementations of distributed locks, 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 set thread will get the lock.

3. Implemented 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 the file directory. When multiple threads create a subdirectory (node) to zookeeper, only one will be created successfully. Using this feature, distributed locks can be realized. Whoever successfully creates the node will get the lock.

Redis NX implements distributed locks

The scheme of redis implementing distributed locks can be found on the http://redis.cn website, the address is http://www.redis.cn/commands/set.html

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

NX: indicates that the key is set successfully only if it does not exist.

EX: Set expiration time

Here start three ssh clients and connect to redis: docker exec -it redis redis-cli

First authentication: auth redis

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

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

Plain Text
SET lock001 001 NX EX 30

The command is sent successfully. After observing the three ssh clients, it is found that only one of the settings is successful, and the other two settings fail. The successful request indicates that the lock001 lock has been grabbed.

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

Set nx can be realized by 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 dependencies, inject restTemplate in the bean. Let's first analyze a piece of pseudocode as follows:

if(缓存中有){

  返回缓存中的数据
}else{

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

1. Acquire distributed locks

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

Consider a question here, when set nx a key/value succeeds 1, does this key (that is, the lock) need to set an expiration time?

If the expiration time is not set, when the lock is acquired but the finally is not executed, the lock will always exist, and other threads cannot acquire the lock. Therefore, the expiration time must be specified when executing set nx, that is, the following command is used.

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 the lock: 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 released automatically when it expires, but there will be a problem that the key expires before the operation such as querying the database is completed, and other threads will grab the lock at this time, and finally repeat the query database execution Repeated business operations.

how to solve this problem?

The expiration time of the key can be set longer, which is enough to perform related operations such as querying the database and setting the cache.

If so, the efficiency will be lower, and the time value is not easy to control.

2) Manually delete the lock

If the lock is manually deleted, it may conflict with the automatic deletion of the key when it expires, resulting in the deletion of other people's locks.

For example: when the query database and other business operations have not been executed, the key expires. At this time, other threads occupy the lock. When the previous thread executes the query database and other business operations, manually deleting the lock deletes the locks of other threads.

To solve this problem, you can determine whether it is a lock you set 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, which will also cause the locks of other threads to be deleted. View the instructions on the document: http://www.redis.cn/commands/set.html

The above optimization method will avoid the following scenario: the lock (key) acquired 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. And client b has re-acquired the lock of the same key after the expiration time is set by a, then executing DEL by a will release the lock added by client b.

An example of an unlock script would be similar to 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 check the key to determine whether it is the vlaue it set at that time, and if so, delete it.

This whole operation is atomic, and the way to achieve it is to execute the lua script above.

Lua is a small scripting language. Redis supports the atomicity of multiple commands by executing Lua scripts in version 2.6.

What is atomicity?

These commands 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, it is necessary to use redis to execute Lua scripts. This is atomic, but the value setting of the expiration time is not inaccurate. .

 

Guess you like

Origin blog.csdn.net/Blue92120/article/details/132488681