1. 简介

Spring 提供了非常方便的注解方式来定义定时任务(@Scheduled),但在生产环境中,当我们部署多个实例时,默认的调度机制就会出现问题。

默认情况下,Spring 并不会在多个节点之间协调定时任务的执行,而是每个节点都会独立运行相同的任务。这显然不是我们想要的行为。

为了解决这个问题,我们可以引入 ShedLock —— 一个轻量级 Java 库,用于确保同一时间只有一个节点执行某个定时任务。相比 Quartz,它更加轻量且易于集成。

核心作用:防止多个应用实例同时执行相同的定时任务。

2. Maven 依赖配置

要在 Spring 项目中使用 ShedLock,首先需要引入以下依赖:

<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>6.3.1</version>
</dependency>

🔍 最新版本可以从 Maven Central 获取。

如果使用的是 JDBC 数据源,还需要添加对应的 provider 依赖,比如我们这里用 H2 数据库:

<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>6.3.1</version>
</dependency>
<dependency>
     <groupId>com.h2database</groupId>
     <artifactId>h2</artifactId>
     <version>2.1.214</version>
</dependency>

3. 配置 ShedLock

3.1 数据库表结构

ShedLock 依赖于共享存储来记录锁信息,最常见的方式是使用数据库。我们需要创建一张名为 shedlock 的表:

CREATE TABLE shedlock (
  name VARCHAR(64),
  lock_until TIMESTAMP(3) NULL,
  locked_at TIMESTAMP(3) NULL,
  locked_by VARCHAR(255),
  PRIMARY KEY (name)
)

字段说明:

  • name: 锁名称,对应方法名或任务名
  • lock_until: 锁定结束时间
  • locked_at: 加锁时间
  • locked_by: 当前持有锁的节点标识

3.2 Spring Boot 配置数据源

以 H2 内存数据库为例,在 application.yml 中配置数据源:

spring:
  datasource:
    driverClassName: org.h2.Driver
    url: jdbc:h2:mem:shedlock_DB;INIT=CREATE SCHEMA IF NOT EXISTS shedlock;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE
    username: sa
    password:

3.3 配置 LockProvider

接着我们需要配置 LockProvider,告诉 ShedLock 使用哪个数据源进行加锁操作:

@Configuration
public class SchedulerConfiguration {
    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(dataSource);
    }
}

3.4 启用调度与锁支持

最后,在主配置类上加上这两个关键注解:

@SpringBootApplication
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class SpringBootShedlockApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBootShedlockApplication.class, args);
    }
}

其中:

  • @EnableScheduling: 开启 Spring 定时任务功能
  • @EnableSchedulerLock: 启用 ShedLock 调度锁支持
  • defaultLockAtMostFor: 设置默认最大锁定时间(ISO8601 格式)

⚠️ 如果节点宕机,这个时间决定了锁多久后自动释放。

4. 编写定时任务

使用 ShedLock 非常简单,只需要在原来的 @Scheduled 方法上再加一个 @SchedulerLock 注解即可:

@Component
class BaeldungTaskScheduler {

    @Scheduled(cron = "0 0/15 * * * ?")
    @SchedulerLock(name = "TaskScheduler_scheduledTask", 
      lockAtLeastFor = "PT5M", lockAtMostFor = "PT14M")
    public void scheduledTask() {
        // 具体业务逻辑
    }
}

参数详解

参数 说明
name 唯一标识该任务的名称,建议使用类名+方法名组合
lockAtLeastFor 最小锁定时间,避免任务频繁触发
lockAtMostFor 最大锁定时间,防止死锁

📌 示例解释:

  • 每 15 分钟触发一次(cron 表达式)
  • 至少锁定 5 分钟(PT5M
  • 最多锁定 14 分钟(PT14M

✅ 正常执行完成后会立即释放锁;若节点崩溃,则等待最大锁定时间后自动释放。

5. 总结

ShedLock 是解决 Spring 多实例环境下定时任务重复执行问题的一个利器。相比重量级的 Quartz,它足够轻量,只需引入少量依赖并配置数据库即可快速使用。

适合场景:

  • 微服务架构下多个实例部署
  • 不想引入复杂调度框架
  • 仅需保证定时任务不被并发执行

踩坑提醒:

  • 必须配置共享存储(如数据库)
  • 不同任务的 name 必须唯一
  • 注意 lockAtMostFor 时间不宜过长,否则可能影响任务恢复速度

如果你正在寻找一种简单粗暴又可靠的分布式定时任务解决方案,那么 ShedLock 绝对值得一试。


原始标题:Guide to ShedLock with Spring | Baeldung