概述
在分布式系统设计中,CAP 理论告诉我们最多只能同时满足一致性、可用性和分区容错性中的两项。微服务架构下,跨服务调用频繁,往往需要在强一致性和高可用性之间做权衡。虽然可以通过分布式事务或异步消息解决大部分问题,但在某些极端场景下,比如防止超卖或余额扣减为负数,我们依然需要阻塞所有节点的线程来保证共享资源的互斥访问。
本地锁依赖语言特性,而分布式锁必须借助中间件,如数据库、Redis 或 ZooKeeper。本文将聚焦于基于 Redis 的 Redisson 客户端,深入剖析其分布式锁的实现机制。
分布式锁的核心要求
无论底层使用何种中间件,一个合格的分布式锁必须满足以下特性:
- 互斥性:同一时刻只能有一个客户端持有锁。
- 防死锁:若持有锁的进程崩溃,锁必须能自动释放,避免永久阻塞。
- 高性能:在高并发下,加锁和解锁操作不能成为瓶颈。
- 高级特性:支持可重入、超时设置、锁判断等类似 Java
Lock接口的功能。
Redisson 快速上手
Redisson 实现了 JDK 的 Lock 接口,使用方式非常直观。获取锁后执行业务逻辑,务必在 finally 块中释放锁。
RLock lock = redisson.getLock("myLockKey");
lock.lock();
try {
// 执行临界区代码
} finally {
lock.unlock();
}
核心原理分析
加锁流程
Redisson 的加锁核心在于 tryAcquireAsync 方法。它通过 Lua 脚本保证操作的原子性,这是 Redis 分布式锁可靠性的基石。
当调用 lock() 时,内部会尝试执行 Lua 脚本。脚本逻辑大致如下:
- 检查 Key 是否存在:如果不存在,说明当前未加锁。此时将 Key 设置为 Hash 结构,Field 为
UUID:ThreadID(唯一标识当前线程),Value 为 1,并设置过期时间(默认 30 秒)。这解决了'进程挂掉导致死锁'的问题。 - 检查是否已持有锁:如果 Key 存在且 Field 匹配当前线程 ID,说明是可重入情况。此时增加 Value 计数,并刷新过期时间。
- 等待锁:如果 Key 存在但 Field 不匹配,说明被其他线程占用。脚本返回剩余过期时间,当前线程进入等待状态。
Lua 脚本示例
-- 1. 没被锁 {key 不存在}
if (redis.call('exists', KEYS[1]) == 0) then
redis.call('hset', KEYS[1], ARGV[2], 1);
redis.call('pexpire', KEYS[1], ARGV[]);
;
;
(redis.call(, KEYS[], ARGV[]) == )
redis.call(, KEYS[], ARGV[], );
redis.call(, KEYS[], ARGV[]);
;
;
redis.call(, KEYS[]);

