[057][调度模块]分布式环境下定时任务的防重复执行方案
本文章代码: gitee , gitcode , github
摘要
在集群部署中,同一个定时任务可能在多台机器上同时触发,造成资源浪费或数据错误。本文基于提供的 AutoRenewalLockTaskRunner 和 FixedLeaseLockTaskRunner 系列接口,阐述如何利用 Redis/Redisson 分布式锁包装任务执行,确保同一时刻只有一个节点执行任务。
1. 任务接口设计
框架定义了多个锁装饰器接口,业务实现只需继承相应接口并实现 doRun():
// Redis 自动续期锁
public interface AutoRenewalLockTaskRunner extends TaskRunner, Lockable {
@Override
default void run(Map<String, String> params) {
RedisLockService.instance.autoRenewal()
.doInLock(key(), () -> doRun(params));
}
void doRun(Map<String, String> params);
// key() 定义锁的标识,如 "task:order:sync"
}
类似地,还提供了:
FixedLeaseLockTaskRunner(Redis 固定租期)AutoRenewalBlockLockTaskRunner(Redisson 阻塞+自动续期)FixedLeaseReentrantLockTaskRunner(Redisson 可重入+固定租期)
2. 两种锁模式对比
| 模式 | 适用场景 | Redis 实现 | Redisson 实现 |
|---|---|---|---|
| 固定租期 | 任务执行时间稳定且较短 | SET NX PX + 不续期 |
lock(leaseTime) |
| 自动续期 | 任务执行时间不确定或较长 | 定时任务每 5s 续期 | lock()(看门狗机制) |
3. 锁的粒度控制
Lockable 接口要求实现类提供 key() 方法,通常按任务名称或业务 ID 区分:
@Override
public String key() {
return "schedule:lock:" + getTaskName(); // 例如通过构造函数注入
}
4. 异常处理
- 若获取锁失败(如已被其他节点占用),会抛出
LockCreateException,任务直接跳过,不会影响下次调度。 - 可覆盖
handleException(LockException)方法自定义失败处理(如记录日志、发送告警)。
5. 集群效果
所有节点使用相同的锁 key,第一个获得锁的节点执行,其他节点快速失败,保证任务在集群内单点运行。