深度解析 | 基于DAG的分布式任务调度平台:Maat
    2018-08-16    斯兰

阿里妹导读:搜索中台建设过程中,单个系统不再能满足复杂业务的需求,更多时候需要多个子系统互相协作,异步地按照指定流程完成一项特定的功能。例如一个应用的上线流程依次需要调用配置同步模块、监控模块、资源更新模块、冒烟模块、引擎创建模块,流程的运行中又有分支判断、上下文传递、失败重试等需求。基于这种需求,Maat将各类流程化的任务集中管理,各个任务节点以分布式的方式运行在不同容器内,保证流程高效稳定地运行。

 

背景

 

什么是Maat?

 

Maat是一个基于开源项目Airflow的流程调度系统,它支持用户自定义地组装流程节点,流程可以在用户指定的时间触发(支持crontab格式),或由用户手动触发。

 

Maat的所有节点分布式地运行在Hippo上,由Drogo调度。用户可以创建自己的调度节点和执行节点,达到资源隔离的目的。

 

用户可以通过配置的方式安装自己执行节点的运行环境,也可以配置执行节点的副本数。


下图展示了一个任务的一次调度流程:

 

 

 

为什么要做Maat?

 

我们在项目的开发过程中,经常遇到一些流程化/定时调度的需求,如上线发布流程、定时分析任务流程等。对于这些流程化的调度任务,我们尝试过自己开发了一套流程调度系统,也尝试过接入集团的工作流,但难免会遇到一些问题:

 

  • 业务代码和调度代码耦合严重,修改流程基本需要入侵到代码级别,业务代码的发布影响调度。

  • 对于这些调度任务,没有一个统一管控的系统,难以管理和追溯。

  • 多分支的复杂流程不能很好支持,上下文传递场景不能很好支持。

  • 缺少可视化的UI,用户使用不友好。

 

技术选型

 

定时任务&流程任务的调度是一个通用的需求,集团内的产品如D2、工作流,开源的产品如Airflow、Quartz等。

 

★ D2

 

D2是基于ODPS生态的一套流程调度系统,承载集团基于ODPS数据产出的调度任务。支持用户自定义编写脚本,支持定时任务触发和手动触发(补运行的方式),适合基于数据状态的任务流程调度(如根据数据的产出执行任务流),由D2团队专门维护,有较好的UI。

但它有一些不足:

 

  • D2的DAG调度是一张大图,每天需要运行的每个节点及拓扑关系是根据前一天的全局的拓扑关系计算得出的,所以新创建/修改的任务理论上只能第二天生效,如果想立即生效需要采用补运行的方式。业务上经常会有任务的变动(如任务配置或调度时间),或手动触发一个调度的场景(如随时发生的上线流程、全量流程),使用D2对业务不是很灵活,也不符合D2的使用场景。

  • 不支持流程上下文的传递,业务上对上下文的传递比较强烈,经常有上个节点产出某个值,下个节点需要使用。

  • 缺乏对搜索生态的支持。搜索技术部整个底层架构有自己的一套生态,如调度(Hippo,Drago)、报警(Kmon)。使用D2,不能充分享受搜索技术生态带来的好处,对于之后的单元化部署也会带来问题。

     

★ 工作流

 

集团工作流是集团审批流程的一个通用调度引擎,很多产品的审批流程都是基于集团工作流的,同时它也可以作为一个简易的任务调度流程使用,我们在Maat之前也使用集团工作流作为我们流程任务的调度引擎。它支持手动触发,支持以HSF的方式调用外部系统,支持上下文传递。但它在配置上较为复杂,且支持外部系统的调用方式有限。

 

★ Quartz

 

Quartz是Java开源的任务调度框架。它支持分布式调度、支持任务持久化、支持定时任务,但不支持流程调度,且任务配置需要耦合在调度系统中,任务的热加载需要做一些改造。

 

★ Airflow

 

开源项目Airflow是一套分布式的流程调度系统,它的优势如下:

 

  • 业务代码和调度系统解耦,每个业务的流程代码以独立的Python脚本描述,里面定义了流程化的节点来执行业务逻辑,支持任务的热加载。

  • 完全支持crontab定时任务格式,可以通过crontab格式指定任务何时进行。

  • 支持复杂的分支条件,每个节点单独设定触发时机,如父节点全部成功时执行、任意父节点成功时执行。

  • 有一套完整的UI,可视化展现所有任务的状态及历史信息。

  • 外部依赖较少,搭建容易,仅依赖DB和rabbitmq。

  • 有同学问到Luigi与Airflow的对比,个人感觉都是基于pipline的一个任务调度系统,功能也大同小异,Airflow属于后来居上,功能更强,找到一篇同类产品的对比。

 

 

