转:html
分布式锁其实能够理解为:控制分布式系统有序的去对共享资源进行操做,经过互斥来保持一致性。 举个不太恰当的例子:假设共享的资源就是一个房子,里面有各类书,分布式系统就是要进屋看书的人,分布式锁就是保证这个房子只有一个门而且一次只有一我的能够进,并且门只有一把钥匙。而后许多人要去看书,能够,排队,第一我的拿着钥匙把门打开进屋看书而且把门锁上,而后第二我的没有钥匙,那就等着,等第一个出来,而后你在拿着钥匙进去,而后就是以此类推java
互斥性redis
保证同一时间只有一个客户端能够拿到锁,也就是能够对共享资源进行操做数据库
安全性安全
只有加锁的服务才能有解锁权限,也就是不能让a加的锁,bcd均可以解锁,若是都能解锁那分布式锁就没啥意义了分布式
可能出现的状况就是a去查询发现持有锁,就在准备解锁,这时候突然a持有的锁过时了,而后b去得到锁,由于a锁过时,b拿到锁,这时候a继续执行第二步进行解锁若是不加校验,就将b持有的锁就给删除了post
避免死锁lua
出现死锁就会致使后续的任何服务都拿不到锁,不能再对共享资源进行任何操做了url
保证加锁与解锁操做是原子性操做spa
假设加锁操做,操做步骤分为两步:
1,设置key set(key,value)2,给key设置过时时间
假设如今a刚实现set后,程序崩了就致使了没给key设置过时时间就致使key一直存在就发生了死锁
实现分布式锁的方式有不少,只要知足上述条件的均可以实现分布式锁,好比数据库,redis,zookeeper,在这里就先讲一下如何使用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实现分布式锁就写完了,下次用zookeeper去实现分布式锁,欢迎留言吐槽 txtx
文中set命令详解:http://redisdoc.com/string/set.html