设计一个高效的定时任务系统( 五 )


很显然上面的方案肯定是行不通了,下面我们开始扩容 。
高可用
回答高可用的问题先说目前的思路:单机纯内存抗所有任务 。要做高可用必然会大于等于 2 台机器 。
那么两台机器都执行任务必然会重复运行,该用什么方案在多机环境中可以统一管理,统一调度,统一运行任务呢?
方案一:传统方案-数据库(独占锁)
任务触发的关键在于 Trigger 触发器,我们只用管住 Trigger 的手让它别乱动 task 就好,基于数据库操作的话,保证任一时刻某个 Trigger 只会被触发一次即可 。这里可以使用行级锁来实现 。
某台机器执行到这个 Trigger 的时候向数据库插入一条 Trigger 记录并持有该锁,那么其余机器即使遇到了这个任务也不能执行 。

设计一个高效的定时任务系统

文章插图
 
方案二:分布式组件特性支持(分布式锁)
一般来说数据库肯定是值得信任的,但是面对实施要求高,任务执行频繁的场景的时候,数据库又是不敢信任的,数据库有一定的并发瓶颈 。
要保证同一时刻的唯一性,除了数据库的锁特性以外,分布式组件肯定也支持,比如 Zookeeper,ETCD 等等 。
可以利用 ZK 的临时节点性质,同一个任务注册一个唯一的节点,哪个机器抢到这个节点谁就来执行任务即可 。
设计一个高效的定时任务系统

文章插图
 
产品加需求
基础功能我们已经完成,高可用也做到了,上线一段时间,产品觉得的整点幺蛾子啊,不然 KPI 咋整 。
①新增功能
基于事件分发的任务机制:可能有一些任务是基于特定的条件触发,这种任务在分布式环境下一般自己实现分布式锁来实现,那么任务系统既然提供分布式特性也可以实现分布式锁的功能 。
所以对于这一类任务完全可以交给任务系统来做,把它当成一次性触发的任务 。
②新增特性
任务终止:如果某个任务因为业务需求不再执行,那么是否可以不发布的条件下终止该任务呢?这个时候任务终止的功能就很重要,产品经理暗暗自喜,老板加鸡腿 。
任务依赖:B 任务依赖 A 任务的结果才能执行,所以要提供任务之间的级联操作 。
任务分片:如果我们有 3 台执行任务的机器,有 10 个每 5s 执行一次的定时任务,恰恰每个任务都打到第一台机器 。它累如黄牛的时候另外两台还在晒太阳这岂不是资源的浪费嘛 。
为了避免任务集中到某一台机和提高资源利用率,我们需要一种将任务均衡分配到当前所有可执行机器的能力,这就是所谓的分片机制 。
常用的分片算法有如下:
平均分配算法:
  • 如果有 3 个任务实例,分成 9 片,每个实例对应到的分片就是:1=[1,2,3],2=[4,5,6],3=[7,8,9] 。
  • 如果有 3 个任务实例,分成 8 片,每个实例对应到的分片就是:1=[0,1,6],2=[2,3,7],3=[4,5] 。
  • 如果有 3 个任务实例,分成 10 片,每个实例对应到的分片就是:1=[0,1,2,9],2=[3,4,5],3=[6,7,8] 。
根据作业名 hash 值决定根据 IP 升序/降序算法:
  • 如果有 3 个任务实例分别为 1,2,3,作业名称对应的 hash 值如果为奇数就按照 IP 升序寻找机器执行,作业名称对应的 hash 值如果为偶数就按照 IP 降序寻找机器执行 。这种算法最多要求最多只有两个分片,即只有两台机器参与执行 。
轮询算法:
  • 轮询的原理就很简单,基于可执行机器依次执行 。
任务日志:日志功能肯定不可少,检测任务执行成功与否,任务执行记录、时长,统计任务系统每日任务量等等 。
③新增容错机制
容错机制:任务执行失败,可能是任务本身逻辑问题,也可能是外部条件,所以可以设置一些容错机制,给它一次重试的机会 。
故障转移:集群中如果某一台机器发生了故障,它如果还在注册中心注册,那么任务会被该机器执行,很显然如果仅有失败重试策略,那么这个任务永远都不会执行成功 。
首先需要心跳检测机制,检测活动机器是否健康;其次需要在重试失败之后做任务转移操作,防止多次失败仍在同一台机器吊死 。
手动触发:如果万不得已遇到任务没有执行到的情况 ,那么是否要提供手动触发的机制呢?我想产品经理这种人精肯定不想背锅,所以你还是做吧!
后话
做完上面的功能之后,产品经理躺在他的折叠床上打着呼噜鼻子不时的还冒几个泡安心的睡着了 。


推荐阅读