Skip to content

Latest commit

 

History

History
44 lines (26 loc) · 3.73 KB

redis_distributed_lock.md

File metadata and controls

44 lines (26 loc) · 3.73 KB

基于redis实现分布式锁

这篇文章介绍下如何基于redis实现一个分布式锁,用于防止多进程对同一个资源的并行访问。

redis提供了一个命令set,大家都很熟悉,除了key和value之外,它还可以接受两个额外参数,分别是ex|pxnx|xx(redis版本要求大于2.6.12,在redis-cli里执行info可以看到版本信息)。

其中,ex|px的作用是设置ttl,也就是过期时间,两者区别在于时间单位不同,一个是秒一个是毫秒。nx代表只在键不存在时,才对键进行设置操作并返回ok,否则不做操作并返回nil,效果等同setnx key valuexx则表示只在键已经存在时,才对键进行设置操作。

redis锁的实现就是基于set key value nx px 3000这条命令,核心思想是:所有对共享资源的访问,都要先向redis设置锁,通过set命令结合nx参数,返回nil代表这个key已经存在,也就意味着锁已经被其它访问者设置了,需要等待锁释放;而返回ok代表设置成功,也就是获取到了锁,访问完资源后,del掉这个key,就等于释放掉了锁。同时,通过px参数设置一个过期时间,防止锁的持有者因为宕机、超时等意外情况无法及时del掉锁,造成死锁。

其中有一个注意点,px设置的过期时间到了之后,锁将被自动释放掉,这将导致持有过期锁的进程误删现有锁的情况出现。可以通过以下修改让锁更加健壮:

1. 使用一个随机串(要具有唯一性)作为value,删除key前对比持有的value和redis里的当前value是否相同,不同则说明持有的锁是过期锁,则不能删除。
2. 通过lua脚本原子化执行对比value + 删除key的操作,防止两个操作之间插入其它进程的操作。下面是一个解锁脚本示例:
if redis.call("get",KEYS[1]) == ARGV[1] //对比value是否相同
then
   return redis.call("del",KEYS[1]) //相同则删除
else
   return 0
end

只要所有对共享资源的访问都遵循以上原则,那么就等于加了把无形的锁,要想访问资源,先得过redis这一关,而redis的单线程模型,保证了不会有多个客户端同时改动相同的key。

不过这个实现有一个致命缺陷,redis对于某个key而言是单点的,一旦宕掉,依赖该key的锁都进入不可靠状态,有人说redis可以通过master-slave&sentinel实现高可用,那么有一个假设:

1. 进程A从redis的master节点获取到了某共享资源的锁。
2. 此master节点在将这个锁同步给slave节点前宕掉了。
3. slave节点上升成为master,而此时该节点上并没有保存客户端A获取到的锁。
4. 进程B此时来请求同一资源的锁,那么它是否会获取到锁呢?

这个假设充分说明了基于redis实现的分布式锁并不是安全可靠的,但是在性能优先,正确性没有太高要求的场景下,使用redis实现锁仍然不失为一个好的选择。

而在性能不重要,正确性要求较高的情况下,则可以使用一个叫redlock的算法实现更可靠的redis分布式锁。

redlock的改进点在于:既然单节点redis不那么可靠,那么就部署N(N>=3)个redis节点,这些节点相互之间完全独立,资源访问者每次加锁时,在N个redis节点上依次加锁,解锁时同样依次解锁,这个操作过程中,只有获得N/2+1(节点数半数以上)个'ok',才认为加解锁成功,否则进行多次重试。

不过即便如此,redlock也不是百分百安全,反而变得太重了,成本太高了,所以在对锁的正确性有要求时,不如使用zookeepr、etcd等实现了一致性协议的分布式存储系统来作为锁的载体。