集群环境的定时任务重复执行的解决方案
在开发的过程中,经常会遇到需要使用定时器的问题,比如需要定时向任务表写任务。但是项目是部署到集群环境下的,如果不做处理,就会出现定时任务重复执行的问题。问题产生的原因:由于我们项目同时部署在多台集群机器上,因此到达指定的定时时间时,多台机器上的定时器可能会同时启动,造成重复数据或者程序异常等问题。
该问题的解决方案可能有以下几种,仅供参考:
一、指定机器执行包含定时器任务
该方案操作简单,但只适合临时解决以下,正常生产环境应该没人会采取该方案。
二、数据库加锁
通过数据库的锁机制,来获取任务执行状态,先更新,后执行的原则,可以实现避免任务重新执行的目标。
三、redis的过期机制和分布式锁
我们最终决定采用的方案。
package com.kiis.schedule;
import *
/**
* @Author: caozz
* @Date: 2023/6/28 17:31
* @Version: v4.6.0
**/
@Component
@Slf4j
public class ScheduledTask {
@Autowired
private IAdcodeTaskService adcodeTaskService;
private RedissonClient redissonClient = SpringUtils.getBean(RedissonClient.class);
@Scheduled(cron = "${kiis.task.adcode}")
public void scheduledSaveAdcode() {
saveAdcodeTask();
}
private void saveAdcodeTask(){
RLock lock = redissonClient.getLock("saveAdcodeTask");
Boolean flag = false;
try {
flag = lock.tryLock(0,10, TimeUnit.SECONDS);
if(flag){
log.info("加锁成功,开始执行业务");
try {
log.info("定时任务开始->saveAdcodeTask:" + new Date());
Collection<String> keys = RedisUtils.keys("vehicle:*");
for (String key:keys) {
Map<String, Object> vehMap = RedisUtils.getCacheObject(key);
Integer online = (Integer) vehMap.get("online");
if (online != null && online == 1) {
String adcodeKey = getAdcodeKey();
//先判断key是否存在,不存在则创建,并执行,否则不执行
log.info("定时任务-for循环内->saveAdcodeTask:" + vin);
Boolean hasKey = RedisUtils.hasKey(adcodeKey);
if (!hasKey) {
RedisUtils.setCacheObject(adcodeKey,0, Duration.ofMinutes(1));
log.info("定时任务-循环内未执行->saveAdcodeTask:" + vin);
AdcodeTaskBo bo = new AdcodeTaskBo();
bo.setName("test");
bo.setStatus(0);
//设置通用属性
bo.fillBaseEntity(true);
adcodeTaskService.insertByBo(bo);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}else{
log.info("加锁失败,没有获取到锁");
}
} catch (Exception ex) {
log.error("定时任务执行失败", ExceptionUtils.getStackTrace(ex));
} finally {
//获取到锁才释放锁
if(!flag){
return;
}
lock.unlock();
log.info("Redisson分布式锁释放锁");
}
}
}
业务代码解读:
当前业务是需要两分钟执行一次任务,所以以当前分钟作为key,并设置超时时间。线程进来先查询有没有key,有则表示已经执行过,没有则创建并执行。设置超时时间主要是为了清理不用的key。
分布式锁代码解读:
核心代码在于 lock.tryLock(0,10, TimeUnit.SECONDS);
/**
* @param waitTime 当前线程发现其他线程持有锁的等到时间
* @param leaseTime 当前线程业务执行满该时间自动释放锁
* @param unit 时间单位 小时、分、秒、毫秒等
*/
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
欢迎大家留言,以便于后面的人更快解决问题!另外亦欢迎大家可以关注我的微信公众号,方便利用零碎时间互相交流。共勉!
------愿来生只做陌上的看花人,无须入尘缘,仅行于陌上,看一川风花,无爱无伤-----