这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
定时任务
单机定时任务 - Timer、Ticker
- Timer —— java
- Ticker —— Go
特点: 可以跨平台,只能控制一台机器的定时任务(仅单机可用)
单机定时任务 - ScheduledExecutorService
特点: 拥有线程池的功能,仅单机可用
任务调度 - Quartz
特点: 单任务极致控制,没有负载均衡机制
分布式定时任务!!!
优点: 平台化管理,分布式部署,支持海量数据
什么是分布式定时任务?
定时任务是指系统为了 自动 完成特定任务,实时、延时、周期性 完成任务调度的过程。分布式定时任务就是把分散的、可靠性差的定时任务纳入统一的平台,并实现集群管理调度和分布式部署的一种定时任务的管理方式。
按触发时机分类 :
- 定时任务 : 特定时间触发
- 延时任务 : 延迟触发,比如 10s 后触发
- 周期任务
分布式定时任务 - 执行方式
- 单机任务 : 随机触发一台机器执行任务,适用于计算量小,并发量低的任务
- 广播任务: 广播到所有机器上执行同一个任务,比如所有机器一起清理日志
- Map 任务: 一个任务拆解成很多个子任务,每个子任务负责一部分的计算,适用于计算量大,单机无法满足要求的任务
- MapReduce 任务:在Map任务的基础上,还可以对所有的子任务结果汇总计算,适用于计算量大,并且需要对子任务结果汇总的任务。
行业内定时任务框架
- Xxl - job : 开发迅速,易拓展,支持分片,支持简单的任务依赖和子任务依赖,不是跨平台的。开源免费,轻量级,开箱即用,操作简单易上手,在中小型公司使用广泛。
- SchedulerX 2.0:基于
AKKa框架自研,提供定时调度,调度任务编排和分布式批量处理的功能。付费使用,功能强大,在阿里巴巴内部广泛使用且久经考验 - TCT : 高性能,高可靠,通用的分布式任务调度中间件,支持国际通用的时间表达式,调度任务执行生命周期管理,解决传统地市任务单点及并发性能问题。支持任务分片,流程编排复杂调度任务处理能力,覆盖广泛的任务调度应用场景。未开源也未商用。
知识补充
分布式定时任务 与 单机定时任务
关系:
- 都可以实现自动化的定时、延时、周期任务调度
差异:
- 分布式定时任务可以支撑更大的业务体量。
- 分布式定时任务的性能、伸缩性、稳定性更高
分布式定时任务 与 大数据处理引擎
关系:
- 都可以对海量数据处理
- 性能、伸缩性、稳定性都很高
差异:
- 定时并不是大数据处理引擎要解决的核心问题
- 大数据处理引擎往往治理于将源数据处理成结果数据,分布式定时任务除了能做这个,还能调用
HTTP和RPC服务
实现原理
核心架构
分布式定时任务核心要解决 触发、调度、执行 三个 关键问题
- 触发器:Trigger, 解析任务,生成出发事件
- 调度器:Scheduler,分配任务,管理任务生命周期
- 执行器:Executor, 获取执行任务单元,执行任务逻辑
除此之外,还需要提供一个控制台(Admin),提供任务管理和干预功能。
控制台
基础概念
任务: Job, 任务元数据
任务实例: JobInstance,任务运行的实例
任务结构: JobResult,任务实例运行的结构
任务历史: JobHistory,用户可以修改任务信息,任务实例对应的任务元数据可以不同,因而使用任务历史存储
任务实例和任务结果之间也是 1:n 的关系,因为每个任务实例执行后,都可能失败,失败后就需要重新执行
任务和任务历史之间也是 1:n 的关系,不同版本的任务信息如果修改,就需要任务历史来记录修改。
任务元数据
任务元数据是用户对任务属性定义,包括任务类型调度时机、执行行为等。
graph LR
A[Job] --> B[基础信息 WHO ]
A --> C[调度时机 When]
A --> D[执行行为 What]
A --> E[执行方式 How]
任务实例
任务实例是一个确定的 Job 的一次运行实例
graph LR
A[JobInstance] --> B[Job_id]
A --> C[触发时间]
A --> D[状态 & 结果]
A --> E[过程信息]
触发器
核心职责
给定一系列任务,解析他们的触发规则,在规定的时间点触发任务的调度
设计约束
- 需要支持大量任务:十万,百万级的任务
- 需要支持秒级的调度:比如秒杀业务需要精确到秒
- 周期任务需要多次执行
- 需要保证秒级扫描的高性能,并避免资源浪费
方案一
定时扫描 + 延时消息(腾讯、字节方案)
方案二
时间轮(Quartz 所用方案)
时间轮是一种高效利用线程资源进行批量化调度的一种调度模型。时间轮是一个存储环形队列,底层采用数组实现,数组中的每个元素可以存放一个定时任务列表
查询复杂度 ,修改复杂度
问题 : 如果出现时间轮刻度不够怎么办?
- 给时间轮加上一个
count属性,相当于时间轮转过的圈数,规定当时间轮转过多少圈的时候再去执行这个任务。 - 使用多级时间轮存储任务,上一级时间轮转过对应刻度后再把任务塞入下一级时间轮中。
比如说上图中:任务二要在第 2 小时执行,所以任务二就会在时轮转到 2 的时候再放入分轮中去,分轮转到 3 后再放入到秒轮,然后秒轮就在 1 的时候执行了任务2。
查询和修改的时间复杂度同样为 ,且解决了单个时间轮刻度不够的问题。
触发器的高可用问题
核心问题:
- 不同业务之间,任务的调度相互影响怎么办
- 负责扫描和触发的机器挂掉了额怎么办
解法思路:
- 存储上:不同国别,不同业务做资源隔离
- 运行时,不同国别,不同业务分开执行
- 部署时 ,采用多机房集群化部署,避免单点故障,通过数据库锁或者分布式锁保证任务只被触发一次
Trigger模式
单 Trigger 模式
单 Trigger 模式下执行一致性高,但是会出现单点故障,机器故障时导致平台崩溃
Trigger 集群模式
有多个 Trigger 同时进行服务,一个 Trigger 挂掉还可以有其他 Trigger 相应,可以避免单点故障。但是需要避免同一个任务被多次触发,导致业务紊乱
数据库设计
行锁模式
为了保证在 Trigger 集群模式 下,同一个业务不被多次触发,数据库可以加上 行锁模式:在触发调度之前,更新数据库中的 JobInstance 状态,成功抢锁的 Trigger 才会触发调度。
graph TD
A[Trigger 1] -- 抢锁成功</br>触发调度 --> D[数据库]
B[Trigger 2] --> D
C[Trigger N] -- 抢锁失败</br>放弃调度 --> D
缺点 : 会有多台机器频繁竞争数据库锁,导致消耗资源,节点越多性能越差。
分布式锁模式
在触发调度之前,尝试抢占分布式锁,这里可以使用 Redis 锁或者 Zookeeper 锁
graph TD
A[Trigger 1] -- 抢锁成功</br>触发调度 --> D[Redis]
B[Trigger 2] --> D
C[Trigger N] -- 抢锁失败</br>放弃调度 --> D
特点: 性能比较高,多家公司使用这种方案,比较推荐。
调度器
核心问题:
- 资源来源:我可以调度的机器从哪里来,有多少。
- 资源调度:我有了这些机器后,要如何对对他们几种调度,如何使用。
- 任务执行:我要如何用资源去解决用户提交的任务。
资源来源
由业务系统提供机器资源
优点:
- 任务执行逻辑与业务系统共用一份资源,利用率很高
缺点:
- 更容易发生定时任务脚本影响在线服务的事故
- 不能由定时任务平台控制扩容缩容
使用该方案的公司:阿里、美团、字节。
由定时任务平台提供机器资源
优点:
- 任务执行逻辑与业务系统提供的在线服务隔离,避免相互影响
- 可以支持优雅的扩容缩容
缺点:
- 消耗更多的机器资源
- 需要额外为定时任务平台申请接口调用权限,而不能直接继承业务系统的权限。
使用该方案的公司:字节等
资源调度
- **随机节点执行:**随机选择集群中一个可用的执行节点执行调度任务。使用场景:定时对账
- 广播执行: 在集群中所有的执行节点分发调度任务并执行。适用场景:批量运维。比如需要所有机器一起删掉日志文件。
- 分片执行: 按照用户自定义分片逻辑进行拆分,分发到集群中不同节点并执行,提升资源利用效率,适用场景:海量日志统计
任务分片
通过任务分片来提高任务执行的效率和资源的利用率
graph LR
A[任务] --单机任务--> B[任务分片]
B--Map任务--> C[Executor1]
B--Map任务--> D[Executor2]
B--Map任务--> E[ExecutorN]
C --区段1--- F[业务数据]
D --区段2--- F
E --区段N--- F
如果有N个执行器 Executor, M 个业务数据区段,最后 ,保证每个机器都有工作,且 是 的整数倍。这样任务的颗粒度会更加细腻。
任务分片流程
- 用户提交任务后,我们可以在单机上通过单机任务对任务进行分片,分成 个区段
- 然后我们将这 个区段分配到 个机器上面去执行
- 台机器会从数据库中获取不同的区段所需要的业务数据
任务编排
可以使用有向无环图进行可视化任务编排
故障转移
确保部分执行单元任务失败时,任务最终执行成功
graph LR
a[Scheduler] --1--> B[Executor 1]--2-->C[调度失败]
a --3--> D[Executor 2] --4-->d[调度成功] --5--> e[结束]
a --> f[Executor N]
单机任务下,如果 Executor 1 任务执行失败,那么他会返回一个结果给调度器,调度器将任务分配给 Executor 2 继续执行。直到成功
分片任务 下,调度器基于一致性hash策略分发任务,当某个 Executor 异常时,调度器会将任务分发到执行指定的Executor 上执行,不会打乱其他的 Executor 工作顺序。
调度器的高可用
调度器可以集群部署,做到完全的无状态,靠消息队列的重试机制保证任务一定会被调度
- 首先是机器注册,调度中心知道有这么一个机器可以被我使用
- 调度器发出调度请求,执行中心进行任务执行,中间可能会回传日志文件,也可能发出回调请求
- 任务执行过程中,执行器会定时进行状态上报,类似心跳检测。确保执行器发生故障时,调度中心可以快速感知。然后调度中心将任务重新分发,并将有问题的执行器摘除
基于注册中心,可以做到执行器的弹性扩容缩容。
业务应用
所有需要定时、延时、周期性执行任务的业务场景,都可以考虑使用分布式定时任务。
电商
- 订单30分钟未付款自动关闭订单
- 定时给商家、达人发送信息,给用户发放优惠券
互动
- 支付宝集五福
- 字节春节集卡瓜分红包
游戏
- 活动结束后批量补发用户未领取的奖励
- 定期更新游戏内榜单
其他解决方案
发货后超过10天未收回时系统自动确认收货
- 使用分布式定时任务的延时任务
- 使用消息队列的延时消息或者定时消息
春节集卡活动统计完成集卡的用户个数
- 使用分布式定时任务的
MapReduce任务 - 使用大数据离线处理引擎
Hive离线做统计 - 使用大数据实时处理引擎
Flink实时做累计