Appearance
从一个 AWS 流程编排,摸到了计算机科学的源头
这篇文章的起点很普通:我在干活时撞见了 AWS Step Functions,随口问了一句"它到底解决什么问题"。然后一个问题接一个问题,等我回过神来,已经站在 1936 年的图灵机面前了。
我把这条"提问的阶梯"原样记下来。因为我后来发现,这一连串的"为什么",恰好就是从工程实践通向计算机科学理论源头、再绕回实践的完整路径。
第一问:为什么需要 Step Functions?
先说背景。我现在的工作流程里有一段是这样的——一台需要重启打补丁的主机,得走这么个流程:
排空主机(drain) → 打补丁(patch) → 等实例重启 → 健康检查 → 恢复服务(restore)听起来就是顺序跑五步,能有多难?我一开始也这么想。直到我认真考虑"不用 Step Functions 怎么实现它":
方案 A:写一个大函数顺序跑完。 问题立刻冒出来——等实例重启加健康检查可能要十几分钟,函数有运行时长上限,超时就挂。更糟的是,中间任何一步失败,整个函数崩掉,而我得自己记录"跑到第几步了",否则重试就从头来一遍——drain 都做完了又 drain 一次。
方案 B:拆成多个函数,自己用消息队列和数据库串起来。 那我就得自己设计状态表、自己写"上一步完成后触发下一步"的胶水代码、自己处理重试和死信队列。这套编排逻辑会越长越复杂,而且——每个项目都要重写一遍。
Step Functions 干的事,就是把方案 B 里那坨"没有业务价值、纯属基础设施"的胶水代码,做成了一个托管服务:
| 痛点 | 它怎么解 |
|---|---|
| 长流程超时 | 一个流程最长能跑一年,天然适合"等重启"这种长等待 |
| 步骤间状态 | 自带状态持久化——当前在哪一步、每步的输入输出,它替你记 |
| 失败重试 | 声明式配置重试和捕获,指数退避不用自己写 |
| 等待 | 原生的"等 N 秒",等待期间不计费 |
| 可观测 | 可视化流程图,卡在哪一步一目了然 |
一句话:它把"流程编排"从"业务逻辑"里抽了出来,变成一个可视化、有状态、可观测的服务。
第二问:没有云的时候,传统怎么做?
这个需求显然不是云时代才有的。我顺着往回想,发现"多步骤流程编排"这件事,解法散落在好几代工具里,从土到洋:
- 最原始:cron + 脚本。用定时错开来假装步骤有依赖,失败了自己
if判断、自己写状态标志位。这就是手搓版的方案 B。 - 企业级:作业调度器,像大型机的 JCL、Unix 时代的 Control-M / Autosys。银行的夜间批处理跑了几十年,干的事和 Step Functions 惊人地像——定义作业依赖、自动重试、可视化监控。区别只是它们是你机房里要买授权、要专人运维的重型软件。
- 数据领域:Informatica、SSIS 这类 ETL 工具,拖拽式画"抽取→清洗→加载"的流程图。今天 Step Functions 那张可视化流程图的审美,源头就在这。
- 业务流程领域:BPEL、jBPM、Camunda 这类工作流引擎,面向"请假→经理批→HR 备案"这种流程。这一支才是 Step Functions 最直接的精神祖先——声明式定义流程、引擎负责持久化状态、长流程能跑几天等人审批。
所以 Step Functions 没发明新东西。它是把"企业作业调度器 + 业务工作流引擎"这两类原本要自建、要买授权的重型能力,做成了按次付费的托管云服务。概念是老的,交付方式是新的。
但写到这我注意到,上面这些工具——从 BPEL 到 Step Functions——它们底层都在反复念叨同一个词:状态机(State Machine)。Step Functions 官方就直接管它叫 State Machine。这不是营销词。于是我往下问。
第三问:状态机到底是什么?
核心一句话:状态机就是"一个东西在任意时刻只处于有限个状态中的某一个,并且只能按预先定义好的规则从一个状态跳到另一个状态"。
红绿灯就是最干净的例子。它任意时刻只可能是红、绿、黄三种之一,不会"既红又绿",也不会从红直接跳黄。规则固定死了:
绿 --(倒计时结束)--> 黄 --(倒计时结束)--> 红 --(倒计时结束)--> 绿它由四个要素构成:
| 要素 | 红绿灯里是什么 |
|---|---|
| 状态 | 红、绿、黄——有限的、明确的几个 |
| 事件 | "倒计时结束"——触发跳转的东西 |
| 转移 | 绿→黄、黄→红、红→绿——哪个状态遇到哪个事件去哪 |
| 初始状态 | 开机默认是红 |
关键在于:绿灯遇到"倒计时结束"只能去黄,不可能去红。没有定义的跳转就是非法的。 这正是状态机的价值——它把"什么情况下能干什么"显式锁死了。
电商的订单就是这么建模的:已完成的订单不可能被取消,已发货的不可能退回待支付。非法跳转在结构上就被挡掉,而不是靠程序员每个地方记得写判断。
而我那个 drain 流程,每一步(drain / patch / wait / health-check / restore)就是一个"状态",每步执行完的结果就是触发跳转的"事件",编排定义里的"下一步去哪"就是"转移规则"。Step Functions 之所以可靠,是因为它帮我持久化了"现在在哪个状态"——状态是显式存着的,不藏在代码的执行流里。
第四问:那为什么偏偏用状态机?它从哪来的?
这是我问到的最有意思的地方。答案出乎我意料:先有数学,后有计算机。状态机不是为了造计算机发明的,反而是"想搞清楚计算到底是什么"这个纯数学问题的副产物——结果它恰好成了造计算机的基石。
我把这个思想的家谱捋了一遍:
1936 年,图灵机。 图灵为了回答一个纯数学问题——是否存在一个机械步骤能判定任意数学命题的真假——虚构了一台机器:一条纸带,加一个有限状态的读写头。这台机器本质就是个状态机。注意:1936 年根本还没有电子计算机。是状态机这个数学模型先定义了"什么叫计算",计算机才照着它被造出来。
1943 年,McCulloch & Pitts。 一个神经生理学家加一个逻辑学家,为了给大脑神经元的开关行为建数学模型,把神经网络建模成了有限状态自动机。这被公认为"有限状态机"概念的正式起点——动机居然是给大脑建模,跟工程毫无关系。
1951 年,Kleene。 逻辑学家 Kleene 把它严格化,证明了一件漂亮的事:有限状态机能识别的东西,等于正则表达式能描述的东西。 正则表达式就是他这时发明的。所以你今天写的每一个 regex,背后跑的就是一台有限状态机——这不是巧合,是数学上的等价。
1955 年前后,Mealy 机与 Moore 机。 到这里才转入工程。贝尔实验室的工程师发现,这玩意儿正好是他们需要的——因为有一个深刻的事实:
任何带记忆的数字电路,本质上就是一台有限状态机。触发器存的就是"当前状态",组合逻辑算的就是"转移函数"。
你电脑里的 CPU、内存控制器、协议芯片,底层全是状态机。
1956 年,Chomsky 层级。 语言学家乔姆斯基把自动机和形式语法对应起来,排出一个能力阶梯:
有限状态机 < 下推自动机 < 图灵机
(正则语言) (上下文无关) (通用计算)这一步回答了"为什么需要状态机"最深刻的版本。
收束:弱,恰恰是它的力量
为什么需要状态机?我现在能给出两个层次的答案。
数学层面: 状态机是用来刻画"计算能力边界"的工具。那个能力阶梯说明了一件深刻的事——问题的难度,可以用"需要多强的机器才能解它"来精确衡量。 能用纯状态机解的最简单(匹配正则、识别协议状态);需要一个栈才能解的复杂一档(解析带嵌套括号的语法,所以编译器的语法分析比词法分析强);需要图灵机的最强(通用计算)。
而状态机的"有限",恰恰是它力量的来源:因为状态有限,关于它的很多问题——会不会停、两台机器等不等价——都可判定、可验证。 一旦你给它加上无限内存变成图灵机,这些问题立刻变成不可判定(这就是著名的停机问题)。
所以工程上有一条心照不宣的原则:能用弱的模型,就绝不用强的。弱,意味着可证明、可验证、不会失控。
工程层面: 现实里大量东西天然就是"有限状态加受规则约束的跳转"——数字电路、网络协议握手、编译器的词法分析、UI 页面流转、订单流程,还有我那个 drain 编排。状态机给了它们一个能穷尽、能验证、非法状态进不去的统一框架。
这就是我从一个 AWS 服务一路问下来的全程。
最让我感慨的是这条线的形状:我从一个最实际的工程问题出发(一个流程怎么可靠地跑完),一路往下挖,挖到了 1936 年一个纯粹的数学问题(什么是计算);然后这条线又绕了回来——那个 1936 年的抽象,今天正以 Step Functions 的形式,替我管着一台服务器的重启。
理论和实践从来不是两件事。它们是同一条线的两端。 我只是恰好有机会,把这条线从头到尾走了一遍。