1, preface
Regarding Redis distributed locks, I checked a lot of information, and found that many of them have only achieved the most basic functions, but it has not solved the problem that the business logic is not completed when the lock is overtime. Set to 10S (in order to solve the problem of dead locks), but the code execution time may require 30S, and then the Redis server is deleted after 10s. At this time, thread B just apply for lock. The redis server does not exist. After the code is executed, the problem comes, and the A and B threads have obtained the lock and execution business logic at the same time. Enjoy.
In order to solve this problem, this article will be verified with a complete code and test cases. I hope to bring a little help to the friends
2, preparation work
pressure testing tool jmeter
https://pan.baidu.com/share/init?surl=NN0c0tDYQjBTTPA-WTT3yg
extract code: 8F2A
Redis-Desktop-Manager client
https://pan.baidu.com/share/init?surl=NoJtZZZOXsk45aQYtveWbQ
extract code: 9BHF
postman
https://pan.baidu.com/share/init?surl=28sGJk4zxoOknAd-47hE7w
extract code: vfu7
can also be downloaded directly on the official website.
Need to postman because I haven’t found a way to open a multi -window of JMETER, haha
3. Explanation
1, SpringMVC project
2, maven dependence
<!–redis–>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.6.5.RELEASE</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
3, core category
-
distributed lock tool class: DistributedLock
-
Test interface class: PCINFORMATIONSERVIMPL
-
Lock latency Guardian thread class: Postponetask
Four, realize ideas
First test without opening the lock -delay thread. After the time time of the A thread is set to 10s, the business logic time is set to 30s, 10s, and the interface is called to see if the lock can be obtained. Thread security issue
Same as above, while locking, turn on the lock -delay thread, call the interface, and check if you can get the lock.
5. Implementation
1, version 01 code
1)、DistributedLock
package com.cn.pinliang.common.util;
import com.cn.pinliang.common.thread.PostponeTask;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import java.io.Serializable;
import java.util.Collections;
@Component
public class DistributedLock {
@Autowired
private RedisTemplate<Serializable, Object> redisTemplate;
private static final Long RELEASE_SUCCESS = 1L;
private static final String LOCK_SUCCESS = “OK”;
private static final String SET_IF_NOT_EXIST = “NX”;
private static final String SET_WITH_EXPIRE_TIME = “EX”;
// Unlock script (LUA)
private static final String RELEASE_LOCK_SCRIPT = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;
/**
*distributed lock
* @param key
* @param value
*@param Expiretime unit: second
* @return
*/
public boolean lock(String key, String value, long expireTime) {
return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
String result = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return Boolean.TRUE;
}
return Boolean.FALSE;
});
}
/**
*Unlock
* @param key
* @param value
* @return
*/
public Boolean unLock(String key, String value) {
return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(key), Collections.singletonList(value));
if (RELEASE_SUCCESS.equals(result)) {
return Boolean.TRUE;
}
return Boolean.FALSE;
});
}
}
Instructions: Just 2 methods, unlock unlocking, lock the lock using the JEDIS SETNX method, unlock the execution of the LUA script, all atomic operation
2)、PcInformationServiceImpl
public JsonResult add() throws Exception {
String key = “add_information_lock”;
String value = RandomUtil.produceStringAndNumber(10);
long expireTime = 10L;
boolean lock = distributedLock.lock(key, value, expireTime);
String threadName = Thread.currentThread().getName();
if (lock) {
system.out.println (ThreadName+”get locks ………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………….ize
Thread.sleep(30000);
distributedLock.unLock(key, value);
} else {
System.out.println (ThreadName+”unlocked …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………..ize
Return jsonresult.fail (“Unlocked”);
}
return JsonResult.succeed();
}
Instructions: The test class is simple, and the value is randomly generated to ensure that the uniqueness will not unlock the locks held by other clients under timeout.
3). Open the Redis-Desktop-Manager client, refresh the cache, you can see that at this time, there is no KEY of the ADD_INFORMATION_LOCK
4), start jmeter, call the interface test
Set 5 threads to access at the same time, and check the redis, add_information_lock during the 10S timeout.
redis
1-4 requests, no locks are obtained
5th request, get the lock
OK, so far, everything is normal. After testing 10s, A is still performing business logic. See if other threads can get the lock
It can be seen that the operation is successful, indicating that A and B have performed this code that should have been exclusive at the same time, which needs to be optimized.
2, version 02 code
1)、DistributedLock
package com.cn.pinliang.common.util;
import com.cn.pinliang.common.thread.PostponeTask;
import com.google.common.collect.Lists;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import redis.clients.jedis.Jedis;
import java.io.Serializable;
import java.util.Collections;
@Component
public class DistributedLock {
@Autowired
private RedisTemplate<Serializable, Object> redisTemplate;
private static final Long RELEASE_SUCCESS = 1L;
private static final Long POSTPONE_SUCCESS = 1L;
private static final String LOCK_SUCCESS = “OK”;
private static final String SET_IF_NOT_EXIST = “NX”;
private static final String SET_WITH_EXPIRE_TIME = “EX”;
// Unlock script (LUA)
private static final String RELEASE_LOCK_SCRIPT = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘del’, KEYS[1]) else return 0 end”;
// Delay script
private static final String POSTPONE_LOCK_SCRIPT = “if redis.call(‘get’, KEYS[1]) == ARGV[1] then return redis.call(‘expire’, KEYS[1], ARGV[2]) else return ‘0’ end”;
/**
*distributed lock
* @param key
* @param value
*@param Expiretime unit: second
* @return
*/
public boolean lock(String key, String value, long expireTime) {
// Lock
Boolean locked = redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
String result = jedis.set(key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
if (LOCK_SUCCESS.equals(result)) {
return Boolean.TRUE;
}
return Boolean.FALSE;
});
if (locked) {
// Successful locks, start a delay thread to prevent the lock release of the lock due to the lockout of the business logic
PostponeTask postponeTask = new PostponeTask(key, value, expireTime, this);
Thread thread = new Thread(postponeTask);
thread.setDaemon(Boolean.TRUE);
thread.start();
}
return locked;
}
/**
*Unlock
* @param key
* @param value
* @return
*/
public Boolean unLock(String key, String value) {
return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(key), Collections.singletonList(value));
if (RELEASE_SUCCESS.equals(result)) {
return Boolean.TRUE;
}
return Boolean.FALSE;
});
}
/**
*Lock latter
* @param key
* @param value
* @param expireTime
* @return
*/
public Boolean postpone(String key, String value, long expireTime) {
return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
Jedis jedis = (Jedis) redisConnection.getNativeConnection();
Object result = jedis.eval(POSTPONE_LOCK_SCRIPT, Lists.newArrayList(key), Lists.newArrayList(value, String.valueOf(expireTime)));
if (POSTPONE_SUCCESS.equals(result)) {
return Boolean.TRUE;
}
return Boolean.FALSE;
});
}
}
Description: Added latter latter method, Lua script, self -brain replenishment related grammar
2), PCINFORMATIONSERVIMPL does not need to be changed
3)、PostponeTask
package com.cn.pinliang.common.thread;
import com.cn.pinliang.common.util.DistributedLock;
public class PostponeTask implements Runnable {
private String key;
private String value;
private long expireTime;
private boolean isRunning;
private DistributedLock distributedLock;
public PostponeTask() {
}
public PostponeTask(String key, String value, long expireTime, DistributedLock distributedLock) {
this.key = key;
this.value = value;
this.expireTime = expireTime;
this.isRunning = Boolean.TRUE;
this.distributedLock = distributedLock;
}
@Override
public void run() {
Long Waittime = Expiretime*1000*2/3; // How long does the thread wait for
……………………….;
while (isRunning) {
try {
Thread.sleep(waitTime);
if (distributedLock.postpone(key, value, expireTime)) {
} else {
this.stop();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private void stop() {
this.isRunning = Boolean.FALSE;
}
}
Instructions: At the same time, call the lock, immediately turn on the postponetask thread. After the thread waits for 2/3 time, start to execute the extension of the passing code. After the implementation, in the process, other threads cannot get the lock, which ensures that thread security
The test results below
After
10s, check the redis server, add_information_lock still exists, indicating that the delay is successful
At this time, use postman to ask again, and find that you can’t get the lock
Look at the console printing
The
A thread gets the lock at 19:09:11, and delay the delay after 10 * 2 /3 = 6s.
A thread is executed, the lock is released, and other threads can compete in the lock
OK, so far, the problem of locking conflict that is still executed by the lockout time and the business logic is still very simple. The most rigorous way is to implement the official Redlock algorithm. They are renewed at the time of timeout to ensure that the business logic is not executed.