在实际开发中由于项目部署在分布式或集群服务器上,代码中如果使用spring-boot的schedule定时任务,那么就会导致定时任务多次触发。解决这个问题思路很简单,就是通过分布式锁,多个应用实例上的定时任务在执行前先去获取锁,那个实例获取到了锁,哪个示例上的定时任务去执行。
本文介绍shelLock(https://github.com/lukas-krecan/ShedLock),给spring-boot的schedule上锁。原文介绍:
ShedLock只做一件事。它确保您的计划任务最多同时执行一次。如果正在一个节点上执行任务,它将获取一个锁,以防止从另一个节点(或线程)执行相同的任务。请注意,如果一个任务已在一个节点上执行,则其他节点上的执行不会等待,只会跳过它。
示例:
1、maven
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>0.16.1</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>0.16.1</version>
</dependency>
shedlock提供了mongo、redis、zookeeper、jdbc等的锁实现,这里使用jdbc。
2、shedlock在springboot项目中配置:
@Configuration
@EnableScheduling
public class ShedlockConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(dataSource);
}
@Bean
public ScheduledLockConfiguration scheduledLockConfiguration(LockProvider lockProvider) {
return ScheduledLockConfigurationBuilder
.withLockProvider(lockProvider)
.withPoolSize(10)
.withDefaultLockAtMostFor(Duration.ofMinutes(10))
.build();
}
}
这里使用了jdbc的lock provider,同时设定默认最大的lock expire时间为10分钟。
3、shedlock表:
CREATE TABLE shedlock(
name VARCHAR(64),
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
)
这个需要手工建立,不过可以优化一下,自动建表。
4、使用:
@Scheduled(cron="0 0 * * * ? ")
@SchedulerLock(name = "channelCronName", lockAtMostFor = 5*1000,lockAtLeastFor = 5*1000)
public void channelCron() {
logger.info("*********每小时执行一次");
try {
List<Channel> channels = channelService.getChannels();
if (channels != null) {
ChannelCache.setChannelList(channels);
}
} catch (Exception e) {
logger.error("channelCron is error.",e);
}
}
- name属性(锁的名称)必须指定,每次只能执行一个具有相同名字的任务。
- lockAtLeastFor属性,指定保留锁的最短时间。主要目的是在任务非常短的且节点之间存在时钟差异的情况下防止多个节点执行。这个属性是锁的持有时间。设置了多少就一定会持有多长时间,再此期间,下一次任务执行时,其他节点包括它本身是不会执行任务的。
- lockAtMostFor属性,设置锁的最大持有时间,为了解决如果持有锁的节点挂了,无法释放锁,其他节点无法进行下一次任务。
当定时任务执行是,查看mysql中的表