分布式双流:业务流稳如老狗,数据流快若闪电


现在我在一家流数据基础设施供应商工作,我被问到:“事件驱动架构、流处理、编排和新的持久执行类别之间有什么关系? ”

我们都曾在架构会议上遇到过类似的情况,有人会问:“这应该是一个事件吗?一个 RPC 吗?一个队列吗? ”,或者“我们如何将这个过程与我们的微服务结合起来?它应该由事件驱动吗?也许是一个工作流编排? ” 各种意见、白板箭头和对传奇故事的模糊引用,应运而生。

我将这个思维框架锚定在工作流的语境中,广义地使用这个术语来指代任何跨多个服务的分布式工作(例如结账流程、预订流程或贷款申请流程)。许多人使用“saga”来描述跨多个服务且需要协调和补偿的长期运行的工作流。本文将使用更通用的术语“工作流”来描述更广泛的分布式工作。


术语定义
编排(Orchestration )、编舞(Choreography)和持久化工作流在今天比以往任何时候都更加重要。Jack Vanlightly 撰写了一个很棒的四部分系列文章,旨在帮助您全面理解分布式流程的执行,如果您错过了,那真是太可惜了。千万不要犯这个错误!

现代系统不再是单体式的,而是庞大的计算图,由 API、队列和流连接在一起;跨微服务、函数、流处理器和 AI 代理实现。复杂的工作流跨越服务边界,需要可靠、易于理解、灵活且适应性强的协调。

在这些图表中,协调和可靠进展的概念至关重要。

1、协调策略决定了工作流程的构建和维护方式:

  • 编舞(反应式、事件驱动)提供了高度的解耦和灵活性。
  • 编排(集中式、程序化)提供了更高的清晰度和可观察性。

但图中并非所有边都是相等的。有些边是直接的,定义了工作流的关键路径,其中失败意味着失败。另一些边是间接的,会触发相邻甚至较远服务中的辅助操作。

良好的思维模型会区分这两者:

  • 编排应该专注于直接边,
  • 而编舞则能自然地处理两者。

2、可靠的进展取决于两个核心概念:

  • 持久触发器:必须以一种能够承受故障的方式启动工作(例如,Kafka、队列、可靠的 RPC)。
  • 可进展的工作:一旦开始,工作必须能够在恶劣条件下通过可重放性使用诸如幂等性、原子性或从保存状态恢复的能力等模式取得进展。

虽然流处理器(例如Flink、Kafka Streams)以及基于队列和主题的事件驱动系统(例如Kafka、RabbitMQ)都内置了持久性,但命令式代码通常却没有。持久执行引擎 (DEE),例如Temporal、Restate、DBOS、Resonate和LittleHorse(以及许多其他引擎),旨在填补命令式函数领域的这一空白。它们提供各种工具和语言支持,用于为命令式过程式代码添加持久触发器和可进展工作。

该分析构建了一个概念框架,用于理解由微服务、功能、流处理和 AI 代理组成的现代分布式架构中的协调和可靠进展,包括 DEE 框架提供的新构建块。

一直到最后都是图表
在每一个抽象层次上,计算都以图的形式展现出来。微服务中的一个函数包含一个控制流图(由分支、循环和条件语句组成),用于描述其执行逻辑。Flink 作业明确地是一个由流连接的运算符和有状态节点组成的有向图。将多个服务连接在一起的工作流(无论是通过编排还是编排)也是一个图,它表示依赖关系、事件流或命令序列。

协调在这个图的图中扮演着至关重要的角色,并且存在于每一层。在单个 Flink 作业中,Flink 本身负责协调多个任务管理器之间的工作。在微服务中,可执行代码充当多个步骤的线性或并发协调,这些步骤可能调用其他服务或数据系统。然而,跨多个系统的工作流所需的协调才是最大的挑战。跨异构系统、编程模型和环境的分布式协调是组织关注的战略问题,具有深远的影响。

这张由跨多个系统的工作流组成的图,可能会使编程模型和协调方法等主题的讨论变得令人困惑。例如,事件驱动架构中的消费者可能以命令式的方式程序化地执行其代码,但在响应式架构中却扮演着节点的角色。工作流可以由事件触发,事件充当重试的可靠触发器,也可以由短暂的 RPC 触发。图中节点和边的类型很重要。

