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

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: (求锤得锤的故事)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
目录
相关文章
|
16天前
|
存储 NoSQL Java
【面试宝藏】Redis 常见面试题解析
Redis 是内存数据结构存储系统,用作数据库、缓存和消息中间件,支持字符串、哈希、列表等数据类型。它的优点包括高性能、原子操作、持久化和复制。相比 Memcached,Redis 提供数据持久化、丰富数据结构和发布/订阅功能。Redis 采用单线程模型,但通过 I/O 多路复用处理高并发。常见的面试问题涉及持久化机制、过期键删除、回收策略、集群和客户端等。
46 4
|
16天前
|
存储 算法 NoSQL
百度面试:如何用Redis实现限流?
百度面试:如何用Redis实现限流?
33 2
|
3天前
|
存储 缓存 NoSQL
Redis八股文(大厂面试真题)
Redis八股文(大厂面试真题)
38 1
Redis八股文(大厂面试真题)
|
1天前
|
存储 缓存 安全
架构面试题汇总:并发和锁(2024版)
架构面试题汇总:并发和锁(2024版)
6 1
|
2天前
|
存储 缓存 NoSQL
经验大分享:Redis简单总结及常见面试题
经验大分享:Redis简单总结及常见面试题
13 1
|
7天前
|
存储 NoSQL Redis
redis面试题库
redis面试题库
|
16天前
|
存储 缓存 NoSQL
【面试宝藏】Redis 常见面试题解析其二
Redis 高级面试题涵盖了哈希槽机制、集群的主从复制、数据丢失可能性、复制机制、最大节点数、数据库选择、连通性测试、事务操作、过期时间和内存优化等。Redis 使用哈希槽实现数据分布,主从复制保障高可用,异步复制可能导致写操作丢失。集群最大支持1000个节点,仅允许单数据库。可通过 `ping` 命令测试连接,使用 `EXPIRE` 设置过期时间,`MULTI/EXEC` 等进行事务处理。内存优化包括合理数据类型、设置过期时间及淘汰策略。Redis 可用作缓存、会话存储、排行榜等场景,使用 `SCAN` 查找特定前缀键,列表实现异步队列,分布式锁则通过 `SET` 命令和 Lua 脚本实现。
28 5
|
19天前
|
Java 程序员
【面试官】知道synchronized锁升级吗
线程A获取了某个对象锁,但在线程代码的流程中仍需再次获取该对象锁,此时线程A可以继续执行不需要重新再获取该对象锁。既然获取锁的粒度是线程,意味着线程自己是可以获取自己的内部锁的,而如果获取锁的粒度是调用则每次经过同步代码块都需要重新获取锁。此时synchronized重量级锁就回归到了悲观锁的状态,其他获取不到锁的都会进入阻塞状态。来获得锁,CAS操作不需要获得锁、释放锁,减少了像synchronized重量级锁带来的。轻量级锁通过CAS自旋来获得锁,如果自旋10次失败,为了减少CPU的消耗则锁会膨胀为。
74 3
|
20天前
|
存储 消息中间件 缓存
Redis--初级面试题
Redis--初级面试题
|
7天前
|
设计模式 SQL JavaScript
java面试宝典全套含答案
java面试宝典全套含答案