经过一段时间的调研,我们选用Airflow作为我们的原型开发一套分布式任务调度系统,它功能全面,基本满足业务需求,在功能上扩展相对方便,外部依赖较少,和搜索生态对接相对容易。

 

原生Airflow的问题

 

Airflow可以解决流程调度中面临的许多问题,但直接将原生的Airflow用于生产,仍面临一些问题:

 

  • 原生Airflow虽然支持分布式,但由于依赖本地状态,不能直接部署在drogo上。

  • 缺乏合适的监控手段,需要结合kmon完善监控和报警设施。

  • 缺乏用户友好的编辑手段,用户需要了解Airflow的原理和细节。

  • 大量任务运行时,调度的性能急剧下降。

  • 分布式模式下,原生Airflow存在一些bug。

 

Maat架构

 

Maat架构:

 

业务层

 

任何流程式调度及定时触发的需求均可以通过Maat创建应用,Maat提供了可视化编辑页面及丰富的api,用户可以方便地创建编辑流程模板,设置复杂的分支逻辑,Maat会在调度时按照运行时的状态决定流程的流转路径。


目前接入Maat的应用场景包括Tisplus、Hawkeye、Kmon、容量平台、离线组件平台、Opensearch

 

管控层

 

由于原生Airflow的管控比较简单,是基于描述任务流程dag的Python脚本调度,用户要进行任务的创建、更新、运行需要深入学习Airflow原理才能上手,并且之后维护只能基于文件操作,非常不便。因此Maat在外层封装一层管控系统Maat Console,降低运维及用户学习的成本。

 

下图是Maat管控系统Aflow的操作界面:

 

 

 

★ 模板管理

 

在任务流程调度的场景中,常见的情况是:不同任务执行的流程基本一致,只有个别参数不同。因此Maat提出了基于模板管理的任务流程,用户在模板管理中定义一个流程的运行模板,对于其中未确定的部分用变量表示。当生成具体任务时,由具体变量和模板渲染出具体的任务。当模板修改时,可以将模板生效到所有依赖该模板的任务。

 

 

模板管理预设了几种任务节点,用户可以自由选择不同的任务节点组装模板流程。

 

★ 应用管理

 

管理所有具体的流程调度任务,包括任务使用的模板、变量的值、报警信息、任务触发crontab等,用户在通过模板创建应用后,后续可以通过应用管理继续维护任务的运行。

 

★ 队列管理

 

由于Maat上运行的任务所属应用各不相同,不同应用运行环境也不相同,另外不同应用也希望达到集群隔离的目的。为了实现这个功能Maat提供了队列的管理,指定队列的任务节点会被调度到相应队列的机器上,相应队列的机器也只会运行指定队列的任务节点。


另外,队列上也可以指定并发数,表示当前队列上最多同时有多少个任务运行,确保机器上同时运行的任务不会太多导致负载过高,超出并发的任务会被挂起直到资源释放。

 

核心模块

 

Maat核心模块完成了任务调度的整个流程。核心模块的每个节点都独立运行在机器上,启动上互相不依赖,通过DB保存数据状态,通过MQ或FaaS完成消息的分发。

 

★ Web Api Service

 

Web Api Service提供了丰富的与外部交互的Api,包括任务增删改、历史任务状态、任务状态修改、任务的触发、任务的重试等接口。

另外原生Airflow提供的web展示功能也是由该角色完成。

 

★ Scheduler

 

scheduler是Maat关键角色,它决定了任务何时被调度运行,也决定一次任务运行中,哪些节点可以被执行。被判定执行的节点会被scheduler通过MQ或FaaS发送给worker执行。

 

随着任务的增多,单一的scheduler负载过高导致调度周期增长,为了减轻scheduler的压力,Maat将scheduler按照业务拆分。不同业务的任务有独立的scheduler负责调度,发送任务到指定的Worker上。

 

★ Scheduler性能优化
 

原生Airflow的调度逻辑吞吐量较低,当任务量增多时,调度周期会很长,一些任务多的Scheduler延迟到达1分钟左右。我们参考社区最新的实现,对原生调度逻辑进行优化,将原先阻塞的调度方式拆分为多个进程池,全异步地完成可执行任务的生产->提交->轮询操作。经过压测原先调度周期为30s-40s的场景降低为5s左右。

 

 

