一、SchedulerLock 使用场景
如果是分布式任务,即多个相同的应用执行定时任务,那么为了防止重复执行可以使用其他分布式锁做内部判断或其他形式的锁机制来防止重复执行。
SchedulerLock 提供了现成的封装好的分布式锁机制来防止定时任务被重复执行
github 官方说明:
https://github.com/lukas-krecan/ShedLock?tab=readme-ov-file
二、集成使用
1. Mave依赖 主体依赖必须添加
<dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-spring</artifactId><version>5.13.0</version>
</dependency>
lockprovider 依赖,下面会说到,但根据选择不同的锁方式,依赖其实也不同,如果是其他方式请改为其他依赖
<dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-provider-jdbc-template</artifactId><version>5.13.0</version>
</dependency>
2.启用 @EnableSchedulerLock
可以直接在springBoot 启动类或者新增配置类上新增
@Configuration // 指定为配置类 启动类上添加时 添加下面两个 这个不需要
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "10m")
class MySpringConfiguration {...
}
defaultLockAtMostFor 指定了默认的最大锁定时间,以免当前节点崩溃后其他节点拿不到锁无法继续执行
如果指定默认三十秒则可以如下
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S") //默认锁的最大占有30秒
3. 在定时任务上增加该锁
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
.......
.......@Scheduled(...) // 任务定时
@SchedulerLock(name = "scheduledTaskName")
public void scheduledTask() {// To assert that the lock is held (prevents misconfiguration errors)LockAssert.assertLocked();// do something
}
name = “scheduledTaskName”: 为指定当前锁的名
例如
import net.javacrumbs.shedlock.core.SchedulerLock;@Scheduled(cron = "0 */15 * * * *")
@SchedulerLock(name = "scheduledTaskName", lockAtMostFor = "14m", lockAtLeastFor = "14m")
public void scheduledTask() {// do something
}
通过设置lockAtMost,我们可以确保即使节点死亡,锁也会被释放。
通过设置lockAtLeastFor,我们确保它在十五分钟内执行的次数不超过一次。
请注意,lockAtMostFor只是一个安全网,以防执行任务的节点死亡,因此请将其设置为明显大于最大估计执行时间的时间。如果任务花费的时间超过lockAtMostFor,则可能会再次执行,结果将是不可预测的(更多的进程将持有锁)。
4. 配置锁提供者 LockProvider
JdbcTemplate 方式,即通过配置一张数据库表 为此提供锁的服务,官方提供的表建表语句,如果需要其他的可以额外加入
但是基础的这些字段都必须要有,且 name 必须为主键,表名可以修改,但配置 LockProvider 时也要保持一致
# 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);
需要的依赖,
<dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-provider-jdbc-template</artifactId><version>5.13.0</version>
</dependency>
添加该配置实体
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
...
// 可以同步添加到上面的配置类中 也可以另开@Bean
public LockProvider lockProvider(DataSource dataSource) {return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder().withJdbcTemplate(new JdbcTemplate(dataSource)).usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2.build());
}
额外指定schema 可以直接在表名中指定:
new JdbcTemplateLockProvider(datasource, "my_schema.shedlock")
也可以存在其他配置
new JdbcTemplateLockProvider(builder().withTableName("shdlck") // 指定表名.withColumnNames(new ColumnNames("n", "lck_untl", "lckd_at", "lckd_by")) // 固定的四列指定为其他名字.withJdbcTemplate(new JdbcTemplate(getDatasource())) // getDatasource() 可以封装数据源的来源 默认可以直接使用上面的 dataSource.withLockedByValue("my-value").withDbUpperCase(true) // 大小写敏感的数据库 默认false.build())
上面 DataSource 可以通过注入的方式作为成员属性直接传入
要使用具有区分大小写的表和列名的数据库,可以使用.withDbUpperCase(true)标志。默认值为false(小写)。
import javax.sql.DataSource;
...................@AutowiredDataSource dataSource;
...................
通过指定usingDbTime(),锁提供程序将使用基于DB服务器时钟的UTC时间。如果未指定此选项,则将使用来自应用程序服务器的时钟(应用程序服务器上的时钟可能不同步,从而导致各种锁定问题)。
强烈建议使用usingDbTime()选项,因为它使用特定于数据库引擎的SQL来防止INSERT冲突。
对于更细粒度的配置,请使用configuration对象的其他选项
不要手动从DB表中删除锁定行。ShedLock有一个现有锁行的内存缓存,因此在应用程序重新启动之前不会自动重新创建该行。如果需要,您可以编辑行/文档,只需冒持有多个锁的风险。
5.使用redis 作为锁提供
使用 Spring RedisConnectionFactory
<dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-provider-redis-spring</artifactId><version>5.13.0</version>
</dependency>
配置:
import net.javacrumbs.shedlock.provider.redis.spring.RedisLockProvider;
import org.springframework.data.redis.connection.RedisConnectionFactory;...@Bean
public LockProvider lockProvider(RedisConnectionFactory connectionFactory) {return new RedisLockProvider(connectionFactory, ENV);
}
使用 Spring ReactiveRedisConnectionFactory
<dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-provider-redis-spring</artifactId><version>5.13.0</version>
</dependency>
import net.javacrumbs.shedlock.provider.redis.spring.ReactiveRedisLockProvider;
import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory;...@Bean
public LockProvider lockProvider(ReactiveRedisConnectionFactory connectionFactory) {return new ReactiveRedisLockProvider.Builder(connectionFactory).environment(ENV).build();
}
使用 Jedis
<dependency><groupId>net.javacrumbs.shedlock</groupId><artifactId>shedlock-provider-redis-jedis4</artifactId><version>5.13.0</version>
</dependency>
import net.javacrumbs.shedlock.provider.redis.jedis.JedisLockProvider;...@Bean
public LockProvider lockProvider(JedisPool jedisPool) {return new JedisLockProvider(jedisPool, ENV);
}
更多支持查看官方说明
github 官方说明:
https://github.com/lukas-krecan/ShedLock?tab=readme-ov-file