分布式多节点定时任务怎么只执行一次
该锁将持有5分钟,5分钟释放,当节点异常或者死亡,该锁默认在15分钟后自动释放。如果一个任务正在一个节点上执行,它会获取一个锁,以防止从另一个节点(或线程)执行相同的任务。如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只需跳过它即可。DB形式的外部存储需要创建表结构,redis等非结构形式的外部存储template会根据@SchedulerLock声明的锁名称自动创建对应的键值对
问题:
当我们有多个服务器,每个服务器上都有相同的定时任务代码时,比如每天凌晨定时插入数据。如果多个服务器上的定时任务都执行了会导致数据的重复。
解决办法:1、@SchedulerLock实现;2、 基于Redis的分布式锁;
1、@SchedulerLock实现;
Shedlock库可以确保你的定时任务最多同时执行一次。如果一个任务正在一个节点上执行,它会获取一个锁,以防止从另一个节点(或线程)执行相同的任务。如果一个任务已经在一个节点上执行,则在其他节点上的执行不会等待,只需跳过它即可 。
导入坐标:
compile('net.javacrumbs.shedlock:shedlock-provider-jdbc-template:2.1.0')
compile('net.javacrumbs.shedlock:shedlock-spring:2.2.0')
// Maven方式
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>2.1.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>2.2.0</version>
</dependency>
在数据库里加上创建提供锁的外部存储表(shedlock)
CREATE TABLE shedlock(
name VARCHAR(64) ,
lock_until TIMESTAMP(3) NULL,
locked_at TIMESTAMP(3) NULL,
locked_by VARCHAR(255),
PRIMARY KEY (name)
)
属性 | 说明 |
---|---|
name 锁名称 | name必须是主键 |
lock_until | 释放锁时间 |
locked_at | 获取锁时间 |
locked_by | 锁提供者 |
//@SchedulerLock声明的锁名称自动创建对应的键值对,提供锁。@SchedulerLock(name = “scheduledTaskName”)name值对应的是库里的name的赋值。
DB形式的外部存储需要创建表结构,redis等非结构形式的外部存储template会根据@SchedulerLock声明的锁名称自动创建对应的键值对,提供锁。
配置类:
@Component
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();
}
}
启用SchedulerLock,使用@EnableSchedulerLock注释将库集成到Spring中。
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
// 开启定时任务注解
@EnableScheduling
// 开启定时任务锁,默认设置锁最大占用时间为30s
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S")
@MapperScan(basePackages="cn.pilipa.finance.salary.persistence")
@ServletComponentScan
public class SalaryApplication {
public static void main(String[] args) {
SpringApplication.run(SalaryApplication.class, args);
}
}
在定时任务上添加@SchedulerLock注解
@Scheduled(cron = "0 */15 * * * *")
@SchedulerLock(name = "scheduledTaskName")
public void scheduledTask() {
// do something
}
SchedulerLock 参数
- @SchedulerLock
只有带注释的方法被锁定,库忽略所有其他计划的任务。您还必须指定锁的名称。同一时间只能执行一个任务。 - name
分布式锁名称,注意 锁名称必须唯一。 - lockAtMostFor & lockAtMostForString
指定在执行节点死亡时应将锁保留多长时间。这只是一个备用选项,在正常情况下,任务完成后立即释放锁定。 您必须将其设置lockAtMostFor为比正常执行时间长得多的值。如果任务花费的时间超过 lockAtMostFor了所导致的行为,则可能无法预测(更多的进程将有效地持有该锁)。
lockAtMostFor 单位 毫秒
lockAtMostForString 使用“ PT14M” 意味着它将被锁定不超过14分钟。 - lockAtLeastFor & lockAtLeastForString
该属性指定应保留锁定的最短时间。其主要目的是在任务很短且节点之间的时钟差的情况下,防止从多个节点执行。
@Scheduled(fixedDelay = 1000*60*10)
@SchedulerLock(name = "queryRechargeBill", lockAtMostFor = 1000*60*15, lockAtLeastFor = 1000*60*5)
public void queryRechargeBill(){
// do something
}
该锁将持有5分钟,5分钟释放,当节点异常或者死亡,该锁默认在15分钟后自动释放。在正常情况下,ShedLock在任务完成后立即释放锁定。实际上,我们不必这样做,因为@EnableSchedulerLock中提供了默认值, 但我们选择在此处覆盖它。
参考链接1:https://www.jianshu.com/p/94a0378798e1/
参考链接2:https://blog.csdn.net/wang20010104/article/details/127730997
参考链接3:https://blog.csdn.net/baidu_41634343/article/details/105660941
2、 基于Redis的分布式锁;
(1) 每个服务器生成随机uuid
(2) 利用redis的Setnx命令将随机uuid作为value存入redis
(3) 获取redis的value,与生成的随机uuid对比;
(4) value与生成的随机uuid匹配则执行,不匹配则不执行;
当前任务获取锁,如果获取到锁,则执行任务,如果获取不到,则什么都不干。
@Configuration
@Slf4j
public class ReportSchedule {
@Autowired
private RedissonClient redissonClient;
@Value("${server.port}")
private String port;
private static final String LOCK = "lock";
@Scheduled(cron = "*/5 * * * * ?")
public void reportTask() throws Exception {
RLock lock = redissonClient.getLock(LOCK);
if (lock.tryLock()) {
try {
String hostAddress = InetAddress.getLocalHost().getHostAddress();
log.info("ip:{},端口:{},在执行上报任务......", hostAddress, port);
log.error("mark");
} finally {
lock.unlock();
}
}
}
}
参考链接:https://blog.csdn.net/qq_34125999/article/details/126525626(有两种进阶改法)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)