
一、 底层实现原理
1. 互斥性加锁:SET NX 方案
实现互斥性的核心在于确保“判断”与“设置”是一个不可分割的原子操作。
核心命令:
SET key value NX PX 30000原理分析:
NX参数保证只有当 Key 不存在时才能设置成功;PX设置毫秒级过期时间。由于 Redis 是单线程处理命令,该操作在服务端天然具备原子性。唯一标识:
value应存储当前请求的唯一标识(如 UUID),用于后续解锁时的身份校验。
2. 原子性释放:防误删逻辑
释放锁时,必须防止“误删”他人的锁(例如 A 线程因阻塞导致锁过期,此时 B 线程获取了锁,A 恢复后不应删除 B 的锁)。
操作流程:先查询(GET)锁的 Value,判断是否与当前线程标识一致,一致则删除(DEL)。
原子性保障:由于“判断+删除”是两步操作,必须使用 Lua 脚本 包裹,利用 Redis 执行脚本的原子性来保证逻辑完整。
-- 解锁 Lua 脚本
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
二、 健壮性与容错机制
1. 超时自动释放
为锁设置过期时间是防止死锁的关键。即使业务进程意外宕机,Redis 也会在到期后自动删除 Key,释放资源。
2. 看门狗 (Watch Dog) 自动续期
解决痛点:业务执行时间超过了预设的过期时间,导致锁提前失效。
实现逻辑:获取锁成功后,同步开启一个后台线程,每隔一段时间(通常为过期时间的 1/3)检查锁是否还存在,若存在则重置其过期时间。
线程属性:续期线程应设置为 守护线程 (Daemon Thread)。
优势:当业务主线程挂掉后,JVM 会自动终止守护线程,续期停止,锁将正常过期。
三、 高级特性实现
1. 可重入锁 (Reentrant Lock)
支持同一线程多次获取同一把锁,提高代码复用性并避免自死锁。
数据结构:采用 Redis Hash 结构。
Key: 锁名称。
Field: 客户端唯一标识 (UUID) + 线程 ID。
Value: 计数器(存储重入次数)。
逻辑:加锁时计数器
+1,解锁时-1,计数器归零时删除 Key。
2. 阻塞锁的实现方案
当锁被占用时,后续线程的等待策略通常有两种:
自旋 (Spin Lock):通过
while循环不断尝试获取锁。实现简单但消耗 CPU。发布订阅 (Pub/Sub):线程加锁失败后订阅锁释放的消息并进入阻塞状态。当持有锁的线程释放时,发布信号唤醒订阅者重新抢锁。这是 Redisson 等主流库的实现方式。
四、 分布式架构下的数据一致性
在 Redis 主从或哨兵架构下,由于异步复制的存在,可能会出现“锁丢失”问题(主节点写入后未同步即宕机)。
1. 连锁 (MultiLock)
客户端同时向多个独立的 Redis 节点申请加锁。只有当所有节点都加锁成功,才算真正持有锁。这种方式极大地提高了可用性,即使某个节点发生故障,也不会导致锁的非法获取。
2. 红锁 (RedLock) 算法
基本原理:部署 $N$ 个独立主节点(无从属关系),客户端需在半数以上($N/2 + 1$)节点加锁成功。
局限性:
极度依赖系统时钟的一致性。
受 Java GC 停顿(STW)影响,可能导致锁在节点上已过期而客户端认为仍持有。
运维成本高,性能略低于单机版。
五、 技术选型总结
常规场景:推荐使用 Redisson 框架。它封装了 Lua 脚本、看门狗、可重入及 Pub/Sub 阻塞机制,代码实现简洁稳健。
极端可靠性要求:如果业务无法容忍任何锁丢失风险,建议考虑基于 Zookeeper 或 etcd 的分布式锁,它们通过强一致性协议(ZAB/Raft)来保证锁状态的绝对同步。