Redis分布式锁安全性问题,分布式系统专家Martin Kleppmann和Redis的作者antirez之间就发生过一场争论。过程:为规范各家对基于Redis的分布式锁实现,Redis作者提出更安全实现Redlock,Martin Kleppmann分析Redlock安全性问题。Redis作者立即反驳
redis锁存在问题:
1、原子性问题(加过期时间、get del顺序) 2、加随机值,保证释放的是同一个锁 3、单节点
一、Redlock算法
防止了 单节点故障造成整个服务停止运行的情况;
借助Redis实现分布式锁(Distributed Lock),思路相近,细节不同。1)Redlock前,基于单个Redis节点。2)Redlock基于多个Redis节点(都是Master)的一种实现。
1、基于单Redis节点的分布式锁
(1)Redis客户端获取锁,向Redis节点发送:SET resource_name my_random_value NX PX 30000
my_random_value是由客户端生成的一个随机字符串,它要保证在足够长的一段时间内在所有客户端的所有获取锁的请求中都是唯一的。
NX表示只有当resource_name对应的key值不存在的时候才能SET成功。这保证了只有第一个请求的客户端才能获得锁,而其它客户端在锁被释放之前都无法获得锁。
PX 30000表示这个锁有一个30秒的自动过期时间。当然,这里30秒只是一个例子,客户端可以选择合适的过期时间。
(2)释放锁:执行Redis Lua脚本来
执行时候要把my_random_value为ARGV[1]、resource_name为KEYS[1]值传进去。
问题1,锁要设置过期时间。避免崩溃一直持有
问题2,获取锁实现成两个Redis命令:SETNX resource_name my_random_value EXPIRE resource_name30 不是原子。SETNX后崩溃,没有机会EXPIRE了,一直持有。解决:Multi/Exec包起确保请求的原子性
问题3,my_random_value必要,保证释放是持有那个锁。如获锁SET不是随机字符串,而是固定值,那么可能会发生下面的执行序列:
1获取锁。阻塞。过期释放
2获取同锁。1恢复,释放2持有的锁。2没锁
问题4,释放锁三步:'GET'、判断、'DEL',Lua脚本来保证原子性。
问题5,failover引起,基于单Redis节点的分布式锁无法解决。催生Redlock
Redis节点宕机,所有客户端无法获锁,服务不可用。挂一个Slave解决,但Redis主从复制异步,导致failover中丧失锁的安全性。考虑下面的执行序列:
客户端1从Master获锁。Master宕,存锁的key没同步到Slave上。
Slave升级为Master。客户端2从新Master获同一资源锁。1和2持同一锁。安全性打破。
2、分布式锁Redlock
基于N个完全独立的Redis节点(通常N=5)获取锁操作:
1)获取当前时间(毫秒数)
2)按顺序向N个Redis节点获取锁,超时时间远<有效时间(几十毫秒量级)。获取锁失败(如节点不可用,或已经被其它持有),试下个节点。
3)计算获锁总时长(减去第1步记录时间)。如大多数节点(>= N/2+1)成功获锁,且获锁总时长 > 锁有效时间,才认为最终获锁成功
4)如成功,重新计算锁有效时间=最初锁有效时间 - 第3步获锁消耗时间。
5)如失败,向所有Redis节点发起释放锁操作(即前面介绍的Redis Lua脚本)。不管这些节点当时在获取锁的时候成功与否
6)大多数节点正常工作,Redlock就正常工作,如节点崩溃重启,对锁安全性有影响,具体影响程度跟Redis对数据的持久化程度有关:
3、宕机后,同时存在多个锁:
假设5个Redis节点:A, B, C, D, E。事件序列:
1、客户端1成功锁住A, B, C,获取锁成功(但D和E没有锁住)。
2、C崩溃重启,C的锁没有持久化,丢失了。
3、C重启后,客户端2锁住C, D, E,获取锁成功。1和2同时获锁
默认,AOF每秒写一次磁盘(即fsync),最坏丢1秒。设置成每次修都fsync,但仍可能丢数据(决于系统,不是Redis的实现)。解决办法:延迟重启(delayed restarts)。崩溃后,不立即重启,等待一会时间(大于锁的有效时间)。不对现有锁造成影响
ps:为什么没获锁客户端也要释放?如果节点收到获取锁请求,成功执行SET,但返回给客户端响应包丢了。超时失败,Redis会认为加锁已经成功了。
链接:https://juejin.im/post/6844903465181773831