背景
在分布式多机集群环境下,一个服务可能有多个实例,其中的定时任务@Scheduled也会多次触发。在一些业务场景下,我们只希望他执行一次,此时可通过@SchedulerLock来对该定时任务进行加锁从而控制定时任务执行次数(也可通过Redis实现分布式锁)。
同时本次案列也配合线程池实现定时任务的多线程处理
依赖
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>2.3.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>2.3.0</version>
</dependency>
启用注解
- 配置类启用
@Component
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class ShedLockConfig {
@Bean
private LockProvider lockProvider(DataSource dataSource) {
//return new JdbcTemplateLockProvider(dataSource);
//自定义表名t_shedlock 默认表名shedlock
return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration
.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.withTableName("t_shedlock")
.build());
}
}
- 定时任务线程池配置
@EnableScheduling
@Configuration
public class ScheduleConfig implements SchedulingConfigurer{
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskExecutor();
scheduler.setPoolSize(50);
scheduler.setThreadNamePrefix("taskScheduler-");
scheduler.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
scheduler.setWaitForTaskToCompleteOnShutdown(true);
scheduler.setAwaitTerminationSeconds(60);
scheduler.initialize();
taskRegistrar.setScheduler(scheduler);
}
}
| 注解 | 含义 |
|---|---|
| @EnableScheduling | 开启定时任务 |
| @EnableSchedulerLock | 开启定时任务锁,defaultLockAtMostFor属性表示默认最大锁持有时间,PT30S表示30秒。 |
| @EnableAsync | 开启异步 |
创建数据库表(mysql)
//自定数据表名(mysql 8.0)
CREATE TABLE `t_shedlock` (
`name` varchar(64) COLLATE utf8mb4_general_ci NOT NULL,
`lock_until` timestamp(3) NULL DEFAULT NULL,
`locked_at` timestamp(3) NULL DEFAULT NULL,
`locked_by` varchar(255) COLLATE utf8mb4_general_ci DEFAULT NULL,
PRIMARY KEY (`name`)
) ENGINE = InnoDB DEFAULT SET = utf8mb4 COLLATE = utf8mb4_general_ci;
创建定时任务
这里为什么创建两个一模一样的定时任务, @SchedulerLock能够在不同实例(节点)进行加锁操作,保证任务执行一次,但@Scheduled在一个节点上是单线程的,也就是说@Scheduled定时任务在单节点执行时间点一致时,不可避免出现线程阻塞的问题,这也就是本次案例为什么一定要配合异步线程使用,同时解决不同节点和同一节点定时任务互相影响的情况,可根据实际情况自行选择策略。
@Component
public class CustomizeSchedule {
@Scheduled(cron = "0/5 * * * * ? ") // 5秒执行一次
@SchedulerLock(name = "test2",lockAtLeastFor = 30 * 1000,lockAtMostFor = 60 * 60 * 1000)
public void test1() {
System.out.println(Thread.currentThread());
}
@Scheduled(cron = "0/5 * * * * ? ") // 5秒执行一次
@SchedulerLock(name = "test1",lockAtLeastFor = 30 * 1000,lockAtMostFor = 60 * 60 * 1000)
public void test2() {
System.out.println(Thread.currentThread());
}
}
| 注解 | 含义 |
|---|---|
| @Async | 自定义异步执行 |
| @Scheduled | 定时任务执行 |
| @SchedulerLock | 定时任务锁 |
@SchedulerLock
| 属性 | 含义 |
|---|---|
| name | 分布式锁名称,锁名称必须是唯一的 |
| lockAtLeastFor | 指定在执行节点应保留锁定的最短时间。主要目的是在任务非常短的且节点之间存在时钟差异的情况下防止多个节点执行。这个属性是锁的持有时间,设置了多少就一定会持有多长时间,在此期间,下一次任务执行时,其他节点包括它本身是不会执行任务的;单位:毫秒 |
| lockAtMostFor | 指定在执行节点死亡时应将锁保留多长时间。这只是一个备用选项,在正常情况下,任务完成后立即释放锁定。 使用时必须将其设置为比正常执行时间长得多的值。如果任务花费的时间超过可能无法预测(更多的进程将有效地持有该锁);单位:毫秒 |
- 注意:
合理设置@SchedulerLock属性,防止定时任务漏掉或重复执行
84

被折叠的 条评论
为什么被折叠?



