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

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

Redlock简介


为了解决上面的问题,Redis 的作者提出了名为 Redlock 的算法。


在 Redis 的分布式环境中,我们假设有 N 个 Redis Master。这些节点完全互相独立,不存在主从复制或者其他集群协调机制。


前面已经描述了在单点 Redis 下,怎么安全地获取和释放锁,我们确保将在 N 个实例上使用此方法获取和释放锁。


在下面的示例中,我们假设有 5 个完全独立的 Redis Master 节点,他们分别运行在 5 台服务器中,可以保证他们不会同时宕机。


从官网上我们可以知道,一个客户端如果要获得锁,必须经过下面的五个步骤:


步骤描述来源:http://redis.cn/topics/distlock.html


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


2.依次尝试从 N 个实例,使用相同的 key 和随机值获取锁。在步骤 2,当向 Redis 设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为 10 秒,则超时时间应该在 5-50 毫秒之间。这样可以避免服务器端 Redis 已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个 Redis 实例。


3.客户端使用当前时间减去开始获取锁时间(步骤 1 记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是 3 个节点)的 Redis 节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。


4.如果取到了锁,key 的真正有效时间等于有效时间减去获取锁所使用的时间(步骤 3 计算的结果)。


5.如果因为某些原因,获取锁失败(没有在至少 N/2+1 个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的 Redis 实例上进行解锁(即便某些 Redis 实例根本就没有加锁成功)。


通过上面的步骤我们可以知道,只要大多数的节点可以正常工作,就可以保证 Redlock 的正常工作。这样就可以解决前面单点 Redis 的情况下我们讨论的节点挂掉,由于异步通信,导致锁失效的问题。


但是,还是不能解决故障重启后带来的锁的安全性的问题。你想一下下面这个场景:

我们一共有 A、B、C 这三个节点。


1.客户端 1 在 A,B 上加锁成功。C 上加锁失败。


2.这时节点 B 崩溃重启了,但是由于持久化策略导致客户端 1 在 B 上的锁没有持久化下来。 客户端 2 发起申请同一把锁的操作,在 B,C 上加锁成功。


3.这个时候就又出现同一把锁,同时被客户端 1 和客户端 2 所持有了。


(接下来又得说一说Redis的持久化策略了,全是知识点啊,朋友们)


比如,Redis 的 AOF 持久化方式默认情况下是每秒写一次磁盘,即 fsync 操作,因此最坏的情况下可能丢失 1 秒的数据。


当然,你也可以设置成每次修改数据都进行 fsync 操作(fsync=always),但这会严重降低 Redis 的性能,违反了它的设计理念。(我也没见过这样用的,可能还是见的太少了吧。)


而且,你以为执行了 fsync 就不会丢失数据了?天真,真实的系统环境是复杂的,这都已经脱离 Redis 的范畴了。上升到服务器、系统问题了。


所以,根据墨菲定律,上面举的例子:由于节点重启引发的锁失效问题,总是有可能出现的。


为了解决这一问题,Redis 的作者又提出了延迟重启(delayed restarts)的概念


意思就是说,一个节点崩溃后,不要立即重启它,而是等待一定的时间后再重启。等待的时间应该大于锁的过期时间(TTL)。这样做的目的是保证这个节点在重启前所参与的锁都过期。相当于把以前的帐勾销之后才能参与后面的加锁操作。


但是有个问题就是:在等待的时间内,这个节点是不对外工作的。那么如果大多数节点都挂了,进入了等待。就会导致系统的不可用,因为系统在TTL时间内任何锁都将无法加锁成功。


Redlock 算法还有一个需要注意的点是它的释放锁操作。


释放锁的时候是要向所有节点发起释放锁的操作的。这样做的目的是为了解决有可能在加锁阶段,这个节点收到加锁请求了,也set成功了,但是由于返回给客户端的响应包丢了,导致客户端以为没有加锁成功。所以,释放锁的时候要向所有节点发起释放锁的操作。


你可能觉得这不是常规操作吗?


有的细节就是这样,说出来后觉得不过如此,但是有可能自己就是想不到这个点,导致问题的出现,所以我们才会说:细节,魔鬼都在细节里。


好了,简介大概就说到这里,有兴趣的朋友可以再去看看官网,补充一下。


中文:http://redis.cn/topics/distlock.html


英文:https://redis.io/topics/distlock


好了,经过这么长,这么长的铺垫,我们终于可以进入到神仙打架环节。


神仙打架


神仙一:Redis 的作者 antirez 。有的朋友对英文名字不太敏感,所以后面我就叫他卷发哥吧。


神仙二:分布式领域专家 Martin Kleppmann,我们叫他长发哥吧。


看完上面两位神仙的照片,再看看我为了写这篇文章又日渐稀少的头发,我忍不住哭出声来。可能只有给我点赞,才能平复我的心情吧。


卷发哥在官网介绍 Redlock 页面的最后写到:如果你也是使用分布式系统的人员,你的观点和意见非常重要,欢迎和我们讨论。


于是,“求锤得锤”!这一锤,锤出了众多的吃瓜网友,其中不乏在相关领域的专业人士。


长发哥出锤


故事得从 2016年2月8号 长发哥发布的一篇文章《How to do distributed locking》说起:


文章地址:http://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html


这一部分直接翻译过来就是:


作为本书(《数据密集型应用系统设计》)研究的一部分,我在Redis网站上 看到了一种称为Redlock的算法。该算法声称在Redis实现容错的分布式锁(或更确切地说, 租约),并且该页面要求来自分布式系统人员的反馈。这个算法让我产生了一些思考,因此我花了一些时间写了我的这篇文章。


由于Redlock已经有10多个独立的实现,而且我们不知道谁已经在依赖此算法,因此我认为值得公开分享我的笔记。我不会讨论Redis的其他方面,其中一些已经在其他地方受到了批评


你看这个文章,开头就是火药味十足:你说要反馈,那我就给你反馈。而且你这个东西有其他问题,我也就不说了。(其实作者在这篇文章中也说了,他很喜欢并且也在使用 Redis,只是他觉得这个 Redlock 算法是不严谨的)


长发哥主要围绕了下面的这张图进行了展开:


要是一眼没看明白,我再给你一个中文版的,来自长发哥于2017年出版的书《数据密集型应用系统设计》:


可以看到上面的图片中提到了申请租约、租约到期的关键词,租约其实就是可以理解为带超时时间的锁。


而在书中,这张图片的下面写的描述这样的,你咂摸咂摸:


拿 HBase 举例,其设计的目标是确保存储系统的文件一次只能由一个客户端访问,如果多个客户端试图同时写入该文件,文件就会被破坏。那么上面的图片解释起来就是:

1.客户端 1 先去申请锁,并且成功获取到锁。之后客户端进行了长时间的 GC 导致了 STW 的情况。


2.在 STW 期间,客户端 1 获取的锁的超时时间到了,锁也就失效了。


3.由于客户端 1 的锁已经过期失效了,所以客户端 2 去申请锁就可以成功获得锁。

4.客户端 2 开始写文件,并完成文件的写入。


5.客户端 1 从 STW 中恢复过来,他并不知道自己的锁过期了,还是会继续执行文件写入操作,导致客户端 2 写入的文件被破坏。而且可以看到,它没有满足锁在任意时刻只有一个客户端持有的原则,即没有满足互斥性。


书里面没有明说,但是你品一品,这里的锁服务难道不是在说 Redis?


有的朋友就会说了,那客户端 1 写入文件的时候,再判断一下自己的锁有没有过期不就可以了吗?


你可真是个小机灵鬼呢,那我问你,GC 可能是发生在任何时间的,万一 GC 发生在判断之后呢?


你继续怼我,如果客户端使用的是没有 GC 的语言呢?

相关实践学习
基于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
目录
相关文章
|
19天前
|
缓存 NoSQL 关系型数据库
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
本文详解缓存雪崩、缓存穿透、缓存并发及缓存预热等问题,提供高可用解决方案,帮助你在大厂面试和实际工作中应对这些常见并发场景。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:如何解决Redis缓存雪崩、缓存穿透、缓存并发等5大难题
|
2月前
|
NoSQL Java API
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试一线互联网企业时遇到了关于Redis分布式锁过期及自动续期的问题。尼恩对此进行了系统化的梳理,介绍了两种核心解决方案:一是通过增加版本号实现乐观锁,二是利用watch dog自动续期机制。后者通过后台线程定期检查锁的状态并在必要时延长锁的过期时间,确保锁不会因超时而意外释放。尼恩还分享了详细的代码实现和原理分析,帮助读者深入理解并掌握这些技术点,以便在面试中自信应对相关问题。更多技术细节和面试准备资料可在尼恩的技术文章和《尼恩Java面试宝典》中获取。
美团面试:Redis锁如何续期?Redis锁超时,任务没完怎么办?
|
2月前
|
NoSQL 算法 Redis
Redis面试篇
Redis面试篇
46 5
|
1月前
|
存储 NoSQL Redis
Redis常见面试题:ZSet底层数据结构,SDS、压缩列表ZipList、跳表SkipList
String类型底层数据结构,List类型全面解析,ZSet底层数据结构;简单动态字符串SDS、压缩列表ZipList、哈希表、跳表SkipList、整数数组IntSet
|
2月前
|
存储 Kubernetes 架构师
阿里面试:JVM 锁内存 是怎么变化的? JVM 锁的膨胀过程 ?
尼恩,一位经验丰富的40岁老架构师,通过其读者交流群分享了一系列关于JVM锁的深度解析,包括偏向锁、轻量级锁、自旋锁和重量级锁的概念、内存结构变化及锁膨胀流程。这些内容不仅帮助群内的小伙伴们顺利通过了多家一线互联网企业的面试,还整理成了《尼恩Java面试宝典》等技术资料,助力更多开发者提升技术水平,实现职业逆袭。尼恩强调,掌握这些核心知识点不仅能提高面试成功率,还能在实际工作中更好地应对高并发场景下的性能优化问题。
|
2月前
|
缓存 NoSQL 算法
面试题:Redis如何实现分布式锁!
面试题:Redis如何实现分布式锁!
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
21天前
|
存储 算法 Java
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
本文详解自旋锁的概念、优缺点、使用场景及Java实现。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
大厂面试高频:什么是自旋锁?Java 实现自旋锁的原理?
|
22天前
|
存储 缓存 Java
大厂面试必看!Java基本数据类型和包装类的那些坑
本文介绍了Java中的基本数据类型和包装类,包括整数类型、浮点数类型、字符类型和布尔类型。详细讲解了每种类型的特性和应用场景,并探讨了包装类的引入原因、装箱与拆箱机制以及缓存机制。最后总结了面试中常见的相关考点,帮助读者更好地理解和应对面试中的问题。
46 4
|
2月前
|
算法 Java 数据中心
探讨面试常见问题雪花算法、时钟回拨问题,java中优雅的实现方式
【10月更文挑战第2天】在大数据量系统中,分布式ID生成是一个关键问题。为了保证在分布式环境下生成的ID唯一、有序且高效,业界提出了多种解决方案,其中雪花算法(Snowflake Algorithm)是一种广泛应用的分布式ID生成算法。本文将详细介绍雪花算法的原理、实现及其处理时钟回拨问题的方法,并提供Java代码示例。
79 2