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`](#📝
lockAtMostForString与lockAtLeastForString)
- [📝 `lockAtMostForString` 与 `lockAtLeastForString`](#📝
- [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 注解的 lockAtMostFor 和 lockAtLeastFor 参数,在设置时间时有三种灵活的写法,可以根据项目的代码风格和可读性需求来选择:
- 纯数字毫秒 (仅支持旧版属性) :时间单位是毫秒(ms),但这种写法只能用在已被弃用 的
lockAtMostFor和lockAtLeastFor属性上。 - 标准缩写格式 (推荐) :使用
数字 + 单位的格式,例如"5m","30s","2h",代码清晰易读。ms(毫秒),s(秒),m(分),h(时),d(天)
- 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分钟
- 格式:
📝 lockAtMostForString 与 lockAtLeastForString
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时间不宜过长,否则可能影响任务恢复速度