(求锤得锤的故事)Redis锁从面试连环炮聊到神仙打架。 (3)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: (求锤得锤的故事)Redis锁从面试连环炮聊到神仙打架。 (3)

**GC 不是导致线程暂停的唯一原因啊,朋友们。**发生这种情况的原因有很多的,你看看长发哥书里举的例子:


上面的内容总结起来,就是就算锁服务是正常的,但是由于锁是有持有时间的,由于客户端阻塞、长时间的 GC 或者网络原因,导致共享资源被一个以上的客户端同时访问了。

其实上面长发哥在书里直接说了:这是不正确的实现。


你多品一品,上面的图是不是有点像由于 Redis 锁的过期时间设置的不合理,导致前一个任务还没执行完成,但是锁的时间到期了,后一个任务也申请到了锁。


对于这种场景,Redission 其实有自己的看门狗机制。但是不在这次 Redlock 的讨论范围内,所以这里就不描述了。


长发哥提出的解决方案是什么呢?


他称为:fencing token。


长发哥认为使用锁和租约机制来保护资源的并发访问时,必须确保因为异常原因,导致锁过期的那个节点不能影响其他正常的部分,要实现这一目标,可以采用一直相当简单的 fencing(栅栏)。


假设每次锁服务在授予锁或者租约时,还会同时返回一个 fencing 令牌,该令牌每次授予都会递增。


然后,要求客户端每次向存储系统发送写请求时,都必须包含所持有的 fencing 令牌。存储系统需要对令牌进行校验,发现如果已经处理过更高令牌的请求,则拒绝执行该请求。


比如下面的图片:


1.客户端 1 获得一个具有超时时间的锁的同时得到了令牌号 33,但随后陷入了一个长时间的暂停直到锁到期。


2.这时客户端2已经获得了锁和令牌号 34 ,然后发送写请求(以及令牌号 34 )到存储服务。


3.接下来客户端 1 恢复过来,并以令牌号 33 来尝试写入,存储服务器由于记录了最近已经完成了更高令牌号(34 ),因此拒绝令牌号 33 的写请求。


这种版本号的机制,让我不禁想起了 Zookeeper。当使用 ZK 做锁服务时,可以用事务标识 zxid 或节点版本 cversion 来充当 fencing 令牌,这两个都可以满足单调递增的要求。


在长发哥的这种机制中,实际上就是要求资源本身必须主动检查请求所持令牌信息,如果发现已经处理过更高令牌的请求,要拒绝持有低令牌的所有写请求。


但是,不是所有的资源都是数据库里面的数据,我们可以通过版本号去支持额外的令牌检查的,那么对于不支持额外的令牌检查资源,我们也可以借助这种思想绕过这个限制,比如对于访问文件存储服务的情况,我们可以将令牌嵌入到文件名中。


总之,为了避免在锁保护之外发生请求处理,需要进行额外的检查机制。


长发哥在书中也说到了:在服务端检查令牌可能看起来有点复杂,但是这其实是推荐的正确的做法:系统服务不能假定所有的客户端都表现的符合预期。从安全角度讲,服务端必须防范这种来自客户端的滥用。


这个就类似于我们作为后端开发人员,也不能相信来自前端或者其他接口过来的数据,必须对其进行校验。


到这里长发哥铺垫完成了,开始转头指向 RedLock,他认为 Redlock 是一个严重依赖系统时钟的分布式锁。


他举了一个例子:


1.客户端 1 从 Redis 节点 A, B, C 成功获取了锁。由于网络问题,无法访问 D 和 E。

2.节点 C 上的时钟发生了向前跳跃,导致它上面维护的锁过期了。


3.客户端 2 从 Redis 节点 C, D, E 成功获取了同一个资源的锁。由于网络问题,无法访问 A 和 B。 现在,客户端 1 和客户端 2 都认为自己持有了锁。


这样的场景是可能出现的,因为 Redlock 严重依赖系统时钟,所以一旦系统的时间变得不准确了,那么该算法的安全性也就得不到保障了。


长发哥举这个例子其实是为了辅佐他前面提出的观点:一个好的分布式算法应该是基于异步模型的,算法的安全性不应该依赖与任何记时假设,就是不能把时间作为安全保障的。在异步模型中,程序暂停、消息在网络中延迟甚至丢失、系统时间错误这些因素都不应该影响它的安全性,只能影响到它的活性。


