在Spring中,配置@EnableScheduling和@Scheduled之后,就能实现定时任务的功能(任务调度(1)-Spring @Scheduled详解 - 掘金 (juejin.cn))。
但是现在的服务实例一般都是部署多个实例,也就是具有水平扩展的能力。如果多个服务实例都在运行定时任务,会产生
- 资源的浪费
- 定时任务的重复执行
所以需要一种机制来保障多个服务实例之间的定时任务正常、合理地运行。
Shedlock
ShedLock(lukas-krecan/ShedLock: Distributed lock for your scheduled tasks (github.com))的出现就是为了解决上述问题,它可以确保你的计划任务在同一时间最多执行一次。如果一个任务正在一个节点上执行,它就会获得一个锁,防止另一个节点(或线程)执行相同的任务。如果一个任务已经在一个节点上执行,其他节点上的执行不会等待,只是被跳过。
ShedLock使用外部存储,如下所示:
ShedLock不是一个分布式调度器。请注意,ShedLock不是也永远不会是成熟的调度器,它只是一个锁而已
ShedLock被设计用于这样的情况:你的调度任务还没有准备好被并行执行,但可以安全地重复执行
此外,锁是基于时间的,ShedLock假定节点上的时钟是同步的。
与Spring的结合
1 创建数据库和表
CREATE TABLE `shedlock` (
`name` varchar(64) NOT NULL,
`lock_until` timestamp(3) NULL DEFAULT NULL,
`locked_at` timestamp(3) NULL DEFAULT NULL,
`locked_by` varchar(255) DEFAULT NULL,
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
2 引入依赖(这里使用MySQL作为外部锁)
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
3 配置文件
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://xxx.xxx.xxx.xxx:3306/shedlock?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull
username: xxx
password: xxx
type: com.mysql.cj.jdbc.MysqlDataSource
4 启动类添加注解
@SpringBootApplication
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
public class ShedlockMain {
public static void main(String[] args) {
// ...
}
}
5 提供provider
@Configuration
public class SchedulerConfiguration {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(JdbcTemplateLockProvider.Configuration.builder().withJdbcTemplate(new JdbcTemplate(dataSource)).build()
);
}
}
6 实际运行定时任务的代码
@Component
public class TaskScheduler {
@Scheduled(cron = "0/10 * * * * ?")
@SchedulerLock(name = "task",
lockAtLeastFor = "2000", lockAtMostFor = "5000")
public void scheduledTask() {
// ...
}
}
通过如上配置,就可以很简单的与Spring提供的调度进行结合,实现分布式锁。