多台或者集群环境下如何保证spring定时器只执行一个

时间:2024-05-22 16:37:32

 多台或者集群环境下如何保证spring定时器只执行一个

 

先说一下我们的系统,

在65和66上分别部署有weblogic节点,共计四个,在项目中我们的定时器会隔一段时间就从其它的五个系统中取数据,这时就出现了问题,本来取一次数据就可以的,现在重复执行了三次,同时还造成了对方服务器的压力。

 

现在说一下我们项目怎么解决的这个问题:

1、首先想到的是在定时执行任务这个文件中添加ip地址,固定成某一台能执行,但我们一台服务器上有两个节点,方案不可行。

2、后来想到使用redis来保存一个状态位,是1表示可以执行,0表示不可以执行,但要是一个节点get到的是1,在它设置此status为0的时候,另一个节点提前一点点get到的也是1,这个时候这两个节点就都会执行,随后想到可不可以使用redis的事务加watch关键字,细想之后redis的事务是一组命名打包执行,中间不再执行其它操作,也实现不了。

3、既然redis的事务不可以,那数据库的应该可以吧。从而最终找到了现在的解决方案。

 

 

能解决的问题:

1、多个节点只有一个节点执行任务

2、即使某个正在执行的节点挂掉了,没有来的及修改状态位,也不影响定时器的执行。

 

 

 

下面是数据库表的设计:

 多台或者集群环境下如何保证spring定时器只执行一个

多台或者集群环境下如何保证spring定时器只执行一个

这个表是用来表示当前查询的任务是否可以执行,通过type字段可以将多个不同类型的定时器进行区分,从而实现表的共用。

此表中的status字段表示是否可以执行定时任务。

Datetime字段用来记录当前任务的执行时间。

Intervallen这个字段是我们规定一段时间间隔。

 

 

 

代码示例如下:

 多台或者集群环境下如何保证spring定时器只执行一个

------------------------------------------

 多台或者集群环境下如何保证spring定时器只执行一个

 

 

这个是具体的查询是否可以执行任务的逻辑。

下面是具体的sql

 多台或者集群环境下如何保证spring定时器只执行一个

 

这里的for update很重要。

 

 

下面针对上面的步骤说明一下:

1、当定时器执行的时候会执行running = sysScheduleStatusService.getAndSetScheduleStatus(parameter);

这个方法,running表示是否可以执行,true表示可以执行。

2、在这个方法中,会先执行SysScheduleStatus scheduleStatus = sysScheduleStatusDao.getScheduleStatus(parameter);这个方法,此方法会调用id为getScheduleStatus的sql语句,这个语句中的for update 关键字会对查询出来的记录加锁。即:

多台或者集群环境下如何保证spring定时器只执行一个

对这一条记录加锁(type为1表示某个类型的定时器)。
然后根据取出来的数据判断status是否为1,为1表示可以执行此定时器,随即调用update方法,将数据库的此字段改为0(不可以执行),提交事务,返回true。由于for update的存在,当某个线程执行这个查询语句的时候,已经对此条记录加锁,别的线程再去查询的时候就会处于等待状态,直到这个线程的事务提交,然后才可以查询,此时查询到的status为0,不可以执行,返回false。
在定时器方法的最后我们要更新表的status为1,以便下一次能顺利执行。这个update的方法我们一定要放到finally中,否则出现异常就有可能这个状态位一直为0了(其实为0也可以解决的);
我们通过status是否为1来判断是否可以执行,但要是节点在执行定时器的时候突然宕机或者其它原因,没有及时的将status设置成1,这个时候我们的定时器就永远走不了了。在这里就使用到了datetime字段和intervallen字段了。
逻辑:当status为0时,我们继续向下走,获取当前时间,当当前时间大于datetime+intervallen时我们就认为某个执行定时器的节点不是宕机就是出现其它异常导致未能及时的将status字段设置成1。这时我们照样返回true,同时更新一下对应type的status和datetime字段。这样即使节点挂掉,也不影响下一次定时任务的执行(由其它节点执行)。
在我这个代码里面我是直接在数据库里面进行了判断,isovertime表示是否超时,为1表示超时我们返回true,执行任务。(这样考虑是因为我们用sysdate来和datetime比较是比较好的,如果我们拿到java代码里面的话,有可能java运行的服务器的时间和数据库的时间不同步)
 
优化:由于我们的定时任务执行的时间比较长,其实还可以将查询出来的list分成三四个list,然后让spring的线程池threadPoolTaskExecutor来执行,从而减少执行时间。
 

要说明一点:

Service 中的这个getAndSetScheduleStatus()方法一定要开启事务。我在做这个的时候发现我们项目中的事务配置的不太对,从而这个方法没有开启事务,当多个节点执行时就出现因为for update而一直等待的情况。下图是事务的配置:

多台或者集群环境下如何保证spring定时器只执行一个

 


https://zhidao.baidu.com/question/510837808.html