1. 什么是分布式锁?#
分析
分布式锁是用于分布式环境下并发控制的一种机制,用于控制某个资源在同一时刻只能被一个应用所使用。如下图所示:

回答
分布式锁是控制分布式系统之间同步访问共享资源的一种方式。在多线程或者多进程并发的情况下,使用锁来保证一个代码块在同一时间内只能由一个线程执行。
2. 如何理解 Redis 原子性操作原理?#
分析
Redis 原子性操作的原理:
- 单线程模型保证单条命令原子性;
- 内置命令(INCR、HSET、LPUSH)本身不可分割;
- MULTI/EXEC 确保事务性操作不会被中断(可以不提这个,很少后端真的用 Redis 事务,都是用 LUA);
- Lua 脚本执行过程中不可被其他命令插入,确保整体原子性。
这些特性让 Redis 在高并发场景下能够安全、高效地执行原子操作。
回答
Redis 提供的 API 都是单线程串行处理的,所以我们用单条对象操作命令都不用担心被中断,如果是多条命令要实现原子性,通常都是用 LUA 脚本来支持。
3. 分布式锁实现要点是什么(其实就是怎么加锁、怎么解锁、怎么用)?#
分析
redis 分布式锁的加锁命令(一行命令实现互斥效果+过期时间,原子性):
// lock_key 就是 key 键;
// unique_value 是客户端生成的唯一的标识,区分来自不同客户端的锁操作;
// NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;
// PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁
SET lock_key unique_value NX PX 10000
要注意 setnx 这个命令,是没办法携带过期时间参数的!如果 setnx + expire 2 个命令,就没法保证加锁的原子性了!所以要用 set 命令,携带 nx 和 px 参数,才能保证加锁的原子性!
而解锁的过程就是将 lock_key 键删除(del lock_key),但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的 unique_value 是否为加锁客户端,是的话,才将 lock_key 键删除。
可以看到,解锁是有两个操作,这时就需要 Lua 脚本来保证解锁的原子性,因为 Redis 在执行 Lua 脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。
// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
这样一来,就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁。
回答的两种讲述思路:
- 一种是直接说有几个要点,要设置 owner,要设置 ttl,释放时需要用 lua;
- 另一种思路是直接说最重要的是加锁、解锁流程,其中加锁是怎样的命令,解锁是怎样的命令。
相对比较建议第二个思路,说出来比较自然,推进式回答可以在讲述时帮助思考。
回答
分加锁解锁来说。
- 加锁是用 SET 命令,然后带上 NX 参数,key 是锁名字,value 是持有者 id,再加一个过期时间,这里 value 要用持有者 id 的原因是谁申请、谁释放的原则,会在解锁时进行检查,过期时间是为了兜底,防止异常情况下锁被永久占据;
- 解锁的话,主要涉及两步操作,一个是查看是不是自己的锁,如果是接着就是释放锁,为了保证原子性,解锁需要用 LUA 脚本进行。
4. 为什么需要引入 owner 的概念呢?#
分析
这个问题本质是问为什么分布式锁要这种对称性,对称性也就是说同一个锁,加锁和解锁必须是同一个竞争者。不能把其他竞争者持有的锁给释放了(超时自动释放除外)。
回答没有对称性会怎样,举个例子说明。
回答
分布式锁需要保证对称性,假设没有这种对称性,会有问题,举个例子,服务 A 获取了锁,由于业务流程比较长,或者网络延迟、GC 卡顿等原因,导致锁过期,而业务还会继续进行。这时候,业务 B 已经拿到了锁,准备去执行,这个时候服务 A 恢复过来并做完了业务,就会释放锁,而 B 却还在继续执行,等 B 完成下次释放的可能又是别人的锁,这种情况是需要避免的。
5. 你提到了 lua,用 lua 一定能保证原子性?#
分析
考察 lua 的掌握情况。

