多服务环境下定时任务重复执行问题解决方案

文章探讨了在多服务器部署中如何避免定时任务重复执行的问题,提出了两种策略:一是通过配置文件固定某一台服务器执行定时任务,但这种方法在服务宕机时可能导致任务丢失;二是利用Redis的过期机制,在执行任务前检查Redis中的键值,确保任务只执行一次,但这种方法在Redis故障或系统时间不同步时可能存在风险。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

        当一个服务部署在多台服务器上时,定时任务可能出现多次执行的情况,就是每个服务上执行一次。有以下两种思路,一是固定死只有某服务器执行定时任务,二是随机暂停几秒,某一服务执行了,其他就不再执行。

1、固定某一个服务器作为执行定时任务的机器

        通过配置文件或者其他方式,在一个服务器懂的时候可以用参数动态配置他是是否执行程序中的定时任务。

        该方式存在一定的弊端,一个项目部署到到多个服务,本意是提高服务的负载和可用性,如果将定时任务固定设置在一个服务上, 那么该服务宕机后,定时任务就无法执行,存在少执行定时任务的可能性。

步骤:

        配置文件中设置参数值,确定是否开启定时任务

# 定时抽取corn表达式
schedule-corn: "0 30 07 * * ?"
# 是否开启定时任务 0不开启,1开启
schedule-tag: 1

        代码中获取参数并校验,改服务是否开启

//获取配置文件中的参数值
@Value("${schedule-tag}")
private Integer ScheduleTag;


@Scheduled(cron = "${schedule-corn}")
public void Schedule(){
    if (Objects.equals("1",ScheduleTag)){
        //执行定时任务的内容
    }
}

2、借助Redis的过期机制

        为你的定时器在Redis中定义一个键值对,可以用项目名称和服务器ip,执行任务前先从Redis中读取键,若没有值代表任务未被执行,同样的该台机器先更新redis,再触发定时任务。由于Redis存在过期机制,因此可以设置过期时间保证下次判断正常。

        该模式下也存在一定问题,当Redis宕机时,所有的服务都能够执行定时任务;当多个服务的系统时间不相同时,若Redis键值对的过期时间过小,也有可能出现重复执行的情况。

步骤:        

        将前置逻辑封装为一个方法

/**
 * 定时任务前置逻辑
 * @param expiryTime 过期时间
 * @param key 
 * @return
 */
public boolean preTask(String key,Long expiryTime) {
    // 随机停1-10秒。
    int randomInt = ThreadLocalRandom.current().nextInt(1, 10);
    try {
        Thread.sleep(randomInt * 1000L);
    } catch (InterruptedException e) {
        log.info(e.getMessage());
    }
    //获取服务所在服务器的名字
    String name = SystemUtil.getHostInfo().getName();
    //判断Redis是否存在该key,若不存在则保存参数中的键值对,并设置过期时间
    Boolean absent = redisTemplate.opsForValue().setIfAbsent(key, name + port, expiryTime, TimeUnit.SECONDS);
    if (Objects.isNull(absent) || !absent) {
        String namePort = (String) redisTemplate.opsForValue().get(key);
        log.info("本实例不执行此轮 {} 定时任务。由 {} 执行。", key, namePort);
        return false;
    }
    log.info("本实例开始执行 {} 定时任务。", key);
    return true;
}

        定时任务调用前置逻辑

//静态变量,作为该定时任务的key
private static final String EXTRACT_SCHEDULE_KEK = "TIME_TASK:EXTRACT_SCHEDULE_KEK";


@Scheduled(cron = "${schedule-corn}")
public void extractSchedule(){
    // 多实例限制
    boolean preTask = redisTaskUtil.preTask(EXTRACT_SCHEDULE_KEK, 3600L);
    if (preTask){
        //定时任务的内容
    }
}

    

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值