用大白话说,就是在极其极端的情况下,分布式系统顶天了也就是在有限的时间内不能给出结果而已,而不能给出一个错误的结果。


这样的算法实际上是存在的,比如 Paxos、Raft。很明显,按照这个标准, Redlock 的安全级别是不够的。


而对于卷发哥提出的延迟启动方案,长发哥还是一棒子打死:你延迟启动咋的?延迟启动还不是依赖于合理准确的时间度量。


可能是长发哥觉得举这个时钟跳跃的例子不够好的,大家都可能认为时钟跳跃是不现实的,因为对正确配置NTP就能摆正时钟非常有信心。


在这种情况下,他举了一个进程暂停可能导致算法失败的示例:


1.客户端 1 向 Redis 节点 A, B, C, D, E 发起锁请求。


2.各个 Redis 节点已经把请求结果返回给了客户端 1,但客户端 1 在收到请求结果之前进入了长时间的 GC 阶段。


3.长时间的 GC,导致在所有的 Redis 节点上,锁过期了。


4.客户端 2 在 A, B, C, D, E 上申请并获取到了锁。


5.客户端 1 从 GC 阶段中恢复,收到了前面第 2 步来自各个 Redis 节点的请求结果。客户端 1 认为自己成功获取到了锁。


6.客户端 1 和客户端 2 现在都认为自己持有了锁。


其实只要十分清楚 Redlock 的加锁过程,我们就知道,这种情况其实对于 Redlock 是没有影响的,因为在第 5 步,客户端 1 从 GC 阶段中恢复过来以后,在 Redlock 算法中,(我们前面 Redlock 简介的时候提到的第四步)如果取到了锁,key 的真正有效时间等于有效时间减去获取锁所使用的时间。


所以客户端1通过这个检查发现锁已经过期了,不会再认为自己成功获取到锁了。

而随后卷发哥的回击中也提到了这点。


但是,细细想来,我觉得长发哥的意图不在于此。抛开上面的问题来讲,他更想突出的是,一个锁在客户端拿到后,还没使用就过期了,这是不好的。从客户端的角度来看,就是这玩意不靠谱啊,你给我一把锁,我还没用呢,你就过期了?


除了上面说的这些点外,长发哥还提出了一个算是自己的经验之谈吧:

我们获取锁的用途是什么?


在他看来不外乎两个方面,效率和正确性。他分别描述如下:


如果是为了效率,那么就是要协调各个客户端,避免他们做重复的工作。这种场景下,即使锁偶尔失效了,只是可能出现两个客户端完成了同样的工作,其结果是成本略有增加(您最终向 AWS 支付的费用比原本多5美分),或者带来不便(例如,用户最终两次收到相同的电子邮件通知)。


如果是为了正确性,那么在任何情况下都不允许锁失效的情况发生,因为一旦发生,就可能意味着数据不一致,数据丢失,文件损坏,或者其它严重的问题。(比如个患者注射了两倍的药剂)


最后,长发哥得出的结论是:neither fish nor fowl(不伦不类)


对于提升效率的场景下,使用分布式锁,允许锁的偶尔失效,那么使用单 Redis 节点的锁方案就足够了,简单而且效率高。用 Redlock 太重。


对于正确性要求高的场景下,它是依赖于时间的,不是一个足够强的算法。Redlock并没有保住正确性。


那应该使用什么技术呢?

长发哥认为,应该考虑类似 Zookeeper 的方案,或者支持事务的数据库。


卷发哥回击


长发哥发出《How to do distributed locking》这篇文章的第二天,卷发哥就进行了回击,发布了名为《Is Redlock safe?》的文章。


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


要说大佬不愧是大佬,卷发哥的回击条理清楚,行文流畅。他总结后认为长发哥觉得 Redlock 不安全主要分为两个方面:


1.带有自动过期功能的分布式锁,需要一种方法(fencing机制)来避免客户端在过期时间后使用锁时出现问题,从而对共享资源进行真正的互斥保护。长发哥说Redlock没有这种机制。


2.长发哥说,无论问题“1”如何解决,该算法本质上都是不安全的,因为它对系统模型进行了记时假设,而这些假设在实际系统中是无法保证的。


