分布式定时任务|青训营笔记

70 阅读4分钟

这是我参与「第五届青训营」伴学笔记创作活动的第三天

前言

春节瓜分红包项目:用户集卡,开奖时对集齐卡片的用户发放随机红包。

简要流程:系统用脚本扫描集卡信息,汇总用户数据(MapReduce任务),计算用户获得的金额然后发放红包(Map任务)。

实现原理

核心架构

分布式定时任务核心要解决触发、调度、执行三个关键问题。

  1. 触发器:Trigger,解析任务,生成触发事件。
  2. 调度器:Scheduler,分配任务,管理任务生命周期。
  3. 执行器:Executor,获取执行任务单元,执行任务逻辑。

除此之外,还需要提供一个控制台(Admin),提供任务管理和干预功能。

数据流

用户将 任务基础信息(创建人等),触发规则(定时、延迟、周期),任务代码发送给控制台。控制台将其存入任务数据库。触发器触发任务执行并调用调度器对任务进行协调与分配。最后执行器执行任务。

控制台

名词解释

  • 任务:job,任务元数据。
  • 任务实例:jobinstance,任务运行的实例。
  • 任务结果:jobresult,任务实例的运行结果。
  • 任务历史:jobhistory,用户可以修改任务信息,任务实例对应的任务元数据可

对应关系

  • 任务与任务实例是一对多的关系。因为任务能执行多次。
  • 任务实例和任务结果是一对多的关系。因为任务可能失败,需要重试。
  • 任务和任务历史是一对多的关系。记录用户对任务元数据的修改日志。

方案

方案一

定时扫描+延时消息(腾讯、字节方案)

Scanner周期性扫描db,将需要执行的任务传给消息队列。由于存在延迟,所以扫描时提前将任务取出,发送延时消息(如10分钟后执行某任务)。为了避免重复执行任务,要对db中的数据修改,避免下次扫描又被取出。

方案二

时间轮(Quartz方案)

时间轮是一种高效利用线程资源进行批量化调度的一种调度模型。时间轮是一个存储环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表。

目标:遍历任务列表,从中找出当前时间点需触发的任务列表。

  1. 使用链表存储任务,每个元素代表一个任务。查询复杂度O(n),修改复杂度O(1)。
  2. 由于不需要知道全部任务什么时候执行,而是知道最先执行的任务即可。因此考虑使用最小堆存储任务,按执行时间排序,每个节点存储同执行时间的任务的列表。查询复杂度O(1),修改复杂度O(logn)。此方法仍然有缺点,因为最小堆实际上是一个数组,而执行时间不同的任务要被存放在不同的节点里,所以当任务量大且任务执行时间各不相同时,最小堆将是一个无限扩大的数组。
  3. 使用时间轮(像是一个时钟)存储任务,每个节点存储同执行时间的任务的列表。查询复杂度O(1),修改复杂度O(1)。缺点是一个时钟可能只有60个刻度(记录每秒要执行的任务),如果一个任务在一分钟后执行,则在任务中加入一个count标致,每次经过任务时count减1,当count等于0时执行任务。
  4. 使用多级时间轮存储任务,上级时间轮转过对应刻度后把任务塞入下级时间轮中。如任务距离下次执行1时5分0秒,则它被插入小时时间轮。当小时时间轮指到1时,将所有1时的任务插入到分钟时间轮。这个任务被插入到5分处,而此时分的指针指在0的位置。同理,5分钟后它被插入到0秒的位置然后直接被触发器送进消息队列等待调度器调度。