节点、边和子图
虽然一切都是图形(甚至代码),但为了进行此分析,我们将内容限制为:

  • 节点是微服务功能、FaaS 功能、流处理作业和 AI 代理。
  • 边缘包括 RPC、队列、事件流。这些在语义上差异很大,有些是短暂的,有些是持久的,这会影响可靠性。
  • 工作流是图的子图(或图论中的连通组件)。
节点通过边连接。工作流作为子图。

直接/间接边
工作流的构成尚有争议,尤其是在我在本分析中使用的广义概念下。但我喜欢根据边的类型来思考工作流,边可以是直接的,也可以是间接的。边还有其他属性,例如请求/响应与单向,以及同步与异步。但现在我们将保持模型的简单性,并思考边是直接的还是间接的。

直接边
触发与正在执行的目标相关的核心工作。以“下单”工作流为例。可能有一组微服务用于处理付款、库存预留、启动发货准备等与订单直接相关的操作。这些微服务都通过直接边连接,构成了核心的“下单”工作流。

间接边
会触发一些间接的辅助工作,例如通知 CRM 系统进行客户管理、通知财务/报告系统或审计服务以确保订单工作流程的合规性。间接边缘甚至可以触发次要的核心工作流程,例如即时库存流程。

边是直接的还是间接的将影响选择什么样的通信媒介

协调
工作流需要协调,无论这种协调是独立服务之间的简单协作,还是更加集中化和可控的机制。协调策略主要有两种:编舞 (Choreography) 和 编排 (Orchestration)。 

编舞 (Choreography) :事件驱动的工作流(响应式)。高度解耦的微服务,在输入事件到达时独立响应。没有阻塞或等待,所有消费者的操作独立于任何上游生产者或后续下游消费者。

通过发布-订阅语义进行协调。上游事件的整体影响可能波及广泛,并且会随时间而动态变化。在更广泛的事件流中,任何给定工作流的边界可以是软的,也可以是硬的(但耦合度较低)。

编排:程序化工作流(if-this-then-that)。逻辑集中在某种编排器(甚至是微服务或函数)中,发出命令并等待下属工作微服务的响应。编排器会跟踪工作流中哪些部分已完成、哪些部分正在进行以及哪些部分尚未启动。它还会跟踪发送给下属服务的命令以及这些服务的响应。

通过过程编排语义进行协调。由于单个节点仍然可以发出事件,因此上游工作流的整体影响可以扩散到很远的地方。给定工作流的边界在编排代码中清晰地编码,尽管这会增加耦合度。

流处理在图中的作用
Apache Flink 和 Kafka Streams 等流处理框架可以被视为微服务,它融合了可配置的连续数据流编程模型和响应式事件处理,旨在实时转换和响应事件流。与微服务类似,流处理器会形成逻辑计算图,使用分支、连接和聚合来处理数据。然而,它们的编程模型受到更多限制,针对以数据为中心的事件流转换进行了优化,而非针对复杂的控制流或按需处理单个请求。

在工作流和传奇的背景下,流处理器作为事件图中的节点自然地融入事件驱动的编排中,不仅执行转换或丰富,还承担与传统微服务处理的角色重叠的角色,包括有状态业务逻辑和触发下游效应。

正如现代微服务遵循领域驱动设计原则分解为有界上下文一样,流处理器也应该被严格限定在特定范围。通常不建议将整个业务工作流(例如,购物车结账、支付、配送、订单履行)嵌入到单个 Flink 或 Kafka Streams 作业中。相反,流处理器最好作为编排系统中的独立节点运行,每个节点独立地对事件做出响应。

除了作为编排的演员参与之外,流处理器在 saga 和工作流架构中还扮演着两个宝贵的角色:

  • 工作流的实时触发器:检测事件模式(例如,“用户添加到购物车但 1 小时内未结账”)并发出信号以启动或分支工作流。
  • 决策的聚合状态:持续计算派生状态(例如,欺诈分数、用户行为模式),以便协调器或服务可以查询以指导工作流逻辑。

