一. 什么是分布式锁
分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性。
举个不太恰当的例子:假设共享的资源就是一个房子,里面有各种书,分布式系统就是要进屋看书的人,分布式锁就是保证这个房子只有一个门并且一次只有一个人可以进,而且门只有一把钥匙。然后许多人要去看书,可以,排队,第一个人拿着钥匙把门打开进屋看书并且把门锁上,然后第二个人没有钥匙,那就等着,等第一个出来,然后你在拿着钥匙进去,然后就是以此类推
二. 保证分布式锁满足的四个条件
1.互斥性
2.安全性
3.避免死锁
4.容错
三. 使用redis实现分布式锁
使用redis命令 set key value NX EX max-lock-time 实现加锁
使用redis命令 EVAL 实现解锁
加锁:
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
Jedis jedis =
new
Jedis(
"127.0.0.1"
,
6379
);
private
static
final
String SUCCESS =
"OK"
;
/**
* 加锁操作
* @param key 锁标识
* @param value 客户端标识
* @param timeOut 过期时间
*/
public
Boolean lock(String key,String value,Long timeOut){
String var1 = jedis.set(key,value,
"NX"
,
"EX"
,timeOut);
if
(LOCK_SUCCESS.equals(var1)){
return
true
;
}
return
false
;
}
解读:
加锁操作:jedis.set(key,value,"NX","EX",timeOut)【保证加锁的原子操作】
key就是redis的key值作为锁的标识,value在这里作为客户端的标识,只有key-value都比配才有删除锁的权利【保证安全性】
通过timeOut设置过期时间保证不会出现死锁【避免死锁】
NX,EX什么意思?
NX:只有这个key不存才的时候才会进行操作,if not exists;
EX:设置key的过期时间为秒,具体时间由第5个参数决定
解锁
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
Jedis jedis =
new
Jedis(
"127.0.0.1"
,
6379
);
private
static
final
Long UNLOCK_SUCCESS = 1L;
/**
* 解锁操作
* @param key 锁标识
* @param value 客户端标识
* @return
*/
public
static
Boolean unLock(String key,String value){
String luaScript =
"if redis.call(\"get\",KEYS[1]) == ARGV[1] then return redis.call(\"del\",KEYS[1]) else return 0 end"
;
Object var2 = jedis.eval(luaScript,Collections.singletonList(key), Collections.singletonList(value));
if
(UNLOCK_SUCCESS == var2) {
return
true
;
}
return
false
;
}
解读:
luaScript 这个字符串是个lua脚本,代表的意思是如果根据key拿到的value跟传入的value相同就执行del,否则就返回0【保证安全性】
jedis.eval(String,list,list);这个命令就是去执行lua脚本,KEYS的集合就是第二个参数,ARGV的集合就是第三参数【保证解锁的原子操作】
上述就实现了怎么使用redis去正确的实现分布式锁,但是有个小缺陷就是锁过期时间要设置为多少合适,这个其实还是需要去根据业务场景考量一下的
上面那只是讲了加锁与解锁的操作,试想一下如果在业务中去拿锁如果没有拿到是应该阻塞着一直等待还是直接返回,这个问题其实可以写一个重试机制,根据重试次数和重试时间做一个循环去拿锁,当然这个重试的次数和时间设多少合适,是需要根据自身业务去衡量的
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
/**
* 重试机制
* @param key 锁标识
* @param value 客户端标识
* @param timeOut 过期时间
* @param retry 重试次数
* @param sleepTime 重试间隔时间
* @return
*/
public
Boolean lockRetry(String key,String value,Long timeOut,Integer retry,Long sleepTime){
Boolean flag =
false
;
try
{
for
(
int
i=
0
;i<retry;i++){
flag = lock(key,value,timeOut);
if
(flag){
break
;
}
Thread.sleep(sleepTime);
}
}
catch
(Exception e){
e.printStackTrace();
}
return
flag;
}
到这,用redis实现分布式锁就写完了