★ Worker

 

worker为具体执行任务的角色,worker会接受scheduler发出的任务,在worker上执行节点中描述的具体任务。worker多副本部署,任务会在任意一个对等的worker上机器,当worker资源不足时,可以动态扩容。

由于不同队列任务所需的基础环境不同,如Python、Java、Hadoop、zk等,不同队列的worker角色启动参数有配置上的差异,不同队列的worker启动时会按照配置中描述的资源完成部署安装。

 

worker上任务完成后会回写db,scheduler察觉到当前任务状态变化后会继续任务的调度。

 

★ Distributers

 

任务分发层负责将scheduler需要调度的任务发送到指定的Worker上。目前Maat同时使用原生Celery+Rabbitmq的方式和搜索生态FaaS的方式实现任务分发。

 

★ Celery + RabbitMQ

 

原生Airflow使用Celery + RabbitMQ完成消息从Scheduler到Worker的分发。


Scheduler将需要运行的任务发送到MQ中,发送到MQ中包含任务对应的队列信息。Worker从MQ获取消息时,仅获取相应队列任务,拉取到对应Worker上执行。MQ在Maat中以rabbitmq实现,MQ和其他角色一样,也是独立部署的。

 

 

Celery + Rabbitmq的模型对消息队列中的任务进行持久化,所有的任务状态也进行持久化,内存Queue的性能满足大部分场景的需求。但由于Maat基于二层调度Drogo部署,任何部署节点都要求无状态的,而消息队列MQ因为保存消息状态显然不满足这个要求,所以我们选择使用搜索生态的FaaS框架作为Celery + RabbitMQ的替代方案。

 

★ FaaS

 

FaaS:FaaS(Function as a Service)是基于搜索生态实现的ServerLess框架,Maat将其作为执行器。Maat的所有任务都抽象成function,任务执行时则调用相应的function,完成后返回任务状态。目前已完成与FaaS的初步对接,期望未来能基于FaaS做更多优化。如:多样化的任务执行方式,可以将轻量级的任务函数化,将重量级的任务服务化;任务资源动态调整,甚至某些任务可以执行时分配资源,完成后即释放。


对于Maat来讲,FaaS支持任务从生产者到消费者的分发,内置消息Queue,提供任务状态接口,同时FaaS自身保证消息不对丢失,后续还具备根据消费者负载自动扩缩容的功能。

 

★ 基础组件

 

  • DB:使用集团IDB,负责Maat信息的持久化,包括任务信息、任务运行历史、任务运行状态、节点运行历史、节点运行状态等。

  • OSS:由于上drogo导致机器迁移的风险,所有日志不能存放在本地,因此所有节点运行日志存放在oss上,需要查看日志上从oss上获取。

  • Kmon:完成监控集群运行状态及任务失败的报警功能。

  • Drogo:完成Maat所有节点的docker容器化部署。

 

Maat平台的优势

 

可视化编辑及通用的节点类型

 

Maat提供了一个管控平台Aflow,用户可以方便地编辑流程节点,管理所有的模板和任务,详细见上文的[管控层]。

 

除此之外,Maat还提供了丰富的通用节点类型。原生Airflow支持许多不同种类的节点类型,这些节点可以执行不同类型的任务。在与用户的接触中,Maat针对用户的使用习惯与需求,对一些节点进行封装,同时开发了几种新的节点类型,可以满足大部分用户的需求。

 

  • Bash节点:直接在worker上执行简单的bash操作,由于bash通常需要依赖其他资源,实际使用较少,参照“带资源的Bash节点”;

  • Http节点:该节点支持http调用,调度时发送http请求触发其他系统,同时该节点提供一个轮询的http接口,触发成功后轮询其他系统是否成功,成功时才继续运行;

  • 带资源的Bash节点:与普通Bash节点不同的是,该节点附带一些资源(如jar包、bash脚本、Python脚本等),节点运行时会先将资源下载到本地,然后执行bash脚本;

  • 分支节点:该节点根据之前节点的运行结果或初始传入的参数决定分之后的节点走哪个分支。

 

Drogo化部署

 