总而言之,流处理可以在编排工作流中取代传统的微服务,并通过实时洞察、触发器和数据转换来增强编排工作流。然而,一个流处理作业很少会包含整个工作流,就像微服务不会包含整个工作流一样。


持久性是首要考虑因素
在分布式系统中,持久性不仅关乎数据,也关乎进度。执行关键操作的工作流必须以可控、可恢复的方式完成或失败。持久协调可确保步骤在崩溃或网络故障后不会消失。无论执行模型(过程式、事件驱动式还是数据流式),持久性都是将短暂逻辑转化为可靠系统的机制。

事件驱动架构 (EDA) 形式的编排默认提供持久性。事件持久存储在队列或日志(例如 Kafka)中,使响应式系统能够从崩溃中恢复、重放历史记录并触发重试。每个服务独立响应,并在事件流中隐式跟踪进度。

在此模型中,事件日志既充当协调媒介,又充当事实来源,对系统行为的因果结构进行编码。把前因后果都记得明明白白!

相比之下,命令式代码缺乏内置的持久性。运行过程逻辑(例如,“执行 A,然后执行 B,然后执行 C”)的服务通常将其状态存储在内存中,并依赖外部系统来持久化选择性状态。如果在执行过程中发生崩溃,除非明确保存,否则调用堆栈中的所有内容都会丢失。

这一缺陷催生了持久执行产品类别,它为命令式工作流带来了类似事件日志的持久性。

时空回溯引擎:持久执行引擎(例如 Temporal、Restate、DBOS)会持久保存工作流的进度、关键变量、中间结果、来自其他服务的响应等,从而允许重试,并从中断处精确恢复。 

能把代码执行过程像游戏存档一样存下来,变量值、执行到哪一步、其他服务的回复全都记住,真正做到"断点续传"!


@durable_workflow
async def refund_order(context, order_id):
    await context.call("cancel_shipping", order_id)
    try:
        await context.call("refund_payment", order_id)
    except:
        await context.call("escalate_issue", order_id)

持久执行引擎实际上是命令式协调(又称通过编排进行协调)的 Kafka 。

持久性也是流处理的基础:
流处理框架(比如Flink)更是"记忆大师"!它们会用检查点(定时拍照)、变更日志(记流水账)等方法,保证数据计算像刻在石板上一样牢固。就算服务器炸了,重启后也能接着算,就像你追剧时突然断网,续播时还能接着上次的画面。

Apache Flink 和 Kafka Streams 等框架通过状态持久化机制(例如检查点、更改日志和恢复日志)实现了原生的持久性,确保事件转换和有状态聚合能够承受故障。虽然该范式以数据为中心且持续进行,但核心概念始终如一:持久记录进度,确保计算能够可靠地持续进行。

归根结底,一切都是日志。
无论是领域事件序列、持久的工作流历史记录,还是支持流处理器的变更日志,其基本思想都是相同的:将系统活动编码为持久的、仅可追加的记录,记录已发生的事情(以及可能接下来应该发生的事情)。 

终极秘诀:万物皆可记日记!不管是订单记录、工作流历史还是数据流水账,核心就一招——把所有操作写成永久保存的"班级日志",既记"已经做了什么",也提示"接下来该干嘛"。

将耐久性作为首要考虑因素可以使系统实现:

  • 崩溃后可恢复。
  • 可以通过可重播的历史进行观察。
  • 跨异步边界的可靠性。
  • 可跨执行模型组合。

这个视角是一个开始,但我们需要通过创建一个简单的模型来思考可靠的执行和协调的进展,将其分解成更精确的术语。我们将在第二部分中讨论这个问题。

协调进展系列链接:

  1. 观察系统:图表
  2. 确保进展可靠
  3. 耦合、同步和复杂性
  4. 宽松的决策框架

第二章:确保进展可靠
上回说到系统要像乌龟一样稳(耐久性),这次咱们解锁新技能——如何让工作流像打不死的小强!想象你们班组织春游,从订车到买零食要经手N个小组,怎么保证某个同学突然请假不影响整体进度?这就是"可靠推进"的终极考题!

