

分布式任务调度介绍
为支撑数字金融的发展,金融系统处理的业务种类随业务发展快速迭代,处理的数据量级迅速增长,信息化架构从传统单体集中式向复杂分布式架构转型,在各类业务系统场景中,存在着大量定时触发、周期触发运行指定业务任务的需求场景,因此就需要分布式任务调度来管理、支撑上述场景。任务调度中周期定时运行只是最为基础的特性,业务的扩张和发展以及分布式的架构体系下,系统对调度平台的能力、健壮性提出了更高的要求。因此,本文先在宏观上剖析分布式调度框架的必要组件,之后分析目前常见的分布式调度框架对上述组件的具体实现,以对比得出各框架的特点及使用场景,最后介绍中国光大银行自主研发平台中的技术选型与功能扩展。

分布式任务调度框架剖析
分布式任务调度,顾名思义,是以分布式架构方式实现任务调度,其中有三个关键定义:分布式、任务调度、配置中心,其中:
分布式
平台是分布式部署的,各个节点之间可以无状态和无限的水平扩展(弹性、动态扩容)。
任务调度
涉及到任务状态管理、任务调度请求的发送与接受、具体的任务分配、任务的具体执行(集群中有哪些机器、什么时候执行什么任务,所以需要一个可以感知整个集群运行状态的配置中心)。
配置中心
可以感知整个集群的状态、任务信息的注册。
在以上关键定义下,对于一个分布式任务调度框架,一般有以下5个核心功能点需要实现:
控制台:负责任务调度配置、任务状态、日志监控、集群信息展示等;
组件间通信(RPC):将控制台的任务调度下发给执行器,向注册中心注册任务、将执行器的状态回调给调度器等;
调度器:接受下发的调度任务,进行任务拆分下发,在注册中心寻找执行器,然后把任务下发到执行器执行,同时也注册到注册中心;
执行器:接受调度任务,完成执行,并上报状态给注册中心;
注册中心:机器、任务状态的同步、协调。
而框架在实现上述功能点时并非界限分明、完全解耦的,有时某个组件可具备多个功能点的实现,因此框架间在高可用、高性能和维护成本上均有各自的特点和限制,接下来本文将对三种常见框架对上述功能的实现做深入分析。

分布式任务调度框架研究
1
Quartz
Quartz是OpenSymphony开源组织在任务调度领域的一个开源项目,完全基于Java实现,具有强大的调度功能、灵活的应用方式、分布式和集群能力,此外,作为Spring默认的调度框架,很容易与Spring集成。Quartz核心组件有三个:
Job(任务): 具体要执行的业务逻辑;
Trigger(触发器):用来定义Job的触发条件、触发时间、触发间隔、终止时间等;
Scheduler(调度器):启动Trigger去执行Job
三者逻辑关系如下:

图1 Quartz架构图
其中,Jobs代表实际执行的业务,但由于Job本身是顶层抽象的接口,因此使用JobDetail来描述Job的具体实现,再由调度器中反射获取Job实例。Trigger中保存了Job的执行信息,而Scheduler作为容器,接收两者的注册,完成绑定并在数据库中完成持久化。
总体来说,Quartz的分布式调度策略是以数据库为边界的异步策略,在调度容器集群部署时,每个节点在数据库中都有同一份Job定义,如果某个节点失效,则Job会在其他节点上执行。各个调度容器节点都遵守一个基于数据库锁的操作规则,从而保证了操作的唯一性,同时多个节点异步运行保证了服务的可靠。但这种策略只解决了任务高可用的问题,无法解决任务分片问题,此外,其还有如下限制:
数据库锁的集中性会产生严重的性能问题,比如大批量任务场景下,数据库成为了业务整体调度的性能瓶颈,同时在应用侧还会造成部分资源的等待限制,缺少分布式并行调度的功能;
需要把任务信息持久化到业务数据库,和业务耦合严重;
调度逻辑和执行逻辑存在同一个项目中,在机器性能固定的情况下,业务和调度之间不可避免会产生竞争,相互影响。
2
Elastic-Job
Elastic-Job是当当网基于Quartz二次开发的弹性无中心化分布式调度框架,采用Zookeeper作为注册中心,实现分布式协调、任务高可用以及任务分片。Elastic-Job继承了Quartz强大的调度能力之外,也补足了Quartz的缺陷,其架构如下:

