Redis分布式锁前面几篇文章已经介绍过了。现在就有通俗易懂的方式再来讲一遍。
在进入正文之前,我们先带着问题去思考:
什么时候需要分布式锁?
加、解锁的代码位置有讲究么?
如何避免出现死锁
超时时间设置多少合适呢?
如何避免锁被其他线程释放
如何实现重入锁?
……
<>什么时候用分布式锁?
这里借用码哥的一个描述
精子喷射那一刻,亿级流量冲向卵子,只有一个精子能获得与卵子结合的幸运。
造物主为了保证只有一个「精子」能获得「卵子」的宠幸,当有一个精子进入后,卵子的外壳就会发生变化,将通道关闭把其余的精子阻挡在外。
亿级别的精子就好比「并发」流量;
卵子就好比是共享资源;
卵子外壳只允许一个精子进入的特殊蛋白就是一把锁。
而多节点构成的集群,就会有多个 JVM 进程,我们获得同样的效果就需要有一个中间人协调,只允许一个 JVM 中的一个线程获得操作共享资源的资格。
分布式锁就是用来控制同一时刻,只有一个 JVM 进程中的一个线程「精子」可以访问被保护的资源「卵子」。
「每一个生命,都是亿级选手中的佼佼者」,加油。
<>分布式锁入门
分布式锁应该满足哪些特性?
互斥:在任何给定时刻,只有一个客户端可以持有锁;
无死锁:任何时刻都有可能获得锁,即使获取锁的客户端崩溃;
容错:只要大多数 Redis的节点都已经启动,客户端就可以获取和释放锁。
前面的文章说到了我们可以使用SETNX来加锁。
给大家描述一个场景:
大家敲代码一天累了,想去按摩放松放松。其中28号技师年轻漂亮服务好,大家都喜欢点,所以并发量大,需要分布式锁控制。
同一时刻只能有一个客户预约28号技师。
张三预约28号技师
这里可以看见张三预约技师成功了,李四再去预约。
李四约不上了,因为这段时间28号技师要服务张三这个客户。
此刻,申请成功的张三就可以享受 28号 技师的服务(共享资源)。
享受结束后,要及时释放锁,给后来者享受 28号 技师的服务机会。
那么这时候问题来了,要怎么去释放锁呢?
很简单。删除就好了。
事情可没这么简单。
这个方案存在一个存在造成锁无法释放的问题,造成该问题的场景如下:
在按摩过程中突然收到线上报警,提起裤子就跑去公司了,没及时执行 DEL 释放锁(客户端处理业务异常,无法正确释放锁);
按摩过程中心肌梗塞嗝屁了,无法执行 DEL指令。
这样,这个锁就会一直占用,锁在我手里,我挂了,这样其他客户端再也拿不到这个锁了。
这个比喻可能不是很好,换一种说法,拿共享充电宝举例子,你租用共享充电宝的时候,就相当于给这个充电宝(共享服务)上了锁,你归还就相当于释放了锁。你要是忘记了还,那么其他客户永远都没办法去租用你手上这个充电宝。这样讲大家是不是好理解多了。
说白了这种方案就是异常导致没有释放锁。
这时候我们可以想到,给锁设置一个超时时间。这样不就解决问题了吗。
Redis 2.6.X 之后,官方拓展了 SET 命令的参数,同时设置超时时间的语义,并且满足原子性。
SET key value NX PX 30000
PX 30000:表示这个锁有一个 30 秒自动过期时间。
TTL这里就是超时时间,当超过30秒的时候锁就自动删除了。
这样能稳妥的享受一条龙服务了么?
No,还有一种场景会导致释放别人的锁:
客户 1 获取锁成功并设置设置 30 秒超时;
客户 1 因为一些原因导致执行很慢(网络问题、发生 FullGC……),过了 30秒依然没执行完,但是锁过期「自动释放了」;
客户 2 申请加锁成功;
客户 1 执行完成,执行 DEL 释放锁指令,这个时候就把 客户 2的锁给释放了。
有两个关键问题需要解决:
如何合理设置过期时间?
如何避免删除别人持有的锁。
<>正确设置锁超时
这个时间不能瞎写,一般要根据在测试环境多次测试,然后压测多轮之后,比如计算出平均执行时间 200 ms。
那么锁的超时时间就放大为平均执行时间的 3~5 倍
因为如果锁的操作逻辑中有网络 IO操作、JVM FullGC 等,线上的网络不会总一帆风顺,我们要给网络抖动留有缓冲时间。
那我设置更大一点,比如设置 1 小时不是更安全?
不要钻牛角,多大算大?
设置时间过长,一旦发生宕机重启,就意味着 1 小时内,分布式锁的服务全部节点不可用。
你要让运维手动删除这个锁么?
只要运维真的不会打你。
当然这个方案并不是完美的,因为时间大小无论怎么设置都不合适。
我们可以让获得锁的线程开启一个守护线程,用来给快要过期的锁「续航」。
加锁的时候设置一个过期时间,同时客户端开启一个「守护线程」,定时去检测这个锁的失效时间。
如果快要过期,但是业务逻辑还没执行完成,自动对这个锁进行续期,重新设置过期时间。
你可能自己不会写,但是Redisson帮你解决了这个问题,并且当程序释放锁的时候,
redission还有广播机制通知其他等待线程可以去获取锁了,关于Redisson我就不多说了,上
一篇文章有讲这个问题。