参考: (9条消息) 如何用Redis实现分布式锁_redis分布式锁_GeorgiaStar的博客-CSDN博客
(9条消息) Redis实现分布式锁_kongmin_123的博客-CSDN博客
步骤:1.加锁;2.执行业务逻辑;3.释放锁
问题:
一、出现死锁:1.业务逻辑出现异常,没有及时释放锁;2.线程挂了,没机会释放锁
解决办法:设置过期时间
可能出现:
1. SETNX执行成功,执行EXPIRE时由于网络问题,执行失败
2. SETNX执行成功,Redis异常宕机,EXPIRE没有机会执行
3. SETNX执行成功,客户端异常崩溃,EXPIRE没有机会执行
解决办法:使用set,在redis2.6.12以上版本set增加了可选参数,例: SET lock_key 1 EX 10 NX
二、由于锁过期,释放了别人的锁:
1. 客户端1加锁成功,开始操作共享资源
2. 客户端1操作共享资源耗时太久,超过了锁的过期时间,锁失效(锁被自动释放)
3. 客户端2加锁成功,开始操作共享资源
4. 客户端1操作共享资源完成,在finally块中手动释放锁,但此时它释放的是客户端2的锁。
解决办法:加锁时将一个只有自己知道的唯一标识作为value设置
可能出现:由于释放锁时需要进行get+del两个操作,出现原子性问题
1. 客户端1执行GET,判断锁是自己的
2. 客户端2执行了SET命令,强制获取到锁(虽然发生概念很低,但要严谨考虑锁的安全性)
3. 客户端1执行DEL,却释放了客户端2的锁
解决办法:使用Lua脚本
三、锁过期
解决办法:加锁时,先设置一个预估的过期时间,然后开启一个守护线程,定时去检测这个锁的失效时间,如果锁快要过期了,操作共享资源还未完成,那么就自动对锁进行续期,重新设置过期时间。
已有方案: Redisson。它可以像操作本地锁一样操作分布式锁。客户端一旦加锁成功,就会启动一个watch dog看门狗线程,它是一个后台线程,会每隔一段时间(这段时间的长度与设置的锁的过期时间有关)检查一下,如果检查时客户端还持有锁key(也就是说还在操作共享资源),那么就会延长锁key的生存时间。如果客户端在加锁成功后就宕机了呢?宕机了那么看门狗任务就不存在了,也就无法为锁续期了,锁到期自动失效。
四、Redis部署方式对锁的影响
Redis发展到现在,几种常见的部署架构有:
1.单机模式;3.主从模式;3.哨兵(sentinel)模式;4.集群模式;
我们使用Redis时,一般会采用主从集群+哨兵的模式部署,哨兵的作用就是监测redis节点的运行状态。普通的主从模式,当master崩溃时,需要手动切换让slave成为master,使用主从+哨兵结合的好处在于,当master异常宕机时,哨兵可以实现故障自动切换,把slave提升为新的master,继续提供服务,以此保证可用性。
可能出现:锁丢失
1. 客户端1在master上执行SET命令,加锁成功
2. 此时,master异常宕机,SET命令还未同步到slave上(主从复制是异步的)
3. 哨兵将slave提升为新的master,但这个锁在新的master上丢失了,导致客户端2来加锁成功了,两个客户端共同操作共享资源
解决方法:集群模式+Redlock实现高可靠的分布式锁(Redlock算法的实现要求Redis采用集群部署模式,无哨兵节点,需要有N个独立的Redis实例(官方推荐至少5个实例))
Redlock算法的基本思路:让客户端和多个独立的Redis实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁了,否则加锁失败。
加锁分三步:
1.客户端获取当前时间。
2.客户端按顺序依次向N个Redis实例执行加锁操作。
3.一旦客户端完成了和所有Redis实例的加锁操作,客户端就要计算整个加锁过程的总耗时。(客户端只有在满足两个条件时,才能认为是加锁成功,条件一是客户端从超过半数(大于等于 N/2+1)的Redis实例上成功获取到了锁;条件二是客户端获取锁的总耗时没有超过锁的有效时间。)
释放锁时客户端就要向所有Redis节点发起释放锁的操作。