Redis分布式锁的实现

分布式锁应满足的四个条件:

  • 互斥性。在任意时刻,只有一个客户端能持有锁。
  • 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
  • 具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。
  • 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

1、加锁原理:
setnx:设置<“lock”,“xxx”>;
setex:设置过期时间;
setnx是SET IF NOT EXIST,即当key不存在时,我们进行set操作;若key已经存在,则不做任何操作。
lock为key无其他含义
xxx为请求ID(每个jvm程序一个),可以通过UUID得到一个随机值将其存入ThreadLocal中。
每次加锁的时候先判断该锁(lock)是否存在,若不存在则加锁;若存在则从自己的ThreadLocal中取出请求ID并与xxx比较,若相同则直接使用该锁,若不同则加锁失败。
低版本的jedis不支持多参数的set命令,所以以前是两条命令分开执行的,但是这样会发生的问题显而易见,如果第一步加锁成功,然而此时加锁的jvm程序突然断电宕机,那么过期时间没有设置 上,该锁将不会被其他jvm程序得到,将会造成阻塞。
现在高版本的jedis支持多参数的set命令,所以将这两条命令合并为一条命令,保证了原子执行。但是对于过期时间的设置不好确定,所以应该增加一个守护线程(所有用户线程结束守护线程自动结束)为该锁续期。
2、解锁原理:
requestId.equals(jedis.get(lockKey)):判断该锁是否属于当前线程;
jedis.del(lockKey):删除这把锁;
这两条命令也要原子执行,否则将会误解别人的锁。通过lua脚本(redis是单进程单线程运行的,它会将整个lua脚本全部执行完才会执行其他命令)将这两条命令原子执行。
3、使用redisson框架
Rlock lock = Redisson.getLock(“lock”);
lock.lock();
lock.unlock();
Redis分布式锁的实现
如果该客户端面对的是一个redis cluster集群,他首先会根据hash节点选择一台机器。
加锁命令:hset myLock 请求ID:x
Redis分布式锁的实现
myLock是key
value为请求ID加使用锁的次数(可重入)
可以看到没有过期时间,那是因为redisson框架默认过期时间是30秒,另外只要客户端一旦加锁成功,就会启动一个watch dog看门狗, 他是一个守护线程,会每隔10秒检查一下 ,如果客户端1还持有锁key,那么就会不断的延长锁key的生存时间。
加锁:
当客户端尝试加锁时,会先执行“exists myLock”,若发现myLock这个锁key已经存在了。然后判断myLock锁key的hash数据结构中,是否是当前客户端的请求ID,若不是,那么当前客户端会获取到pttl myLock返回的一个数字,这个数字代表了myLock这个锁key的 剩余生存时间。 比如还剩15000毫秒的生存时间。此时客户端2会进入一个while循环,不停的尝试加锁;若是则重入这把锁,“incrby myLock”,x加1。
释放锁:
每次对x减1,若x等于0,则删除该锁。
参考链接:http://blog.itpub.net/31545684/viewspace-2221023/
(IT小白,给自己看的,如果能帮到你我会很开心的)