【硬核实战】彻底搞懂 Redis 分布式锁:从 SETNX 到 Redisson 看门狗机制
很多同学不理解,如果我的业务执行了 30 秒,但锁默认过期时间只有 30 秒,业务没跑完锁断了怎么办?Redisson 的看门狗机制就是解决这个问题的:当我们调用没有设置过期时间时,Redisson 默认会设置 30 秒过期(Redisson 会启动一个后台定时任务(TimeTask),每隔秒检查一次。如果当前线程还持有锁,就自动把锁的过期时间重新设回 30 秒。这就像一只忠诚的看门狗,只要你还在
【硬核实战】彻底搞懂 Redis 分布式锁:从 SETNX 到 Redisson 看门狗机制
摘要:在高并发场景下,如何防止商品超卖?分布式锁是必修课。本文将从最原始的
SETNX讲起,剖析其缺陷,一步步带你深入 Redisson 框架的底层原理,特别是经典的“看门狗”机制。拒绝背八股文,实战代码带你起飞! 关键词:Redis, 分布式锁, Redisson, Java, 高并发
1. 为什么我们需要分布式锁?
想象一个经典的秒杀场景: 数据库里只有 1 个 库存,此时有 A、B 两个用户 同时点击购买。
如果代码只是简单的:
-
查询库存 (
select * from stock where id=1) -
判断库存 > 0
-
扣减库存 (
update stock set num = num-1)
在单机环境下,我们可以用 Java 的 synchronized 或 ReentrantLock 解决。但在微服务/分布式架构下,A 请求打到了服务器 1,B 请求打到了服务器 2,JVM 内部的锁就失效了。
这时候,我们需要一个所有服务都能访问到的第三方来充当“裁判”,这就是分布式锁。
2. 为什么选择 Redis?
市面上常见的分布式锁方案有三种:
-
数据库唯一索引:性能差,容易死锁,不推荐。
-
Zookeeper:可靠性高(CP模型),但性能不如 Redis,且实现复杂。
-
Redis:性能极高(AP模型),实现简单,是目前互联网大厂的主流选择。
3. 从 SETNX 到 Redisson 的进化之路
3.1 原始阶段:SETNX
最简单的 Redis 锁利用了 SETNX (SET if Not eXists) 指令。
Bash
SET lock_key value NX PX 10000
致命缺陷:
-
原子性问题:早期版本加锁和设置过期时间是两步操作,可能死锁。
-
锁误删:A 线程卡顿了,锁过期自动释放;B 线程拿到锁;A 恢复后删除了 B 的锁。
-
业务没跑完锁就过期了:这是最头疼的,如何给锁“续命”?
3.2 终极方案:Redisson
为了解决上述痛点,Java 社区诞生了 Redisson 框架。它不仅封装了复杂的 Lua 脚本保证原子性,还引入了自动续期的**看门狗(Watch Dog)**机制。
4. Redisson 实战代码演示
4.1 引入依赖
XML
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.0</version>
</dependency>
4.2 业务代码实现(标准姿势)
这是面试和生产环境中的标准写法,请背诵!
Java
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class StockService {
@Autowired
private RedissonClient redissonClient;
public void deductStock() {
// 1. 获取锁对象 (定义锁的名称,粒度越细越好)
String lockKey = "product_stock_lock_1001";
RLock lock = redissonClient.getLock(lockKey);
try {
// 2. 尝试加锁
// tryLock(等待时间, 自动过期时间, 单位)
// 建议:不设置自动过期时间,激活看门狗机制
boolean isLocked = lock.tryLock(3, TimeUnit.SECONDS);
if (isLocked) {
// 3. 执行业务逻辑
System.out.println("获取锁成功,正在扣减库存...");
Thread.sleep(5000); // 模拟业务耗时
} else {
System.out.println("获取锁失败,系统繁忙,请稍后再试");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 4. 释放锁 (关键:判断是否是当前线程持有的锁)
if (lock.isLocked() && lock.isHeldByCurrentThread()) {
lock.unlock();
System.out.println("锁已释放");
}
}
}
}
5. 核心原理:什么是“看门狗” (Watch Dog)?
很多同学不理解,如果我的业务执行了 30 秒,但锁默认过期时间只有 30 秒,业务没跑完锁断了怎么办?
Redisson 的看门狗机制就是解决这个问题的:
-
当我们调用
lock.tryLock()没有设置过期时间时,Redisson 默认会设置 30 秒过期(lockWatchdogTimeout)。 -
Redisson 会启动一个后台定时任务(TimeTask),每隔
30 / 3 = 10秒检查一次。 -
如果当前线程还持有锁,就自动把锁的过期时间重新设回 30 秒。
-
这就像一只忠诚的看门狗,只要你还在跑业务,它就一直帮你“续命”。
流程图解 (Mermaid)
Code snippet
sequenceDiagram
participant ThreadA as 线程A
participant Redis as Redis服务端
participant WatchDog as 看门狗(后台线程)
ThreadA->>Redis: 申请加锁 (SETNX)
Redis-->>ThreadA: 加锁成功 (默认30s过期)
loop 每10秒检测
WatchDog->>Redis: 线程A还在吗?
Redis-->>WatchDog: 还在
WatchDog->>Redis: 重置过期时间为30s (续命)
end
ThreadA->>Redis: 业务结束,释放锁 (DEL)
Redis-->>ThreadA: 锁已释放
WatchDog->>WatchDog: 停止续命任务
6. 避坑指南
-
不要在 tryLock 中设置 leaseTime:如果你显式设置了过期时间(如
lock.tryLock(10, 5, TimeUnit.SECONDS)),看门狗机制将失效!锁到期会强制释放。 -
finally 块的重要性:无论业务代码是否抛出异常,必须在
finally中释放锁,否则会导致死锁。 -
锁的粒度:
lockKey最好带上商品 ID(如product_101),不要直接锁整个stock,否则并发性能会直线下降。
7. 总结
分布式锁是高并发系统的基石。虽然 Redis 官方还提出了红锁(RedLock)算法来解决主从切换丢锁的问题,但在实际绝大多数生产环境中,Redisson 的普通锁 + 看门狗机制 已经足够应对 99% 的场景。
更多推荐


所有评论(0)