Redis单节点锁在网络分区下必然失效,因客户端无法确认锁写入状态且主从切换后新主未同步旧锁,导致两个客户端同时持锁,违反互斥性。

网络分区下 Redis 单节点锁为什么必然失效
Redis 单实例锁在分区时完全不可信:一旦客户端与 Redis 之间出现网络中断,客户端既无法确认锁是否已成功写入,也无法感知锁是否已被其他节点释放。更危险的是,主从切换后,新主可能没同步到旧锁状态,导致两个客户端同时持有同一把锁——SETNX 的原子性只在单节点内成立,跨节点不保证。
常见错误现象包括:秒杀超卖、定时任务重复执行、订单状态被并发覆盖。这不是代码 bug,而是模型缺陷。
- 不要用
redis://localhost:6379模拟集群来测试分区行为——本地 loopback 不会触发真实分区 - 别依赖
DEL直接删 key 解锁:没有所有权校验,任何客户端都能删掉别人的锁 - 避免把锁过期时间设成固定值(如
10 * time.Second)而不评估业务最大耗时,否则锁提前释放是大概率事件
Redlock 在分区场景下的真实安全边界
Redlock 不是“自动防分区”的银弹。它只在满足三个前提时才提供比单节点更强的容错能力:
- 至少
3个物理隔离的 Redis 实例(不能是同一集群的主从副本) - 加锁总耗时
< ttl,且每个节点响应延迟稳定(建议retryDelay = 100ms,retryTimes = 3) - 业务能容忍“最多一次”不一致:Redlock 允许极小概率下两个客户端同时获得锁(比如刚好卡在多数派达成前发生分区)
如果你的部署环境无法保证节点间时钟漂移 < 50ms(例如跨可用区、不同云厂商),或者锁有效期 <= 2s,Redlock 的安全假设就已崩塌——它依赖各节点对“当前时间”的共识。
立即学习“go语言免费学习笔记(深入)”;
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
etcd Lease + Txn 是目前最稳的分区应对方案
etcd 的 Lease 机制天然适配网络分区:租约由服务端续期,客户端只需定期心跳;即使客户端失联,服务端在 lease 过期后自动清理 key,无需依赖客户端主动解锁。
关键实操点:
- 加锁必须用
Txn:Compareversion == 0(key 不存在) + OpPut 绑定 lease ID,两步不可拆分 - lease TTL 必须由
client.Grant(ctx, ttl)申请,不能手动生成时间戳或随机数 - Watch 节点变更时,回调里只做
Lock()重试,不要做 DB 查询或 HTTP 调用——否则 Watcher goroutine 会堆积阻塞
注意:WithPrevKV 可以在抢锁失败时直接拿到当前持有者的 value,方便定位谁卡住了锁,但这个 value 必须包含可识别的 client ID,否则无意义。
ZooKeeper 临时顺序节点的公平性代价
ZooKeeper 的 ephemeral sequential node 确保了强一致性与自动清理,但它在分区时的表现取决于 session timeout 设置:
- session timeout 太短(如
5s),网络抖动就会误判客户端死亡,触发不必要的锁释放 - session timeout 太长(如
60s),真实故障后锁释放延迟过高,影响业务 SLA - Watcher 回调不是事务性的:监听
/locks/lock-0000000001的客户端,在前序节点删除后收到通知,但此时自己还没真正拿到锁,中间存在竞态窗口
真正容易被忽略的是:ZooKeeper 的顺序节点序号是全局递增的,但创建节点本身不是原子操作——如果客户端在创建节点后、写入 value 前崩溃,该节点会残留且无法被后续客户端识别为有效锁持有者。

















