86.如何使用 Redis 实现分布式锁?
Redis 实现分布式锁,需要考虑如下几个方面:
1、正确的获得锁
set 指令附带 nx 参数,保证有且只有一个进程获得到。
2、正确的释放锁
使用 Lua 脚本,比对锁持有的是不是自己。如果是,则进行删除来释放。
3、超时的自动释放锁
set 指令附带 expire 参数,通过过期机制来实现超时释放。
4、未获得到锁的等待机制
sleep 或者基于 Redis 的订阅 Pub/Sub 机制。
一些业务场景,可能需要支持获得不到锁,直接返回 false ,不等待。
5、【可选】锁的重入性
通过 ThreadLocal 记录是第几次获得相同的锁。
1)有且第一次计数为 1 && 获得锁时,才向 Redis 发起获得锁的操作。
2)有且计数为 0 && 释放锁时,才向 Redis 发起释放锁的操作。6、锁超时的处理
一般情况下,可以考虑告警 + 后台线程自动续锁的超时时间。通过这样的机制,保证有且仅有一个线程,正在持有锁。
7、Redis 分布式锁丢失问题
具体看「方案二:Redlock」。
下面,我们来详细说下每个方案。
? 方案一:set 指令
先拿 setnx 来争抢锁,抢到之后,再用 expire 给锁加一个过期时间防止锁忘记了释放。
- 这时候对方会告诉你说你回答得不错,然后接着问如果在 setnx 之后执行 expire 之前进程意外 crash 或者要重启维护了,那会怎么样?
- 这时候你要给予惊讶的反馈:唉,是喔,这个锁就永远得不到释放了。紧接着你需要抓一抓自己得脑袋,故作思考片刻,好像接下来的结果是你主动思考出来的,然后回答:我记得 set 指令有非常复杂的参数,这个应该是可以同时把 setnx 和 expire 合成一条指令来用的!对方这时会显露笑容,心里开始默念:摁,这小子还不错。
所以,我们可以使用 set 指令,实现分布式锁。指令如下:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
- 可以使用
SET key value EX seconds NX
命令,尝试获得锁。 - 具体的实现,可以参考如下文章:
? 方案二:Redlock
set 指令的方案,适合用于在单机 Redis 节点的场景下,在多 Redis 节点的场景下,会存在分布式锁丢失的问题。所以,Redis 作者 Antirez 基于分布式环境下提出了一种更高级的分布式锁的实现方式:Redlock 。
具体的方案,胖友可以看看飞哥的两篇博客:
Redisson 实现分布式锁的流程图,胖友可以点击传送门阅读。
? 对比 Zookeeper 分布式锁
- 从可靠性上来说,Zookeeper 分布式锁好于 Redis 分布式锁。
- 从性能上来说,Redis 分布式锁好于 Zookeeper 分布式锁。
所以,没有绝对的好坏,可以根据自己的业务来具体选择。如果想要更简单,甚至可以考虑基于 MySQL 行锁来实现分布式锁。