这是我参与「第五届青训营」伴学笔记创作活动的第三天
前言
春节瓜分红包项目:用户集卡,开奖时对集齐卡片的用户发放随机红包。
简要流程:系统用脚本扫描集卡信息,汇总用户数据(MapReduce任务),计算用户获得的金额然后发放红包(Map任务)。
实现原理
核心架构
分布式定时任务核心要解决触发、调度、执行三个关键问题。
- 触发器:Trigger,解析任务,生成触发事件。
- 调度器:Scheduler,分配任务,管理任务生命周期。
- 执行器:Executor,获取执行任务单元,执行任务逻辑。
除此之外,还需要提供一个控制台(Admin),提供任务管理和干预功能。
数据流
用户将 任务基础信息(创建人等),触发规则(定时、延迟、周期),任务代码发送给控制台。控制台将其存入任务数据库。触发器触发任务执行并调用调度器对任务进行协调与分配。最后执行器执行任务。
控制台
名词解释
- 任务:job,任务元数据。
- 任务实例:jobinstance,任务运行的实例。
- 任务结果:jobresult,任务实例的运行结果。
- 任务历史:jobhistory,用户可以修改任务信息,任务实例对应的任务元数据可
对应关系
- 任务与任务实例是一对多的关系。因为任务能执行多次。
- 任务实例和任务结果是一对多的关系。因为任务可能失败,需要重试。
- 任务和任务历史是一对多的关系。记录用户对任务元数据的修改日志。
方案
方案一
定时扫描+延时消息(腾讯、字节方案)
Scanner周期性扫描db,将需要执行的任务传给消息队列。由于存在延迟,所以扫描时提前将任务取出,发送延时消息(如10分钟后执行某任务)。为了避免重复执行任务,要对db中的数据修改,避免下次扫描又被取出。
方案二
时间轮(Quartz方案)
时间轮是一种高效利用线程资源进行批量化调度的一种调度模型。时间轮是一个存储环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表。
目标:遍历任务列表,从中找出当前时间点需触发的任务列表。
- 使用链表存储任务,每个元素代表一个任务。查询复杂度O(n),修改复杂度O(1)。
- 由于不需要知道全部任务什么时候执行,而是知道最先执行的任务即可。因此考虑使用最小堆存储任务,按执行时间排序,每个节点存储同执行时间的任务的列表。查询复杂度O(1),修改复杂度O(logn)。此方法仍然有缺点,因为最小堆实际上是一个数组,而执行时间不同的任务要被存放在不同的节点里,所以当任务量大且任务执行时间各不相同时,最小堆将是一个无限扩大的数组。
- 使用时间轮(像是一个时钟)存储任务,每个节点存储同执行时间的任务的列表。查询复杂度O(1),修改复杂度O(1)。缺点是一个时钟可能只有60个刻度(记录每秒要执行的任务),如果一个任务在一分钟后执行,则在任务中加入一个count标致,每次经过任务时count减1,当count等于0时执行任务。
- 使用多级时间轮存储任务,上级时间轮转过对应刻度后把任务塞入下级时间轮中。如任务距离下次执行1时5分0秒,则它被插入小时时间轮。当小时时间轮指到1时,将所有1时的任务插入到分钟时间轮。这个任务被插入到5分处,而此时分的指针指在0的位置。同理,5分钟后它被插入到0秒的位置然后直接被触发器送进消息队列等待调度器调度。