Maat服务有多种角色,每种角色都需要不同的运行环境,为了维护这些运行环境,对运维同学来说绝对是个噩梦,这种情况下上hippo成为Maat运维最好的选择。drogo作为基于二层调度服务的管控平台,为Maat各个节点部署在hippo上成为可能。具体来说,Drogo化的优势如下:

 

  • 低成本增加新节点。上Drogo前,有新增节点的需求时,每次都需要准备运行资源,重新写部署脚本,而每个节点的脚本都要运维同学自己维护。上Drogo后,所有这些部署信息保存在Drogo平台上,有新的的节点也只需要将之前类似的部署信息复制,稍加修改即可。

  • 扩容简单。上Drogo前,某类任务水位太高,为这类任务扩容,每次都需要准备机器、准备环境、调试运行参数,可能需要半天到一天的时间。上Drogo后,调整副本数,Drogo会自动扩容。

  • 有效防止机器迁移带来的服务中断。上Drogo前,机器出现问题后,只能另找机器扩容,对于某些只能单点运行的节点,只能烧香祈祷机器不挂了。上Drogo后,机器迁移后,会Drogo会自动分配一台机器将服务拉起,对于可中断的服务,单节点部署也不用担心机器挂了导致服务消失了。

 

下图展示了目前Drogo上部署的Maat各个角色:

 

 

由于原生Airflow的一些节点是有状态的,需要依赖本地一些文件,机器迁移会导致这些节点的状态丢失,我们对Maat做了一些修改,保证机器迁移不会丢失运行状态:

 

  • 之前的调度依赖本地Python dag文件,机器迁移导致本地文件丢失。我们做了修改,所有dag保存在db,依赖db中保存的信息调度,保证机器迁移后,dag信息也不会丢失。

  • 由于基于本地文件,web service和scheduler读写的都是同一份dag文件,导致原生Airflow的scheduler和web service角色必须绑定运行。以db中信息调度后,web service和scheduler可以单独部署。

  • 由于原来日志文件保存在本地,机器迁移会导致日志文件丢失。我们改造后,将日志文件保存在oss远端,每次读取日志从远端读取。

 

分集群管理

 

由于不同任务隔离的需求,Maat基于Airflow原生的队列管理扩展不同任务的集群管理功能,不同类型的任务可以创建自己的scheduler和worker,创建应用时可以使用指定的scheduler调度或运行在指定worker上(如果不指定由默认scheduler和worker调度)。

 

集群的配置参数包括以下信息:

 

  • worker部署配置:该worker依赖的资源,drogo启动时会将任务运行需要的资源部署到worker机器上,机器迁移时也会使用这份部署配置重新分配资源。

  • worker个数:控制worker角色的个数。

  • 集群并发数:控制集群中正在运行的并发数,防止任务运行过多导致下游系统压力过大。

  • scheduler:目前每个集群只有一个scheduler,后续会改造成支持多个scheduler调度节点。

 

监控&报警

 

★ 平台监控报警

 

为了掌握平台的运行状况,Maat在各个节点的关键步骤向kmon汇报metric信息,metric异常状态下会发送报警给开发同学。也可以根据这些metric信息判断当前集群的负载状况,对任务负载较高的节点进行优化。

 

 

 

★ 任务报警

 

对于具体任务,Maat支持在每个任务节点运行异常的情况下发送报警,如节点运行异常、任务未按时运行、任务运行超时等。用户可以在管控平台设置报警条件及报警接收人。

 

平台现状

 

Maat是一个通用基于Dag的任务调度系统,服务于集团内部和云上的许多场景:

 

  • 搜索中台Tisplus:调度Tisplus的上线流程及其他需要定时触发的任务;

  • Hawkeye:定时调度Hawkeye的分析任务;

  • 搜索监控平台Kmon:支持kmon的监控托管服务及报警处理流程;

  • 搜索容量预估平台Torch:支持容量预估流程的管控;

  • 搜索离线平台Bahamut:支持离线组件平台发布流程、全量流程;

  • Opensearch:一些算法场景的离线任务;

  • Tpp:推荐场景的流程调度任务。

 

Maat线上集群任务执行现状(2018.8.13数据):

 

  • 日均调度任务:3000+个

  • 日均运行任务:24K+次

 

随着更多应用场景的接入,平台能力将会接受进一步的考验。

 

未来展望

 

随着业务的接入和数据规模的增大,Maat平台也需要进一步提升用户体验,提升平台稳定性。

 

  • 与Aflow进一步结合,在管控平台上一站式完成集群的创建、配置、部署。

  • 提供更加丰富的报警选项,进一步加强错误的反馈机制。

  • 随着任务数量的增多,一些调度上的缺陷逐渐体现出来,对于这些缺陷做进一步优化。

  • 和FaaS深度合作,为各类任务创建单独的FaaS服务,降低资源利用率。