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. .