现有 redis 分布式锁 方案整理与学习

Posted 看,未来

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了现有 redis 分布式锁 方案整理与学习相关的知识,希望对你有一定的参考价值。

分布式锁的演进

前面比较简单的我就直接在这一块儿带过:

首先是本地锁,由服务器自己加锁,可保证单台服务器上的访问有序性。但是在分布式环境下明显不适用;
接着是 setnx,setnx 是set If not exist的简写,返回 OK,表示设置成功,返回 nil表示设置失败。

既然是锁,那就都绕不开一个问题:上了锁,要是解不了锁怎么办?别说是分布式了,在单体架构里这也是大问题。单体架构里可以采用无锁编程手法,one loop per thread。但是分布式可以这样做吗?one loop per thread 一个很重要的核心思想叫做线程间不通信,诶,我不通信,你能怎么让我死锁啊?分布式环境下能满足这一点吗?可以好好想想。

于是乎,又演进出了一套方案,超时解锁。就是那个 timelock 的思想。timelock 在操作系统下就不被推荐使用,所以在分布式里怎么就会被推荐呢?

(先贴一下操作)

// 设置某个 key 的值并设置多少毫秒或秒 过期。
set <key> <value> PX <多少毫秒> NX
或
set <key> <value> EX <多少秒> NX

这里有一个什么问题呢?
假设现在有 A/B 两个session,A先拿到锁,但是速度慢,还没执行完,锁就过期了,B顺理成章的拿到了锁。A执行完之后,解锁,这时候锁是属于B的,但是由于锁是同一把锁,所以A可以解锁。A把B的锁给解了,B还没执行完,锁就没了,那B不就直接暴露了?

对这个问题进行修正,有以下两个方案:
1、将钥匙设置为一个随机值
2、用令牌环当钥匙

其实两个方案一个意思?不,不是一个意思。
对方案一:A 先拿到锁,自己配了一把钥匙,但是速度慢,锁快过期之前执行完了,向 redis 查询当前锁配对的钥匙。查完之后,锁过期了,B顺利成章的拿到了锁,又配了一把钥匙。A 拿到了匹配信息,去解锁,然后又把 B 的锁给释放了。

这个概率已经是很低了,我有个不成熟的想法:A在任务完成之后,无非两种可能:1、锁还是A的。2、锁已经是别人的了。而为什么会出现A查完配对之后再去删除的时候成了别人的锁?是中间传输效率过慢了?那为什么不直接查杀呢?或者,A干脆先不查,先给锁续个命。续命还是这两种可能,锁还是A的,则续命之后就有充足的时间由A自己来匹配解锁了。如果锁已经是别人的了,那也没关系呀,反正别人也会去解锁的。不过这就要多走一条指令了。

对于上面直接查杀的想法,是有办法实现的了。使用 lua 脚本。如果是用服务器向redis通信,是会模拟一个客户端进行通信的,走session通道。lua不一样,人家可以是redis肚子里的蛔虫。

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else    
    return 0
end

Redisson

Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格(In-Memory Data Grid)。

Redisson的宗旨是促进使用者对 Redis 的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。

Redisson 内置了一个“看门狗”,只要 A线程活着,锁就不会过期。看门狗会定期将过期时间补足,如果A挂了,狗也就不复存在了,锁也就会过期了。

可惜的是 Redisson 是Java实现的,C++没有找到开源的、公众认可的、类似的工具。


Redlock算法过程

Redlock算法是Antirez在单Redis节点基础上引入的高可用模式。

在Redis的分布式环境中,我们假设有N个完全互相独立的Redis节点,在N个Redis实例上使用与在Redis单实例下相同方法获取锁和释放锁。

现在假设有5个Redis主节点(大于3的奇数个),这样基本保证他们不会同时都宕掉,获取锁和释放锁的过程中,客户端会执行以下操作:

1.获取当前Unix时间,以毫秒为单位

2.依次尝试从5个实例,使用相同的key和具有唯一性的value获取锁。
当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间,这样可以避免客户端死等

3.客户端使用当前时间减去开始获取锁时间就得到获取锁使用的时间。
当且仅当从半数以上的Redis节点取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功

4.如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间,这个很重要

5.如果因为某些原因,获取锁失败(没有在半数以上实例取到锁或者取锁时间已经超过了有效时间),
客户端应该在所有的Redis实例上进行解锁,无论Redis实例是否加锁成功,
因为可能服务端响应消息丢失了但是实际成功了,毕竟多释放一次也不会有问题

马丁博士文章的主要观点

2016年2月8号分布式系统的专家马丁·克莱普曼博士(Martin Kleppmann)在一篇文章How to do distributed locking 指出分布式锁设计的一些原则并且对Antirez的Redlock算法提出了一些质疑。