对于第一个点,卷发哥列了5大点来反驳这个问题,其中一个重要的观点是他认为虽然 Redlock 没有提供类似于fencing机制那样的单调递增的令牌,但是也有一个随机串,把这个随机串当做token,也可以达到同样的效果啊。当需要和共享资源交互的时候,我们检查一下这个token是否发生了变化,如果没有再执行“获取-修改-写回”的操作。


最终得出的结论是一个灵魂反问:既然在锁失效的情况下已经存在一种fencing机制能继续保持资源的互斥访问了,那为什么还要使用一个分布式锁并且还要求它提供那么强的安全性保证呢?


然而第二个问题,对于网络延迟或者 GC 暂停,我们前面分析过,对 Redlock 的安全性并不会产生影响,说明卷发哥在设计的时候其实是考虑过时间因素带来的问题的。


但是如果是长发哥提出的时钟发生跳跃,很明显,卷发哥知道如果时钟发生跳跃, Redlock 的安全性就得不到保障,这是他的命门。


但是对于长发哥写时钟跳跃的时候提出的两个例子:


1.运维人员手动修改了系统时钟。


2.从NTP服务收到了一个大的时钟更新事件。

卷发哥进行了回击:


第一点这个运维人员手动修改时钟,属于人为因素,这个我也没办法啊,人家就是要搞你,怎么办?加强管理,不要这样做。


第二点从NTP服务收到一个大的时钟更新,对于这个问题,需要通过运维来保证。需要将大的时间更新到服务器的时候,应当采取少量多次的方式。多次修改,每次更新时间尽量小。


关于这个地方的争论,就看你是信长发哥的时间一定会跳跃,还是信卷发哥的时间跳跃我们也是可以处理的。


关于时钟跳跃,有一篇文章可以看看,也是这次神仙打架导致的产物:


https://jvns.ca/blog/2016/02/09/til-clock-skew-exists/

文章得出的最终结论是:时钟跳跃是存在的。


其实我们大家应该都经历过时钟跳跃的情况,你还记得2016年的最后一天,当时有个“闰秒”的概念吗?导致2017年1月1日出现了07:59:60的奇观。


打架的焦点


经过这样的一来一回,其实双方打架的焦点就很明确了,就是大延迟对分布式锁带来的影响。


而对于大延迟给Redlock带来的影响,就是长发哥分析的那样,锁到期了,业务还没执行完。卷发哥认为这种影响不单单针对 Redlock ,其他具有自动释放锁的分布式锁也是存在一样的问题。


而关于大延迟的问题,我在某社交平台上找到了两位神仙的下面的对话:


卷发哥问:我想知道,在我发文回复之后,我们能否在一点上达成一致,就是大的消息延迟不会给Redlock的运行造成损害。


长发哥答:对于客户端和锁服务器之间的消息延迟,我同意你的观点。但客户端和被访问资源之间的延迟还是有问题的。


所以通过卷发哥的回击文章和某社交平台的记录,他是同意大的系统时钟跳跃会造成 Redlock 失效的。在这一点上,他与长发哥的观点的不同在于,他认为在实际系统中是可以通过好的运维方式避免大的时钟跳跃的。


所以到这里,两位神仙好像又达到了一个平衡,实现了争论上的求同存异。


打架总结


作为一个互联网行业的从业者,也是分布式系统的使用者,读完他们的文章以及由此文章衍生出来的知识点后,受益良多,于是写下此文作为学习总结,也与大家分享。本文还有很多不足之处,还请各位海涵。


如同文章开篇说的,这场争论没有最后的赢家。很明显卷发哥是没有说服长发哥的,因为在长发哥2017年出版的《数据密集型应用系统设计》一书中,专门有一小节的名称叫做:不可靠的时钟


其实在这场争论的最后,长发哥对这场争论进行了一个非常感性的总结,他说:


下面翻译来自:https://www.jianshu.com/p/dd66bdd18a56


对我来说最重要的一点在于:我并不在乎在这场辩论中谁对谁错 —— 我只关心从其他人的工作中学到的东西,以便我们能够避免重蹈覆辙,并让未来更加美好。前人已经为我们创造出了许多伟大的成果:站在巨人的肩膀上,我们得以构建更棒的软件。


对于任何想法,务必要详加检验,通过论证以及检查它们是否经得住别人的详细审查。那是学习过程的一部分。但目标应该是为了获得知识,而不应该是为了说服别人相信你自己是对的。有时候,那只不过意味着停下来,好好地想一想。


