集群环境的定时任务重复执行的解决方案

在开发的过程中,经常会遇到需要使用定时器的问题,比如需要定时向任务表写任务。但是项目是部署到集群环境下的,如果不做处理,就会出现定时任务重复执行的问题。问题产生的原因:由于我们项目同时部署在多台集群机器上,因此到达指定的定时时间时,多台机器上的定时器可能会同时启动,造成重复数据或者程序异常等问题。
该问题的解决方案可能有以下几种,仅供参考:
一、指定机器执行包含定时器任务
该方案操作简单,但只适合临时解决以下,正常生产环境应该没人会采取该方案。
二、数据库加锁
通过数据库的锁机制,来获取任务执行状态,先更新,后执行的原则,可以实现避免任务重新执行的目标。
三、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;
欢迎大家留言,以便于后面的人更快解决问题!另外亦欢迎大家可以关注我的微信公众号,方便利用零碎时间互相交流。共勉!

posted @ 2023-08-17 16:57  东方欲晓_莫道君行早  阅读(881)  评论(0编辑  收藏  举报