最简单的方案:防止同一个定时任务被多个节点重复执行(基于 MongoDB 实现分布式锁)
Spring Boot 3.x 适配版本
步骤一:添加 Maven 依赖
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>5.16.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-mongo</artifactId>
<version>5.16.0</version>
</dependency>
步骤二:配置 LockProvider
创建一个配置类,用于向 Spring 容器中注入一个基于 MongoDB 的 LockProvider
import net.javacrumbs.shedlock.provider.mongo.MongoLockProvider; import net.javacrumbs.shedlock.core.LockProvider; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.scheduling.annotation.EnableScheduling; @Configuration @EnableScheduling // 开启 Spring 的定时任务功能 public class ShedlockConfig { @Bean public LockProvider lockProvider(MongoTemplate mongoTemplate) { // 注意:这里传入的是 MongoDatabase 对象 return new MongoLockProvider(mongoTemplate.getDb()); } }
步骤三:启用 ShedLock
在 Spring Boot 的启动类上,添加 @EnableSchedulerLock 注解来启用 ShedLock 功能。你可以在这里设置一个全局的默认锁超时时间。
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication @EnableScheduling @EnableSchedulerLock(defaultLockAtMostFor = "PT30S") // 默认锁最大持有时间30秒 public class YourApplication { public static void main(String[] args) { SpringApplication.run(YourApplication.class, args); } }
步骤四:在定时任务上添加 @SchedulerLock 注解
最后,也是最重要的一步,在你需要防重复执行的定时任务方法上,同时使用 @Scheduled 和 @SchedulerLock 两个注解。
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; @Component public class MyScheduledTasks { // 例如:每天凌晨1点执行 @Scheduled(cron = "0 0 1 * * ?") @SchedulerLock(name = "dailyReportTask", lockAtMostFor = "PT5M", // 锁最大持有5分钟 lockAtLeastFor = "PT1M") // 锁最少持有1分钟 public void runDailyReport() { // 你的业务逻辑代码 System.out.println("执行每日报表生成任务..."); } }
关键参数说明
-
name(必填):锁的唯一标识符。所有服务实例中,相同任务的这个值必须完全一致,ShedLock 才能识别并协调它们。 -
lockAtMostFor(强烈建议设置):锁的最大持有时间。这是一个兜底机制,防止执行任务的节点突然宕机,导致锁永远无法释放而造成死锁。-
建议:这个值要设置为远大于任务正常执行的时间。例如,任务通常跑1分钟,可以设为
"PT5M"(5分钟)。
-
-
lockAtLeastFor(可选):锁的最短持有时间。它的作用是防止因各节点系统时钟不同步,导致任务执行太快、锁释放过早,从而被另一个节点再次抢到并重复执行。如果你的任务执行时间很短(比如几秒钟),可以考虑设置这个参数。
工作原理简述
ShedLock 会在你的 MongoDB 中创建一个名为 shedlock 的集合来存储锁信息。
-
抢占锁:当定时任务触发时,ShedLock 会尝试向 MongoDB 的
shedlock集合中插入或更新一条记录(以name字段为唯一标识)。 -
执行或跳过:如果操作成功,说明当前节点抢到了锁,任务开始执行。如果操作失败(因为锁已被其他节点持有),任务则被直接跳过。
-
释放锁:任务执行完毕后,会主动删除或更新这条锁记录。如果节点在执行途中宕机,
lockAtMostFor设置的超时时间会生效,锁将自动失效。
浙公网安备 33010602011771号