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 绝对值得一试。