What is Workflow Orchestration#

Overview#

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

举个例子. 假设你是一个电商平台公司, 就如欧美的 Amazon.com, 中国的 taobao.com 这种. 你有一个许多个内部系统, 例如:

  • 订单下单系统

  • 发货系统

  • 库存增减管理系统

  • 支付系统

  • 快递系统

  • 通知系统

  • 结算系统

那么在一个用户的下单行为中, 我们就有需要将这些系统组合起来来执行一个完整的订单处理. 有的时候可能需要走一遍全部流程, 有的时候只需要走一部分流程 (例如用户取消了订单, 或者用户申请了退款). 流程的分支变化可能根据实际情况变得非常复杂且无法预测. 那么为每种情况专门开发一套系统不仅工作量巨大, 而且难以维护. 所以从工程实践上来说, 我们通常会像上面的列表那样将系统拆分成多个子系统, 然后用工作流编排来实现不同情况下的业务逻辑.

为什么要使用工作流编排#

顺从上一节的例子中可以看出来, 对于变幻莫测的业务情况, 从工程实践的角度来说就是将其拆分为一个个独立的系统, 然后将其排列组合起来以适应不同的业务需求. 这也是业内比较流行的 微服务架构. 所谓 微服务就是将系统分拆成尽可能独立的子系统, 分开维护, 部署. 这样每个子系统的开发速度快, 维护简单. 相比 monolithic 架构迭代速度快. 但是微服务架构的一大痛点就是服务之间协作. 所以要想将微服务架构落地, 并最终产生商业价值, 提高生产效率, 工作流编排必不可缺.

为什么需要专门的工作流编排系统#

不用编排系统, 每个服务之间互相调用行不行?

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

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

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

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

不用编排系统, 自己写一个 If else 的脚本行不行?

很多人会问, 既然我们都有很多微服务了, 为什么我们不能写一个脚本, 用 if else 之类的逻辑进行编排呢? 为什么一定要一个工作流编排系统呢?

原因也很简单. 对于重要的生产业务, 除了编排, 我们还需要这个系统高可用, 容错, 可追溯 (日志), 可扩展, 有权限管理. 这些都不是写一个脚本能做到的. 你用来跑脚本的机器可能会挂, 跑到一半结果停了, 无法恢复继续, 就可能造成业务损失. 你跑完之后日志如何收集, 如何 debug, 如何统计成功率, 耗时等. 如果你把这些问题都解决了, 你相当于自己实现了一个编排系统.

工作流编排的痛点#

承接上一节, 我们知道一个专用的工作流编排系统是必要的了. 那么我们来看看这样的一个系统可以解决哪些痛点:

  1. 运行 worker 和监控状态.

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

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

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

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

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

调度者本身也是一个系统, 是有可能挂掉的, 如果复杂工作流运行到一半调度者挂掉, 要如何能换一个新的调度者来接替呢? 这其实对应着两个子问题, 调度者本身的冗余和高可用, 以及调度状态数据持久化. 所以一个调度者程序本身要在每个节点有 check point, 在开始和结束的时候把 metadata 写入持久化数据库, 这样即使调度者挂掉, 另一个调度者接手后就能获得调度者挂掉时的状态了.

  1. 异常以及错误处理.

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

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

市场上流行的编排系统介绍#

市场上的编排系统很多. 有一些是专门领域的编排系统, 例如运维届的 CI/CD. 而有一些是通用型的编排系统.

运维届的主流 CI/CD 系统都支持编排, 例如 Jenkins, GitHub, CircleCI, AWS CodePipeline 等. 我们这里不展开. 而我们重点说一下通用型编排系统.

Airflow

而通用型的编排系统业内的事实标准是 2015 年 6 月 Airbnb 公司捐给 Apache 基金会的 Airflow 项目. 它是一个基于 Python 的, 以集群化运行的编排服务器. 它很灵活, 扩展性很强, 用户界面也很友好. 不过你需要有人来维护这个服务器本身, 不是说你说用就用的.

AWS StepFunction

这是 AWS 于 2016 年 12 月推出的一款 Serverless 编排系统. 主打的一个就是只要提交 Code, 无需维护基础设施, 并且按使用量收费. 由于编排系统大放异彩的领域就是 Serverless / Microservices, 那么编排系统本身也是 Serverless 的也就显得非常自然了. 这也是我个人最喜欢的一款编排系统. 对于开发者而言, 你的学习环境跟生产环境用的系统是一摸一样的, 不像 Airflow 你在自己单机本地配出来的效果跟生产环境中还是有差别. 由于你无需像 Airflow 一样自己安装和部署集群, 直接就可以上手开发, 并且你无需为租服务器付钱, 而是为实际使用量付费 (1000 个 transition 步骤 0.025 美元), 你的学习成本也非常低. 从公司的角度来说, 开发者学习成本低, 上手速度快, 意味着你培训起来更容易, 市场上会的人也会更多, 你更容易招聘到合格的人.

其他

市场上还有很多的编排系统, 我个人对它们不太熟悉, 就不多做介绍了.

Finite State Machine (有限状态机) 和 DAG (有向无环图)#

FSM 是表示有限个状态以及在这些状态之间的转移和动作等行为的数学模型, 在有限状态机中, 要尽量避免循环. 以我的个人理解有限状态机就像是转移过程有动作的马尔科夫链. 其中业内的编排工具事实标准 Airflow 使用的是 DAG 有向无环图. 而 AWS Step Function 使用的是 FSM. 两者本质上是一样的.

介绍有限状态机的文章:

总结#

在现代软件架构中, 工作流编排是不可或缺的一环. 我认为是非常有学习价值的一门技术.