1142 字
6 分钟
Redis 中如何实现分布式锁?
  1. 1Redis 系列导读:知识地图、必会主线与复习顺序
  2. 2Redis 中常见的数据类型有哪些?
  3. 3Redis 通常应用于哪些场景?
  4. 4Redis为什么这么快?
  5. 5Redis 常用的过期时间、删除 Key 命令有哪些?
  6. 6Redis 的过期 Key 是如何被删除的?
  7. 7Redis 内存满了怎么办?有哪些淘汰策略?
  8. 8缓存穿透、击穿、雪崩的区别与解决方案
  9. 9Redis 如何保证缓存与数据库的数据一致性?
  10. 10Redis 的持久化机制有哪些?
  11. 11Redis 主从复制的完整流程是什么?
  12. 12Redis 的哨兵机制是什么?
  13. 13Redis Cluster 模式与 Sentinel 模式的区别是什么?
  14. 14Redis 支持事务吗?如何实现?
  15. 15Redis 中如何实现分布式锁?本文
  16. 16Redis 的 Lua 脚本功能是什么?如何使用?
  17. 17如何解决 Redis 中的热点 Key 问题?
  18. 18Redis BigKey 有什么危害?如何发现和治理?
  19. 19为什么 Redis 设计为单线程?6.0 版本为何引入多线程?
  20. 20Redis 的 LRU 和 LFU 有什么区别?
  21. 21Redis 数据类型和底层编码有什么区别?
  22. 22Redis 中跳表的实现原理是什么?
  23. 23Redis 的 ZSet 为什么使用跳表,而不是平衡树?
  24. 24Redis 的 RDB 快照如何生成?为什么会有写时复制?
  25. 25Redis 的 AOF 刷盘和重写有什么区别?
  26. 26Redis 断线后如何通过 PSYNC 部分重同步?
  27. 27Redis 主从切换为什么会丢数据?WAIT 能解决吗?
  28. 28Redis 集群会出现脑裂问题吗?
  29. 29Redisson 看门狗(watch dog)机制了解吗?
  30. 30Redis Cluster:哈希槽、Gossip 与故障转移
  31. 31Redis Pipeline 为什么能提高吞吐?能保证原子性吗?
  32. 32Redis 的 SCAN 和 KEYS 有什么区别?
  33. 33Redis 变慢时如何排查?SLOWLOG 能看到所有耗时吗?
  34. 34Redis 和 MySQL 有什么区别?
  35. 35Redis 为什么使用 SDS,而不是 C 字符串?
  36. 36Redis 的哈希表如何处理冲突和渐进式 rehash?
  37. 37Redis Cluster 的 MOVED 和 ASK 重定向有什么区别?
  38. 38Redis Cluster 为什么会报 CROSSSLOT?Hash Tag 怎么用?
  39. 39Redis 如何实现限流?固定窗口和滑动窗口怎么选?
  40. 40Redis Stream 如何保证消息消费?ACK 和 PEL 是什么?
  41. 41Redis 删除数据后为什么内存不下降?如何理解内存碎片?

一、为什么需要分布式锁?#

假设你开发了一个电商系统,商品库存只剩 1 件,两个请求同时购买。

分布式锁竞争示意

如果只有一个 Java 进程,可以考虑 synchronized 或 ReentrantLock。

但是,如果服务部署了三个实例:

  • Java 实例 A 有自己的锁。
  • Java 实例 B 有自己的锁。
  • Java 实例 C 也有自己的锁。

它们的锁互不相通,无法协调所有实例。

所以,需要一个所有实例都能访问的公共锁服务,Redis 就可以承担这个角色。

二、Redis 分布式锁怎么实现?#

最基础的实现是使用 Redis 的 SET 命令:

SET lock:product:1001 unique-token NX EX 30

这条命令包含四个关键部分:

部分含义
lock:product:1001锁的名称
unique-token当前持有者的唯一标识
NX只有 Key 不存在时才设置成功
EX 30锁在 30 秒后过期

加锁过程#

假设请求 A 和请求 B 同时抢同一把锁。

分布式锁获取示意

这里最重要的是:加锁必须通过一条原子命令完成。

不能先执行 EXISTS 检查锁是否存在,再执行 SET,因为两个请求可能同时检查到锁不存在,随后都加锁成功。

三、为什么需要唯一标识?#

假设请求 A 获得了锁,但业务执行超过 30 秒,锁自动过期了。

此时请求 B 成功获得了同一把锁。

如果请求 A 执行完后,直接执行:

DEL lock:product:1001

就会把请求 B 的锁也删除!

所以,锁必须带有唯一标识,释放锁时必须确认自己仍然是锁的持有者。

例如:

请求 A 获得锁:token-A
请求 B 获得锁:token-B

释放锁时,先判断锁的值是否等于自己的 token,只有相等才能删除。

通常使用 Lua 脚本原子完成比较和删除:

if redis.call("GET", KEYS[1]) == ARGV[1] then
return redis.call("DEL", KEYS[1])
else
return 0
end

这里:

  • KEYS[1]:锁的 Key。
  • ARGV[1]:当前请求持有的唯一标识。
  • 相等:删除锁。
  • 不相等:不删除,避免误删其他请求的锁。

为什么要用 Lua?因为比较和删除必须作为一个不可分割的操作执行,否则可能在比较后、删除前发生锁被其他请求重新获取的情况。

四、锁过期了,但业务还没执行完怎么办?#

这是分布式锁的另一个重要问题。

例如:

  1. 请求 A 获得锁,有效期 30 秒。
  2. 请求 A 执行业务,需要 40 秒。
  3. 第 30 秒时,锁过期。
  4. 请求 B 获得锁,开始执行业务。
  5. 请求 A 仍然没有执行完。

此时 A、B 可能同时操作共享资源。

常见解决方式是 Redisson 看门狗机制。

  • 使用 Redisson 获取锁时,如果没有显式指定租约时间,且看门狗机制启用,Redisson 会定期续期。
  • 只要持锁客户端仍然正常运行并满足续期条件,就会延长锁的有效期。
  • 客户端异常退出后,续期停止,锁最终会过期释放。

注意:看门狗不能彻底解决所有并发安全问题。对于执行时间不确定、可能发生长时间暂停的业务,还可以考虑使用** fencing token(栅栏令牌)**等机制保护资源。

五、面试时怎么回答?#

Redis 分布式锁通常通过 SET key value NX EX 实现。

首先,使用 SET 命令原子地完成加锁,其中 NX 表示 Key 不存在时才加锁成功,EX 用于设置过期时间,避免客户端异常退出后锁一直无法释放。

其次,锁的 value 必须使用唯一标识,防止其他请求误释放自己的锁。释放锁时,通常通过 Lua 脚本原子地比较锁的持有者标识并删除锁。

另外,如果业务执行时间超过锁的过期时间,可能出现多个请求同时执行业务的问题。可以使用 Redisson 的看门狗机制自动续期;对于要求更高的场景,还需要考虑锁过期、客户端暂停和资源保护等问题。

六、记住这四个关键词#

  • SET NX EX:原子加锁,并设置过期时间。
  • 唯一标识:防止误删别人的锁。
  • Lua 脚本:原子地校验并释放锁。
  • Redisson 看门狗:在满足条件时自动续期。

这四个知识点掌握好,Redis 分布式锁的基础面试题基本就有了回答框架。

Redis 中如何实现分布式锁?
https://blog.xqcherry.top/posts/interview/redis/redis-distributed-lock/
作者
xqcherry
发布于
2026-10-12
许可协议
CC BY-NC-SA 4.0