Redis分布式锁

用 redis 实现分布式锁思路:

setnx命令,作用是当key存在时不设置。

指定一个分布式锁的key,如“synclock”。

线程获取锁的操作就是执行setnx key value命令,key=synclock,value=这个key的超时时间(当前时间+指定毫秒数)和当前线程的某个标识(若在一个线程中要重复获取锁时会用上)

若set成功,说明成功获取锁,返回。

若set失败,则get synclock,查看此时synclock的超时时间是否已经超时,或者是否为当前线程已经得到的锁,如果超时或是当前线程的锁,则重新设置超时时间,获取锁返回。

其余情况说明未获取到锁,sleep几秒后再次尝试获取,在指定时间内一直未获取到锁则放弃。

代码示例:

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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
public class RedisLock {

private static final Lock NO_LOCK = new Lock(new UUID(0l,0l), 0l);

private static final int ONE_SECOND = 1000;

public static final int DEFAULT_EXPIRY_TIME_MILLIS = Integer.getInteger("com.github.jedis.lock.expiry.millis", 60 * ONE_SECOND);
public static final int DEFAULT_ACQUIRE_TIMEOUT_MILLIS = Integer.getInteger("com.github.jedis.lock.acquiry.millis", 10 * ONE_SECOND);
public static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = Integer.getInteger("com.github.jedis.lock.acquiry.resolution.millis", 100);

private final RedisClient jedis; // 自己封装StringRedisTemplate CRUD方法的类

private final String lockKeyPath;

private final int lockExpiryInMillis;
private final int acquiryTimeoutInMillis;
private final UUID lockUUID;

private Lock lock = null;

protected static class Lock {
private UUID uuid;
private long expiryTime;

protected Lock(UUID uuid, long expiryTimeInMillis) {
this.uuid = uuid;
this.expiryTime = expiryTimeInMillis;
}

protected static Lock fromString(String text) {
try {
String[] parts = text.split(":");
UUID theUUID = UUID.fromString(parts[0]);
long theTime = Long.parseLong(parts[1]);
return new Lock(theUUID, theTime);
} catch (Exception any) {
return NO_LOCK;
}
}

public UUID getUUID() {
return uuid;
}

public long getExpiryTime() {
return expiryTime;
}

@Override
public String toString() {
return uuid.toString()+":"+expiryTime;
}

boolean isExpired() {
return getExpiryTime() < System.currentTimeMillis();
}

boolean isExpiredOrMine(UUID otherUUID) {
return this.isExpired() || this.getUUID().equals(otherUUID);
}
}

/**
* Detailed constructor with default acquire timeout 10000 msecs and lock
* expiration of 60000 msecs.
*
* @param jedis
* @param lockKey
* lock key (ex. account:1, ...)
*/
public RedisLock(RedisClient jedis, String lockKey) {
this(jedis, lockKey, DEFAULT_ACQUIRE_TIMEOUT_MILLIS, DEFAULT_EXPIRY_TIME_MILLIS);
}

/**
* Detailed constructor with default lock expiration of 60000 msecs.
*
* @param jedis
* @param lockKey
* lock key (ex. account:1, ...)
* @param acquireTimeoutMillis
* acquire timeout in miliseconds (default: 10000 msecs)
*/
public RedisLock(RedisClient jedis, String lockKey, int acquireTimeoutMillis) {
this(jedis, lockKey, acquireTimeoutMillis, DEFAULT_EXPIRY_TIME_MILLIS);
}

/**
* Detailed constructor.
*
* @param jedis
* @param lockKey
* lock key (ex. account:1, ...)
* @param acquireTimeoutMillis
* acquire timeout in miliseconds (default: 10000 msecs)
* @param expiryTimeMillis
* lock expiration in miliseconds (default: 60000 msecs)
*/
public RedisLock(RedisClient jedis, String lockKey, int acquireTimeoutMillis, int expiryTimeMillis) {
this(jedis, lockKey, acquireTimeoutMillis, expiryTimeMillis, UUID.randomUUID());
}

/**
* Detailed constructor.
*
* @param jedis
* @param lockKey
* lock key (ex. account:1, ...)
* @param acquireTimeoutMillis
* acquire timeout in miliseconds (default: 10000 msecs)
* @param expiryTimeMillis
* lock expiration in miliseconds (default: 60000 msecs)
* @param uuid
* unique identification of this lock
*/
public RedisLock(RedisClient jedis, String lockKey, int acquireTimeoutMillis, int expiryTimeMillis, UUID uuid) {
this.jedis = jedis;
this.lockKeyPath = lockKey;
this.acquiryTimeoutInMillis = acquireTimeoutMillis;
this.lockExpiryInMillis = expiryTimeMillis+1;
this.lockUUID = uuid;;
}

/**
* @return lock uuid
*/
public UUID getLockUUID() {
return lockUUID;
}

/**
* @return lock key path
*/
public String getLockKeyPath() {
return lockKeyPath;
}

/**
* Acquire lock.
*
* @return true if lock is acquired, false acquire timeouted
* @throws InterruptedException
* in case of thread interruption
*/
public synchronized boolean acquire() throws InterruptedException {
return acquire(jedis);
}

/**
* Acquire lock.
*
* @param jedis
* @return true if lock is acquired, false acquire timeouted
* @throws InterruptedException
* in case of thread interruption
*/
protected synchronized boolean acquire(RedisClient jedis) throws InterruptedException {
int timeout = acquiryTimeoutInMillis;
while (timeout >= 0) {

final Lock newLock = asLock(System.currentTimeMillis() + lockExpiryInMillis);
if (jedis.setIfNotExist(lockKeyPath, newLock.toString())) {
this.lock = newLock;
return true;
}

final String currentValueStr = jedis.getKeyString(lockKeyPath);
final Lock currentLock = Lock.fromString(currentValueStr);
if (currentLock.isExpiredOrMine(lockUUID)) {
String oldValueStr = jedis.getKeyStringAndSet(lockKeyPath, newLock.toString());
if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
this.lock = newLock;
return true;
}
}

timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
}

return false;
}

/**
* Renew lock.
*
* @return true if lock is acquired, false otherwise
* @throws InterruptedException
* in case of thread interruption
*/
public boolean renew() throws InterruptedException {
final Lock lock = Lock.fromString(jedis.getKeyString(lockKeyPath));
if (!lock.isExpiredOrMine(lockUUID)) {
return false;
}

return acquire(jedis);
}

/**
* Acquired lock release.
*/
public synchronized void release() {
release(jedis);
}

/**
* Acquired lock release.
* @param jedis
*/
protected synchronized void release(RedisClient jedis) {
if (isLocked()) {
System.out.println("----------Release lock------------");
jedis.delKeyString(lockKeyPath);
this.lock = null;
}
}

/**
* Check if owns the lock
* @return true if lock owned
*/
public synchronized boolean isLocked() {
return this.lock != null;
}

/**
* Returns the expiry time of this lock
* @return the expiry time in millis (or null if not locked)
*/
public synchronized long getLockExpiryTimeInMillis() {
return this.lock.getExpiryTime();
}


private Lock asLock(long expires) {
return new Lock(lockUUID, expires);
}


}