【Spring 中使用 ShedLock 实现分布式锁任务调度】

Spring 中使用 ShedLock 实现分布式锁任务调度

  • [Spring 中使用 ShedLock 实现分布式任务调度](#Spring 中使用 ShedLock 实现分布式任务调度)
    • [1. 简介](#1. 简介)
    • [2. Maven 依赖配置](#2. Maven 依赖配置)
    • [3. 配置 ShedLock](#3. 配置 ShedLock)
      • [3.1 数据库表结构](#3.1 数据库表结构)
      • [3.2 Spring Boot 配置数据源](#3.2 Spring Boot 配置数据源)
      • [3.3 配置 LockProvider](#3.3 配置 LockProvider)
      • [3.4 启用调度与锁支持](#3.4 启用调度与锁支持)
    • [4. 编写定时任务](#4. 编写定时任务)
    • @SchedulerLock时间格式详解
      • [📝 `lockAtMostForString` 与 `lockAtLeastForString`](#📝 lockAtMostForStringlockAtLeastForString)
    • [5. 总结](#5. 总结)
    • [如果你正在寻找一种简单粗暴又可靠的分布式定时任务解决方案,那么 ShedLock 绝对值得一试。](#如果你正在寻找一种简单粗暴又可靠的分布式定时任务解决方案,那么 ShedLock 绝对值得一试。)

Spring 中使用 ShedLock 实现分布式任务调度

1. 简介

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

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

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

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

2. Maven 依赖配置

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

shedlock-spring 5.x以上要求,java 17

xml 复制代码
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>4.48.0</version>
</dependency>
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>4.48.0</version>
</dependency>

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

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

xml 复制代码
<!-- mysql 数据库可忽略 -->
<dependency>
     <groupId>com.h2database</groupId>
     <artifactId>h2</artifactId>
     <version>2.1.214</version>
</dependency>

3. 配置 ShedLock

3.1 数据库表结构

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

sql 复制代码
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 中配置数据源:

yaml 复制代码
# mysql 对应修改,或者忽略
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 使用哪个数据源进行加锁操作:

java 复制代码
@Configuration
public class SchedulerConfiguration {
    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(dataSource);
    }
}

3.4 启用调度与锁支持

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

java 复制代码
@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 注解即可:

java 复制代码
@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

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

@SchedulerLock时间格式详解

@SchedulerLock 注解的 lockAtMostForlockAtLeastFor 参数,在设置时间时有三种灵活的写法,可以根据项目的代码风格和可读性需求来选择:

  1. 纯数字毫秒 (仅支持旧版属性) :时间单位是毫秒(ms),但这种写法只能用在已被弃用lockAtMostForlockAtLeastFor 属性上。
  2. 标准缩写格式 (推荐) :使用 数字 + 单位 的格式,例如 "5m", "30s", "2h",代码清晰易读。
    • ms (毫秒), s (秒), m (分), h (时), d (天)
  3. ISO-8601 标准格式 (最严谨) :这是国际标准化组织定义的时长格式,以 P 开头,T 后跟时间部分。
    • 格式:P[n]Y[n]M[n]DT[n]H[n]M[n]S
    • 示例
      • "PT30S": 30秒
      • "PT1M45S": 1分45秒
      • "PT10M": 10分钟
      • "PT2M": 2分钟

📝 lockAtMostForStringlockAtLeastForString

Spring项目常用的 shedlock-spring 包中,@SchedulerLock 注解提供了两个独立的属性来专门接收字符串格式的时间:

  • lockAtMostForString:用于接收字符串格式的 lockAtMostFor
  • lockAtLeastForString:用于接收字符串格式的 lockAtLeastFor

你可以根据喜好选择使用标准缩写或 ISO-8601 格式。

java

java 复制代码
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;

@SchedulerLock(name = "myTask",
               lockAtMostForString = "5m",     // 标准缩写: 5分钟
               lockAtLeastForString = "PT30S") // ISO-8601: 30秒
public void myScheduledTask() {
    // 你的业务逻辑...
}

5. 总结

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

适合场景:

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

踩坑提醒:

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

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