引言
在分布式系统中,多个服务实例往往需要同时访问同一共享资源,例如:
- 多节点同时修改同一条库存记录
- 并发执行同一订单处理流程
- 多实例定时任务同时触发
- 并发写入同一个数据对象
如果没有分布式锁机制做互斥控制,系统容易出现数据竞争、写冲突、脏写、重复执行等问题。
Redis 因为高性能、单线程执行模型、丰富的原子命令和 Lua 脚本支持,成为当前最主流的分布式锁实现底座。
而 Redisson 作为 Redis 官方推荐的 Java 客户端之一,在锁语义、可重入性、自动续期、原子性保障等方面具有较高的工程完备度。
本文将从工作原理到工程实践全面介绍 Redisson 分布式可重入锁(RLock)。
一、为什么选择 Redisson 实现分布式锁?
如果直接手写 Redis 分布式锁(例如使用 SETNX + EXPIRE),会面临大量工程级问题:
| 问题 | 原因 | 典型风险 |
|---|---|---|
| SETNX 与 EXPIRE 非原子 | 两条命令 | 获取锁后未设置过期时间 → 死锁 |
| 锁删除不安全 | 业务线程崩溃 / 误删 | 删除了别的线程的锁 |
| 锁超时后自动释放 | EX 过期时间不足 | 业务未执行完 → 锁提前过期导致数据错误 |
| 不支持可重入 | SETNX 无法表达线程所有者 | 同一线程再次加锁会造成自阻塞 |
| 缺乏自动续期机制 | 自己实现困难 | 业务执行长任务容易导致锁失效 |
Redisson 提供的 RLock 特性完整解决了上述问题:
- 可重入语义
- 自动续期(看门狗机制)
- 原子加锁 / 解锁(Lua 脚本)
- 多种锁模式(公平锁、联锁、红锁等)
- 分布式环境稳定可靠
因此 Redisson 是 Java 体系中工程落地最完善的 Redis 分布式锁方案。
二、Redisson 分布式锁底层原理
1. 锁在 Redis 中的结构
Redisson 使用 Redis 的 Hash 结构保存锁元数据:
Key: myLock
Type: Hash
Field: <uuid:threadId>
Value: <reentrant count>
Expire: 30 seconds (默认锁自动过期时间)
示例:
HSET myLock "e8c0d-...-12:23" 1
EXPIRE myLock 30
这带来几个重要特性:
- 支持可重入:同一线程再次加锁只需增加次数,不会阻塞
- 线程级别锁所有权:clientId+threadId 作为唯一标识
- Redis TTL 控制过期:防止死锁
- Hash 支持多个 field,可扩展到联锁等高级场景
2. 加锁流程详解
加锁过程使用 Lua 脚本确保原子性:
核心流程(简化逻辑)
- 锁不存在 → 设置 Hash(field=当前线程)并设置 TTL
- 锁存在但属于当前线程 → 可重入次数+1
- 锁存在且不属于当前线程 → 阻塞等待或立即返回失败
所有分支均由 Lua 脚本完成,确保操作原子、不被打断。
三、Spring Boot + Redisson 配置示例
java
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setDatabase(0);
return Redisson.create(config);
}
}
四、加锁与释放锁使用方法
Redisson 提供两类常用加锁方式:
lock.lock():阻塞式加锁,自动启动看门狗续期lock.tryLock(...):可设定等待时间与租期,更灵活
1. 使用 lock() ------ 自动续期(Watchdog)
java
@Service
public class LockExampleService {
@Autowired
private RedissonClient redissonClient;
public void testLock() {
RLock lock = redissonClient.getLock("order:lock");
try {
lock.lock(); // 不指定时间 → 自动续期机制生效
System.out.println("获取到锁,执行操作...");
Thread.sleep(60000); // 业务耗时,看门狗续期保障锁有效
} catch (Exception e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
特点:
- 默认锁过期时间:30 秒
- 自动续期(看门狗)每 10 秒刷新过期时间
2. 使用 tryLock()
java
public void tryLockExample() {
RLock lock = redissonClient.getLock("product:lock");
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
System.out.println("成功获取锁");
Thread.sleep(30000);
} else {
System.out.println("未获取到锁");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
特点:
- 5 秒内阻塞等待
- 拿到锁后:启用看门狗自动续期
五、看门狗(Watchdog)机制完整解析
1. 看门狗的工作机制
默认:
- 检查周期:10 秒
- 每次续期:将锁 TTL 重置为 30 秒
工作流程:
- 定时任务触发
- 判断锁是否仍被当前线程持有
- 若是 → 调用
PEXPIRE myLock 30000 - 若线程结束或锁已释放 → 停止续期
2. 为什么需要续期?
如果业务耗时超过锁 TTL(30s),传统分布式锁会提前失效,导致并发问题。
续期确保业务执行再长,也不会中途释放锁。
3. 如何判断"锁属于当前线程"?
通过检查:
HEXISTS myLock <clientId:threadId>
只要当前线程还是 lock 的 owner,就继续续期。
六、Redis 中锁的实时具象表现
加锁后执行:
HGETALL order:lock
TTL order:lock
可能看到:
1) "7ab1e9...:23"
2) "1"
(integer) 30
随着看门狗执行,TTL 会保持波动但始终在 30 秒附近。
七、锁释放原理(Lua 脚本)
Redisson 使用 Lua 保证解锁的原子性。
- 检查锁是否属于当前线程
- 若重入次数 > 1 → 次数 -1
- 若重入次数为 1 → 删除整个 key
- 若锁不属于当前线程 → 不执行任何操作(避免误删)
这个机制保证:"只有锁持有者才能释放锁"。
八、典型问题与常见误区(FAQ)
1. 调用 lock.lock(10, TimeUnit.SECONDS) 会启用看门狗吗?
不会。
指定租期时不启用看门狗,锁达到指定时间后自动释放。
2. 看门狗续期时间可以调整吗?
可以:
java
config.setLockWatchdogTimeout(45000); // 默认30s
3. Redisson 如何避免死锁?
- 线程异常退出 → 看门狗停止续期
- 默认 30s TTL 自动过期
- 锁结构中包含线程标识,避免误删
4. 可重入锁意味着什么?
同一个线程可以多次获取该锁,不会进入自阻塞:
lock.lock();
lock.lock(); // 重入,计数+1
5. 可否用于分布式事务?
可以用于分布式资源互斥,但不能代替事务。
十、完整业务案例:库存扣减(典型高并发场景)
java
@Service
public class StockService {
@Autowired
private RedissonClient redissonClient;
public void deductStock() {
RLock lock = redissonClient.getLock("stock:lock");
try {
lock.lock(); // 触发看门狗续期
System.out.println("开始扣减库存...");
// 模拟扣减操作
Thread.sleep(30000);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
适用于:
- 秒杀减库存
- 订单去重
- 并发任务幂等控制
- 分布式调度系统
总结:Redisson 是 Java 开发中最成熟的 Redis 分布式锁实现
| 功能点 | Redisson 是否支持 | 说明 |
|---|---|---|
| 可重入分布式锁 | ✔ | Hash 结构记录重入次数 |
| 自动续期(看门狗) | ✔ | lock() 自动启用 |
| 原子加锁与解锁 | ✔ | Lua 脚本保障原子性 |
| 死锁自动恢复 | ✔ | TTL 到期自动释放 |
| 公平锁、联锁、读写锁 | ✔ | 丰富的分布式同步结构 |
| 高性能、可伸缩 | ✔ | 基于 Redis 单线程执行模型 |
凭借其完善的锁语义、自动续期机制与 Lua 原子操作设计,Redisson 是 Java 生态中分布式锁的首选方案,适用于中大型业务中的强一致性场景。