What is Airflow

Airflow 是由 Airbnb 公司开发, 于 2015 年 6 月开园并捐献给 Apache 软件基金会的开源项目. 它是一个 Workflow Orchestration (工作流编排) 的软件. 类似于 Jenkins, 你需要自己部署服务器集群并安装软件, 搭建一个内部使用的平台, 然后才能开始使用 Airflow.

Airflow 是业内用做复杂工作流编排的事实标准, 使用者众多, 社区非常活跃. 如果你是第一次接触 Workflow Orchestration (WO), 你可能会觉得不太好理解. 下面我们来简单介绍一下什么是 WO, 为什么要使用 WO, 它有什么痛点, 以及 Airflow 是如何解决这些痛点的.

什么是 Workflow Orchestration 工作流编排

什么是工作流编排

所谓工作流编排就是, 假如你有很多相互独立的 worker, 它们有各自的输入和输出, 以及不同的运行环境. 这些 worker 可以是虚拟机上的程序, 也可以是容器中的程序, 可以是任何计算单元. 你需要将这些 worker 按照一定的顺序和依赖关系组织起来, 在它们之间传递数据, 监控状态, 使得它们能按照你的需要排列组合到一起. 这就是工作流编排.

为什么要使用工作流编排

在软件工程的世界里, 依次运行多个任务的编程模型有很多种, 分类方法也有很多. 我们先来按 有无调度者 来讨论:

  • 有调度者: 有一个程序本身不运行任何业务, 而是调用其他的 worker 来运行业务. 也就是我们所说的工作流编排.

  • 无调度者: 每个 worker 运行完自己的业务之后, 再调用其他的 worker 来运行业务. 也就是说 worker 自己来负责编排.

很显然, 无调度者不适用于复杂的业务逻辑. 因为它意味着每个 worker 不仅要完成自己的业务逻辑, 还要完成启动其他 worker 的任务, 相当于担负了一些调度责任. 这不适合业务的解耦和复用. 比如你今天 worker A 和 worker B 联动, 但是明天要换成跟 worker C 联动, 而你 worker A 的代码是写死跟 B 联动的, 这就意味着你要重新部署. 而且每个 worker 挂掉都会影响整个系统. 而在有专用的调度者的情况下, 你可以让 worker 只专注于业务逻辑和自己的输入输出, 而让如何跟其他 worker 联动的逻辑交给调度者来执行, 使得业务逻辑和编排逻辑分离, 从而实现业务的解耦和复用. 并且你可以用各种分布式设计确保调度者高可用, 不会因为 worker 的挂掉而影响整个系统 (你为 worker 设计一套分布式系统保证高可用的成本可就太高了).

有的人会说, 我平时就写一个脚本, 按顺序运行各个 worker, 也没有用编排系统啊? 但你的脚本本质上就是一个简单的编排系统, 只不过无法保证高可用, 缺少了监控, 日志, 复用等很多重要功能. 如果你只有一个特定的需求, 那么你确实可以用一个脚本来做到工作流编排. 但对于企业中复杂的业务逻辑, 我们非常需要一个专用的工作流编排系统.

工作流编排的痛点

  1. 运行 worker 和监控状态.

编排系统的主要任务就是运行 worker 并且监控它们的状态, 那么我们就来看看如何做到这一点. 运行 worker 和监控状态的方法有很多, 主要分为以下两种:

  • 长轮询, 也叫 pull: 调度者启动了 worker 之后, 隔一段时间 (例如 1 秒) 就看看 worker 是否还在运行, 是否断线, 成功还是失败了, 然后根据这些状态决定下一步要做什么. 这个的本质是调度者主动去拉取状态信息.

  • 事件驱动, 也叫 push: 每个 worker 是由事件来触发的, 这些事件可以是定时任务, 也可以是其他的 worker 的状态发生变更 (例如成功或失败), 例如当前一个 worker 成功了的事件被调度者所接收到, 调度者就会开始运行下一个 worker. 这个的本质是 worker 主动通知调度者自己的状态.