一、可靠推进 = 靠谱闹钟 + 防呆工作法 ⏰
就像早上起床:

  1. 靠谱闹钟(可靠触发):必须能连环call(比如消息队列),直到你真正起床
  2. 防呆流程(可推进工作):
    • 方案A:设10个闹钟也没关系(幂等性)
    • 方案B:记录"已经刷完牙"(持久化进度)
    • 方案C:把起床→洗漱→吃早饭绑定成原子操作(事务)
就算中途被班主任叫走,回来也能接着干!

二、各科代表の生存指南
1️⃣ 数据流处理组(Flink/Kafka)
绝招:随身带"错题本"(状态持久化)

  • 每做5道题就存档(检查点)
  • 用Kafka当作业登记表(持久数据源)
  • 就算教室停电,恢复后也能从最后一道题继续

2️⃣ 微服务小组
生存守则:

  • 接活只认班长签字的任务单(队列/RPC)
  • 普通电话(RPC)可能断线,要用对讲机(可靠RPC)
  • 每完成一步就画✓(持久记录进度)

3️⃣ AI课代表(新转学生)
特殊要求:

  • 调用API时要像写保证书(持久执行)
  • Flink给TA配了专属错题本(FLIP-531)
  • 决策过程必须可回放(Exactly-Once语义)

三、两大门派对决:编舞派vs编排派 vs

编舞派(事件驱动)
✅ 优点:

  • 像班级匿名信箱(解耦)
  • 新同学随时加入(灵活扩展)
  • 各组自己管进度(独立演进)

❌ 缺点:

  • 像玩传话游戏(难追踪源头)
  • 可能先收到"修改订单"才看到"创建订单"(乱序)

编排派(集中控制)
✅ 优点:

  • 像班长指挥大扫除(流程清晰)
  • 有全班值日表(全局视图)
  • 掉链子自动启动B计划(明确补偿)
❌ 缺点:
  • 班长笔记本丢了全乱套(单点风险)
  • 换新班长要全班培训(学习成本)
编舞派就像课间操——广播一响各自行动编排派像合唱比赛——指挥棒指哪唱哪

四、学霸的混合战术
真实世界要"左右互搏":

  1. 核心流程用编排:比如收班费必须步步确认
  2. 周边操作用编舞:比如通知家长可以异步处理
  3. 中间加个传话员:用Kafka当班级小喇叭

五、终极心法
所有系统本质上都是: 一本不断续写的班级日志 +⏳ 一台可以回放的监控摄像头
只要做到:✓ 每个动作记入史册(持久化)✓ 每次中断可续播(可恢复)✓ 跨组协作有存证(可靠触发)
你的系统就能修炼成—— 永不崩溃的六边形战士!


第三章:耦合、同步和复杂性
前情回顾:

  • Part 1:系统要像乌龟一样稳(耐久性)
  • Part 2:工作流要像打不死的小强(可靠推进 = 靠谱闹钟 + 防呆工作法)
  • Part 3:今天解锁终极奥义——"服务之间到底该怎么谈恋爱?"(耦合、同步与异步)


电商大战:编舞派 vs 编排派

场景
假设你在做一个电商系统,有四个服务:

  1. 库存服务(管货)
  2. 支付服务(收钱)
  3. 物流服务(发货)
  4. 通知服务(发邮件/短信)

编舞派(事件驱动EDA)——像班级匿名信箱
✅ 怎么玩?

  • 用户下单 → "订单创建"事件 发到班级群(Kafka)
  • 库存服务看到事件 → 扣库存 → 发 "库存已扣"事件
  • 支付服务看到事件 → 收钱 → 发 "支付成功"事件
  • 物流服务看到 "库存已扣" + "支付成功" → 发货
 如果支付失败?
  • 支付服务发 "支付失败"事件
  • 库存服务看到 → 自动回滚库存(补偿)
  • 物流服务直接无视(因为它只认成功订单)
优点
  • 解耦:支付服务根本不知道库存服务存在
  • 弹性:物流服务宕机?事件会等它恢复
  • 灵活:随时加新服务(比如风控服务监听订单)
缺点
  • 调试像破案:订单出问题?得翻遍所有服务的日志
  • 乱序可能:可能先收到 "修改订单" 才看到 "创建订单"

编排派(集中控制)——像班长指挥大扫除
✅ 怎么玩?班长(订单协调器)直接发号施令:

