业务需要用到定时任务,但是必须考虑到定时任务在集群中可能在同一时刻被多次启动,这不是我们想要的,所以下面收集了一些常用方案,供大家参考。
问题
先来说说定时任务在集群中需要解决的问题:
1、如果集群中每台机器都启动定时任务,容易造成数据重复处理的问题
2、如果采用定时任务开关的方式,只一台机器的开关on,其他机器的开关off,可以避免数据重复处理的问题,但是存在单点故障的问题。
方案
①指定执行定时任务的机器
在多台机器中选择一台执行定时任务,每次执行的时候回判断当前机器和指定的机器是否一致,一致才会执行。
这种方法可以避免执行多次的情况,但是最明显的缺点就是单点故障问题,当这台指定的机器挂了以后,任务就不会执行了。
②通过动态的加载配置文件来实现
例如:新建多个Timmer的配置文件,对任务进行分配。针对不同的机器加载不同的配置文件,即不同的机器执行不同的任务。
这种方法缺陷就是要有至少两种不同的配置文件,那么维护起来就很麻烦。
③将定时任务单独执行
将程序中牵扯到的定时任务,从主程序中剥离出来单独执行。
这种方法缺陷就是增加了开发和维护的成本,如果不是大型项目的话,一般不建议这么做。
④监听程序
对程序进行监听,监听是否有重复执行的定时器任务,有的话则不执行相关的业务逻辑。
⑤从数据库中读取定时任务
由于都是连接同一个数据库,给数据库里的定时任务打上相应的标记,来区别有无重复执行定时任务。
create table timed_task (
type int(3) not null,
status tinyint(1) not null,
exec_timestamp bigint(20) not null,
interval_time int(11) not null,
PRIMARY KEY (type)
);
这个表是用来表示当前查询的任务是否可以执行,如果项目中有很多定时任务,通过type字段可以将多个不同类型的定时器进行区分,从而实现表的共用。
type # 将多个不同类型的定时器进行区分
status # 状态,是否可以执行定时任务,0为可执行,1为不可执行
exec_timestamp # 上一次定时任务的执行时间
interval_time # 时间阈值,以秒为单位
这里主要说一下字段interval_time
,主要用来检测执行定时任务的节点有无出现故障或宕机和防止短时间内定时任务被重复执行的情况。
可以设立一个检查机制,当执行节点出现故障或宕机的时候没有及时把字段status重新设置为0,让这个定时任务重新处于可执行状态的话,那么可以根据字段exec_timestamp和字段interval_time和当前时间进行比较。如果当前时间是大于字段exec_timestamp和字段interval_time之和的,而字段status始终为1的话,说明出现了故障或宕机,那么就可以去检查程序哪里出现了问题。
定时任务在集群中的执行,因为每台机器可能存在一个几秒的时间差,所以有可能出现
定时任务已经被某台机器处理过了,处理时间只用了几毫秒,所以字段status被重置为0,让这个定时任务重新处于可执行的状态,但是因为几秒钟的偏差,这时某台机器才到了定时任务的指定定时时间,发现数据库中这个定时任务的字段status为0处于可执行的状态,那么会又重新执行了一次这个定时任务,出现数据重复处理的情况。所以这个字段interval_time
也可以用来防止重复执行的问题,当前时间如果小于字段exec_timestamp和字段interval_time之和的话,并且字段status为0,说明短期内这个定时任务是已经被执行过一次的,那么就不允许再次执行了。
⑥使用redis的过期机制和分布式锁
借用redis的分布式锁来解决数据重复处理的问题,为了防止锁被长久锁定,或者防止客户端崩掉了没有删掉锁,可以用expire加入过期时间。