目录

style="margin-left:40px">一、背景:为什么需要分布式锁?
style="margin-left:40px">二、RedLock
算法原理
style="margin-left:80px">核心思想
style="margin-left:80px">加锁流程(5步)
style="margin-left:80px">容错性分析
style="margin-left:40px">三、RedLock
style="margin-left:80px">Redisson
style="margin-left:80px">适用场景
style="margin-left:80px">实践建议
style="margin-left:40px">四、为什么官方不推荐?Martin
Kleppmann
style="margin-left:80px">质疑一:时间假设的脆弱性
style="margin-left:80px">质疑二:没有
Fencing
style="margin-left:80px">质疑三:Redis
style="margin-left:40px">五、现代分布式锁的最佳实践
style="margin-left:80px">选型决策树
Fencing
style="margin-left:40px">六、总结
/>
一、背景:为什么需要分布式锁?
在单机系统中,我们用synchronized、ReentrantLock等手段解决并发问题。
但在分布式环境下,多个进程运行在不同的机器上,JVM
级别的锁完全失效。
我们需要一种跨进程、跨机器的互斥机制——分布式锁。
分布式锁的核心诉求只有三点:
- 互斥性(Mutual
Exclusion)
:同一时刻,只有一个客户端持有锁 - 安全释放(Safe
Release)
:锁只能被持有者释放,不能被其他人抢走 - 避免死锁(Deadlock
Avoidance)
:持有者崩溃后,锁能自动释放
早期最简单的
Redis
分布式锁方案是:
SETlock_key
30000
NX保证只有
key
不存在时才能设置成功(互斥),PX
30000设置
秒过期(防死锁)。
这个方案在单节点
Redis
本身是单点——一旦宕机,锁就没了,主从切换时还可能出现"锁消失而客户端还认为自己持有锁"的幻觉。
为了解决这一问题,Redis
Salvatore
Sanfilippo(antirez)提出了RedLock
/>二、RedLock
算法原理
核心思想
RedLock
的核心思路是多数派(Quorum)投票。
它要求部署N
Redis
主节点,这些节点之间没有任何主从复制关系,完全独立运行。
加锁时,客户端必须在超过半数(N/2
+
个)节点上成功加锁,才算真正获得了锁。
这借鉴了
Paxos/Raft
的多数派思想:只要多数节点存活,系统就能正确工作。
加锁流程(5步)
第一步:记录当前时间戳
start_time=
current_time_ms()
第二步:依次向
Redis
和一个**全局唯一的随机值(UUID)**作为
value,并设置一个远小于锁过期时间的超时时间(例如锁过期
30s,单节点请求超时设为
5ms):
fornode
timeout=5ms)
使用极短的请求超时是关键:避免某个节点挂掉后,客户端长时间阻塞在那一个节点上。
第三步:计算实际耗时,验证是否成功
elapsed=
LockResult(success=False)
clock_drift是时钟漂移因子,通常取锁过期时间的
2%,用于补偿不同机器间的时钟误差。
第四步:执行业务逻辑
客户端在validity_time内完成业务,如果业务执行时间超过有效期,需要放弃操作。
第五步:释放锁
向所有节点发送释放命令,使用
Lua
脚本保证原子性:
--value
end
容错性分析
- 如果少于
N/2个节点挂掉,加锁请求可以在剩余节点上完成多数派,系统继续工作 - 5
个节点同时故障
- 即使某个节点重启,由于锁有过期时间,重启后的节点不会立即颠覆之前的决策
/>三、RedLock
生态中,Redisson是最成熟的
RedLock
config1.useSingleServer().setAddress("redis://192.168.1.1:6379");
RedissonClient
client1.getLock("myLock");
RLock
client2.getLock("myLock");
RLock
client3.getLock("myLock");
RLock
client4.getLock("myLock");
RLock
client5.getLock("myLock");
RedissonRedLock
}
适用场景
RedLock
适合以下场景:
- 防重复提交:支付场景下防止同一订单被处理两次
- 限流与配额:分布式环境下的资源争抢控制
- 任务调度互斥:分布式
Job
中同一任务只能被一个节点执行
- 秒杀库存扣减:高并发下的库存超卖防护
实践建议
锁过期时间设置:锁的过期时间必须大于业务最长执行时间,同时留有足够的余量。
建议:过期时间
=
生成:每次加锁必须生成全局唯一的
value(UUID
ID),这是"锁只能被持有者释放"的保障。
失败重试:加锁失败后应随机等待一段时间(如random(0,
lock_ttl
10))再重试,避免多个客户端同时重试形成活锁。
Kleppmann
年,《数据密集型应用系统设计》(DDIA)的作者Martin
Kleppmann发表了一篇著名的博客文章,对
RedLock
发起了系统性的批判。
这场争论成为分布式系统领域最具影响力的公开辩论之一。
质疑一:时间假设的脆弱性
RedLock
的安全性强依赖于各节点时钟的相对准确性。
算法依靠"锁的剩余有效时间
>
0"来判断锁是否还在有效期内。
但在真实系统中:
- 时钟可能因
NTP
GC、虚拟机迁移、内核调度等情况下可能产生进程暂停(Process
Pause)
考虑以下场景:
T0:Client
中恢复,依然认为自己持有锁
此时,Client
Client
同时认为自己持有锁,互斥性被打破。
这个问题的本质是:分布式锁的安全性不能依赖于时间假设,因为"时间"在分布式系统中是不可靠的。
质疑二:没有Fencing
指出,正确使用分布式锁的模式应该是这样的:
客户端持锁向资源服务器发送操作请求
token
Fencing
Token是一个单调递增的数字,每次成功获取锁时递增。
资源服务器通过比较
token
大小,可以拒绝"迟到的"旧锁请求,即使客户端误以为自己仍持有锁。
RedLock
没有提供
机制,因此在面对进程暂停时,即使算法本身是正确的,也无法保护下游资源。
质疑三:RedisSentinel
后,如果还未同步到从节点就崩溃,此时发生故障转移,从节点升主,锁数据会丢失。
虽然
RedLock
使用多个独立节点规避了这一问题,但它要求运维部署和维护
Redis
实例,成本相当高。
而且,任何一个节点发生以下情况都可能影响安全性:
- 时钟漂移
- 网络分区
- 持久化配置不当(RDB
在重启后可能遗失锁数据)
antirez
RedLock
中启用fsync强制持久化来应对重启问题,但这又会显著降低
Redis
的性能,与其高性能的定位背道而驰。
安全性FLP
理论告诉我们:在网络异步模型中,不可能同时满足安全性(Safety)和活性(Liveness)。
- Zookeeper的分布式锁(基于
ZAB
协议,线性一致性)在网络分区时选择牺牲可用性,保证安全性
- RedLock在某些边界条件下为了可用性,悄悄牺牲了安全性——而且这种牺牲是隐式的、难以察觉的
这是
Martin
给用户一种"强安全性"的错觉,但实际上在极端情况下并不安全。
/>
五、现代分布式锁的最佳实践
选型决策树
你需要分布式锁对强一致性要求极高(金融、库存、防重复扣款)?
ZooKeeper
etcd(强一致性,线性化写入)
+
高并发,可以接受极小概率的安全性松弛?
Redis
配合
无论使用哪种分布式锁,下游资源的幂等性保护才是真正的安全兜底:
//数据库层面:使用唯一约束
仍是合理的选择:
- 业务逻辑执行时间远小于锁过期时间(如业务
<
30s)
- 下游已有幂等性保护,即使锁偶尔失效也不会造成灾难
- 系统对强一致性要求不是极端严苛(如防止缓存击穿、任务调度去重)
在这些场景下,RedLock
提供的多节点容错相比单节点锁确实有价值,风险也在可控范围内。
/>
style="background-color:transparent">六、总结
维度 单节点Redis
锁
RedLock ZooKeeper/etcd锁
实现复杂度 低 中 高 性能 极高 高 中 强一致性 ❌ ⚠️(有争议) ✅ 容错性 无 容忍少数节点故障 多数节点存活即可 FencingToken
无 无 有(zxidrevision)
官方推荐程度 一般场景 官方存疑 强一致场景首选
RedLock
Redis
的批评并非要彻底否定它,而是提醒我们:分布式锁不是银弹,必须配合业务层面的幂等性和防御性设计,才能构建真正安全的系统。
对于工程师而言,最大的收获或许不是"该不该用
RedLock",而是这场争论揭示的更深层真理:在分布式世界中,时间是不可靠的,网络是不可靠的,进程是会暂停的。
任何假设了"部分同步"的算法,都必须被严格审视其安全边界。
/>