图2 Elastic-Job架构图
APP:业务应用,其中包含任务执行的具体逻辑和Elastic-Job-Lite组件,既担任执行器角色也担任调度器的角色。无中心化的特点也在这一组件中体现,整体框架中不存在调度中心,而是由应用节点在到达相应时间时各自触发调度,而节点之间则通过注册中心进行分布式协调;
Elastic-Job-Lite:负责任务调度和日志监控,作为调度器的角色集成在App中,负责调度、分片、定时执行等;
Registry:使用Zookeeper作为注册中心组件,存储调度任务信息,并进行应用节点的选举,Elastic-Job没有调度中心,但存在主节点,不过该节点只用于处理一些集中式任务,如分片、清理日志信息等,并没有调度功能,定时调度都是由应用节点自行触发;
Console:可视化运维管理台,通过读取注册中心的数据展现任务执行状态,或修改配置,以及通过Elastic-Job-Lite组件的数据查看任务执行日志和历史记录等。
Elastic-Job的工作流程为:应用程序启动时,在其内嵌的Elastic-Job-Lite组件会向Zookeeper注册该实例的信息,并触发选举,从众多节点中选举出执行节点,当满足某个任务的执行条件后,Lite组件直接触发任务实际逻辑的执行,并生成相应的执行日志。这期间如果某个节点宕机,注册中心会感知到并重新触发执行节点的选举。
Elastic-Job减弱了Quartz中数据库锁对性能的限制,引入Zookeeper集中管理,在分布式协调、任务高可用、资源利用率上均有大幅提升,同时还支持了数据分片,将真实数据和逻辑分片对应,解耦了调度框架和数据的关系,调度框架只负责将分片合理的分配给相关的应用节点,而应用节点则根据被分配的分片匹配数据进行处理,而这些信息均存储在注册中心里,各个服务器根据自身的IP地址获取分片。但Elastic-Job的最大限制也在于它依赖的外部组件Zookeeper,该框架虽然很成熟,但原理复杂,使用较难,维护成本及难度较大,在海量数据支持的情况下也会有性能和网络问题。
3
Xxl-Job
Xxl-Job同样是基于Quartz进行的二次开发,不同于Elastic-Job,Xxl-Job是中心化的调度框架,它将任务调度抽离出调度、执行两个动作,将调度和执行完全解耦,它通过中心式的调度平台,调度多个执行器执行任务,调度中心通过DB锁保证集群分布式调度的一致性,其架构如下:

图3 Xxl-Job架构图
Xxl-Job整体分为两个模块:调度中心和执行器,调度中心兼顾注册中心、调度、日志管理以及可视化前台管理页面的功能;而执行器则是接收相应的调度,执行实际的任务,并负责向调度中心同步调度结果和执行结果。两者之间是完全解耦的,部署在不同的容器中,相互之间不存在资源竞争,并且由于将调度动作抽离出来,使得这一过程逻辑变得非常“轻”,而对执行器的调度也是全异步化的设计。其工作流程如下:
1
执行器项目中静态配置调度中心地址(可部署多活),执行器上线后,向调度中心发起注册,注册过程实际是执行器信息在调度中心的持久化。
2
调度中心里的调度线程会不断从数据库轮询任务信息,符合调度条件的任务将加入基于时间轮算法(模拟钟表指针,按时间刻度触发任务)的调度队列,满足条件后触发调度。
3
任务发起调度后,调度中心通过资源检查、路由策略等条件过滤出对应的执行器组,之后通过自研RPC发起远程异步调度,并记录调度日志。
4
执行器端收到调度请求后,同步调度结果,之后执行相应任务。
5
任务完成后,向调度中心发起异步回调,同步执行结果,调度中心侧收到执行结果后更新任务日志。
相比于Elastic-Job中调度和执行均在一个节点方式,Xxl-Job对两者的解耦了极大的降低了调度线程占用时间,对容器资源的利用率更高。在维护和扩展方面,也是由于解耦了调度和执行两个动作,使得每个动作都变得相对“简单”,而且Xxl-Job没有引用其他组件,整体更加轻量化,项目引入时也更加简单灵活,易于掌握原理进行二次开发。此外Xxl-Job的开源社区也更加活跃,能够在平台中开发交流。不过,Xxl-Job的调度中心仍然是通过Quartz实现,所以DB锁仍然也是Xxl-Job性能的瓶颈,但相较于其他框架,它的调度过程更加简单,因此完成一次调度时所需的资源会更少,所以相对来说能满足更多业务场景的要求。

分布式任务调度框架总结
基于以上分析,将各框架特点总结如下:

通过以上三种分布式任务调度框架的对比,以可用性、可维护性和可扩展性来说,Xxl-Job都是比较适合的技术选型,它有活跃的开源社区,有详细的文档介绍,不依赖其他组件,有直观的可视化操作界面,可实时监控的任务进度,引入成本和学习成本都相对更低。在业务起步阶段可直接使用,可以只专注开发业务逻辑;而在业务成熟阶段,也易于二次开发以支持业务个性化的需求。例如,在中国光大银行自主研发平台中,基于Xxl-Job结合行内业务的特定需求做了二次开发扩展:

以上只是笔者进行的部分框架调研,当然还有更多框架值得去研究,本文中如有错误,也欢迎沟通指正。

作者 | 孙哲 朱光轩
视觉 | 王朋玉
统筹 | 郑 洁

