Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结一

lemonvita
发布于 2022-7-19 10:40
浏览
0收藏

❝可能是最完善的 redis 分布式锁原理与实战总结,建议收藏


redis 分布式锁使用 SET 指令就可以实现了么?在分布式领域 CAP 理论一直存在。


分布式锁的门道可没那么简单,我们在网上看到的分布式锁方案可能是有问题的。

 

「码哥」一步步带你深入分布式锁是如何一步步完善,在高并发生产环境中如何正确使用分布式锁。

 

在进入正文之前,我们先带着问题去思考:

 

 什么时候需要分布式锁?
 加、解锁的代码位置有讲究么?
 •如何避免出现锁再也无法删除?
 超时时间设置多少合适呢?
 •如何避免锁被其他线程释放
 如何实现重入锁?
 •主从架构会带来什么安全问题?
 什么是 Redlock
 •redisson 分布式锁最佳实战
 看门狗实现原理
……


什么时候用分布式锁?

 

❝码哥,说个通俗的例子讲解下什么时候需要分布式锁呢?


诊所只有一个医生,很多患者前来就诊。

 

医生在同一时刻只能给一个患者提供就诊服务。

 

如果不是这样的话,就会出现医生在就诊肾亏的「肖菜鸡」准备开药时候患者切换成了脚臭的「谢霸哥」,这时候药就被谢霸哥取走了。

 

治肾亏的药被有脚臭的拿去了。

 

当并发去读写一个【共享资源】的时候,我们为了保证数据的正确,需要控制同一时刻只有一个线程访问。

 

分布式锁就是用来控制同一时刻,只有一个 JVM 进程中的一个线程可以访问被保护的资源。

 

分布式锁入门


❝65 哥:分布式锁应该满足哪些特性?


1.互斥:在任何给定时刻,只有一个客户端可以持有锁;
2.无死锁:任何时刻都有可能获得锁,即使获取锁的客户端崩溃;
3.容错:只要大多数 redis的节点都已经启动,客户端就可以获取和释放锁。


❝码哥,我可以使用 SETNX key value 命令是实现「互斥」特性。


这个命令来自于SET if Not eXists的缩写,意思是:如果 key 不存在,则设置 value 给这个key,否则啥都不做。redis 官方地址说的:

 

命令的返回值:

 

1:设置成功;
0:key 没有设置成功。


如下场景:

 

敲代码一天累了,想去放松按摩下肩颈。

 

168 号技师最抢手,大家喜欢点,所以并发量大,需要分布式锁控制。

 

同一时刻只允许一个「客户」预约 168 技师。

 

肖菜鸡申请 168 技师成功:

> SETNX lock:168 1
(integer) 1 # 获取 168 技师成功

谢霸哥后面到,申请失败:

> SETNX lock 2
(integer) 0 # 客户谢霸哥 2 获取失败

此刻,申请成功的客户就可以享受 168 技师的肩颈放松服务「共享资源」。

 

享受结束后,要及时释放锁,给后来者享受 168 技师的服务机会。

❝肖菜鸡,码哥考考你如何释放锁呢?

 

很简单,使用 DEL 删除这个 key 就行。

> DEL lock:168
(integer) 1

❝码哥,你见过「龙」么?我见过,因为我被一条龙服务过。
Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结一-鸿蒙开发者社区肖菜鸡,事情可没这么简单。

 

这个方案存在一个存在造成锁无法释放的问题,造成该问题的场景如下:

 

1.客户端所在节点崩溃,无法正确释放锁;


2.业务逻辑异常,无法执行 DEL指令。


这样,这个锁就会一直占用,锁在我手里,我挂了,这样其他客户端再也拿不到这个锁了。

 

超时设置


❝码哥,我可以在获取锁成功的时候设置一个「超时时间」


比如设定按摩服务一次 60 分钟,那么在给这个 key 加锁的时候设置 60 分钟过期即可:

> SETNX lock:168 1  // 获取锁
(integer) 1
> EXPIRE lock:168 60  // 60s 自动删除
(integer) 1

这样,到点后锁自动释放,其他客户就可以继续享受 168 技师按摩服务了。

 

❝谁要这么写,就糟透了。


「加锁」、「设置超时」是两个命令,他们不是原子操作。

 

如果出现只执行了第一条,第二条没机会执行就会出现「超时时间」设置失败,依然出现锁无法释放。

 

❝码哥,那咋办,我想被一条龙服务,要解决这个问题

redis 2.6.X 之后,官方拓展了 SET 命令的参数,满足了当 key 不存在则设置 value,同时设置超时时间的语义,并且满足原子性。

