面试官:分布式锁怎么实现?Redis vs Zookeeper vs 数据库——从原理到 Redisson
引言
"你简历上写了分布式锁,说说怎么实现的?"——这是 Java 后端面试的高频题。大部分候选人的回答停留在"用 Redis 的 SETNX"——面试官追问三个问题就卡住了:锁过期了任务还没执行完怎么办?Redis 主从切换锁丢了怎么办?同一个线程递归调用能不能重入?
这三个问题才是分布式锁的真正考点:SETNX 只是"加锁"这一个动作,而分布式锁的完整语义包括——互斥、防死锁、可重入、防误删、续期、故障容忍。每一项都有对应的坑和解决方案。这篇文章从"为什么需要分布式锁"讲起,对比数据库/Redis/Zookeeper 三种实现的底层原理,再深入 Redisson 看门狗的自动续期机制,最后逐一拆解三大坑的成因与解法。
一、为什么需要分布式锁
1.1 本地锁管不住分布式场景
// 单机时代:synchronized / ReentrantLock 就能解决并发
public synchronized void deductStock(Long skuId) {
Stock stock = stockMapper.selectBySkuId(skuId);
if (stock.getCount() > 0) {
stockMapper.deduct(skuId);
}
}
单机时 JVM 内的锁有效——所有线程共享同一把锁对象。但服务多实例部署后:
实例 A(JVM 1): synchronized 拿到本地锁 → 扣库存
实例 B(JVM 2): synchronized 拿到本地锁 → 扣库存 ← 锁不住!不同 JVM 的锁互不相干
实例 C(JVM 3): synchronized 拿到本地锁 → 扣库存
3 个实例各拿各的锁,库存被扣了 3 遍——超卖
本地锁的可见范围是单个 JVM 进程,跨 JVM/跨机器的互斥需要一个"所有实例都能看到的共享存储"来协调——这就是分布式锁。
1.2 分布式锁的六个必备特性
| 特性 | 含义 | 不满足的后果 |
|---|---|---|
| 互斥性 | 任意时刻只有一个客户端持有锁 | 超卖/重复执行 |
| 防死锁 | 持锁客户端宕机后锁能自动释放 | 锁永久被占,其他客户端永远拿不到 |
| 可重入 | 同一客户端可多次获取同一把锁 | 递归调用/嵌套方法死锁 |
| 防误删 | 只能删自己的锁,不能删别人的 | A 的锁过期被 B 拿到,A 删了 B 的锁 |
| 锁续期 | 任务没执行完时自动延长锁过期时间 | 锁提前过期,并发进入临界区 |
| 故障容忍 | 存储组件主从切换时锁语义尽量不丢 | 主从切换后锁"消失",两个客户端同时持锁 |
后面的方案对比和三大坑,全部围绕这六个特性展开。
二、方案一:数据库锁——最简单但性能最差
2.1 唯一索引方案(悲观锁)
-- 利用唯一索引的冲突拒绝实现互斥
CREATE TABLE distributed_lock (
lock_key VARCHAR(64) NOT NULL PRIMARY KEY, -- 锁名(唯一索引)
holder VARCHAR(64) NOT NULL, -- 持有者标识(实例ID:线程ID)
expire_time DATETIME NOT NULL, -- 过期时间(防死锁)
create_time DATETIME NOT NULL DEFAULT NOW()
);
-- 加锁:插入成功=拿到锁,唯一键冲突=别人持有
INSERT INTO distributed_lock(lock_key, holder, expire_time)
VALUES ('order:create:1001', 'instance-A:thread-3', DATE_ADD(NOW(), INTERVAL 30 SECOND));
-- 成功 → 拿到锁
-- 报错 Duplicate entry → 锁被占用
-- 释放锁:只能删自己的锁(防误删)
DELETE FROM distributed_lock
WHERE lock_key = 'order:create:1001' AND holder = 'instance-A:thread-3';
-- 防死锁:定时清理过期锁(宕机持有者的锁能被回收)
DELETE FROM distributed_lock WHERE expire_time < NOW();
2.2 乐观锁方案(版本号)
-- 用版本号实现 CAS 互斥(适合"更新同一行"场景)
UPDATE stock SET count = count - 1, version = version + 1
WHERE sku_id = 1001 AND version = 17 AND count > 0;
-- affected rows = 1 → 抢到更新权
-- affected rows = 0 → 版本号已变,别人先改了
2.3 数据库锁的优劣
| 维度 | 评价 |
|---|---|
| ✅ 实现简单 | 不引入新组件,有数据库就能用 |
| ✅ 强一致 | 数据库 ACID 保证,锁语义可靠 |
| ❌ 性能差 | 加锁/释放都是磁盘 IO,QPS 上限低(单库 ~1000 TPS) |
| ❌ 单点风险 | 数据库挂了锁全失效(主从切换有延迟) |
| ❌ 续期麻烦 | 要自己写 UPDATE expire_time 的续期 SQL + 定时任务 |
| ❌ 不可重入 | 唯一索引方案天然不可重入,要加可重入计数字段 |
适用场景:并发量低(< 100 QPS)、不想引入 Redis/ZK、团队只有数据库运维能力——比如后台管理系统的低频定时任务防重。高并发场景不要用数据库锁——锁表成为整个系统的瓶颈。
三、方案二:Redis 锁——性能与可靠性的平衡点
3.1 从 SETNX 演进到正确写法
第一代(错误):
SETNX lock 1 ← 加锁
(业务执行中宕机,DEL 没执行)→ 锁永久存在 → 死锁
第二代(仍有漏洞):
SETNX lock 1
EXPIRE lock 30 ← 两条命令非原子!中间宕机一样死锁
第三代(原子命令,正确基础版):
SET lock 1 NX EX 30 ← 加锁+过期一条命令原子完成
// 正确的基础版加锁:SET key value NX EX seconds
public boolean tryLock(String lockKey, String requestId, long expireSeconds) {
Boolean ok = redisTemplate.opsForValue().setIfAbsent(
lockKey, requestId, expireSeconds, TimeUnit.SECONDS);
return Boolean.TRUE.equals(ok);
}
// 释放锁:必须校验 value 是自己的 requestId(防误删)
// 且"判断+删除"要用 Lua 脚本保证原子
private static final String UNLOCK_SCRIPT = """
if redis.call('get', KEYS[1]) == ARGV[1] then
return redis.call('del', KEYS[1])
else
return 0
end
""";
public boolean unlock(String lockKey, String requestId) {
Long result = redisTemplate.execute(
new DefaultRedisScript<>(UNLOCK_SCRIPT, Long.class),
Collections.singletonList(lockKey), requestId);
return Long.valueOf(1).equals(result);
}
3.2 为什么 value 必须是唯一 requestId
时刻 T1: A 拿到锁,requestId=A,过期时间 30s
时刻 T31: 锁过期(A 的任务还没执行完)
时刻 T32: B 拿到同一把锁,requestId=B
时刻 T35: A 执行完,执行 DEL lock ← 删的是 B 的锁!
时刻 T36: C 拿到锁 → B 和 C 同时持锁 → 互斥被打破
解法:DEL 前先 GET 校验 value == A 的 requestId
且 GET+DEL 用 Lua 脚本原子执行(否则 GET 后、DEL 前锁刚好过期又被别人拿到)
requestId 通常用 UUID + 线程ID——全局唯一标识锁的持有者。
3.3 Redis 锁的优劣
| 维度 | 评价 |
|---|---|
| ✅ 性能高 | 内存操作,单实例 10 万+ QPS |
| ✅ 实现相对简单 | SET NX EX + Lua 释放,几十行代码 |
| ✅ 生态成熟 | Redisson 提供生产级实现(见第五章) |
| ❌ 续期要自己做 | 裸 SETNX 没有自动续期,锁过期了任务没完就出并发 |
| ❌ 主从切换丢锁 | 锁写到 Master 后异步同步到 Slave,Master 宕机时锁可能没同步过去 |
| ⚠️ 非强一致 | Redis 是 AP 模型,极端故障下锁互斥可能被打破(见第七章坑 2) |
四、方案三:Zookeeper 锁——强一致但重
4.1 临时顺序节点原理
锁节点路径:/locks/order_lock/
客户端 A 加锁:
创建临时顺序节点 /locks/order_lock/node_0000000001
→ 列出所有子节点 → 自己是最小的 → 拿到锁
客户端 B 加锁:
创建临时顺序节点 /locks/order_lock/node_0000000002
→ 列出所有子节点 → 前面有 node_1 → 不是最小
→ 在 node_1 上注册 watcher(只监听前一个节点,避免惊群)
→ 等待 node_1 删除
A 释放锁:删除 node_1(或 A 宕机,临时节点随 session 断开自动删除)
→ B 的 watcher 被触发 → B 检查发现自己变成最小 → 拿到锁
// Curator 框架的标准写法(不用自己处理节点/watcher 细节)
InterProcessMutex lock = new InterProcessMutex(client, "/locks/order_lock");
// 尝试加锁(等待最多 10 秒)
boolean acquired = lock.acquire(10, TimeUnit.SECONDS);
if (acquired) {
try {
// 业务逻辑
} finally {
lock.release(); // 释放锁
}
}
4.2 ZK 锁的四个特性如何被天然满足
| 特性 | ZK 的实现机制 |
|---|---|
| 互斥 | 只有序号最小的节点持锁 |
| 防死锁 | 临时节点(EPHEMERAL):客户端 session 断开节点自动删除 |
| 可重入 | Curator 的 InterProcessMutex 内部记录本线程重入次数,同一 JVM 可重入 |
| 防惊群 | 只 watch 前一个节点,而非所有节点——锁释放时只唤醒一个等待者 |
4.3 三种方案横向对比
| 维度 | 数据库 | Redis | Zookeeper |
|---|---|---|---|
| 一致性 | 强一致 | AP(异步复制) | CP(Zab 协议强一致) |
| 性能(加锁 QPS) | ~1K | 10 万+ | ~1~2 万(写 Leader + 半数确认) |
| 防死锁 | 自己写过期清理 | TTL 过期 | 临时节点自动删除 |
| 锁等待 | 轮询 SQL | 自旋/订阅 | watcher 事件通知(不轮询) |
| 运维成本 | 低(已有 DB) | 低(已有 Redis) | 高(独立 ZK 集群,至少 3 节点) |
| 典型实现 | 唯一索引/版本号 | Redisson | Curator |
| 适用 | 低并发 | 高并发 + 容忍极端故障 | 高并发 + 要求强一致 |
一句话选型:90% 的业务场景用 Redis(Redisson)——性能够高、生态成熟、团队基本都有 Redis;对锁正确性要求到"宁可拒绝服务也不能并发进入"的金融级场景(如资金账户操作),考虑 ZK;数据库锁只在低频场景用。
五、Redisson:生产级 Redis 锁的标准答案
5.1 最简用法
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.31.0</version>
</dependency>
@Service
@RequiredArgsConstructor
public class OrderService {
private final RedissonClient redissonClient;
public void createOrder(Long userId) {
String lockKey = "lock:order:create:" + userId;
RLock lock = redissonClient.getLock(lockKey);
boolean acquired = false;
try {
// tryLock(等待时间, 锁过期时间, 单位)
// 不传锁过期时间 → 启用看门狗(默认 30s,自动续期)
acquired = lock.tryLock(10, TimeUnit.SECONDS);
if (!acquired) {
throw new BizException("操作太频繁,请稍后重试");
}
// 业务逻辑(耗时可能超过 30 秒也不怕,看门狗续期)
doCreateOrder(userId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new BizException("加锁被中断");
} finally {
// 必须校验是当前线程持有才释放(否则抛 IllegalMonitorStateException)
if (acquired && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
5.2 看门狗(WatchDog)自动续期机制
加锁(不指定 leaseTime):
→ 锁的过期时间 = 30s(lockWatchdogTimeout 默认值)
→ 启动一个定时任务(Netty 的 HashedWheelTimer 时间轮)
→ 每 10s(看门狗超时的 1/3)检查一次:
如果当前线程还持有锁 → 把过期时间重置回 30s
→ 业务执行多久,锁就续多久(不会出现"锁过期了任务没完")
释放锁 / 客户端宕机:
→ 正常:unlock 删除 key + 取消续期任务
→ 宕机:续期任务随进程消失 → key 在 30s 后自动过期 → 防死锁
时间线:
T0 加锁,TTL=30s,看门狗启动
T10 看门狗续期,TTL 重置为 30s(实际剩余 30s)
T20 看门狗续期,TTL=30s
T30 看门狗续期,TTL=30s
T45 业务执行完毕 unlock,key 删除,看门狗停止
如果 T20 业务实例宕机:
T20 续期任务停止 → T30(key 还剩 10s)后 key 过期 → 其他实例可拿锁
关键细节:
| 细节 | 说明 |
|---|---|
| 看门狗只在不传 leaseTime 时生效 | tryLock(10, 30, SECONDS) 指定了 30s → 锁 30s 后必过期,不续期 |
| 续期间隔 = 看门狗超时 / 3 | 默认 30s / 3 = 10s 续一次,留两次容错 |
| 看门狗续的是"存在性" | 续期操作用 Lua 脚本:只有 key 还属于当前线程才续(防止误续别人的锁) |
| 锁可重入 | Redisson 锁的 value 是 Hash:field=客户端ID:线程ID,value=重入次数,重入 +1,解锁 -1,归零删 key |
5.3 Redisson 锁的存储结构
# 可重入锁在 Redis 里是一个 Hash(不是简单 String)
Key: lock:order:create:1001
Field: 9f8e7d6c...:thread-3 (Redisson 客户端 UUID + 线程 ID)
Value: 2 (重入次数)
TTL: 30s(看门狗持续续期)
加锁 Lua 脚本核心逻辑(简化):
if not exists(key) → 没人持有 → HSET key field 1 + PEXPIRE
if field == currentClientThread → 自己重入 → HINCRBY field 1 + PEXPIRE
else → 别人持有 → 返回 TTL(加锁失败,客户端按 TTL 等待)
5.4 Redisson 还提供了什么
| 锁类型 | 适用场景 |
|---|---|
| RLock(可重入锁) | 通用互斥,默认选择 |
| RReadWriteLock(读写锁) | 读多写少:读读共享,读写/写写互斥 |
| RFairLock(公平锁) | 按请求顺序拿锁,防饥饿(性能略低) |
| RCountDownLatch | 分布式闭锁:N 个任务都完成后继续 |
| RSemaphore | 分布式信号量:限流(如限制 DB 并发连接数) |
| RMultiLock(红锁 RedLock 的实现) | 多个独立 Redis 实例同时加锁,应对主从切换丢锁 |
六、分布式锁三大坑
坑 1:锁过期了,任务还没执行完
T0 A 拿到锁,TTL=30s
T30 锁过期(A 的批处理任务跑了 40 秒还没完)
T31 B 拿到同一把锁 → A 和 B 同时在临界区执行 → 超卖/重复扣款
两种解法:
| 解法 | 说明 |
|---|---|
| 看门狗自动续期(推荐) | Redisson 不传 leaseTime,任务不结束锁不过期;宕机后 30s 自动释放 |
| 合理评估过期时间 | 按任务 P99 耗时的 2~3 倍设 leaseTime——但无法覆盖极端长尾,治标 |
注意:看门狗不是万能——GC 停顿超过 30s(如大堆 Full GC)时续期任务也会被暂停,key 一样过期。极端场景把 lockWatchdogTimeout 调大,或配合幂等设计兜底(锁是效率手段,幂等才是正确性底线)。
坑 2:主从切换,锁丢了
T0 A 向 Master 申请锁 SET lock A NX EX 30 → 成功
T0.1 Master 还没把锁同步给 Slave(Redis 主从复制是异步的)
T0.2 Master 宕机
T1 Sentinel 把 Slave 提升为新 Master(新 Master 上没有 lock 这个 key)
T2 B 向新 Master 申请同一把锁 → 成功!
T3 A 和 B 同时持锁 → 互斥失效
三种应对:
| 方案 | 做法 | 代价 |
|---|---|---|
| RedLock(红锁) | 在 N(通常 5)个独立 Master 上加锁,超过半数(3)成功才算拿到 | 部署成本高、争议大(网络分区/时钟漂移下仍有质疑)、性能下降 |
| wait 复制确认 | 用 WAIT 命令等锁写入复制到指定数量从节点再返回 | 牺牲可用性,WAIT 不保证严格一致 |
| 接受 + 幂等兜底(工程主流) | 承认 Redis 锁在极端故障下可能失效,业务层做幂等(唯一键/状态机) | 成本最低,正确性由业务幂等保证 |
工程实践的普遍选择是第三种:Redis 分布式锁用于"防 99.9% 的并发重复"(效率),业务幂等保证"极端情况下也不出资损"(正确性)。真正强一致的资金核心操作走数据库唯一约束/状态机,不依赖锁。
坑 3:不可重入导致的自锁死锁
// 场景:加锁方法内部调用了另一个也要加同一把锁的方法
public void methodA() {
lock.lock();
try {
methodB(); // methodB 内部也要获取同一把锁
} finally {
lock.unlock();
}
}
public void methodB() {
lock.lock(); // ← 如果锁不可重入:自己等自己释放,永久死锁
// ...
}
解法:
- Redisson 的 RLock 天然可重入——基于线程标识 + Hash 重入计数,同一线程重复加锁只是计数 +1
- 自研锁必须在 value 里记录持有者(客户端+线程),重入时识别"持有者是自己"并计数,不能只存一个 "1"
- 注意陷阱:
@Transactional方法里加锁/锁内开事务可能让"锁释放在事务提交前"——锁先放、事务后提交,并发请求在事务提交前读到旧数据。正确顺序是锁包在事务外层(锁获取 → 开事务 → 提交事务 → 释放锁)。
七、常见问题
7.1 Redisson 看门狗续期失败怎么办?
看门狗续期是"尽力而为"——续期 Lua 脚本执行失败(网络抖动/Redis 短暂不可用)时 Redisson 会重试,但如果 Redis 长时间不可达,续不上期 key 就会过期。此时任务还在跑但锁没了——所以业务临界区必须有幂等兜底,看门狗降低概率但不承诺绝对。监控上可以订阅 Redisson 的 lock expired 事件打告警,出现续期失败立刻排查。
7.2 锁的等待时间(waitTime)设多少?
按"临界区平均执行时间 × 预估并发数"估算:临界区平均 200ms,最多 10 个并发排队 → waitTime 设 2~3 秒。拿不到锁快速失败比长时间阻塞好——用户等 10 秒拿到锁时上游网关早就超时了。配合"快速失败 + 友好提示"("操作太频繁,请稍后重试")比死等体验更好。
7.3 Redis 集群模式(Cluster)下锁 key 怎么处理?
Redisson 在 Cluster 模式下按 key 的 hash slot 定位节点——同一把锁的 key 落在固定 slot,加锁/续期/释放都打到同一节点,语义和单机一致。注意 hash tag:如果多把锁需要落在同一节点(如 RedLock 或批量操作),用 lock:{order:1001}:a、lock:{order:1001}:b 的 {} 语法强制同 slot。
7.4 RedLock 到底该不该用?
Martin Kleppmann(《DDIA》作者)与 antirez(Redis 作者)有过著名论战:Kleppmann 认为 RedLock 在 GC 停顿、时钟跳变下仍不安全,且依赖各节点时钟同步;antirez 认为在合理运维下 RedLock 提供了足够的概率保证。实践共识:RedLock 部署复杂(5 个独立 Master)、性能损耗大,而它防的主从切换丢锁是低频事件——绝大多数团队选择"单集群 Redisson 锁 + 业务幂等兜底"。只有当你已经有 5 个独立 Redis 集群且确实不能接受极端故障时才考虑。
7.5 分布式锁能替代 synchronized 吗?
不能互相替代,是两层防线:synchronized/ReentrantLock 防单机内多线程并发(无网络开销,纳秒级),分布式锁防跨实例并发(有网络开销,毫秒级)。典型用法:外层分布式锁保证跨实例互斥,进入临界区后对本地共享资源仍可用本地锁保护。不要为了"统一"把单机并发也上分布式锁——10 万 QPS 的本地锁场景换 Redis 锁,Redis 直接被打爆。
7.6 Zookeeper 临时节点为什么比 Redis TTL 更可靠?
ZK 临时节点的生命周期绑定 session:客户端与 ZK 集群保持心跳(session alive)节点就存在,session 超时(心跳断了超过 sessionTimeout)节点才被删除。这是集群通过 Zab 协议达成一致后删除的——不存在"主节点单方面有锁、从节点没有"的窗口期。而 Redis TTL 是基于时间的过期,主从异步复制存在"锁已写 Master 未同步 Slave"的窗口。代价是 ZK 每次加锁都要 Leader 写 + 半数 Follower 确认(至少一次磁盘 fsync),性能低于 Redis 内存写。
八、总结
三方案速查卡
┌────────────┬──────────┬──────────────┬──────────────┐
│ 维度 │ 数据库 │ Redis │ Zookeeper │
├────────────┼──────────┼──────────────┼──────────────┤
│ 一致性 │ 强 │ AP(极端丢锁)│ CP(强一致) │
│ 加锁性能 │ ~1K QPS │ 10万+ QPS │ 1~2万 QPS │
│ 防死锁 │ 过期清理 │ TTL+看门狗 │ 临时节点自动删│
│ 等待机制 │ 轮询 │ 自旋/订阅 │ watcher 通知 │
│ 运维成本 │ 低 │ 低 │ 高 │
│ 实现 │ 唯一索引 │ Redisson │ Curator │
│ 适用 │ 低频 │ 90% 业务 │ 金融强一致 │
└────────────┴──────────┴──────────────┴──────────────┘
Redisson 看门狗:不传 leaseTime → 默认 30s,每 10s 检查续期
三大坑:锁过期任务没完(看门狗) / 主从切换丢锁(幂等兜底) / 不可重入(RLock 天然可重入)
一句话
分布式锁的考点从来不是 SETNX 这个命令,而是互斥、防死锁、可重入、防误删、续期、故障容忍六个特性的完整闭环:数据库唯一索引强一致但性能只有千级 QPS 只适合低频场景;Redis SET key NX EX + Lua 原子释放是基础版,生产直接用 Redisson——看门狗机制在不传 leaseTime 时每 10 秒把锁续回 30 秒,任务不结束锁不过期、实例宕机 30 秒后自动释放;Zookeeper 用临时顺序节点 + watcher 天然解决防死锁和锁等待,强一致但运维重。三大坑要记住:锁过期任务没完靠看门狗但要防 GC 停顿,主从切换丢锁靠业务幂等兜底而不是迷信 RedLock,可重入靠 Redisson 的 Hash 重入计数。最后一句话送给所有候选人:锁是效率手段,幂等才是正确性底线——资金类操作永远以数据库唯一约束为准,不要把命押在分布式锁上。
给团队的建议
| 项 | 建议 |
|---|---|
| 默认选择 | Redisson RLock(tryLock + 看门狗 + isHeldByCurrentThread 释放) |
| 锁粒度 | Key 带业务维度(lock:order:create:{userId}),别用全局大锁 |
| 超时参数 | waitTime 按临界区耗时×并发估算,快速失败优于长等 |
| 事务顺序 | 锁在事务外层:加锁 → 事务 → 提交 → 释放 |
| 正确性底线 | 业务幂等(唯一键/状态机)兜底锁失效,资金操作不信锁 |
| 强一致场景 | 资金核心走 ZK 或数据库约束,Redis 锁用于高并发防重 |
| 监控 | 加锁失败率、持锁时长 P99、看门狗续期失败事件 |
互动话题:你们生产环境用的哪种分布式锁?遇到过锁续期失败或主从切换丢锁的真实事故吗?RedLock 在评论区吵了很多年,你站哪边?
参考资料
- Redisson 官方文档:分布式锁和同步器
- Redis 官方文档:SET 命令(NX/EX 选项)
- Martin Kleppmann:How to do distributed locking(RedLock 质疑)
- antirez:RedLock 安全性分析(回应)
- Apache Curator 官方文档(InterProcessMutex)
- Zookeeper 临时节点与 watcher 机制
标题:面试官:分布式锁怎么实现?Redis vs Zookeeper vs 数据库——从原理到 Redisson
作者:jiangyi
地址:http://jiangyi.space/articles/2026/09/17/1789224193026.html
公众号:服务端技术精选
- 引言
- 一、为什么需要分布式锁
- 1.1 本地锁管不住分布式场景
- 1.2 分布式锁的六个必备特性
- 二、方案一:数据库锁——最简单但性能最差
- 2.1 唯一索引方案(悲观锁)
- 2.2 乐观锁方案(版本号)
- 2.3 数据库锁的优劣
- 三、方案二:Redis 锁——性能与可靠性的平衡点
- 3.1 从 SETNX 演进到正确写法
- 3.2 为什么 value 必须是唯一 requestId
- 3.3 Redis 锁的优劣
- 四、方案三:Zookeeper 锁——强一致但重
- 4.1 临时顺序节点原理
- 4.2 ZK 锁的四个特性如何被天然满足
- 4.3 三种方案横向对比
- 五、Redisson:生产级 Redis 锁的标准答案
- 5.1 最简用法
- 5.2 看门狗(WatchDog)自动续期机制
- 5.3 Redisson 锁的存储结构
- 5.4 Redisson 还提供了什么
- 六、分布式锁三大坑
- 坑 1:锁过期了,任务还没执行完
- 坑 2:主从切换,锁丢了
- 坑 3:不可重入导致的自锁死锁
- 七、常见问题
- 7.1 Redisson 看门狗续期失败怎么办?
- 7.2 锁的等待时间(waitTime)设多少?
- 7.3 Redis 集群模式(Cluster)下锁 key 怎么处理?
- 7.4 RedLock 到底该不该用?
- 7.5 分布式锁能替代 synchronized 吗?
- 7.6 Zookeeper 临时节点为什么比 Redis TTL 更可靠?
- 八、总结
- 三方案速查卡
- 一句话
- 给团队的建议
- 参考资料
评论