在分布式架构中,单机锁机制已无法满足跨节点的资源同步需求。分布式锁作为协调多节点共享资源的核心手段,其可靠性直接关系到业务的一致性与稳定性。本文将深入探讨基于 Redis 构建健壮分布式锁的方案,涵盖原子性保障、防误删机制及超时续期策略。
分布式锁的核心挑战
在多线程环境下,Java 提供了 synchronized 和 ReentrantLock 等本地锁机制,确保进程内线程同步。然而,当应用扩展至多个物理或虚拟节点时,本地锁无法感知其他节点的运行状态。分布式锁通过外部组件(如 Redis、Zookeeper)提供跨节点协调能力,确保同一时刻只有一个节点能访问共享资源。
一个健壮的分布式锁需满足以下特性:互斥性(独占访问)、可重入性(同一线程多次获取)、锁超时(防止死锁)、高性能与高可用性,以及支持阻塞与非阻塞模式。
Redis 基础实现及其局限
利用 Redis 的 SETNX 命令可实现简单的分布式锁。基本逻辑是尝试设置键值对,若不存在则加锁成功,并设置过期时间以防死锁。任务完成后执行 DEL 释放锁。
虽然实现简单,但存在显著缺陷:
- 非原子性:SETNX 与 EXPIRE 分两步执行。若中间过程崩溃,锁将无过期时间,导致死锁。
- 误删风险:若持有锁的线程因 GC 停顿导致 TTL 到期,锁被自动释放,此时其他线程获取锁。原线程恢复后执行 DEL,会误删他人的锁。
- 不可重入:标准实现不支持同一线程重复获取锁。
构建健壮锁的关键方案
1. 唯一标识符防误删
为每个锁分配唯一标识(如 UUID),存储于 value 中。解锁前校验当前标识是否匹配,仅允许持有者释放。这解决了误删问题,也为可重入锁奠定了基础。
2. Lua 脚本保障原子性
使用 Lua 脚本将加锁(SETNX + EXPIRE)和解锁(GET + DEL)封装为原子操作。Redis 执行 Lua 脚本期间不会插入其他命令,确保关键路径不被中断。
示例 Lua 脚本用于加锁:
if redis.call('setnx', KEYS[1], ARGV[1]) == 1 then
return redis.call('expire', KEYS[1], tonumber(ARGV[2]))
end
return 0
解锁脚本则先校验 value 再执行 del。
3. 看门狗机制处理超时
业务执行时间不确定时,固定 TTL 可能导致锁提前释放。引入守护线程(Watchdog)定期检测锁状态,若业务未结束且锁即将过期,自动续期。一旦业务完成,立即停止守护线程并释放锁。
Java 实现中,可使用 ScheduledExecutorService 启动定时任务,每间隔略小于 TTL 的时间检查并续期。注意需在 finally 块中确保资源释放,避免线程泄漏。
总结
分布式锁的实现并非一蹴而就。从基础的 SETNX 到引入 UUID 校验、Lua 原子化脚本,再到 Watchdog 续期机制,每一步都是为了解决特定场景下的并发隐患。在实际生产中,建议结合 Redisson 等成熟框架,它们已内置了上述所有优化逻辑,能大幅降低开发复杂度与出错概率。选择合适的实现方式,配合合理的监控与熔断策略,方能确保分布式系统的稳定运行。