SET resource_name random_value NX PX 30000

 NX:表示只有 resource_name 不存在的时候才能 SET 成功,从而保证只有一个客户端可以获得锁;
 •


这样写还不够,我们还要防止不能释放不是自己加的锁。我们可以在 value 上做文章。

 

继续往下看……

 

释放了不是自己加的锁


❝这样我能稳妥的享受一条龙服务了么?


No,还有一种场景会导致释放别人的锁

 

1.客户 1 获取锁成功并设置设置 30 秒超时;
2.客户 1 因为一些原因导致执行很慢(网络问题、发生 FullGC……),过了 30 秒依然没执行完,但是锁过期「自动释放了」;
3.客户 2 申请加锁成功;
4.客户 1 执行完成,执行 DEL 释放锁指令,这个时候就把客户 2 的锁给释放了。


有个关键问题需要解决:自己的锁只能自己来释放。

 

❝我要如何删除是自己加的锁呢?


在执行 DEL 指令的时候,我们要想办法检查下这个锁是不是自己加的锁再执行删除指令。

 

解铃还须系铃人

 

❝码哥,我在加锁的时候设置一个「唯一标识」作为 value 代表加锁的客户端。SET resource_name random_value NX PX 30000

在释放锁的时候,客户端将自己的「唯一标识」与锁上的「标识」比较是否相等,匹配上则删除,否则没有权利释放锁。

 

伪代码如下:

// 比对 value 与 唯一标识
if (redis.get("lock:168").equals(random_value)){
   redis.del("lock:168"); //比对成功则删除
 }

❝有没有想过,这是 GET + DEL 指令组合而成的,这里又会涉及到原子性问题。


我们可以通过 Lua 脚本来实现,这样判断和删除的过程就是原子操作了。

// 获取锁的 value 与 ARGV[1] 是否匹配,匹配则执行 del
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

这样通过唯一值设置成 value 标识加锁的客户端很重要,仅使用 DEL 是不安全的,因为一个客户端可能会删除另一个客户端的锁。

 

使用上面的脚本,每个锁都用一个随机字符串“签名”,只有当删除锁的客户端的“签名”与锁的 value 匹配的时候,才会删除它。

 

官方文档也是这么说的:https://redis.io/topics/distlock

 

这个方案已经相对完美,我们用的最多的可能就是这个方案了。

 

正确设置锁超时


❝锁的超时时间怎么计算合适呢?


这个时间不能瞎写,一般要根据在测试环境多次测试,然后压测多轮之后,比如计算出平均执行时间 200 ms。

 

那么锁的超时时间就放大为平均执行时间的 3~5 倍。

 

❝为啥要放放大呢?


因为如果锁的操作逻辑中有网络 IO 操作、JVM FullGC 等,线上的网络不会总一帆风顺,我们要给网络抖动留有缓冲时间。

 

❝那我设置更大一点,比如设置 1 小时不是更安全?

 

不要钻牛角,多大算大?

 

设置时间过长,一旦发生宕机重启,就意味着 1 小时内,分布式锁的服务全部节点不可用。

 

你要让运维手动删除这个锁么?

 

只要运维真的不会打你。

 

❝有没有完美的方案呢?不管时间怎么设置都不大合适。


我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁「续航」。

 

加锁的时候设置一个过期时间,同时客户端开启一个「守护线程」,定时去检测这个锁的失效时间。

 

如果快要过期,但是业务逻辑还没执行完成,自动对这个锁进行续期,重新设置过期时间。

 

❝这个道理行得通,可我写不出。


别慌,已经有一个库把这些工作都封装好了他叫 redisson

 

在使用分布式锁时,它就采用了「自动续期」的方案来避免锁过期,这个守护线程我们一般也把它叫做「看门狗」线程。

 

❝一路优化下来,方案似乎比较「严谨」了,抽象出对应的模型如下。

 

1.通过 SET lock_resource_name random_value NX PX expire_time,同时启动守护线程为快要过期但还没执行完的客户端的锁续命;
2.客户端执行业务逻辑操作共享资源;
3.通过 Lua 脚本释放锁,先 get 判断锁是否是自己加的,再执行 DEL
Redis 分布式锁的正确实现原理演化历程与 Redisson 实战总结一-鸿蒙开发者社区这个方案实际上已经比较完美,能写到这一步已经打败 90% 的程序猿了。

 

但是对于追求极致的程序员来说还远远不够:

 

1.可重入锁如何实现?
2.主从架构崩溃恢复导致锁丢失如何解决?
3.客户端加锁的位置有门道么?

标签
已于2022-7-19 10:40:47修改
收藏
回复
举报
回复
    相关推荐