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 指令,实现分布式锁。指令如下:

  1. SET key value [EX seconds] [PX milliseconds] [NX|XX]

? 方案二:Redlock

set 指令的方案,适合用于在单机 Redis 节点的场景下,在多 Redis 节点的场景下,会存在分布式锁丢失的问题。所以,Redis 作者 Antirez 基于分布式环境下提出了一种更高级的分布式锁的实现方式:Redlock 。

具体的方案,胖友可以看看飞哥的两篇博客:

Redisson 实现分布式锁的流程图,胖友可以点击传送门阅读。

? 对比 Zookeeper 分布式锁

  • 从可靠性上来说,Zookeeper 分布式锁好于 Redis 分布式锁。
  • 从性能上来说,Redis 分布式锁好于 Zookeeper 分布式锁。

所以,没有绝对的好坏,可以根据自己的业务来具体选择。如果想要更简单,甚至可以考虑基于 MySQL 行锁来实现分布式锁。