当前位置:网站首页>Redis thread level reentrant distributed lock (different unique IDs can be locked cyclically)
Redis thread level reentrant distributed lock (different unique IDs can be locked cyclically)
2022-06-25 20:05:00 【gwddszrrjpdr】
Examples of scenarios
Because the real scenario involves specific business , Here I will give an inappropriate example to roughly compare the problems I encountered .
such as “ I go home every day - Turn on all smart devices in your home ” This matter , It can be divided into two scenarios , One is that I go home from work , Then turn on the water heater 、 Curtain and other intelligent devices ; One is that I go out on business , Go home from somewhere else and you need to turn on all the devices . The trigger action of these two scenarios is to go home , But the realization of going home is different , In our old code , These two scenarios are written as two interfaces . The problem is , If I add a new smart device in my family Air conditioner , I will add... To both interfaces at the same time “ Turn on the air conditioning ” This code , Business scenarios are increasing 、 New students are not familiar with the code , This code forgot to add , Over the years, it has led to the actions that should have been done at home , The two interfaces make a difference , Maybe I turned on the air conditioner when I came home from the company , I didn't turn on the air conditioner when I came home from outside orz.
So I recently made an optimization , Converge these two interfaces , Because there are many upstream calling systems , The calling posture of the interface cannot be changed , Then we can only take the internal action Unite , So I put “ Go home from outside ” The internal implementation in the interface is directly changed to call “ Go home from the company ” The implementation of the . It looks very simple , Just evaluate the validation logic of the input parameter and its compatibility 、 Business scenario coverage ( Although it is not so simple to do ) You can send it to the test environment . That's what I thought .
Problem generation
The implementation logic of these two interfaces opens the database transaction , As long as the propagation feature is configured , That's not a problem . The problem is that these two interfaces avoid concurrency , Also used redis Distributed lock , such as “ Turn on the smart device ” This matter , To prevent my parents and I from operating at the same time , Locks are also required , For the same device , For example, the air conditioner in the living room , Just lock it as the only sign for my family ( The only equipment in the world 69 code or “ Living room air conditioning ”) Can tell my parents that I am using this resource .
problem 1: Both methods are right 69 Code locking , Method 1 Transfer in method 2 after , Find the same 69 The code is locked , Code can't go down .
solve the problem
Because the same resource is locked and the lock is not reentrant . Digression , Because our code Spring and redis Lower version , It is not possible to set the value and expiration time at the same time , therefore redis All locks pass through StringRedisTemplate + lua Script to implement non reentrant distributed locks . The operations of throwing exceptions and logging are omitted here .
// Business entrance , Specific business realization needs to be realized Action Of execute()
public static <T> T doAction(Action<T> action, StringRedisTemplate redisTemplate, String key,String value, int timeout) {
RedisLock redisLock = new RedisLock(redisTemplate, key, value, timeout, TimeUnit.SECONDS);
try {
if (redisLock.tryLockWithLua()) {
return action.execute();
} else {
// Throw exceptions , this 69 The code has been locked ;
}
} finally {
if (redisLock.isLock()) {
redisLock.unLockWithLua();
}
}
}
// Specific locking implementation
public boolean tryLockWithLua() {
RedisCallback<String> callback = new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
return ((Jedis) nativeConnection).eval( Lock lua Script , 1, key, value, expireTimeStr).toString();
}
};
String success = redisTemplate.execute(callback);
if (SUCCESS.equals(success)) {
isLock = true;
return true;
}
return false;
}
// Specific unlocking implementation
public void unLockWithLua() {
if (isLock) {
RedisCallback<String> callback = new RedisCallback<String>() {
@Override
public String doInRedis(RedisConnection connection) throws DataAccessException {
Object nativeConnection = connection.getNativeConnection();
return ((Jedis) nativeConnection).eval( Unlock lua Script , 1, key, value).toString();
}
};
String success = redisTemplate.execute(callback);
if (SUCCESS.equals(success)) {
isLock = false;
}
}
}
//lua Script
Lock lua Script = "if redis.call('setNx',KEYS[1],ARGV[1])==1 then\n" +
"return redis.call('expire',KEYS[1],ARGV[2])\n" +
"else\n" +
"return 0\n" +
"end\n";
Unlock lua Script = "if redis.call('get', KEYS[1]) == ARGV[1] " +
"then return redis.call('del', KEYS[1]) else return 0 end";
Then according to the existing locking code , Write reentrant distributed locks . I read this classmate's article first https://blog.csdn.net/u014634309/article/details/106755403,hash The method must not work , Because we want to control that the current thread can re-enter the lock , That's all ThreadLocal You can choose , The student's article is also very clear ,ThreadLocal The possible problem with implementation is the expiration time , It is impossible to determine whether the data stored in your current memory is redis Is it out of date , therefore ThreadLocal Put only key and count Definitely not enough . So let's expand one content, because value Time stamp information can be carried in , Then put value Also put in , Before locking, I judge that there is value and redis The same in key Pick up to value equally , You can judge that it has not expired , So I wrote . edition 1:
private static final ThreadLocal<ThreadLocalReentrantLockContent> threadLocal = new ThreadLocal<>();
@Data
@Builder
private static class ThreadLocalReentrantLockContent{
private String key;
private String value;
private Integer count;
}
public static <T> T doActionUseReentrantLock(Action<T> action, StringRedisTemplate redisTemplate, String key, String value, int timeout) {
RedisLock redisLock = new RedisLock(redisTemplate, key, value, timeout, TimeUnit.SECONDS);
// Get the lock held by the current thread
ThreadLocalReentrantLockContent content = threadLocal.get();
try {
if (content == null) {
content = ThreadLocalReentrantLockContent.builder().key("").value("").count(0).build();
threadLocal.set(content);
}
// Reentrant
//1. content It needs to be preserved in the library key And judge the incoming key Same as in memory , Compatible with a thread lock first 69 code , Lock after handling “ Living room air conditioning ” The situation of
//2. In the memory value and redis In the same way Ensure that the lock held by the current thread does not time out (value It carries time information )
//3. Judge the timeout time , Can don't
if (StringUtils.isNotBlank(content.getKey())
&& content.getKey().equals(key)
&& StringUtils.isNotBlank(content.getValue())
&& content.getValue().equals(redisTemplate.opsForValue().get(key))
&& redisTemplate.getExpire(key) > 0) {
content.count++;
} else {
// Lock
if (redisLock.tryLockWithLua()) {
content.setKey(key);
content.setValue(value);
content.setCount(1);
} else {
// Throw exceptions , this key Locked
}
}
// perform
return action.execute();
} finally {
content = threadLocal.get();
if (null != content) {
// Release the lock
content.count--;
if (redisLock.isLock() && 0 == content.getCount()) {
threadLocal.remove();
redisLock.unLockWithLua();
}
}
}
}
problem 2: So here comes the question , This posture can only achieve the first lock 69 code , Unlock this after processing key Then lock it “ Living room air conditioning ” The situation of , If the code is written abnormally in the future , Existence needs to be locked first 69 code , The next operation is direct locking “ Living room air conditioning ” The situation of , Then the code is incompatible . I discussed with my partner , Using nesting , stay content One inside content, Used to save the context of the previous lock , Is it possible to achieve , So we did . edition 2:
@Data
@Builder
private static class ThreadLocalReentrantLockContent{
private String key;
private String value;
private Integer count;
/**
* Used for front and rear locking key A different situation , Keep the previous content
*/
private ThreadLocalReentrantLockContent lastContent;
}
public static <T> T doActionUseReentrantLock(Action<T> action, StringRedisTemplate redisTemplate, String key, String value, int timeout) {
RedisLock redisLock = new RedisLock(redisTemplate, key, value, timeout, TimeUnit.SECONDS);
// Get the lock held by the current thread
ThreadLocalReentrantLockContent content = threadLocal.get();
try {
// Reentrant
if (content != null
&& content.getKey().equals(key)
&& content.getValue().equals(redisTemplate.opsForValue().get(key))
&& redisTemplate.getExpire(key) > 0) {
content.count++;
} else {
// Lock
if (redisLock.tryLockWithLua()) {
ThreadLocalReentrantLockContent newContent = ThreadLocalReentrantLockContent.builder().key(key).value(value).count(1).build();
newContent.setLastContent(content);
threadLocal.set(newContent);
} else {
// Throw exceptions , Locked
}
}
// perform
return action.execute();
} finally {
content = threadLocal.get();
if (null != content) {
// Release the lock
content.count--;
if (redisLock.isLock() && 0 == content.getCount()) {
content = content.lastContent;
if (content == null) {
// Currently, this is the outermost layer of locking logic
threadLocal.remove();
} else {
threadLocal.set(content);
}
redisLock.unLockWithLua();
}
}
}
}
It looks perfect , But in debug I found a problem , I am re entering the number of times ++ And reentry times – When , All you get is ThreadLocal The latest in content, If it is locked first 69 code , Relock “ Living room air conditioning ”, Relock 69 code What about this situation ? The current code will report an error in the third stage , He didn't find 69 Code lock and put count++, But try to give 69 Code locking , This time will certainly be abnormal .
that , This complex and deformed situation must be recursive . Although this scenario should not happen in my career , But it's all written here , Just finish it . So I tried again . edition 3:
/**
* Reentrant lock
* 1. Can be realized key Different locking logic For example, lock first 69 Code and lock again “ Living room air conditioning ”
* 2. Can be realized key Same lock reentry For example, lock first 69 Code and lock again 69 code
* 3. Multiple cycle locking can be realized For example, lock first 69 code , Relock “ Living room air conditioning ”, Relock 69 code
*
* @param Action
* @param redisTemplate
* @param key
* @param value
* @param timeout
* @param <T>
* @return
*/
public static <T> T doActionUseReentrantLock(Action<T> action, StringRedisTemplate redisTemplate, String key, String value, int timeout) {
RedisLock redisLock = new RedisLock(redisTemplate, key, value, timeout, TimeUnit.SECONDS);
//threadLocal get To the latest content, It should be stored temporarily , Because we have to put count The update of is put into memory
ThreadLocalReentrantLockContent latestContent = threadLocal.get();
try {
// Reentrant ,curContent For the current key The use of content
ThreadLocalReentrantLockContent curContent = checkAndGetReentrant(latestContent, key, redisTemplate.opsForValue().get(key));
if (null != curContent) {
curContent.count++;
// Reset threadLocal Medium count
threadLocal.set(latestContent);
} else {
// Lock
if (redisLock.tryLockWithLua()) {
ThreadLocalReentrantLockContent newContent = ThreadLocalReentrantLockContent.builder().key(key).value(value).count(1).build();
newContent.setLastContent(latestContent);
threadLocal.set(newContent);
} else {
// Throw exceptions , Locked
}
}
// perform
return action.execute();
} finally {
// If there is a lock 69 code -“ Living room air conditioning ”-69 code The situation of , The following two content inequality
latestContent = threadLocal.get();
ThreadLocalReentrantLockContent curContent = checkAndGetReentrant(latestContent, key, redisTemplate.opsForValue().get(key));
if (null != curContent) {
// Release the lock
curContent.count--;
threadLocal.set(latestContent);
if (redisLock.isLock() && 0 == curContent.getCount()) {
if (curContent.lastContent == null) {
// Currently, this is the outermost layer of locking logic
threadLocal.remove();
} else {
threadLocal.set(curContent.lastContent);
}
redisLock.unLockWithLua();
}
}
}
}
/**
* Recursively check for reentrancy , Compatible with lock first 69 code Relock “ Living room air conditioning ” Relock 69 code … The logic of
*
* @param content
* @param curKey
* @param redisValue
* @return Used in the current round content
*/
private static ThreadLocalReentrantLockContent checkAndGetReentrant(ThreadLocalReentrantLockContent content, String curKey, String redisValue) {
// First lock
if (null == content) {
return null;
}
// And last time key Same reentry
if (content.getKey().equals(curKey)
&& content.getValue().equals(redisValue)) {
return content;
} else {
// Recursive reentry
if (null != content.getLastContent()) {
content = content.getLastContent();
return checkAndGetReentrant(content, curKey, redisValue);
} else {
// Non reentrant locking
return null;
}
}
}
complete ! This set of code can realize various postures , Different and unique id The situation of locking in a different way . If the actual situation is not so complicated , So the version 1 You can fully realize your needs !
边栏推荐
- Jsonp non homologous interaction (click trigger)
- Determine whether it is a web page opened on wechat
- 在打新債開戶證券安全嗎?低傭金靠譜嗎
- 2.5 find the sum of the first n terms of the square root sequence
- mysql load data infile
- PAT B1076
- How do I delete an entity from symfony2
- 打新债网上开户安全吗,需要注意什么
- <C>. Figure guessing game
- Mail monitoring cloud script execution progress
猜你喜欢
Applet wx Request encapsulation
Suddenly found that the screen adjustment button can not be used and the brightness can not be adjusted
Print 1 cute every 100 milliseconds ~ with a running lantern effect
Single chip microcomputer line selection method to store impression (address range) method + Example
Applet canvas generate sharing Poster
Profile path and name
Redis practice: smart use of data types to achieve 100 million level data statistics
The native JS mobile phone sends SMS cases. After clicking the button, the mobile phone number verification code is sent. The button needs to be disabled and re enabled after 60 seconds
II Traits (extractors)
mysql load data infile
随机推荐
2.16([Usaco2005 Nov]Ant Counting)
Appearance of object attributes
PHP little knowledge record
2.3 partial sum of square and reciprocal sequences
Hdoj topic 2005 day
Vulnhub range - the planes:venus
Bloom filter
PAT B1086
PAT B1063
Some pictures of real machine preview development and debugging are not shown
Huawei fast application access advertising service development guide
2.6 finding the sum of the first n terms of factorial sequence
Profile path and name
How to understand var = a = b = C = 9? How to pre parse?
Arduino read temperature
Applet wx Request encapsulation
Force wechat page font size to be 100%
Does redis transaction support acid?
From now on, I will blog my code
PAT B1064