最简单的方案:防止同一个定时任务被多个节点重复执行(基于 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 的集合来存储锁信息

  1. 抢占锁:当定时任务触发时,ShedLock 会尝试向 MongoDB 的 shedlock 集合中插入或更新一条记录(以 name 字段为唯一标识)。

  2. 执行或跳过:如果操作成功,说明当前节点抢到了锁,任务开始执行。如果操作失败(因为锁已被其他节点持有),任务则被直接跳过

  3. 释放锁:任务执行完毕后,会主动删除或更新这条锁记录。如果节点在执行途中宕机,lockAtMostFor 设置的超时时间会生效,锁将自动失效

 

posted on 2026-07-21 20:16  lshan  阅读(30)  评论(0)    收藏  举报