吃瓜网友的收获


这里的吃瓜网友就是指我啦。


写这篇文章我的收获还是挺大的,首先我买了长发哥的《数据密集型应用系统设计》一书,读了几节,发现这书是真的不错,豆瓣评分9.6,推荐。


其次完成了这周的周更任务,虽然写的很艰难,从周六中午,写到周日凌晨3点。。。

然后还吃到了另外的一个瓜,可谓是瓜中瓜。


这周五的时候 Redis 官网不是出现了短暂的宕机吗,宕机其实也没啥稀奇的,但是页面上显示的是连不上 Redis 。这就有点意思了。


我在写这篇文章的时候,在卷发哥的某社交平台上发现了这个:


我关心的并不是 OOM,而是卷发哥居然让 Redis 官网运行在一台一个月仅 5 美元,内存只有 1G 的虚拟机上。哈哈哈,震惊,这瓜味道不错。


最后,由于卷发哥是个意大利人,由于最近疫情,四川专家组驰援意大利的事,big thank 中国人。其实这个网友的回答挺好的:投桃报李。


疫情早点过去吧,世界和平。


最后说一句(求关注)


我写到这里的时候,不知不觉已经凌晨3点多了,但是因为一直跟着这两位大神的激烈讨论,我的思维异常的清晰。


写完之后我也说不出谁对谁错。我觉得对于系统的设计,每个人的出发点都不一样,没有完美的架构,没有普适的架构,但是在完美和普适能平衡的很好的架构,就是好的架构。

瞟了一眼文章字数,快突破了1.2w字。可能又是一篇写了没人看的劝退文吧,但是没有关系,只要有一个人看了我的文章觉得有帮助就行。


点个赞吧,写文章很累的,不要白嫖我,需要一点正反馈。


才疏学浅,难免会有纰漏,如果你发现了错误的地方,还请你留言给我指出来,我对其加以修改。(我每篇技术文章都有这句话,我是认真的说的。)


感谢您的阅读,我坚持原创,十分欢迎并感谢您的关注。


我是why技术,一个不是大佬,但是喜欢分享,又暖又有料的四川好男人。


参考链接


1.http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html


2.https://redis.io/topics/distlock


3.http://antirez.com/news/101


4.https://www.jianshu.com/p/dd66bdd18a56


5.《数据密集型应用系统设计》

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
27天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
21天前
|
存储 NoSQL 算法
阿里面试:亿级 redis 排行榜,如何设计?
本文由40岁老架构师尼恩撰写,针对近期读者在一线互联网企业面试中遇到的高频面试题进行系统化梳理,如使用ZSET排序统计、亿级用户排行榜设计等。文章详细介绍了Redis的四大统计(基数统计、二值统计、排序统计、聚合统计)原理和应用场景,重点讲解了Redis有序集合(Sorted Set)的使用方法和命令,以及如何设计社交点赞系统和游戏玩家排行榜。此外,还探讨了超高并发下Redis热key分治原理、亿级用户排行榜的范围分片设计、Redis Cluster集群持久化方式等内容。文章最后提供了大量面试真题和解决方案,帮助读者提升技术实力,顺利通过面试。
|
22天前
|
存储 NoSQL 算法
面试官:Redis 大 key 多 key,你要怎么拆分?
本文介绍了在Redis中处理大key和多key的几种策略,包括将大value拆分成多个key-value对、对包含大量元素的数据结构进行分桶处理、通过Hash结构减少key数量,以及如何合理拆分大Bitmap或布隆过滤器以提高效率和减少内存占用。这些方法有助于优化Redis性能,特别是在数据量庞大的场景下。
面试官:Redis 大 key 多 key,你要怎么拆分?
|
2月前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
1月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
28天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
1月前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
52 4
|
2月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
85 2
|
2月前
|
JSON 安全 前端开发
第二次面试总结 - 宏汉科技 - Java后端开发
本文是作者对宏汉科技Java后端开发岗位的第二次面试总结,面试结果不理想,主要原因是Java基础知识掌握不牢固,文章详细列出了面试中被问到的技术问题及答案,包括字符串相关函数、抽象类与接口的区别、Java创建线程池的方式、回调函数、函数式接口、反射以及Java中的集合等。
32 0