nodejs学习笔记——基于 Redis 的分布式锁
前言
关于redis的分布式锁,redis官方引出了一个算法,命名为redlock
。
同时,提供了各类的实现可供使用,例如Redlock-rb
for Ruby、Redlock-py
for Python、Redisson
for Java等。
因此,深入了解Redis分布锁的运用同时分析下node-redlock
。
基本特性
概括了三个特性,利用这三个特性,从最小程度上去约束分布锁。
- 互斥。在任何场景下,只有一个用户可以持有锁。
- 死锁释放。最终一个锁总是会被回收,即便持有锁的用户崩溃或是被隔离。
- 错误容忍。只要大部分的Redis节点在运行,用户就可以获取和释放锁。
“为什么基于故障切换的实现是不够的?”
使用Redis锁住一个资源最简单的方式是,生成一个限时的key,使用Redis 的expire特征,所以最终这个key会被释放(以上的特征2)当用户需要释放资源时,释放key。
表面上还不错,但是存在一个问题:如果master挂了怎么办?那添加一个slave。很不幸这不可行,违背了以上的特征1,因为Redis的复制是异步进行的。
范例:用户A在master持有key,master在把key传输给slave前挂了,slave被提升为master,然后用户B又获得了一份A早已锁住的资源的key。SAFETY VIOLATION!
因此redis提供了SET resource_name my_random_value NX PX 30000
这种方式去设置锁。
NX:只在键不存在时, 才对键进行设置操作
PX milliseconds:将键的过期时间设为xx毫秒
// 通过该语句判断只有key存在并且是预期的那个才释放
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
避免误删别的用户的key是很重要的。
Redlock算法
为了获取key用户会进行以下的操作:
- 获取当前的毫秒数
- 尝试去陆续获得所有设备的锁,使用相同的key名称和随机的value。 在这个阶段,当设置锁的时候会有一个相比auto-release时间小的延迟。这样可以避免用户在Redis节点挂了之后任然尝试去访问:如果一个实例不可用,我们应该尽快转移到下一个。
- 用户可以算出加锁花费了多少时间。只有用户可以获得大部分设备的锁,并且其中花费的总时间少于锁设置的有效时间,该锁可以考虑被加上。
- 锁的有效时间 = 初始有效时间 - 加锁消耗时间
- 假如用户加锁失败,无法锁 N/2+1 的设备或者是设置的有效时间非法,其将会尝试解锁所有设备,包括那些不能锁的
系统活性参数(Liveness arguments)
基于以下三个特征:
- 锁自动释放(keys过期之后): 最终可以再次被上锁。
- 用户们若没有成功加锁,或者是加锁之后工作中断了,会共同释放锁。尽量让用户重新获得锁的时候不必等待keys释放。
- 事实上当用户需要重新尝试上锁,很显然需要的时间比获得大部分锁的时间多的多,从而使资源竞争时分脑情况(split brain)不那么容易发生。
了解了Redis分布式的实现以后,其实觉得大多数的分布式系统其实原理很简单,但是为了保证分布式系统的可靠性需要注意很多的细节,琐碎异常,极端情况。
引申内容
我们要锁用来干什么?
- 提升效率,用锁来保证一个任务没有必要被执行两次。(比如很昂贵的计算)
- 保证正确,使用锁来保证任务按照正常的步骤执行,防止两个节点同时操作一份数据,造成文件冲突,数据丢失。
对于第一种原因,我们对锁是有一定宽容度的,就算发生了两个节点同时工作,对系统的影响也仅仅是多付出了一些计算的成本,没什么额外的影响。这个时候 使用单点的 Redis 就能很好的解决问题,没有必要使用RedLock,维护那么多的Redis实例,提升系统的维护成本。
对于第二种原因,对正确性严格要求的场景(比如订单,或者消费),就算使用了 RedLock 算法仍然不能保证锁的正确性。
好的分布式系统应当是异步的,且不能时间作为安全保障的。因为在分布式系统中有会程序暂停,网络延迟,系统时间错误,这些因数都不能影响分布式系统的安全性,只能影响系统的活性(liveness property)。换句话说,就是在极端情况下,分布式系统顶多在有限的时间内不能给出结果,但是不能给出错误的结果。
因此Martin提出对Redlock的批评:对于提升效率的场景下,RedLock 太重。对于对正确性要求极高的场景下,RedLock 并不能保证正确性。
Redlock的作者同时也是Redis的作者 antirez 回答了这两个问题。
对于第一个问题其他具有自动释放锁的分布式锁都没办解决这个问题。
第二个问题,主要考虑系统暂停,网络延迟,还有就是系统的时间发生阶跃。RedLock做了一些微小的工作,但是没办法完全避免,其他带有自动释放的分布式锁也没有办法。至于系统时间发生阶跃,因为RedLock建立在了 Time 是可信的模型上,理论上 Time 也是会发生错误,但是在现实中,良好的运维和工程一些机制是可以最大限度的保证 Time 可信。并不是说单靠Redlock就能解决全部的问题。
最后Martin 推荐使用ZooKeeper 实现分布事务锁。Zookeeper 和 Redis的锁有什么区别? Zookeeper解决了Redis没有解决的问题了么?之后我们再研究。
node-redlock
其实这个没有太多值得分析的内容。node-redlock的代码不多,本质也就是一个js文件。核心的加锁释放依旧是通过redlock的lua脚本,通过Promise去处理多个异步事务,lock存放在array中等等。
心得
Redis锁的原理其实并不复杂,甚至封装好了方法,使用时不需要管内部的处理流程。但是为了保证分布式系统的可靠性,还是尽可能多的了解其中的细节。
redis的分布锁真的完美吗?推荐完整阅读Redis RedLock 完美的分布式锁么?或者是直接查看里面的英文原文。
参考
redis的分布锁
node-redlock仓库
NodeJS 中基于 Redis 的分布式锁解决方案。
Redis RedLock 完美的分布式锁么?