阿里妹导读:搜索中台建设过程中,单个系统不再能满足复杂业务的需求,更多时候需要多个子系统互相协作,异步地按照指定流程完成一项特定的功能。例如一个应用的上线流程依次需要调用配置同步模块、监控模块、资源更新模块、冒烟模块、引擎创建模块,流程的运行中又有分支判断、上下文传递、失败重试等需求。基于这种需求,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 服务,降低资源利用率。