🙋🏻♀️ 编者按:本文作者是蚂蚁集团客户端开发工程师企立,介绍了支付宝精细化调度的技术演进以及未来规划,欢迎查阅~
从 Unix 诞生到现在 Android,iOS 系统生态完善,对设备资源如何合理分配一直是开发者,用户等各个角色所努力的方向。对于 Android 这样的 linux 内核来说,设备对外调度的资源单位是进程,但是进程内部还有线程,目的就是为了更充分的利用 CPU 资源。
随之带来的资源争夺,系统开发者也设计了不少的调度器 ,只为更合理的安排执行“用户”提交上来的线程任务,从基本的 FIFO 和 RT 调度策略,再到 CFS 和各项调度器的混合使用,每一次变化其实都是在针对不同场景,不同系统,不用时代,做不同的策略转换。
支付宝作为生态中的重要一员,并且拥有强大的用户群体,也在这个过程中演进,也时刻参与着这个竞争,支付宝内部的业务复杂繁多,导致“内部竞争”也异常激烈,针对这样外有与系统其他资源的争夺,内有业务内部的资源抢占的情况, 如何能合理的制定出适合支付宝自身的调度方案,满足不同时期的业务需求一直是端上一个重大的挑战。
支付宝发展到现在,已为用户提供了大量的服务,这对性能的挑战异常艰巨。性能的优化,主要经历了三个阶段。
雏形阶段:最初的版本中,业务处于高速发展期,大部分业务直接使用原生系统提供的线程池进行开发
问题与挑战
由于每个业务维护自己的线程池,他们很少从端上整体资源的角度去考虑,并且随着支付宝的发展业务也越来越多,凸显出如下问题:
雏形阶段最大的问题,是支付宝超负荷运行,框架也没有进行合理调度,所以 1.0 开始框架提供了统一的线程池服务,业务根据不同的任务类型,可以拿到一个合适的线程类型去执行任务。这样框架可以从整体上把控线程数量。并且不同类型线程池策略是不一样的。这样系统资源的分配就更加合理了,并且也有了框架干预的能力。
构建核心线程池调度
为前台 UI 所依赖,优先级最高
一类紧急任务,专为首页渲染相关任务使用
二类紧急任务,优先级一般,不能容忍排队
普通不太紧急,可容忍排队的后台任务
文件 IO 类操作,持久化任务,耗时可以预计,要么不久成功,要么发生异常
网络相关的后台任务,耗时波动较大,典型使用场景为发起 RPC 请求
相同 KEY 的 Task 会保证有序串行执行(但不一定全在同一线程),不同的 KEY 对应的 Task 之间会并发
1.0 时代架构解决了统一对业务线程进行管理、分级的问题,但是我们所服务的业务也在不断增加扩展,要支持的场景也越来越多,这样也暴露出新的问题。
为解决上面的问题,就需要对执行的任务有干预能力,理解当前的用户场景,于是我们构建了基于任务调度的框架,架构图如下:
任务调度框架在线程调度的基础上,添加任务调度层,业务层,监控诊断工具
任务调度的优势:
对于基于场景的调度,例如进入扫一扫后的性能,效果显著:
在解决完重点场景的调度问题后,还需要解决启动后任务无序、高并发的情况,进一步优化整体的执行效率,那么如何提供能力解决这个问题呢?这里我们参考了 google 提供的 WorkManager 的设计,我们命名为 Captain 调度。
构建一个 worker 任务族,用来提供存放 worker 任务,编排任务执行过程及控制并发数量的能力, 将业务提交的任务(runnable)封装为一个 worker,并将其放入上述的任务族中,使用对外提供的一个 WorkManager 设置各项执行参数和调度行为。通过 worker 专属线程池,执行被封装为 worker 的任务。添加 Captain 调度接口,用来业务接入和调度时机配置。
创建工作链,建立任务依赖执行关系能力
可支持最大并行数量限制
打散能力,根据任务调度优先级,触发时机等条件构建出对应任务族,任务族根据上述参数变化执行任务
描述:A 先执行,接着并行执行 BC 和 D(此时并发数量限制为2个线程并发),其中 B 和 C 是串行,最后等 C 和 D 都执行完,再执行 E
针对 2.0 时代功能机制上基本满足需求,但随着业务发展暴露如下问题:
染色接入成本高,除自身业务模块接入外,还需要对依赖的模块也进行接入
调度范围有限,只支持框架线程池任务调度
针对调度 2.0 产生的新问题,我们又进行了升级,首先是要收口所有非框架线程池的任务,这里用到了 AOP 的技术,对 java 层的所有启动线程/任务的方式进行了切面收口,收口的发起方式有:
调度 2.0 在遇到 native 线程、三方的 SDK 是无法做到染色传递的,并且对于任务(Runnable)启动任务也是无法染色传递,上述情况都需要手动染色加白,业务方适配起来成本高,容易“踩坑”,调度 3.0 为了解决这个问题,采用了任务树的构建原理,进行依赖染色传递。
任务树构建原理
使用 DexAop 的能力,对任务的构造函数添加切面,在每个构造任务的时机去反向抓取任务的执行链路,可以寻找到拉起当前任务的上一个任务,根据上一个任务的染色标记决定当前任务的相关属性,以此类推生成调用链关系,导出运行时任务的执行"树"。如下图:
任务调用链关系生成方式(规则):
监控:建立以任务树为维度的执行监控,对支付宝的整体任务执行情况,不同场景内任务执行时间,执行占比,执行次数进行分析
让线程进入 TIMED_WAITING 状态
任务调度升级 | V2.0 | V3.0 |
---|---|---|
接入成本 | 高 | 低 |
是否有漏染 | 是 | 无 |
调度范围 | 框架线程池线程及任务 | 所有Java线程任务 |
调度场景隔离 | 无 | 有 |
3.0 任务调度仍然有很多问题:框架缺乏对业务的理解,任务没有业务归属,调度的范围也只有任务这一单一维度,各种启动时机、广播等过度使用,缺少规范,没有站在用户的角度去思考和决策端上的任务编排。后续新的调度框架会以“按需加载”为原则,建立更符合当前情况的精细化调度,解决上述问题。
精细化调度要具备的能力
有点意思,那就点个关注呗 💁🏼♀️
👇🏾点击「阅读原文」,在评论区与我们互动噢