Redisson 的看门狗(Watch Dog)机制是分布式锁的核心特性之一,其核心作用是自动为持有锁的线程续期,避免因任务执行时间过长导致锁提前释放(从而引发并发问题)。下面通过具体代码实现,并详细说明使用时的注意事项。
Redisson 的看门狗机制触发条件:
使用tryLock
或lock
方法时,
需要未指定锁的过期时间,即不能手动指定过期时间(或者将leaseTime = -1
),
Redisson 会自动启动后台线程(看门狗),每隔 lockWatchdogTimeout / 3
时间(默认 10 秒)为锁续期,将锁的过期时间重置为 lockWatchdogTimeout
(默认 30 秒)。
直到线程主动释放锁或崩溃(此时看门狗线程终止,锁最终会过期释放)。
<!-- Spring Boot 基础 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- Redisson 分布式锁(含看门狗机制) -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.23.3</version> <!-- 兼容 Spring Boot 2.7+,3.x 需用 4.x 版本 -->
</dependency>
<!-- Redis 连接配置(Redisson 会自动依赖,可选显式声明) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
spring:
redis:
host: 127.0.0.1 # Redis 服务器地址
port: 6379 # Redis 端口
password: 123456 # Redis 密码(生产环境必须配置)
database: 0 # 数据库索引
# 自定义看门狗参数(可选,默认值如下)
redisson:
lockWatchdogTimeout: 60000 # 看门狗默认锁过期时间(毫秒,默认30000ms=30秒)
singleServerConfig:
connectTimeout: 5000 # 连接超时时间(毫秒)
timeout: 3000 # 命令执行超时时间(毫秒)
若需自定义 Redis 连接模式(如集群、哨兵),可通过配置类覆盖默认配置:
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
// 单节点模式(生产环境推荐集群/哨兵模式,参考官方文档)
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("123456")
.setDatabase(0);
// 配置看门狗超时时间(与application.yml二选一)
config.setLockWatchdogTimeout(60000); // 60秒
return Redisson.create(config);
}
}
封装分布式锁的获取、释放逻辑,简化业务层调用:
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
@Component
public class RedissonDistributedLock {
private static final Logger log = LoggerFactory.getLogger(RedissonDistributedLock.class);
@Resource
private RedissonClient redissonClient;
/**
* 获取分布式锁并执行任务(带看门狗自动续期)
* @param lockKey 锁的唯一标识(如 "order:lock:123")
* @param waitTime 获取锁的最大等待时间(毫秒)
* @param task 待执行的任务
* @return 任务执行结果
*/
public <T> T executeWithLock(String lockKey, long waitTime, Supplier<T> task) {
RLock lock = null;
try {
// 1. 获取锁对象
lock = redissonClient.getLock(lockKey);
// 2. 尝试获取锁:waitTime=等待时间,leaseTime=-1(启用看门狗)
boolean isLocked = lock.tryLock(waitTime, -1, TimeUnit.MILLISECONDS);
if (!isLocked) {
log.warn("获取锁失败,lockKey: {}", lockKey);
throw new RuntimeException("系统繁忙,请稍后重试");
}
// 3. 执行任务(看门狗会自动续期,直到任务完成)
log.info("获取锁成功,开始执行任务,lockKey: {}", lockKey);
return task.get();
} catch (InterruptedException e) {
log.error("获取锁被中断,lockKey: {}", lockKey, e);
Thread.currentThread().interrupt(); // 恢复中断状态
throw new RuntimeException("操作被中断");
} catch (Exception e) {
log.error("任务执行异常,lockKey: {}", lockKey, e);
throw e;
} finally {
// 4. 释放锁(必须检查当前线程是否持有锁,避免误释放)
if (lock != null && lock.isHeldByCurrentThread()) {
try {
lock.unlock();
log.info("释放锁成功,lockKey: {}", lockKey);
} catch (Exception e) {
log.error("释放锁失败,lockKey: {}", lockKey, e);
}
}
}
}
}
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.math.BigDecimal;
@Service
public class OrderService {
@Resource
private RedissonDistributedLock distributedLock;
/**
* 创建订单(需要分布式锁防止并发创建)
*/
public String createOrder(Long userId, BigDecimal amount) {
// 锁的唯一标识:按用户ID粒度(减少锁竞争)
String lockKey = "order:create:lock:" + userId;
// 调用工具类,获取锁并执行任务(最大等待10秒)
return distributedLock.executeWithLock(lockKey, 10000, () -> {
// 核心业务逻辑(如检查库存、扣减余额、创建订单)
// 模拟长任务(超过默认锁过期时间30秒,测试看门狗续期)
try {
Thread.sleep(40000); // 执行40秒
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return "订单创建成功,userId: " + userId + ", amount: " + amount;
});
}
}
lock:order
),所有订单创建都竞争同一把锁,导致并发性能骤降;lock:order:userId
或 lock:order:productId
),仅限制同一资源的并发,减少锁竞争。lockWatchdogTimeout
lockWatchdogTimeout
(如 60 秒),避免续期不及时导致锁提前释放;application.yml
的 redisson.lockWatchdogTimeout
或 RedissonConfig
中的 config.setLockWatchdogTimeout(60000)
设置。finally
中检查并释放lock.isHeldByCurrentThread()
确认当前线程持有锁,避免释放其他线程 / 节点的锁(如线程 A 获取锁后阻塞,线程 B 超时未获取锁却误释放 A 的锁);finally
块中,确保无论任务成功 / 失败,锁都会被释放(除非节点崩溃,此时看门狗会在锁过期后自动释放)。finally
块的作用);InterruptedException
),需调用 Thread.currentThread().interrupt()
恢复中断状态,避免上层逻辑忽略中断信号。leaseTime
(非 - 1),则看门狗机制不生效,锁会在 leaseTime
后自动释放;此时需确保 leaseTime
远大于任务最大耗时,否则会导致任务未完成锁已释放;leaseTime = -1
启用看门狗,由 Redisson 自动管理锁的过期时间。Redisson 的看门狗机制通过自动续期解决了 “长任务锁过期” 的问题,核心代码需注意锁的获取参数(leaseTime = -1
)、释放逻辑(finally + isHeldByCurrentThread
)、锁粒度设计。实际使用中需结合 Redis 高可用、任务幂等性、监控告警等措施,确保分布式锁在高并发场景下的可靠性。
在使用 Redisson 的看门狗(Watch Dog)机制时,默认情况下不需要手动修改锁的过期时间和续期间隔,Redisson 会自动处理。
但如果业务场景有特殊需求(如任务执行时间很长),可以手动调整锁的初始过期时间,续期间隔会自动跟随调整,无需单独配置。
Redisson 的看门狗机制有一套默认参数,满足大多数常规场景:
lockWatchdogTimeout
):30 秒(30000 毫秒)。当调用 tryLock
或 lock
方法时,若未指定 leaseTime
(或设为 -1
),看门狗会自动启动:
unlock()
释放锁,或线程崩溃(此时看门狗线程终止,锁最终会在 30 秒后过期释放)。如果业务任务的平均执行时间超过 30 秒(如大数据处理、复杂计算),默认的 30 秒过期时间可能导致续期不及时(极端情况下,任务未完成但锁已过期)。此时需要手动调整 lockWatchdogTimeout
(锁过期时间),续期间隔会自动变为 lockWatchdogTimeout / 3
,无需单独设置。
通过配置文件或代码修改 lockWatchdogTimeout
即可,续期间隔会自动适配。
application.yml
配置(推荐)redisson:
lockWatchdogTimeout: 60000 # 设置锁过期时间为 60秒(毫秒)
singleServerConfig:
host: 127.0.0.1
port: 6379
password: 123456
60000 / 3 = 20秒
,即每隔 20 秒续期一次,将锁过期时间重置为 60 秒。import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379")
.setPassword("123456");
// 设置锁过期时间为 60秒(毫秒)
config.setLockWatchdogTimeout(60000);
return Redisson.create(config);
}
}
lockWatchdogTimeout / 3
,由 Redisson 自动计算,无法单独修改。例如:
lockWatchdogTimeout = 90000
毫秒(90 秒),续期间隔为 30 秒。leaseTime
会禁用看门狗:若调用 tryLock
时指定了具体的 leaseTime
(如 tryLock(10, 50, TimeUnit.SECONDS)
),则看门狗机制不生效,锁会在 50秒
后强制过期,无论任务是否完成。因此,只有当 leaseTime = -1
时,看门狗才会启用。lockWatchdogTimeout
应大于任务的最大可能执行时间(如任务最长执行 5 分钟,则 lockWatchdogTimeout
可设为 10 分钟),避免极端情况下续期失败导致锁提前释放。lockWatchdogTimeout
过大(如几小时),一旦持有锁的线程崩溃,锁会长期占用,影响其他线程获取,需结合业务容错机制(如定时清理异常锁)。lockWatchdogTimeout
调整锁过期时间(如 60 秒),续期间隔会自动变为其 1/3,无需单独配置。lockWatchdogTimeout
需大于任务最大耗时,且不指定 leaseTime
(保持 -1
)以启用看门狗。