Martin指出即使我们拥有一个完美实现的分布式锁,在没有共享资源参与进来提供某种fencing栅栏机制的前提下,
我们仍然不可能获得足够的安全性

Martin指出,由于Redlock本质上是建立在一个同步模型之上,对系统的时间有很强的要求,本身的安全性是不够的

那个栅栏先不看,看时间:
马丁·克莱普曼指出Redlock是个强依赖系统时间的算法,这样就可能带来很多不一致问题,他给出了个例子一起看下:

假设多节点Redis系统有五个节点A/B/C/D/E和两个客户端C1和C2,如果其中一个Redis节点上的时钟向前跳跃会发生什么?

客户端C1获得了对节点A、B、c的锁定,由于网络问题,法到达节点D和节点E

节点C上的时钟向前跳,导致锁提前过期

客户端C2在节点C、D、E上获得锁定,由于网络问题,无法到达A和B

客户端C1和客户端C2现在都认为他们自己持有锁

马丁其实是要指出分布式算法研究中的一些基础性问题,好的分布式算法应该基于异步模型,算法的安全性不应该依赖于任何记时假设

分布式异步模型中进程和消息可能会延迟任意长的时间,系统时钟也可能以任意方式出错。这些因素不应该影响它的安全性,只可能影响到它的活性,即使在非常极端的情况下,算法最多是不能在有限的时间内给出结果,而不应该给出错误的结果,这样的算法在现实中是存在的比如Paxos/Raft,按这个标准衡量Redlock的安全级别是达不到的。


马丁表达了自己的观点,把锁的用途分为两种:

  • 效率第一
    使用分布式锁只是为了协调多个客户端的一些简单工作,锁偶尔失效也会产生其它的不良后果,就像你收发两份相同的邮件一样,无伤大雅

  • 正确第一
    使用分布式锁要求在任何情况下都不允许锁失效的情况发生,一旦发生失效就可能意味着数据不一致、数据丢失、文件损坏或者其它严重的问题,就像给患者服用重复剂量的药物一样,后果严重

最后马丁出了如下的结论:

为了效率而使用分布式锁
单Redis节点的锁方案就足够了Redlock则是个过重而昂贵的设计

为了正确而使用分布式锁
Redlock不是建立在异步模型上的一个足够强的算法,它对于系统模型的假设中包含很多危险的成分

马丁认为Redlock算法是个糟糕的选择,因为它不伦不类:出于效率选择来说,它过于重量级和昂贵,出于正确性选择它又不够安全。


Antirez的回应

栅栏那个就不提了,关于记时假设

Antirez针对算法在记时模型假设集中反驳,马丁认为Redlock失效情况主要有三种:

1.时钟发生跳跃

2.长时间的GC pause

3.长时间的网络延迟

后两种情况来说,Redlock在当初之处进行了相关设计和考量,对这两种问题引起的后果有一定的抵抗力。
时钟跳跃对于Redlock影响较大,这种情况一旦发生Redlock是没法正常工作的。
Antirez指出Redlock对系统时钟的要求并不需要完全精确,只要误差不超过一定范围不会产生影响,在实际环境中是完全合理的,通过恰当的运维完全可以避免时钟发生大的跳动。

地址:http://antirez.com/news/101


马丁的总结和思考

For me, this is the most important point: I don’t care who is right or wrong in this debate — I care about learning from others’ work, so that we can avoid repeating old mistakes, and make things better in future. So much great work has already been done for us: by standing on the shoulders of giants, we can build better software.

By all means, test ideas by arguing them and checking whether they stand up to scrutiny by others. That’s part of the learning process.
But the goal should be to learn, not to convince others that you are right. Sometimes that just means to stop and think for a while.

简单翻译下就是:
对马丁而言并不在乎谁对谁错,他更关心于从他人的工作中汲取经验来避免自己的错误重复工作,正如我们是站在巨人的肩膀上才能做出更好的成绩。

另外通过别人的争论和检验才更能让自己的想法经得起考验,我们的目标是相互学习而不是说服别人相信你是对的,所谓一人计短,思考辩驳才能更加接近真理

大师就是大师!!!


我比较菜一点,开局我就有点懵:为啥要去那么多个节点上共同确认一个锁呀?是哨兵模式吗?
操作是能看懂,就是不知道这样子的好处具体体现在哪里,emmm

以上是关于现有 redis 分布式锁 方案整理与学习的主要内容,如果未能解决你的问题,请参考以下文章

利用多写Redis实现分布式锁原理与实现分析

利用多写Redis实现分布式锁原理与实现分析

七种方案!探讨Redis分布式锁的正确使用姿势

Redis进阶学习03---Redis完成秒杀和Redis分布式锁的应用

RedisZookeeper实现分布式锁——原理与实践

Redis实现分布式锁与Zookeeper实现分布式锁区别