Spring Boot中如何保证定时任务任意时刻仅运行单个实例?
Spring Boot定时任务防重入与崩溃锁释放最优方案
原生@Scheduled的能力边界
- 如果你未修改Spring默认的单线程调度器配置,原生
@Scheduled(fixedRate = 600000)本身就不会在当前任务未执行完成时触发新的调度实例,但存在两个明显缺陷:- 一旦自定义修改调度线程池大小、或服务多实例部署,该机制完全无法防止任务并发执行
- 无异常兜底能力,若任务执行过程中出现死锁,后续所有调度都会被阻塞
分场景最优实现
场景1:单实例部署(无多实例需求)
直接使用进程内原子标记实现即可,零额外依赖,完全不存在锁残留问题:
import java.util.concurrent.atomic.AtomicBoolean; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class YourScheduleTask { // 任务运行状态标记 private final AtomicBoolean taskRunning = new AtomicBoolean(false); @Scheduled(fixedRate = 600000) // 每10分钟触发一次 public void executeTask() { // CAS抢占执行权,抢占失败直接退出,不启动新实例 if (!taskRunning.compareAndSet(false, true)) { return; } try { // 写入你的业务逻辑,执行时长10-20分钟不受影响 } finally { // 无论业务执行成功、抛出异常,都会重置状态 taskRunning.set(false); } } }
- 该方案下如果应用中途崩溃,JVM进程直接销毁,内存中的状态标记会随进程一起清除,应用重启后第一次调度即可正常执行任务,完全不需要等待锁过期。
场景2:多实例部署
选择ShedLock + Redis实现分布式锁,通过短TTL+心跳续期机制解决崩溃后锁残留等待时间过长的问题:
- 不要将锁TTL设置为任务最长执行时长(20分钟),改为设置为2~3分钟的短TTL
- 开启ShedLock内置的锁续期能力,任务正常运行时每隔TTL时长的1/2(即1~1.5分钟)自动续期锁的过期时间,保证任务运行过程中锁不会提前释放
- 若应用中途崩溃,续期线程随进程一起终止,锁最多等待2~3分钟就会自动过期,不需要等待十几分钟的长TTL
- 核心配置参数参考:
# ShedLock锁最小持有时间、最大持有时间 shedlock.lock-at-least-for=0ms shedlock.lock-at-most-for=3m # 开启锁续期,续期间隔1.5分钟 shedlock.lock-extender-interval=1m30s
不推荐使用DynamoDB作为锁存储,DynamoDB的TTL为后台异步清理机制,实际过期时间误差可达数十分钟,无法满足锁快速释放的需求。
内容的提问来源于stack exchange,提问作者trio
相关产品推荐
相关产品推荐

