这是我参与「第五届青训营 」笔记创作活动的第7天
前言
考虑一种QPS较高的场景,抖音春节的集卡瓜分红包
,支付宝的集五福瓜分红包,服务端的处理流程整体分为两步。
- 收集每个用户的集卡信息,计算出每个用户瓜分的固定红包金额。
- 除夕零点,定时开奖,所有用户收到红包 用户规模亿万级 资金规模亿级 读写QPS百万级
这样的场景需要采用分布式定时任务进行处理
发展历程
- windows批处理
- window任务计划程序 比如每天12点自动打卡,可以写一个python脚本,抓取页面,查看发送的请求。然后设置为定时任务
- Linux命令-CronJob定期清理linux日志
- 单机定时任务-java的timer类,Go的Ticker
- 单机定时任务-ScheduledExecuteorService 不用为每个定时任务都开启一个线程,实现线程复用,但是还是单机可用
public static void main(String[] args) {
scheduler = Executors.newScheduledThreadPool(5);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
scheduler.scheduleAtFixedRate((new Runnable() {
@Override
public void run() {
System.out.println("执行定时任务"+ sdf.format(new Date()));
}
}),0,10, TimeUnit.SECONDS );
}
十秒执行一次,结果如下
- 任务调度-Quartz
一个由java编写的开源的任务调度框架 www.w3cschool.cn/quartz_doc/…
- 分布式定时任务 平台化管理,分布式部署,支持海量数据 需要很多的机器去执行任务
定时任务是系统为了自动完成特定任务,定时,延时,周期性完成任务调度的过程
分布式定时任务是把这些分散的、可靠性差的定时任务纳入统一的平台,并实现集群管理调度和分布式部署的一种定时任务的管理方式
- 按触发时机分类:
- 定时任务:特定时间点,比如今天12:00
- 延时任务:延时触发,比如一个小时后执行
- 周期性任务:固定周期时间,或固定频率周期性调度触发,比如每天12点或者每隔3小时
特点
执行方式
对于红包场景
单机任务由于任务过多,单机不合适
广播任务所有机器执行的是同一个任务
春节集卡选择的合适的执行方式
实现原理
核心架构
- 触发器:Trigger,解析任务,生成触发事件
- 调度器:Scheduler,分配任务,管理任务生命周期
- 执行器:Executor,获取执行任务单元,执行任务逻辑
除此之外,还有控制台Admin,提供任务管理和干预的功能
数据流
功能架构
任务之间的依赖 任务暂停执行就是干预的一种
Trigger需要触发一些任务的执行,所以需要Scanner扫描任务DB,进行触发,然后用消息队列做一些消息的投递,通过消息通知调度器执行
调度器主要是解决如何调度任务。哪些执行器可用被调度等。
真正工作的是执行器
控制台
基本概念
- 任务:job,任务元数据
- 任务实例:JobInstance,任务运行的实例(一个任务可能在不同时间执行多次,所以对应多个实例)
- 任务结果:JobResult,任务实例运行的结果(任务可能执行失败,可能执行多次,每次对应一个任务结果)
- 任务历史:JobHistory,用户可用修改任务信息,任务实例对应的任务元数据可用不同,因而用任务历史存储
任务元数据
任务元数据是用户对任务属性进行定义,
- 基础信息 Who 任务名字,属于什么业务
- 调度时机 When 任务什么时间调度
- 执行行为 What 任务执行的行为,任务代码,做的事情
- 执行方式 How 单机还是广播,还是分片触发
任务实例
是一个确定的Job的一次运行实例
- Job_id 和任务元数据建立关联
- 触发时间 任务实例的执行时间
- 状态&结果 任务实例当前状态和执行的结果
- 过程信息 比如进行消息追回
触发器
核心职责
核心职责:
给定一系列的任务,解析他们的触发规则,在规定的时间点触发任务的调度
设计约束:
- 需支持大量任务
- 需支持秒级的调度
- 周期任务需要多次执行
- 需保证秒级扫描的高性能,避免资源浪费
方案一
定期扫描+延时消息(腾讯字节方案)
扫描器每10秒扫描一次DB,扫描出最近10分钟将要执行的任务,(扫描出的任务不是立马执行的,就可以用大延时任务的方式)交给Processor,处理后发送延时消息到MQ。
方案2
时间轮(Quartz所用方案)
高效利用线程资源进行批量化调度的一种调度模型。时间轮是一个存储环形队列,底层采用数组实现,数组每个元素存放一个定时任务列表
目标:遍历任务列表,找出当前时间点需要触发的任务列表
- 链式存储,每个元素代表一个任务,查询复杂度O(n),修改复杂度O(1)
- 使用最小堆优化,因为我们不需要知道所有任务的执行时间,只需要知道最近的要执行的任务。按照任务的执行时间进行排序,查询复杂度O(1),修改复杂度O(logn).当任务增加时,堆是无限扩展的,所以不符合
- 时间轮:查询和修改复杂度都是O(1),按照时间取对应的槽位
因为秒轮时间刻度可能不够,所以可用使用多级时间轮
高可用性
核心问题
- 不同业务之间,任务的调度互相影响怎么办
- 负责扫描和触发的机器挂了怎么办
解法思路
- 存储上,不同国别、业务做资源隔离
- 运行时、不同国别、业务分开执行官
- 部署时,采用多机房集群化部署,避免单节点故障,通过数据库锁或分布式锁保证任务只被触发一次。
高可用-问题引出
高可用-数据库行锁模式
高可用-分布式锁模式
性能较高,也是多家公司使用的方案
调度器
- 资源来源
- 资源调度
节点选择
- 任务执行
高级特性-任务编排
使用有向无环图DAG进行可视化任务编排
高级特性-故障转移
确保部分执行单元任务失败时,任务最终成功
分片任务基于一致性hash策略分发任务,当某个executor异常时,调度器会将任务分发到其他executor上
调度器-高可用
调度器可用集群部署,做到完全的无状态,靠消息队列的重试机制保障任务一定会被调度到
执行器
注册
调度
回调
状态上报类似于心跳检测
业务应用
消息队列不做存储,所以可控性较低。