ShedLock-定时任务锁
大约 3 分钟
ShedLock-定时任务锁
- 官网
- 配合Spring的Scheduled定时任务使用
- 通过数据存储实现分布式锁,保证多容器的情况下,定时任务只执行一次
设置
配置
- 这里演示使用Jdbc,支持Mysql、Postgres等关系型数据库
- 还可以支持Mongo、Redis等存储方案,依赖包查询官网
<properties>
<!-- JDK>17 && Spring 6 使用5.x以上 ,旧版本 使用4.x -->
<shedlock.version>5.10.2</shedlock.version>
</properties>
<dependencies>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>${shedlock.version}</version>
</dependency>
<!-- 配套锁表,演示使用jdbc,还支持Mongo、Redis等存储方案 -->
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>${shedlock.version}</version>
</dependency>
<dependencies>
- 入口增加 @EnableSchedulerLock注解
- defaultLockAtMostFor:默认最长锁定时间
- 用于异常情况,如:执行过程中节点挂掉,未调用锁释放方法,该时长达到会强制释放锁
- 如果执行时间小于该时长,执行完成会自动释放锁
- defaultLockAtLeastFor:默认最短锁定时长
- 用于避免执行时长过短,如下情况:
- 两个容器调用间隔100ms,执行时间是50ms
- 第一个容器获得锁,50ms执行完成,释放锁
- 第二个容器在100ms时成功获得锁(已第一个容器被释放),执行程序
- 因此两个容器重复执行了
- 该参数可避免以上情况,设置1分钟(1m)、10分钟(10m)等
- 用于避免执行时长过短,如下情况:
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "1m", defaultLockAtLeastFor = "1m")
class Application{
...
}
数据库配置
- 数据库创建表
-- MySql
CREATE TABLE IF NOT EXISTS shedlock(
name VARCHAR(64) NOT NULL,
lock_until TIMESTAMP(3) NOT NULL,
locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name)
);
---------------分割线,只需要选择一种
-- 官网介绍说明
# MySQL, MariaDB
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL,
locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
# Postgres
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP NOT NULL,
locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
# Oracle
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until TIMESTAMP(3) NOT NULL,
locked_at TIMESTAMP(3) NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
# MS SQL
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL, lock_until datetime2 NOT NULL,
locked_at datetime2 NOT NULL, locked_by VARCHAR(255) NOT NULL, PRIMARY KEY (name));
# DB2
CREATE TABLE shedlock(name VARCHAR(64) NOT NULL PRIMARY KEY, lock_until TIMESTAMP NOT NULL,
locked_at TIMESTAMP NOT NULL, locked_by VARCHAR(255) NOT NULL);
- jdbc配置
- 需要上面的jdbc配套锁表依赖
@Configuration
public class ShedLockConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
// 使用数据库时区
// .usingDbTime()
// 自定义时区
.withTimeZone(TimeZone.getTimeZone(ZoneId.systemDefault()))
.build()
);
}
}
使用
- 使用@SchedulerLock注解任务
- name: 名称id,用来识别任务
- lockAtMostFor:最长锁定时间(不填则使用入口类的设置的默认值)
- lockAtLeastFor:最短锁定时长(不填则使用入口类的设置的默认值)
import net.javacrumbs.shedlock.core.SchedulerLock;
@Slf4j
@Component
public class SchedulerTask {
// Spring定时任务
@Scheduled(cron = "*/15 * * * * *")
// 锁配置
@SchedulerLock(name = "scheduledTaskName", lockAtMostFor = "10m", lockAtLeastFor = "1m")
public void scheduledTask() {
// do something
log.info("执行,时间:{}", LocalDateTime.now());
}
}
- 约一分钟执行一次
- 虽然定时是每15秒执行一次,但是锁表是1分钟
- 所以后面几次执行由于锁表跳过了
- 定时任务的15秒是按时钟,而锁表时长是执行代码开始计算,可能已消耗了几毫秒,所以有可能第1分钟的时候依然在锁表状态(差几毫秒),导致在1分15秒时才执行第二次
- 数据表生成的数据如下: | | lock_untill | locked_at | locked_by | | --- | --- | --- | --- | | scheduledTaskName | 2024-01-01 00:01:00.000 | 2024-01-01 00:00:00.000 | xxc-computer |