朋友咨询了这么个事情,他们应用之前是单体应用,现在用nginx做了集群,但是项目中有定时任务,集群前一直运行稳定,集群后任务一直重复跑,这种情况怎么处理呢?
一般这种情况处理方法:
1.把定时任务拆分出来,单独作为一个项目跑,但针对老项目投入太多,一般没人愿意这么干
2.打补丁,这种方法是最常用的,在现有基础上添加条件,使得其只执行一次
打补丁的方法也有很多,可以根据需要自行选择
1).通过指定IP和端口,确保只有一个服务在跑定时任务
这种方法快捷,但是一般做了集群,不愿意这么做,一旦这个服务挂掉,定时任务就不起作用了,所以适用于对定时任务精度要求不高的
2).数据库记录定时任务执行状态,每次先从数据库抢占一个状态,谁抢到就由谁来执行
这种方法其实也是可以实现目的,但是适用于并发量小的,并发量如果过高,会发生状态抢占错误的情况,所以呢不是很可靠。但是很大程度上避免重复执行
3).利用redis数据库的单线程特点,抢占锁
这种方式最常用,效果也最好。不过利用这种方式也分多钟处理方式
(1)设置key值,服务发现有key,就暂停执行;如果没key,就开始执行定时任务,定时任务执行完毕,删除该key;
这样确保了不会重复执行,但是有可能漏执行,比如执行定时任务期间,服务挂掉,没有删除redis的key,这样以后服务都会当做别的服务占用了该key
(2)在上面的方法基础上,加个key的有效期;到时间自动删除key,重新被抢占
这样不会进入死锁,但是需要精准的设计好,有效期的时长,避免上次任务没执行完毕,就被下次任务抢占
(3)我们采取了,上面两种方式的集合,定时任务的key既有手动删除,也有通过时效自动时效
这个我们是这样做的,利用redisTemplate.opsForValue().increment(lockName,1)数字累加的方式,并同时设置有效期,这个有效期尽可能长点,因为我们主要靠定时任务执行完毕主动删除key,但是怕死锁,我们设置了失效期;同时我们利用累加,当累加数字大于3,们就认为已经死锁了,就会删除key,重新抢占;这样很大程度避免了重复执行和死锁,截止目前运行稳定,验证成功;
//任务锁
public static String taskLock(String lockName){
RedisTemplate redisTemplate=(RedisTemplate)SpringContextUtil.getBean(StringRedisTemplate.class);
String s=redisTemplate.opsForValue().increment(lockName,1).toString();
if(Long.parseLong(s)>=2){
delete(lockName);
}
return s;
}
以上是针对老项目而言,新项目最好是吧定时任务剥离处理,利用分布式定时任务框架来处理;比如利用quartz集群分布式(并发)部署解决方案
利用它拯救单体应用,应用服务挂了也不用担心了