Spring 中使用 ShedLock 实现分布式锁任务调度
- Spring 中使用 ShedLock 实现分布式任务调度
-
- 1. 简介
- 2. Maven 依赖配置
- 3. 配置 ShedLock
-
- 3.1 数据库表结构
- 3.2 Spring Boot 配置数据源
- 3.3 配置 LockProvider
- 3.4 启用调度与锁支持
- 4. 编写定时任务
-
- 参数详解
- @SchedulerLock时间格式详解
-
- 📝 `lockAtMostForString` 与 `lockAtLeastForString`
- 5. 总结
- 如果你正在寻找一种简单粗暴又可靠的分布式定时任务解决方案,那么 ShedLock 绝对值得一试。
Spring 中使用 ShedLock 实现分布式任务调度
1. 简介
Spring 提供了非常方便的注解方式来定义定时任务(@Scheduled),但在生产环境中,当我们部署多个实例时,默认的调度机制就会出现问题。
默认情况下,Spring 并不会在多个节点之间协调定时任务的执行,而是每个节点都会独立运行相同的任务。这显然不是我们想要的行为。
为了解决这个问题,我们可以引入 ShedLock —— 一个轻量级 Java 库,用于确保同一时间只有一个节点执行某个定时任务。相比 Quartz,它更加轻量且易于集成。
✅ 核心作用:防止多个应用实例同时执行相同的定时任务。
2. Maven 依赖配置
要在 Spring 项目中使用 ShedLock,首先需要引入以下依赖: shedlock-spring 5.x以上要求,java 17
<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 数据库:
<!– mysql 数据库可忽略 –>
<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 中配置数据源:
# 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 使用哪个数据源进行加锁操作:
@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)
✅ 正常执行完成后会立即释放锁;若节点崩溃,则等待最大锁定时间后自动释放。
@SchedulerLock时间格式详解
@SchedulerLock 注解的 lockAtMostFor 和 lockAtLeastFor 参数,在设置时间时有三种灵活的写法,可以根据项目的代码风格和可读性需求来选择:
- ms (毫秒), s (秒), m (分), h (时), d (天)
- 格式: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
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 时间不宜过长,否则可能影响任务恢复速度






