redis 分布式锁
分布式锁的安全和活性保证:
1.安全性:相互排斥,在任何时刻里只有一个客户端可以获取锁.
2.活性A:无死锁,即使锁定资源的客户端崩溃或者被隔离,其他也总可以获得锁.
3.活性B:容错,只要大部分的redis节点是运行的,客户端就可以获得及释放锁.
为什么基于failover的实现是不够的
1.客户端A在master获取了锁,
2.master在吧key传输给slave之前崩溃了.
3.slave晋升为master.
4.B获取了A已经持有的锁.违反安全性
单实例的正确实现
获取锁的指令SET resource_name my_random_value NX PX 30000
只有在key 不存在的时候(NX)设置值,30000ms的过期时间.key设置为'myrandomvalue'.这个值必须是所有客户端和锁请求之间是唯一的.random value是用来安全的释放锁.使用一个脚本告诉redis:只有在key存在的时候,而且key下的value必须和预期的一样.如下lua脚本做的一样.:
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
这可以避免删除另一个客户端创造的锁.一个客户端获得锁,在某些操作中花费的时间长于锁有效时间,之后再移除锁,这是锁已经被其他客户端获取了.直接使用del是不安全的,因为可能删除别的客户端持有的锁.所以上面的脚本保证锁只会被持有它的客户端移除.
redlock算法
使用分布式版本的算法,我们假定我们有N个redis的master,它们互相独立,我们已经描述了在单实例模式下安全地获取和释放锁.我们使用这种算法在单个节点获取和释放锁.在我们的例子里设置N=5,这样需要运行5个redis master在不同的电脑或者虚拟机上,来保证它们的失败不会相互影响.
获取锁的操作步骤:
1.获取当前的时间戳.
2.依次获取N个实例的锁,使用相同的key和random value.在第2步.设置每个实例的锁,单个实例的过期时间要小于总的锁自动释放时间.比如总的自动释放时间是10s.,过期时间应该少5~50ms.这样可以防止客户端长时间阻塞,试图与已经关闭的redis节点通话.如果一个实例不可用,我们应该链接下一个实例ASAP.
3.客户端计算获取锁需要花多少时间,通过减去步骤1里的时间戳.只有client可以获取大多数的实例(至少3),然后总的花费时间少于锁的有效时间,锁被认为是获取的.
4.如果锁被获取了.它的有效时间是初始化有效时间减去已花掉的时间.如步骤3里计算的那样.
5.如果客户端获取锁失败(也包括没锁住N/2+1个实例或者有效时间失效).它就会释放所有的实例(甚至包括它没锁住的).