当前位置:网站首页>Jedislock redis distributed lock implementation
Jedislock redis distributed lock implementation
2022-06-11 10:21:00 【qq_ twenty-eight million nine hundred and seventeen thousand fo】
from :https://www.cnblogs.com/0201zcr/p/5942748.html
One 、 Several conditions for using distributed lock :
- The system is a distributed system ( The key is distributed , Stand alone can use ReentrantLock perhaps synchronized Code blocks to implement )
- Shared resources ( Each system accesses the same resource , The carrier of resources may be traditional relational database or NoSQL)
- Synchronous access ( That is, there are many process colleagues accessing the same shared resource . No synchronous access , Who cares if you compete for resources or not )
Two 、 Examples of application scenarios
Manage the deployment architecture of the background ( More than one tomcat The server +redis【 More than one tomcat The server accesses a redis】+mysql【 More than one tomcat The server accesses... On a server mysql】) The conditions for using distributed locks are met . Multiple servers need to access redis Globally cached resources , If you do not use distributed locks, you will have problems . Look at the following pseudo code :
long N=0L;
//N from redis Get value
if(N<5){
N++;
//N Write back to redis
}The above code mainly realizes the functions :
from redis Get value N, For the value N Carry out boundary checks , Self adding 1, then N Write back to redis in . This kind of application scenario is very common , Like a second kill , Global incremental ID、IP Access restrictions, etc . With IP In terms of access restrictions , A malicious attacker may initiate unlimited access , The amount of concurrency is relatively large , In a distributed environment N The boundary check is unreliable , Because from redis Read N It may already be dirty data . The traditional locking method ( Such as java Of synchronized and Lock) It's no use , Because this is a distributed environment , The firefighters of this synchronization problem are also helpless . In this critical autumn , Distributed locks are finally in use .
Distributed locks can be implemented in many ways , such as zookeeper、redis.... Either way , His The basic principle It is the same. : Use a state value to represent a lock , The occupation and release of locks are identified by status values .
Here we mainly talk about how to use redis Implement distributed locks .
3、 ... and 、 Use redis Of setNX Command to implement distributed locks
1、 Implementation principle
Redis Single process single thread mode , Using queue mode to change concurrent access into serial access , And multi client pairs Redis There's no competition for the connection .redis Of SETNX Command can easily realize distributed locking .
2、 Basic command parsing
1)setNX(SET if Not eXists)
grammar :
SETNX key value
take key The value of the set value , If and only if key non-existent .
If a given key Already exist , be SETNX Don't do anything .
SETNX yes 『SET if Not eXists』( If it doesn't exist , be SET) Abbreviation
Return value :
Set up the success , return 1 .
Setup failed , return 0 .
Example :
redis> EXISTS job # job non-existent (integer) 0 redis> SETNX job "programmer" # job Set up the success (integer) 1 redis> SETNX job "code-farmer" # Try to override job , Failure (integer) 0 redis> GET job # Not covered "programmer"
So we use to execute the following command
SETNX lock.foo <current Unix time + lock timeout + 1>
Such as return 1, Then the client gets the lock , hold lock.foo The key value of is set to the time value to indicate that the key has been locked , Finally, the client can use DEL lock.foo To release the lock .
Such as return 0, Indicates that the lock has been acquired by another client , At this time, we can go back or try again, wait for the other party to complete or wait for the lock to time out .
2)getSET
grammar :
GETSET key value
Will be given key The value of the set value , And back to key The old value (old value).
When key When it exists but is not a string type , Return an error .
Return value :
Return to a given key The old value .
When key When there is no old value , That is to say , key When there is no , return nil .
3)get
grammar :
GET key
Return value :
When key When there is no , return nil , otherwise , return key Value .
If key Not a string type , So return an error
Four 、 Solve the deadlock
There's a problem with the lock logic above : If a client holding a lock fails or crashes, the lock cannot be released , How to solve ?
We can judge whether this happens by the time stamp corresponding to the key of the lock , If the current time is greater than lock.foo Value , Indicates that the lock has failed , It can be reused .
When this happens , You can't just go through DEL To delete the lock , And then again SETNX once ( Be reasonable , The operation of deleting a lock should be performed by the lock owner , Just wait for it to time out ), When multiple clients detect a lock timeout, they will try to release it , There may be a race condition here , Let's simulate the scene :
C0 The operation timed out , But it still holds the lock ,C1 and C2 Read lock.foo Check the timestamp , I've found that it's time-out . C1 send out DEL lock.foo C1 send out SETNX lock.foo And succeeded in . C2 send out DEL lock.foo C2 send out SETNX lock.foo And succeeded in . thus ,C1,C2 They all got the locks ! It's a big problem !
Fortunately, this problem can be avoided , Let's see C3 How does this client do it :
C3 send out SETNX lock.foo Want to get the lock , because C0 And hold the lock , therefore Redis Return to C3 One 0 C3 send out GET lock.foo To check if the lock timed out , If it doesn't time out , Wait or try again . conversely , If it has timed out ,C3 Try to get the lock by doing the following : GETSET lock.foo <current Unix time + lock timeout + 1> adopt GETSET,C3 If the time stamp you get is still time-out , That explains. ,C3 I got the lock . If in C3 Before , There's a man named C4 It's better than C3 Quick step to perform the above operation , that C3 The timestamp you get is a value that doesn't time out , At this time ,C3 Didn't get the lock as scheduled , Need to wait or try again . Pay attention , Even though C3 I didn't get the lock , But it rewrites C4 Set the timeout value of the lock , But the impact of this very small error can be ignored .
Be careful : In order to make the distributed lock algorithm more stable , The client holding the lock should check again whether its lock has timed out before unlocking , Do it again DEL operation , Because it is possible that the client is suspended due to a time-consuming operation , At the end of the operation, the lock has been acquired by someone else due to timeout , There's no need to unlock .
5、 ... and 、 Code implementation
expireMsecs Lock holding timeout , Prevent the thread from entering the lock , Unlimited execution , So that the lock cannot be released
timeoutMsecs Lock wait timeout , Prevent thread starvation , There is never a chance to lock in and execute code
Be careful : The project needs to be built first redis Related configuration of
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis distributed lock implementation.
*
* @author zhengcanrui
*/
public class RedisLock {
private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
private RedisTemplate redisTemplate;
private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
/**
* Lock key path.
*/
private String lockKey;
/**
* Lock timeout , Prevent the thread from entering the lock , Infinite execution waiting
*/
private int expireMsecs = 60 * 1000;
/**
* Lock wait time , Prevent thread starvation
*/
private int timeoutMsecs = 10 * 1000;
private volatile boolean locked = false;
/**
* Detailed constructor with default acquire timeout 10000 msecs and lock expiration of 60000 msecs.
*
* @param lockKey lock key (ex. account:1, ...)
*/
public RedisLock(RedisTemplate redisTemplate, String lockKey) {
this.redisTemplate = redisTemplate;
this.lockKey = lockKey + "_lock";
}
/**
* Detailed constructor with default lock expiration of 60000 msecs.
*
*/
public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs) {
this(redisTemplate, lockKey);
this.timeoutMsecs = timeoutMsecs;
}
/**
* Detailed constructor.
*
*/
public RedisLock(RedisTemplate redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
this(redisTemplate, lockKey, timeoutMsecs);
this.expireMsecs = expireMsecs;
}
/**
* @return lock key
*/
public String getLockKey() {
return lockKey;
}
private String get(final String key) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] data = connection.get(serializer.serialize(key));
connection.close();
if (data == null) {
return null;
}
return serializer.deserialize(data);
}
});
} catch (Exception e) {
logger.error("get redis error, key : {}", key);
}
return obj != null ? obj.toString() : null;
}
private boolean setNX(final String key, final String value) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
connection.close();
return success;
}
});
} catch (Exception e) {
logger.error("setNX redis error, key : {}", key);
}
return obj != null ? (Boolean) obj : false;
}
private String getSet(final String key, final String value) {
Object obj = null;
try {
obj = redisTemplate.execute(new RedisCallback<Object>() {
@Override
public Object doInRedis(RedisConnection connection) throws DataAccessException {
StringRedisSerializer serializer = new StringRedisSerializer();
byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
connection.close();
return serializer.deserialize(ret);
}
});
} catch (Exception e) {
logger.error("setNX redis error, key : {}", key);
}
return obj != null ? (String) obj : null;
}
/**
* get lock.
* Realize the idea : Mainly used redis Of setnx command , Cache lock .
* reids The cache key It's locked key, All the sharing , value It's the expiration time of the lock ( Be careful : Let's put the expiration date here value 了 , There's no time to set its timeout )
* Execution process :
* 1. adopt setnx Try to set something up key Value , success ( There is no such lock at present ) Then return to , Successful lock acquisition
* 2. Get the expiration time of the lock if the lock already exists , Compare it to the current time , If the timeout , Then set the new value
*
* @return true if lock is acquired, false acquire timeouted
* @throws InterruptedException in case of thread interruption
*/
public synchronized boolean lock() throws InterruptedException {
int timeout = timeoutMsecs;
while (timeout >= 0) {
long expires = System.currentTimeMillis() + expireMsecs + 1;
String expiresStr = String.valueOf(expires); // Lock expiration time
if (this.setNX(lockKey, expiresStr)) {
// lock acquired
locked = true;
return true;
}
String currentValueStr = this.get(lockKey); //redis Time in
if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
// Determine whether it is null , Not empty , If the value is set by another thread , Then the second condition is not to pass
// lock is expired
String oldValueStr = this.getSet(lockKey, expiresStr);
// Get last lock expiration time , And set the current lock expiration time ,
// Only one thread can get the setting time of the previous line , because jedis.getSet It's synchronous
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
// Prevent false deletion ( Cover , because key It's the same ) Someone else's lock —— It doesn't work here , Here the value will be overridden , But because there is little time difference , So it's acceptable
//[ In the case of distributed ]: If after this time , Multiple threads happen to be here , But only one thread has the same set value as the current one , He has the right to a lock
// lock acquired
locked = true;
return true;
}
}
timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
/*
Delay 100 millisecond , It might be better to use random time here , Can prevent the emergence of hunger process , namely , When multiple processes arrive at the same time ,
Only one process gets the lock , The others are trying on the same frequency , There's some going on in the back , Apply for locks at the same frequency , This may result in the front lock not being satisfied .
Using random waiting time can guarantee fairness to some extent
*/
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}
return false;
}
/**
* Acqurired lock release.
*/
public synchronized void unlock() {
if (locked) {
redisTemplate.delete(lockKey);
locked = false;
}
}
}call :
RedisLock lock = new RedisLock(redisTemplate, key, 10000, 20000);
try {
if(lock.lock()) {
// Code that needs to be locked
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
// In order to make the distributed lock algorithm more stable , The client holding the lock should check again whether its lock has timed out before unlocking , Do it again DEL operation , Because it is possible that the client is suspended due to a time-consuming operation ,
// At the end of the operation, the lock has been acquired by someone else due to timeout , There's no need to unlock . ———— It's not done here
lock.unlock();
}6、 ... and 、 Some of the problems
1、 Why not use it directly expire Set timeout , The number of milliseconds of time is taken as value Put it in redis in ?
In the following way , Give the overtime to redis Handle :
lock(key, expireSec){
isSuccess = setnx key
if (isSuccess)
expire key expireSec
}There seems to be no problem in this way , But if setnx after ,redis collapsed ,expire There is no implementation , The result is a deadlock . The lock will never time out .
2、 Why the front lock has timed out , And use it getSet Set the time of the new timestamp to get the old value , Then compare it with the timestamp used to determine the timeout time ?

Because it is a distributed environment , When the previous lock fails , There are two processes that enter the judgment of lock timeout . Such as :
C0 It's overtime , And hold the lock ,C1/C2 At the same time, the request enters the method
C1/C2 Got it C0 Timeout for
C1 Use getSet Method
C2 It also carried out getSet Method
If we don't add oldValueStr.equals(currentValueStr) The judgment of the , will C1/C2 Will be locked , After adding , Can guarantee C1 and C2 Only one can get the lock , One can only wait .
Be careful : This may cause the timeout time to be different from its original timeout time ,C1 The timeout of may be C2 covers , But the millisecond difference between them is extremely small , It's ignored here .
边栏推荐
- RSA signature issues
- Introduction to ZigBee module wireless transmission star topology networking structure
- ZigBee模块通信协议的树形拓扑组网结构
- [Bert]: Calculation of last ave state when training tasks with similar Bert semantics
- Mysql--索引
- 知识点滴 - 性格分析-四类法
- B站到底能不能赚到钱?
- puppeteer入门之 BrowserFetcher 类
- 電子設備輻射EMC整改案例
- BCGControlBar库专业版,完整记录的MFC扩展类
猜你喜欢

What hydraulic oil is used for Denison hydraulic pump? What are the requirements

Wuenda machine learning course - week 7

Technology cloud report: privacy computing under the wave of Web3.0

帝国CMS仿《手艺活》DIY手工制作网源码/92kaifa仿手艺活自适应手机版模板

How much do you know about software compatibility testing? How to select a software compatibility testing organization?

With determination to forge ahead, JASMINER continues to deepen its brand strength

Mysql--事务

选择DC-DC开关电源控制器的实战过程

科技云报道:Web3.0浪潮下的隐私计算

Internet of things security in the era of remote work
随机推荐
接口调优的大致思路
Start jar
What is the SOA or ASO of MOSFET?
IPhone 15 forced to use type-C interface
Secret behind the chart | explanation of technical indicators: tangqi'an channel
Cas de rectification du CEM rayonné par des équipements électroniques
Initial deployment of Servlet
科技云报道:Web3.0浪潮下的隐私计算
RSA signature issues
【bert】:在训练bert 语义相似的任务时,last ave state 的计算
吴恩达机器学习课程-第七周
浅谈wandfluh比例阀的功能与作用
Interview review - closure
Differences between beanfactorypostprocessor and beanpostprocessor
Some code fragments of a universal and confession wall system developed by PHP
MySQL basic learning notes 03
有哪些ABAP关键字和语法,到了ABAP云环境上就没办法用了?
Override and reload?
puppeteer入门之 Browser 类
[Bert]: Calculation of last ave state when training tasks with similar Bert semantics