发布于 

多节点定时任务的同步

背景

现在有两个或以上的web节点,启动时,会查询数据库,根据数据库中配置的数据(cron表达式等)创建定时任务。由于期望一个任务定时只有一个节点执行,所以需要多个web节点间做master竞选,这个已经实现了,身为master的web节点会将定时任务发送给实际执行的worker节点执行,非master节点的web节点会跳过此定时任务。

然而,由于系统有个功能,用户可以在web页面对定时任务进行增删改查。当用户访问某一个web节点增删改时,该节点可以通过quartz添加、修改、删除定时任务,但是,由于没有消息组件,其他的web节点无法得知定时任务的修改,还是按照它启动时查询数据库得知的信息在运转,这肯定是有问题的。

解决思路

思考了几种解决方式:

  1. 只在master节点上做定时任务的增删改
    直接pass掉这个方案,首先,这样做,非master的节点没有意义,负载均衡没有意义,如果master挂掉同样会遇到定时任务丢失的问题
  2. web节点定时查询数据库做更新
    这并不是一种好的解决方式,频率低了任务会不及时,频率高了会频繁查询更新,并不友好。就好像websocket之前使用http做轮询,除非逼不得已才这样做
  3. 引入消息队列比如kafka
    为了这一个问题,需要重新搭建一套kafka集群,似乎成本太大(还要申请机器、环境、端口巴拉巴拉)。如果已经有一套公用的消息集群和接口..这其实是个好办法
  4. 用zookeeper作信息同步
    既然前面已经用了zookeeper做主从竞选,那么也继续用它作为一个定时任务信息的同步器,多个web节点都对它保持监听,如果发生变化,按规则更新自己本地的定时任务

具体方案

总的来说,每个web节点在启动时,查询所有的定时任务,将每个定时任务写到zookeeper上,并保持监听,如果有修改,则根据情况更新本地定时任务

  1. web节点启动时,将所有定时任务通过quartz添加,同时,对于每一个定时任务,向zookeeper的/jobs节点下创建新的临时节点,节点为/jobs/{taskId}, data为任务的具体信息
  2. web节点创建/jobs/{taskId}节点后,对该节点保持数据监听,同时对/jobs节点做子节点变化监听
  3. 另一个web节点启动时,将所有定时任务通过quartz添加,同时,对于每一个定时任务,向zookeeper中的/jobs节点下创建新的临时节点,若节点/jobs/{taskId}已存在,则保持数据监听,同时,对/jobs节点做子节点变化监听
  4. 某个web节点更新了某个定时任务的信息,首先通过quartz更新本地定时任务,然后判断zookeeper中该任务对应的临时节点是否存在
    4.1. 若存在,则更新该节点的数据;这时候,其他的web节点会收到该/jobs/{taskId}节点的数据变化消息,拿到变化后的数据,判断与本地缓存的是否一致,不一致则更新定时任务
    4.2. 不存在,则新增节点/jobs/{taskId}, 数据为该任务信息,并添加监听;这时候,其他的web节点会收到/jobs节点的子节点变化信息,重新获取/jobs下所有子节点信息,并与本地缓存的对比,判断是否一致,若不一致,则更新定时任务
  5. 某个web节点新增了定时任务,首先通过quartz新增本地定时任务,然后向zookeeper中新增节点/jobs/{taskId},并添加监听;这时候,其他web节点会收到/jobs节点的子节点变化信息,依次获取,添加监听,判断本地是否缓存了此任务,若没有,则新增,若有,则判断是否有变化,变化则更新
  6. 某个web节点删除了某个定时任务,由于业务上的删除并不是实际删除,只是修改该定时任务状态,所以,该web节点并不是去删除/jobs/{taskId}节点,而是去修改它的data信息。其他web节点监听到此节点变化,需要判断修改后状态,再判断是否需要本地删除此定时任务
  7. 某个节点恢复了某个定时任务,由于不是物理删除,等同于更新
  8. 如果某个web节点挂了,它创建的临时节点会被删除,其他web节点会收到通知,然后去获取/jobs节点下所有子节点。由于这时候,仅仅是zookeeper上节点被删除,但实际上,这个定时任务并没有被修改,所以,我认为不需要对删除的临时节点做处理,其他临时节点也不用去尝试创建新的临时节点,因为节点的删除并没有影响定时任务的执行,仅仅是添加和修改会影响

上面是大致的流程,感觉有些细节可能还会有问题,写代码的时候再考虑和测试

看代码

1