/
RedisDistributedLock.java
116 lines (99 loc) · 3.74 KB
/
RedisDistributedLock.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package tech.pdai.springboot.redis.jedis.lock.lock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCluster;
import redis.clients.jedis.commands.JedisCommands;
import redis.clients.jedis.params.SetParams;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* @author pdai
*/
@Slf4j
public class RedisDistributedLock {
/**
* lua script for unlock.
*/
private static final String UNLOCK_LUA;
static {
StringBuilder sb = new StringBuilder();
sb.append("if redis.call(\"get\",KEYS[1]) == ARGV[1] ");
sb.append("then ");
sb.append(" return redis.call(\"del\",KEYS[1]) ");
sb.append("else ");
sb.append(" return 0 ");
sb.append("end ");
UNLOCK_LUA = sb.toString();
}
/**
* unique lock flag based on thread local.
*/
private final ThreadLocal<String> lockFlag = new ThreadLocal<>();
private final StringRedisTemplate redisTemplate;
public RedisDistributedLock(StringRedisTemplate redisTemplate) {
this.redisTemplate = redisTemplate;
}
public boolean lock(String key, long expire, int retryTimes, long retryDuration) {
// use JedisCommands instead of setIfAbsense
boolean result = setRedis(key, expire);
// retry if needed
while ((!result) && retryTimes-- > 0) {
try {
log.debug("lock failed, retrying..." + retryTimes);
Thread.sleep(retryDuration);
} catch (Exception e) {
return false;
}
// use JedisCommands instead of setIfAbsense
result = setRedis(key, expire);
}
return result;
}
private boolean setRedis(String key, long expire) {
try {
RedisCallback<String> redisCallback = connection -> {
JedisCommands commands = (JedisCommands) connection.getNativeConnection();
String uuid = UUID.randomUUID().toString(); // change to distribute UUID generation.
lockFlag.set(uuid);
return commands.set(key, uuid, SetParams.setParams().nx().px(expire));
};
String result = redisTemplate.execute(redisCallback);
return !StringUtils.isEmpty(result);
} catch (Exception e) {
log.error("set redis occurred an exception", e);
}
return false;
}
public boolean unlock(String key) {
boolean success = false;
try {
List<String> keys = new ArrayList<>();
keys.add(key);
List<String> args = new ArrayList<>();
args.add(lockFlag.get());
// use lua script
RedisCallback<Long> redisCallback = connection -> {
Object nativeConnection = connection.getNativeConnection();
if (nativeConnection instanceof JedisCluster) { // cluster mode
return (Long) ((JedisCluster) nativeConnection).eval(UNLOCK_LUA, keys, args);
} else if (nativeConnection instanceof Jedis) { // single mode
return (Long) ((Jedis) nativeConnection).eval(UNLOCK_LUA, keys, args);
}
return 0L;
};
Long result = redisTemplate.execute(redisCallback);
success = result != null && result > 0;
} catch (Exception e) {
log.error("release lock occurred an exception", e);
} finally {
if (success) {
lockFlag.remove();
}
}
return success;
}
}