async def 处理订单(订单数据):
    try:
        库存 = await 库存服务.扣库存(订单数据.商品)
        支付结果 = await 支付服务.收钱(订单数据.信用卡)
        物流 = await 物流服务.发货(订单数据.地址)
        await 通知服务.发确认邮件(订单数据.邮箱)
    except 支付失败:
        await 库存服务.回滚库存(库存.id)  # 明确补偿
        await 通知服务.发失败邮件(订单数据.邮箱)


优点

  • 一目了然:所有逻辑在一个地方
  • 精准补偿:支付失败?班长直接叫库存回滚
  • 可靠恢复:如果协调器崩溃,DEE(如Temporal)会从断点继续
缺点
  • 耦合高:班长必须认识所有同学(服务)
  • 版本地狱:改流程?得小心处理进行中的订单

耦合:服务之间的"恋爱关系"
1️⃣ 设计时耦合(结婚前要了解多少?)

  • RPC(远程调用):像相亲——必须知道对方喜欢啥、讨厌啥(接口、数据结构)
  • 事件驱动:像匿名笔友——只关心信的内容(事件格式),不关心谁读

2️⃣ 运行时耦合(婚后要不要随时回消息?)

  • RPC:像打电话——对方必须秒接,否则你就卡住(强依赖)
  • 事件:像发短信——你发完就干别的,对方爱啥时候回啥时候回(解耦)
 Leslie Lamport 名言:"分布式系统就是——一个你根本不知道存在的电脑挂了,你的电脑也用不了。"(说的就是RPC调用链!)

⏱️ 同步 vs 异步:沟通方式的频谱
1️⃣ 立即响应(RPC)

  • 场景:像问同桌"借橡皮"——必须马上拿到才能继续写作业
  • 例子:下单时必须立刻检查库存,否则用户会等疯
2️⃣ 宽松响应(异步RPC/队列)
  • 场景:像让同学"放学帮我带奶茶"——你不需要等他,他回头告诉你
  • 例子:支付成功后慢慢发邮件通知
3️⃣ 无需响应(纯事件)
  • 场景:像班级公告栏——你贴个通知,谁爱看谁看
  • 例子:记录审计日志,不影响核心流程

可靠RPC的魔法:像游戏存档的远程调用
普通RPC的问题:

  • 如果调用方崩溃,即使对方成功了,调用方也不知道
