RedisLock.java
4.46 KB
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package com.uccc.base.springboot.redisLock;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
/**
* Redis Lock:redis锁(注意:必须在事务外使用,否则解锁-事务提交之间,可能被其他线程lock,导致脏读)
*/
public class RedisLock {
private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
RedisTemplate<String, String> redis;
private String[] keys;
private int expireSeconds; // 过期时间
private int retrySeconds; // 重试时间
private long timeValue = 0; // lock设置的时间戳:0 失败;>0 时间戳
private int retryMillis;
private String lockPrefix;
private List<String> lockKeys; // 已加锁的key列表
public RedisLock(RedisTemplate<String, String> redis,int expireSeconds,int retrySeconds,String lockPrefix,int retryMillis,String... keys) {
if(keys==null||keys.length==0)
{
throw new RuntimeException("redisLock keys can't be empty");
}
this.keys = keys;
this.expireSeconds = expireSeconds;
this.retrySeconds = retrySeconds;
this.lockPrefix = lockPrefix;
this.lockKeys = new LinkedList<String>();
this.redis = redis;
this.retryMillis = retryMillis;
}
public RedisLock(String prefix, Object... objs) {
this.keys = new String[objs.length];
for (int i = 0; i < objs.length; i++) {
keys[i] = prefix + objs[i];
}
}
/**
* 排序自旋锁:setnx && expire
*/
public boolean acquire() {
ValueOperations<String, String> operations = redis.opsForValue();
try {
Arrays.sort(keys);
lockKeys.clear();
long now = System.currentTimeMillis();
// 避免死锁:value = 当前时间 + 过期时间 + 1毫秒
this.timeValue = now + expireSeconds * 1000 + 1;
while (true) {
for (String key : keys) {
String lockKey = lockPrefix + key;
if (lockKeys.contains(lockKey))
continue;
boolean result = operations.setIfAbsent(lockKey, String.valueOf(timeValue));
if (result) {
// 使用expire,如果代码运行至此,redis连接崩溃,该key没有设置超时时间 => 死锁
redis.expire(lockKey, expireSeconds, TimeUnit.SECONDS);
lockKeys.add(lockKey);
}
// 解决死锁:判断key对应的value是否超时
else {
String currentValueStr = operations.get(lockKey);
// String currentValueStr = jedis.get(lockKey);
if (currentValueStr != null && System.currentTimeMillis() > Long.valueOf(currentValueStr)) {
// 并发竞争:getset取出旧值再判断一次,避免被其他线程set
String oldValueStr = operations.getAndSet(lockKey, String.valueOf(timeValue));
if (oldValueStr == null || oldValueStr.equals(currentValueStr)) {
lockKeys.add(lockKey);
continue;
}
}
break;
}
}
// 全部锁住
if (lockKeys.size() == keys.length) {
return true;
}
// 超时未成功:释放加的锁
else if (System.currentTimeMillis() - now > retrySeconds * 1000) {
if (lockKeys.size() > 0) {
redis.delete(lockKeys);
lockKeys.clear();
}
return false;
}
// TODO 当前线程将持有jedis一段时间(retrySeconds),如果并发数过大,可能会导致jedis连接数不足
Thread.sleep(retryMillis);
}
} catch (Throwable e) {
logger.warn("获取redis lock异常", e);
if (lockKeys.size() > 0) {
redis.delete(lockKeys);
lockKeys.clear();
}
return false;
}
}
/**
* 解锁:删除 key(从redis线程池未获取到jedis => 解锁失败)
*/
public void release() {
ValueOperations<String, String> operations = redis.opsForValue();
try {
for (String lockKey : lockKeys) {
String valueStr = operations.get(lockKey);
// timeValue不等:可能发生当前线程lock后,业务操作比较耗时,导致超时后被其他线程lock
if (valueStr != null && timeValue == Long.parseLong(valueStr))
redis.delete(lockKey);
}
lockKeys.clear();
} catch (Exception e) {
logger.warn("释放redis lock异常", e);
} finally {
this.timeValue = 0;
}
}
}