这两种执行模型各有优劣, 可以分开使用也可以合起来使用. 长轮询的优势是实现简单, 但是性能有损失, 因为你需要每隔一段时间就查询一下状态, 在你的任务数量很多的情况下这是一笔不小的开销. 并且你的业务延迟取决于你的轮询间隔. 间隔久了延迟就大, 间隔短了开销就大. 而事件驱动的优势是性能好, 仅仅在需要采取行动的时候才行动. 但是实现复杂, 因为你需要实现一个事件系统, 并且需要考虑事件的顺序, 事件的重复, 事件的丢失等问题. 实际业务中往往这两种模型要合起来使用.

  1. 保持调度者的高可用.

调度者本身也是一个系统, 是有可能挂掉的, 如果复杂工作流运行到一半调度者挂掉, 要如何能换一个新的调度者来接替呢? 这其实对应着两个子问题, 调度者本身的冗余和高可用, 以及调度状态数据持久化.

  1. 异常以及错误处理.

因为 Worker 是一个调度者之外的系统, 你很难说能设计一个调度者系统, 然后把所有的 Worker 都放在调度者系统上运行. 而对于调度者来说, 外部的系统不是那么可控. 外部系统不像本地脚本, 失败了你立刻就能知道. 而外部系统失败的原因可能是网络, 可能是延迟, 可能是各种奇奇怪怪的错误. 调度者如何确保自收集到的 Worker 的状态是准确的呢?

以上三点只是编排系统的核心痛点. 如果从产品角度, 还有很多其他要求, 例如: 用户友好, 能可视化运行状态, 方便 debug, 方便测试, 调度资源弹性伸缩, 方便部署, 数据有保障, 访问权限控制等等. 我们就不展开说了. 但是你要知道这些都是编排系统要考虑的问题.

Airflow 是如何解决这一问题的

Airflow 本身是一个软件, 它提供了一个三个核心组件:

  1. 调度器 Scheduler: 用来调度. 实现了上面所说的长轮询以及事件驱动两种模型. 并且提供了一个 DAG (Directed Acyclic Graph, 有向无环图) 的概念, 用来描述工作流的依赖关系.

  2. 执行器 Executor: 被封装在调度器内的一个运行环境, 用来执行外部的 worker. 这确保了 worker 的状态能被调度器准确的收集到, 而这个执行器其实是对真正的 worker 的封装. Airflow 支持三种不同的 Worker.
    • 第一种 Operator 是对具体不同类型的 worker, 例如虚拟机, 容器, 云厂商提供的服务进行的封装, 采取不同的执行策略并来确保收集到的状态信息是尽可能准确的.

    • 第二种 Sensors 是专门用来监控外部系统的状态改变的, 主要负责处理事件.

    • 第三种 TaskFlow decorated @task: 是一个 Python 装饰器, 可以让任何 Python 函数变成一个 worker.

  3. 一个 Web App: 作为用户界面, 可以用来操作调度器和执行器, 监控状态, 查看 Dashboard, 并提供了一套 API 给开发者进行远程调用.

它同时还依赖于一个用户自选的数据库用来保存调度数据. 确保工作流中间挂掉后状态不会丢失.

Airflow 本身需要集群化部署, 安装和配置后就能确保集群上的节点能互相发现彼此, 在失败后能让新的节点接上, 确保高可用.

总结下来, Airflow 的调度器 + 执行器的封装解决了 “运行 worker 和监控状态” 的痛点. 用集群化部署解决了 “保持调度者的高可用” 的痛点. 用 Executor 把真正的 worker 包装了一层, 确保了执行单元的可控, 解决了 “异常以及错误处理” 的痛点. 而 Web App 则是作为一个产品解决了很多上面提到用户需求.

Reference: