多节点下定时任务单个线程执行

微服务系统,系统按照业务被拆分成多个模块,每个模块被部署在不同节点服务器上一个或者多个。这样的话,对定时任务而言就产生一些不确定性。对于包含定时任务的模块,你不确定这个服务被部署了多少份,假如被部署了3份,每天8点执行一些定时任务,如果不加处理的话,定时任务会被执行3次,这样非常不好。

目前有很多方法来解决这个问题

  • 如把定时任务统一写在一个模块内,让这个开发|测试|生产只跑一个服务,就可以避免这个问题,按照这个思路有现成的、更好的第三方框架 如 xxl-job
  • 第二个就是自己写业务逻辑来控制定时任务,保证在分布式情况下,只有一个定时任务会正常执行业务逻辑,其他的都不执行业务逻辑。

这里介绍的就是第二种方法,直接上代码

定时任务调度器

 @Scheduled(cron = "59 59 23 * * ?")
    public void sc12() {
        dataHupService.pushData2RushLibrary("pushData2RushLibrary");
    }

定时任务逻辑部分

/**
     * 推送大保活动数据到一网通办办证库
     *
     * @param keyPre redis key前缀
     */
    public void pushData2RushLibrary(String keyPre) {
        String runKey = DeveloperSetting.REDIS_KEY_PRO + keyPre;
        boolean run = true;
        try {
            log.info(" [办证库] 推送大保活动数据到一网通办办证库 ");
            Boolean hasRun = redisTemplate.opsForValue().setIfAbsent(runKey, true, 3, TimeUnit.MINUTES);
            //保证只有一个现成在运行
            if (hasRun != null && hasRun) {
                do {
                    try {
                        //获取访问token
                        Success success = checkModuleFeignService.giveMeToken(thirdService);
                        HttpHeaders requestHeaders = new HttpHeaders();
                        requestHeaders.add(INSIDE_TOKEN_HEADER, success.getObj());
                        requestHeaders.setContentType(MediaType.APPLICATION_JSON);
                        HttpEntity request = new HttpEntity<>(null, requestHeaders);
                        ResponseEntity response = restTemplate.exchange("http://dbsys-admin-check-place-module/activity/pushData2RushLibrary", HttpMethod.POST, request, Success.class);
                        if (Objects.requireNonNull(response.getBody()).getCode() == 0) {
                            run = false;
                            log.info(" [办证库] 推送大保活动数据到一网通办办证库 成功 ");
                        }
                    } catch (Exception e) {
                        redisTemplate.expire(runKey, 3, TimeUnit.MINUTES);
                        e.printStackTrace();
                        Thread.sleep(30000);
                    }
                } while (run);
            } else {
                log.info(" [办证库] 推送大保活动数据到一网通办办证库  lock fail: {} ", "锁未竞争到");
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info(" [办证库] 推送大保活动数据到一网通办办证库 exception {}", e.getLocalizedMessage());
            redisTemplate.delete(runKey);
        }
    }
 
 

这里借助于redis,在定时任务开始后,在redis尝试设置一个值,设置成功,允许执行业务代码,设置失败,逻辑结束,简单地说就是竞争执行锁。这样确保定时任务始终只能有一个线程能在执行。抢占到锁的线程执行对应的业务逻辑,估算好该任务的执行总时长,将锁的时间设置的比任务执行的时间更长,以防止任务还在执行,锁没了,被其他线程抢占了资源。业务执行出现异常,可以先捕捉,让线程睡眠一段时间,期间不放锁,稍后继续重新尝试执行业务。

你可能感兴趣的:(多节点下定时任务单个线程执行)