解决方案:
  1. 队列+回调(手动版):
    • 发请求时带个唯一ID(就像快递单号)
    • 对方处理完,往另一个队列回消息(像快递签收通知)
    • 你的代码要写一堆回调处理,复杂!
  • DEE(如Temporal)自动版:
    • 代码写得像同步调用,但引擎偷偷帮你:
      • 存进度(像游戏存档)
      • 崩溃后换台电脑接着玩(自动恢复)
      • 甚至随机数都能存住(保证重试不会乱)


    终极心法:直接边 vs 间接边
    1️⃣ 直接边(必须成功的步骤)

    • 例子:下单→扣库存→支付→发货
    • 适合:编排(集中控制更清晰)
    2️⃣ 间接边(锦上添花的步骤)
    • 例子:更新CRM、发营销短信、记录审计日志
    • 适合:编舞(事件驱动,不影响核心流程)
    黄金法则:核心流程用编排,周边操作靠事件!(就像班长只管收作业,谁爱看黑板报谁看)

    总结

    1. 编舞派(事件驱动)→ 适合松散协作(如通知、日志)
    2. 编排派(集中控制)→ 适合关键路径(如支付、库存)
    3. 耦合越低,系统越稳(少点"恋爱脑",多点"笔友关系")
    4. 同步 vs 异步 → 按业务需求选,别一根筋!

    第四章:宽松的决策框架
    第一关:执行模型怎么选?
    1️⃣ 流处理器(Flink/Kafka Streams)——"自带存档的流水线"
    ✅ 特点:

    • 天生会自动存档(检查点/changelog)
    • 数据像水流过管道,崩溃了也能从上次断点继续
    • 例子:实时统计销量,就算服务器炸了重启也不丢数据
     适合:需要持续处理数据的场景(如实时大屏、风控)

    2️⃣ 微服务/函数——"需要自己写存档的金鱼脑"
    ✅ 特点:

    • 写起来简单(普通代码)
    • 但默认不存档!崩溃了就失忆
    • 补救方案:
      • 方案A:自己写防重试逻辑(幂等性)→ 像备忘录标记"已做"
      • 方案B:用Durable Execution引擎(如Temporal)→ 自动帮你存进度
     适合:传统业务逻辑(如订单处理),但想稳就选方案B

    ⚖️ 第二关:决策框架——灵魂四问
    Q1:触发方式靠谱吗?(可靠触发器)

    • 事件驱动(Kafka):像班级信箱,发完就不管(适合解耦)
    • RPC调用:像打电话,必须对方接通(适合强依赖)
    • Reliable RPC(DEE提供):像挂号信,必达+可回溯

    Q2:工作能断点续传吗?(可推进工作)

    • 幂等性:重复做也不怕(如:扣库存前先查是否已扣)
    • 事务:要么全成功,要么全回滚(适合钱相关)
    • 持久化进度:像游戏存档,死了重来(DEE自动帮你)

    Q3:喜欢集中管理还是放养?(协调策略)

    • 编排(Orchestration):像班长指挥,流程清晰但容易成瓶颈
    • 编舞(Choreography):像匿名传纸条,灵活但难追踪

    Q4:能接受多少基础设施?(依赖成本)

    • Flink:要单独部署,但功能强
    • Kafka Streams:只需Kafka,但功能少
    • DEE(如Temporal):不用自己写重试逻辑,但多学个框架

    实战例子:电商系统设计
    核心流程(直接边)→ 用编排+Reliable RPC
    # 用Temporal写订单流程(自动存档!)  

    async def 处理订单():  
        库存 = await 扣库存()  # 崩溃了会自动重试  
        支付 = await 收钱()    # 支付失败会自动触发补偿  
        await 发货()  


    周边操作(间接边)→ 用编舞+事件

    • 订单创建事件 → 触发:
      • 发邮件(通知服务)
      • 更新CRM(销售团队)
      • 记录日志(审计服务)好处:核心流程不用改,随便加新监听者!

    终极心法:万物皆可"日志化"
    无论用哪种技术,核心思想都是: 把系统所有操作当成日记记下来(存到Kafka/DB/检查点)这样无论哪里崩溃,都能:

    • 回放历史(像看监控录像)
    • 精准恢复(像读档重玩)
     隐藏彩蛋:Flink和DEE本质都是高级版"自动存档器",只是Flink专注流数据,DEE专注业务逻辑

    总结:一张表搞定选择困难症

    需求    推荐方案    例子
    实时数据处理    Flink/Kafka Streams    点击流分析
    关键业务流程    DEE(Temporal等)    订单支付
    灵活扩展    事件驱动(Kafka)    通知、审计
    简单小功能    普通微服务+幂等性    用户签到


    完结撒花!现在你已掌握:

    1. 耐久性→像乌龟一样稳
    2. 可靠推进→像小强一样顽强
    3. 解耦艺术→服务之间"谈恋爱"的姿势
    4. 决策框架→四步选出最优方案

    下次系统设计会,你就是最靓的仔!✨

    分布式系统双流真经:业务流稳如老狗,数据流快若闪电
    业务工作流三铁律:

    • 事务要么全成功,要么滚回娘胎(Saga模式)
    • 每个步骤必须打不死(幂等设计)
    • 进度随时可查(持久化日志)

    数据处理流三要素:

    • 数据流动永不停止(流式处理)
    • 计算结果必须保真(Exactly-Once)
    • 故障恢复要像倒带(Checkpoint机制)

    对比清单:

    特性    业务工作流    数据处理流
    核心目标    业务正确性    数据时效性
    生命特征    离散任务    持续流动
    杀手锏    补偿事务    状态快照
    代表技术    Temporal/Saga    Flink/Spark
    崩溃恢复    断点续传    精准回放
    吞吐量    千级TPS    百万级EPS

    黄金法则:

    1. 业务流要像银行转账 - 分毫不能差
    2. 数据流要像心跳监测 - 时刻不能停