微服务系统,系统按照业务被拆分成多个模块,每个模块被部署在不同节点服务器上一个或者多个。这样的话,对定时任务而言就产生一些不确定性。对于包含定时任务的模块,你不确定这个服务被部署了多少份,假如被部署了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<String> success = checkModuleFeignService.giveMeToken(thirdService);
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.add(INSIDE_TOKEN_HEADER, success.getObj());
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Object> request = new HttpEntity<>(null, requestHeaders);
ResponseEntity<Success> 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尝试设置一个值,设置成功,允许执行业务代码,设置失败,逻辑结束,简单地说就是竞争执行锁。这样确保定时任务始终只能有一个线程能在执行。抢占到锁的线程执行对应的业务逻辑,估算好该任务的执行总时长,将锁的时间设置的比任务执行的时间更长,以防止任务还在执行,锁没了,被其他线程抢占了资源。业务执行出现异常,可以先捕捉,让线程睡眠一段时间,期间不放锁,稍后继续重新尝试执行业务。
网友评论