回答
lua 本身不具备原子性,上面提到用 lua 来保证原子性是因为 Redis 是单线程执行,一个流程放进 lua 来执行,相当于是打包在一起,Redis 执行他的过程中不会被其他请求打断,所以说保证了原子性。
这里我们也提到,我们是在释放的时候将查询 key,删除 key 打包到一起,其中只有最后删除是写操作,所以这个流程本身是保证了原子性的。
6. 基于 Redis 实现分布式锁有什么优缺点?#
分析
基于 Redis 实现分布式锁的优点:
- 性能高效(这是选择缓存实现分布式锁最核心的出发点);
- 实现方便。很多研发工程师选择使用 Redis 来实现分布式锁,很大成分上是因为 Redis 提供了 setnx 方法,实现分布式锁很方便;
- 避免单点故障(因为 Redis 是跨集群部署的,自然就避免了单点故障)。
基于 Redis 实现分布式锁的缺点:
- 超时时间不好设置:如果锁的超时时间设置过长,会影响性能,如果设置的超时时间过短会保护不到共享资源。比如在有些场景中,一个线程 A 获取到了锁之后,由于业务代码执行时间可能比较长,导致超过了锁的超时时间,自动失效,注意 A 线程没执行完,后续线程 B 又意外的持有了锁,意味着可以操作共享资源,那么两个线程之间的共享资源就没办法进行保护了。
- 那么如何合理设置超时时间呢?我们可以基于续约的方式设置超时时间:先给锁设置一个超时时间,然后启动一个守护线程,让守护线程在一段时间后,重新设置这个锁的超时时间。实现方式就是:写一个守护线程,然后去判断锁的情况,当锁快失效的时候,再次进行续约加锁,当主线程执行完成后,销毁续约锁即可,不过这种方式实现起来相对复杂。
- Redis 主从复制模式中的数据是异步复制的,这样导致分布式锁的不可靠性:如果在 Redis 主节点获取到锁后,在没有同步到其他节点时,Redis 主节点宕机了,此时新的 Redis 主节点依然可以获取锁,所以多个应用服务就可以同时获取到锁。
回答
Redis 实现分布式锁的主要优点在于其性能高效、实现简单和被业界成熟应用。但是也有其局限性,首先是 Redis 的可用性直接影响到锁的可靠性,如果 Redis 服务出现故障,可能会导致锁服务不可用,虽然 Redis 提供了持久化机制,但在极端情况下,如 Redis 突然崩溃,可能会导致锁信息的丢失,从而引发锁失效的问题。
7. 使用 Redis 实现分布式锁的优点?#
分析
优点挺多的,核心是高性能、简单易用和广泛应用,面试时候一定要提到这两点,其它酌情应对即可。
- 高性能:
- Redis 是内存数据库,读写速度极快,适合高并发场景;
- 单线程模型避免了锁竞争问题,操作具有原子性。
- 简单易用:
- Redis 提供了简单的命令(如
SET key value EX seconds NX)来实现分布式锁,开发成本低; - 支持多种客户端(如 Java 的 Redisson、Python 的 redis-py),易于集成。
- Redis 提供了简单的命令(如
- 自动释放锁:
- 通过 EX 参数设置超时时间,锁会在超时后自动释放,避免死锁。
- 灵活性:
- 支持动态调整锁的超时时间;
- 可以通过 Lua 脚本实现复杂的原子操作(如锁的释放和续期)。
- 高可用性:
- 结合 Redis Sentinel 或 Redis Cluster,可以实现高可用性和容错性,避免单点故障。
- 轻量级:
- 相比于 Zookeeper 等分布式协调服务,Redis 更加轻量,部署和维护成本较低。
回答
Redis 是最常用的分布式锁,核心优势我认为在于 3 点,第一是性能高,速度非常快;第二是简单易用、简单意味着容易接入和不易出错;第三是广泛应用,即有丰富的实践经验。
8. 使用 Redis 实现分布式锁的缺点?#
分析
Redis 分布式锁其实很好用了,但是一切事务都有正反两面,要找缺点肯定能说不少,比如:
- 非强一致性:
- Redis 的异步复制机制可能导致锁数据在主从节点之间不一致,极端情况下可能出现多个客户端同时持有锁;
- 即使使用 Redis Cluster,也无法完全避免脑裂问题。
- 单点故障:
- 在单节点 Redis 模式下,如果 Redis 宕机,分布式锁将完全失效;
- 虽然可以通过 Sentinel 或 Cluster 提高可用性,但增加了部署和运维的复杂性。
- 竞争问题:
- 在高并发场景下,多个客户端可能频繁竞争锁,导致 Redis 性能下降;
- 需要实现合理的重试机制(如指数退避)来缓解竞争。
回答
Redis 分布式锁足够简单好用,但是也存在一些局限,比如 Redis 锁不具备强一致性,主从节点数据有不一致的情况,可能会因此出现多个客户端同时持有锁的情况,导致业务不必要的重复执行。
9. 如何为 Redis 分布式锁设置合理的超时时间?#
分析
两个方面,核心是评估业务逻辑:
- 正常情况下业务耗时:估算分布式锁保护的代码块或任务执行时间,包括数据库操作、外部 API 调用等;
- 分布式锁访问耗时:Redis 操作通常很快,但在高延迟环境中,需考虑客户端与 Redis 服务器之间的通信时间;
- 考虑最坏情况:确定任务在最坏情况下的执行时间(如网络抖动、资源竞争等)。
回答
核心是根据业务逻辑来评估,其次是网络延迟等交互消耗,比如业务最大执行时间是 3s,网络延迟为 200 毫秒,多给 1s 缓冲,则超时时间可设置为 4.2 秒,向上取整为 5 秒。
10. 除了 Redis 实现分布式锁,还有哪些方案可以实现分布式锁?各有什么优缺点?#
分析
实现方式很多,大多数组件都可以被利用为分布式锁,常见的如下:
基于数据库(比如 MySQL)的实现
- 实现方式:
- 利用数据库的唯一约束或乐观锁机制(如版本号)来实现分布式锁;
- 例如,创建一个锁表,通过插入一条唯一记录(如锁名称)来获取锁,删除记录来释放锁。
- 优点:
- 实现简单,直接利用现有数据库,无需引入额外组件;
- 对于已经依赖数据库的系统,可以避免额外的依赖。
- 缺点:
- 性能较差,数据库的读写操作在高并发场景下可能成为瓶颈;
- 过期时间实现比较麻烦。
基于 ZooKeeper 的实现
- 实现方式:
- 利用 ZooKeeper 的临时顺序节点(Ephemeral Sequential Node)实现分布式锁;
- 客户端创建一个临时顺序节点,判断自己是否是最小节点,如果是则获取锁;否则监听前一个节点的删除事件。
- 优点:
- 可靠性高,ZooKeeper 的设计保证了强一致性和高可用性;
- 锁的释放由 ZooKeeper 自动处理(临时节点在客户端断开时自动删除),避免死锁;
- 支持公平锁(按顺序获取锁)。
- 缺点:
- 性能较低,ZooKeeper 的写操作需要集群多数节点确认,延迟较高;
- 部署和维护 ZooKeeper 集群的成本较高;
- 对网络分区敏感,可能出现脑裂问题。
基于 Etcd 的实现
- 实现方式:
- 类似于 ZooKeeper,利用 Etcd 的分布式一致性特性实现锁;
- 通过创建带有 TTL(Time-To-Live)的键值对来获取锁,并通过租约机制保证锁的自动释放。
- 优点:
- 强一致性,Etcd 基于 Raft 协议,保证数据一致性;
- 性能优于 ZooKeeper,适合高并发场景;
- 支持自动释放锁(通过 TTL 机制)。
- 缺点:
- 需要额外部署和维护 Etcd 集群;
- 对网络分区敏感,可能出现锁失效的情况。
基于 Consul 的实现
- 实现方式:
- 利用 Consul 的 Key-Value 存储和会话机制实现分布式锁;
- 客户端在 Consul 中创建一个 Key 并绑定会话,会话失效时 Key 自动删除,释放锁。
- 优点:
- 支持高可用和一致性;
- 锁的自动释放机制避免死锁;
- Consul 提供了健康检查和服务发现功能,适合微服务架构。
- 缺点:
- 性能较低,适合低频锁场景;
- 部署和维护 Consul 集群的成本较高。
回答
Redis 性能高,并被最广泛应用,最为推荐;数据库如 MySQL 优势在于不依赖额外组件,缺点是性能差;ZooKeeper 可靠但性能低;Etcd 强一致但部署复杂;Consul 集成服务发现但性能一般;在具体开发工作中,我们需要根据场景选择合适方案。
11. 怎么用 Redis 实现可重入的分布式锁?#
可重入锁即同一个线程或客户端可以多次获取同一把锁,核心目的是允许同一客户端多次获取锁,减少不必要的等待,提升效率,比较适用于嵌套调用或递归场景。
Redis 实现可重入的分布式锁的实现思路如下:
基本思路
- 锁标识:使用 Redis 的 SET 命令设置一个键值对,键为锁的名称,值为持有锁的客户端标识(如 UUID);
- 可重入性:维护一个计数器,记录锁的重入次数。
实现步骤
- 获取锁:使用 SET 命令设置键值对,并设置过期时间,如果键已存在且值为当前客户端标识,则增加重入计数。
if redis.call('set', lock_name, client_id, 'NX', 'EX', 30) then
return 1
elseif redis.call('get', lock_name) == client_id then
redis.call("INCR", "lock_name:count") //比如lock_name为abc,这里计数器就是abc:count
return 1
else
return 0
end
- 释放锁:减少重入计数,如果计数为 0,则删除键。
if redis.call("GET", lock_name) == client_id then
local count = redis.call("DECR", "lock_name:count")
if count == 0 then
redis.call("DEL", KEYS[1])
redis.call("DEL", "lock_name:count")
end
return 1
else
return 0
end
回答
可重入锁的核心是计数,即额外维护一个计数器,记录锁的重入次数,我们拿加锁流程举例,即先尝试 Set 命令(结合 NX 参数),如果不存在就 Set 成功,存在的话则查询是否是我的 id,是的话就增加计数,解锁也是一个思路,加锁解锁都需要用 LUA 保证原子性。
12. 对 Redisson 分布式锁了解多少?(Java)#
分析
Redisson 是一个基于 Redis 的 Java 客户端,提供了封装好的分布式锁功能,这里既然是问了解,我们可以提下他的可重入、自动续期特性,不用主动展开,面试官有兴趣会追问。
回答
我对 Redisson 分布式锁的实现有深入了解。Redisson 是一个基于 Redis 的 Java 客户端,提供了丰富的分布式数据结构和服务,其中分布式锁是其核心功能之一。Redisson 的分布式锁不仅支持可重入,还实现了锁的自动续期(Watchdog 机制)。
13. Redisson 是怎么实现锁自动续期的?(Java)#
分析
- 加锁成功后,Redisson 会启动一个后台线程(Watchdog),定期检查锁的状态并续期;
- 默认情况下,Watchdog 每隔
lockWatchdogTimeout / 3时间(默认 10 秒)执行一次续期操作; - 续期逻辑:
if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then
redis.call('pexpire', KEYS[1], ARGV[1])
return 1
end
return 0
回答
本质就是一个后台线程,我们可以称之为看门狗,定期检查锁的状态并续期,即使不用 Redisson 我们也可以自己通过线程来实现,只是说 Redisson 封装好了这个能力。
14. Redis Redisson 看门狗续期,是用一个后台线程去续期的,如果发生了 GC 停顿,导致这个线程无法执行,导致锁没有续期,这时候怎么办(Java)#
分析
Redis 的看门狗(Watchdog)机制通过后台线程定期续期锁,但如果发生 GC 停顿或线程阻塞,可能导致续期失败,进而引发锁过期,影响系统的可靠性。针对这个问题,Redisson 和其他分布式锁的实现通常会采取以下策略来缓解或解决:
回答
可以有多重策略来应对这个问题:
- 将锁的默认过期时间设置得足够长(例如 30 秒或更长),以减少因 GC 停顿导致锁过期的概率;
- 调整 JVM 参数,减少 Full GC 的频率和时间,但是这种方式影响比前两种方式大,因为会对整个 Java 程序的运行造成影响。
15. Redis 如何解决集群情况下分布式锁的可靠性?#
分析
可以了解红锁算法:
分布式锁算法 Redlock(红锁)。基于多个 Redis 节点的分布式锁,即使有节点发生了故障,锁变量仍然是存在的,客户端还是可以完成锁操作。官方推荐是至少部署 5 个 Redis 节点,而且都是主节点,它们之间没有任何关系,都是一个个孤立的节点。
基本思路:是让客户端和多个独立的 Redis 节点依次请求申请加锁,如果客户端能够和半数以上的节点成功地完成加锁操作,那么我们就认为,客户端成功地获得分布式锁,否则加锁失败。即使有某个 Redis 节点发生故障,锁的数据在其他节点上也有保存,客户端仍然可以正常地进行锁操作,锁的数据也不会丢失。
Redlock 算法加锁三个过程:
- 第一步是,客户端获取当前时间(t1);
- 第二步是,客户端按顺序依次向 N 个 Redis 节点执行加锁操作:加锁操作使用 SET NX,EX/PX 选项,以及带上客户端的唯一标识。如果某个 Redis 节点发生故障了,为了保证在这种情况下,Redlock 算法能够继续运行,需要给「加锁操作」设置一个超时时间,加锁操作的超时时间需要远远地小于锁的过期时间;
- 第三步是,一旦客户端从超过半数(大于等于 N/2+1)的 Redis 节点上成功获取到了锁,就再次获取当前时间(t2),然后计算整个加锁过程的总耗时(t2-t1)。如果 t2-t1 < 锁的过期时间,此时,认为客户端加锁成功,否则认为加锁失败。
加锁成功要同时满足两个条件:有超过半数的 Redis 节点成功的获取到了锁,并且总耗时没有超过锁的有效时间,那么就是加锁成功。
加锁成功后,客户端需要重新计算这把锁的有效时间,计算的结果是「锁最初设置的过期时间」减去「客户端从大多数节点获取锁的总耗时(t2-t1)」。如果计算的结果已经来不及完成共享数据的操作了,可以释放锁,以免出现还没完成数据操作,锁就过期了的情况。加锁失败后,客户端向所有 Redis 节点发起释放锁的操作,执行释放锁的 Lua 脚本就可以。
回答
Redlock 可以增强可靠性,他的思路就是通过多个节点加锁,在超过一半的节点成功才算获得锁。Redlock 相对比较麻烦,但是也无法完全保证可靠,可以说没有完全可靠的分布式锁,实际上,在我接入过的业务中,几乎没有用 Redlock 做分布式锁的,引入更大的复杂度,也无法彻底解决问题。