又到了一周一次的分享时间啦,老规矩,还是先荒腔走板的聊聊生活。
有上面的图是读大学的时候,一次自行车骑行途中队友抓拍的我的照片。拍照的地方,名字叫做牛背山,一个名字很 low,实际很美的地方。
那条上山的路很难骑,超级烂路和极度变态的陡坡。真是一种折磨,是对意志力的完全考验。
在我们几近崩溃,弹尽粮绝,离山顶还有近两个多小时的时候,一个卡车司机主动要求把我们免费带到山顶。我们拒绝了。
因为同行的星哥他说了一句话:“骑行牛背山这件事情,我这辈子只会做这一次。现在的情况还不是那么糟,如果我搭车上去了,之后想起来我会有遗憾的。所以我更情愿推车。”
第二天下午山上下了一场暴风雪。于是我们几个南方孩子在这少见的雪景中肆意打闹。打雪仗,堆雪人,滑雪......
晚上雪停了之后,我看到了让我终身难忘的场景。星空,美丽到让人想哭的星空!
后来,在人生路上的很多场景中,我都会想起星哥的那句话:这件事,我这辈子只会做这一次,我不想留下遗憾。还会想起那晚璀璨的、触手可及般的星空。
坚持不住的时候再坚持一下,确实是一种难能可贵的精神。
好了,说回文章。
背景铺垫
面试的时候,不管你的简历写没写 Redis,它基本上是一个绕不过的话题。
为了引出本文要讨论的关于 Redlock 的神仙打架的问题,我们就得先通过一个面试连环炮:
1.Redis 做分布式锁的时候有需要注意的问题?
2.如果是 Redis 是单点部署的,会带来什么问题?
3.那你准备怎么解决单点问题呢?
4.集群模式下,比如主从模式,有没有什么问题呢?
5.你知道 Redis 是怎么解决集群模式也不靠谱的问题的吗?
6.那你简单的介绍一下 Redlock 吧?
7.你觉得 Redlock 有什么问题呢?
很明显,上面是一个常规的面试连环套路题。中间还可以插入很多其他的 Redis 的考察点,我这里就不做扩展了。
单点的 Redis 做分布式锁不靠谱,导致了基于 Redis 集群模式的分布式锁解决方案的出现。
基于 Redis 集群模式的分布式锁解决方案还是不靠谱,Redis 的作者提出了 Redlock 的解决方案。
Redis 作者提出的 Redlock 的解决方案,另一位分布式系统的大神觉得它不靠谱,于是他们之间开始了 battle。
基于这场 battle,又引发了更多的讨论。
这场 battle 难分伯仲,没有最后的赢家。如果一定要选出谁是最大的赢家的话,那一定是吃瓜网友。因为对于吃瓜网友来说(比如我),可以从两位大神的交锋中学习到很多东西。
让你深刻的体会到:看起来那么无懈可击的想法,细细推敲之下,并不是那么天衣无缝。
所以本文就按照下面的五个模块展开讲述。
先来一波劝退:本文近1.2w字,谨慎观看。看不下去不要紧,点个赞就是对于我最大的鼓励。奥利给!
单点Redis
按照我的经验,当面试聊到 Redis 的时候,百分之 90 的朋友都会说**:Redis在我们的项目中是用来做热点数据缓存的**。
然后百分之百的面试官都会问:
Redis除了拿来做缓存,你还见过基于Redis的什么用法?
接下来百分之 80 的朋友都会说到:我们还用 Redis 做过分布式锁。
(当然, Redis 除了缓存、分布式锁之外还有非常非常多的奇技淫巧,不是本文重点,大家有兴趣的可以自己去了解一下。)
那么面试官就会接着说:
那你给我描述(或者写一下伪代码)基于Redis的加锁和释放锁的细节吧。
注意面试官这里说的是加锁和释放锁的细节,魔鬼都在细节里。
问这个问题面试官无非是想要听到下面几个关键点:
关键点一:原子命令加锁。因为有的“年久失修”的文章中对于 Redis 的加锁操作是先set key,再设置 key 的过期时间。这样写的根本原因是在早期的 Redis 版本中并不支持原子命令加锁的操作。不是原子操作会带来什么问题,就不用我说了吧?如果你不知道,你先回去等通知吧。
而在 2.6.12 版本后,可以通过向 Redis 发送下面的命令,实现原子性的加锁操作:
SET key random_value NX PX 30000
关键点二:设置值的时候,放的是random_value。而不是你随便扔个“OK”进去。
先解释一下上面的命令中的几个参数的含义:
random_value:是由客户端生成的一个随机字符串,它要保证在足够长的一段时间内在所有客户端的所有获取锁的请求中都是唯一的。
NX:表示只有当要设置的 key 值不存在的时候才能 set 成功。这保证了只有第一个请求的客户端才能获得锁,而其它客户端在锁被释放之前都无法获得锁。
PX 30000:表示这个锁有一个 30 秒的自动过期时间。当然,这里 30 秒只是一个例子,客户端可以选择合适的过期时间。
再解释一下为什么 value 需要设置为一个随机字符串。这也是第三个关键点。
关键点三:value 的值设置为随机数主要是为了更安全的释放锁,释放锁的时候需要检查 key 是否存在,且 key 对应的值是否和我指定的值一样,是一样的才能释放锁。所以可以看到这里有获取、判断、删除三个操作,为了保障原子性,我们需要用 lua 脚本。
(基本上能答到这几个关键点,面试官也就会进入下一个问题了。常规热身送分题呀,朋友们,得记住了。)
集群模式
面试官就会接着问了:
经过刚刚的讨论,我们已经有较好的方法获取锁和释放锁。基于Redis单实例,假设这个单实例总是可用,这种方法已经足够安全。如果这个Redis节点挂掉了呢?
到这个问题其实可以直接聊到 Redlock 了。但是你别慌啊,为了展示你丰富的知识储备(疯狂的刷题准备),你得先自己聊一聊 Redis 的集群,你可以这样去说:
为了避免节点挂掉导致的问题,我们可以采用Redis集群的方法来实现Redis的高可用。
Redis集群方式共有三种:主从模式,哨兵模式,cluster(集群)模式
其中主从模式会保证数据在从节点还有一份,但是主节点挂了之后,需要手动把从节点切换为主节点。它非常简单,但是在实际的生产环境中是很少使用的。
哨兵模式就是主从模式的升级版,该模式下会对响应异常的主节点进行主观下线或者客观下线的操作,并进行主从切换。它可以保证高可用。
cluster (集群)模式保证的是高并发,整个集群分担所有数据,不同的 key 会放到不同的 Redis 中。每个 Redis 对应一部分的槽。
(上面三种模式也是面试重点,可以说很多道道出来,由于不是本文重点就不详细描述了。主要表达的意思是你得在面试的时候遇到相关问题,需要展示自己是知道这些东西的,都是面试的套路。)
在上面描述的集群模式下还是会出现一个问题,由于节点之间是采用异步通信的方式。如果刚刚在 Master 节点上加了锁,但是数据还没被同步到 Salve。这时 Master 节点挂了,它上面的锁就没了,等新的 Master 出来后(主从模式的手动切换或者哨兵模式的一次 failover 的过程),就可以再次获取同样的锁,出现一把锁被拿到了两次的场景。
锁都被拿了两次了,也就不满足安全性了。一个安全的锁,不管是不是分布式的,在任意一个时刻,都只有一个客户端持有。