diff --git a/演讲稿/260313.md b/演讲稿/260313.md new file mode 100644 index 0000000..0ad2093 --- /dev/null +++ b/演讲稿/260313.md @@ -0,0 +1,258 @@ +# 演讲稿:从集中式到分布式,为什么消息中间件成为关键基础设施 + +建议时长:约 12–16 分钟。 +使用方式:可以按页讲,也可以根据时间压缩第 4–6 页与第 10 页的展开部分。 + +--- + +## 封面页 + +大家好,今天我想汇报的主题是:**从集中式到分布式,为什么消息中间件会成为关键基础设施**。 + +我这次不会把重点放在某一个具体产品的接口细节上,比如 Kafka、RabbitMQ 或 RocketMQ 的命令怎么写。今天我更想回答三个问题。 + +第一,集中式系统为什么在早期很好用,但后面又会遇到瓶颈。 +第二,系统拆成分布式之后,为什么会自然出现解耦、异步、削峰填谷、最终一致性、重试、幂等、顺序性和可靠投递这些问题。 +第三,消息中间件为什么会在这个阶段被引入,它到底解决了什么,又把复杂度转移到了哪里。 + +--- + +## 第 1 页:集中式系统——单体架构与数据流 + +我们先看最传统的集中式系统,也就是单体系统。 + +它的典型结构是:用户请求先进入统一入口层,然后进入一个单体应用。这个单体应用内部虽然可能也划分了用户、商品、订单、库存、支付、通知这些模块,但它们本质上还是运行在同一个进程里。最后,这些模块再统一访问同一个数据库,处理结束后同步把结果返回给用户。 + +这种架构在早期通常非常高效。原因也很直接。 + +第一,开发和部署成本低。大家维护的是一个工程、一个进程、一个数据库,协作路径很短。 +第二,调用发生在进程内,延迟低,定位问题也比较直接。 +第三,数据集中在一个库里,本地事务和强一致性都更容易实现。 + +所以单体并不是“错误架构”。相反,在业务规模还不大、边界也还比较稳定的时候,它往往是最经济、最务实的起点。 + +--- + +## 第 2 页:集中式系统的缺点——为什么要转向分布式 + +但随着业务增长,集中式系统的优势会慢慢变成它的约束。 + +首先,**发布开始耦合**。哪怕你只是改一个很小的库存逻辑,也可能需要重新构建和发布整个系统。 +其次,**扩容开始粗糙**。如果热点其实只在订单和支付链路上,你还是只能把整个单体一起扩容。 +第三,**故障容易放大**。一个局部模块出现问题,可能把整个进程拖慢,最后影响全站。 +第四,**数据库压力会集中**。当订单、库存、支付都共享一个核心库时,单库就会逐渐成为吞吐和可用性的瓶颈。 + +所以系统转向分布式,并不是因为单体“落后”,而是因为随着业务、流量和团队规模的上升,单体的边际成本越来越高。 + +换句话说,分布式不是为了“更先进”,而是为了获得三种能力:**按业务能力拆分、按热点独立扩容、按故障域做隔离**。 + +--- + +## 第 3 页:拆成分布式后——问题不是消失了,而是迁移了 + +当系统拆成分布式以后,我们确实会得到一些很明显的好处,比如独立部署、按需扩容、团队边界更清楚。 + +但是,这个时候复杂度并没有消失,它只是从“代码内部”转移到了“服务之间”。 + +在单体里,一个模块调用另一个模块,本质上是函数调用。 +可是在分布式里,一个服务调用另一个服务,已经变成了跨网络的 RPC。 + +同样,在单体里,订单、支付、库存如果都在一个数据库里,本地事务就能把它们一起包住。 +可是在分布式里,这些数据往往分散在不同服务、不同数据库里,于是跨服务协同就不再能靠一个本地事务解决。 + +这就是后面所有问题的来源。 +也就是说,**解耦、异步、削峰填谷、最终一致性、重试、幂等、顺序性、可靠投递,并不是消息中间件发明出来的问题,而是分布式系统天然会遇到的问题。** + +--- + +## 第 4 页:问题来源之一——解耦、异步、削峰填谷 + +先看前三个问题:解耦、异步和削峰填谷。 + +### 1. 解耦从哪里来? + +如果支付成功以后,订单服务还要直接去调用积分服务、短信服务、物流服务,那么每增加一个下游,主服务就多一条同步依赖。 + +这样一来,订单或支付服务不仅要关心自己的业务,还要感知每个下游的接口、可用性和上线节奏。这就是强耦合。 + +消息中间件的做法是,把“直接调用”改成“发布事件”。 +比如支付成功后,只发布一条 `PaymentSucceeded` 事件,下游谁需要谁订阅。这样发布者只负责表达业务事实,不再关心具体有几个消费者。 + +### 2. 异步从哪里来? + +很多动作其实并不要求用户同步等待。比如发短信、发优惠券、写埋点、做分析,这些都不是主链路必须立刻完成的结果。 + +如果我们把这些动作都塞在同步请求里,用户的等待时间就会越来越长。 + +所以更合理的方式是:主链路只做关键事务,旁路任务通过 MQ 异步处理。这样用户更快拿到响应,系统也更轻。 + +### 3. 削峰填谷从哪里来? + +大促、秒杀这类场景里,上游写入速度往往会在瞬间暴涨,而下游数据库和服务的处理能力是相对稳定的。 + +如果没有缓冲层,流量就会直接冲到下游,轻则超时,重则把下游打垮。 + +消息中间件在这里承担的是“缓冲区”的角色。上游先把消息写入队列,下游按照自己能承受的速率平滑消费。再配合限流、扩容、重试和死信治理,就能把高峰流量整形成可处理的流量。 + +--- + +## 第 5 页:问题来源之二——最终一致性、重试、幂等 + +接下来再看另外三个经常同时出现的问题:最终一致性、重试和幂等。 + +### 1. 最终一致性从哪里来? + +在单体里,订单、支付、库存可以共享一个事务。 +但拆成分布式以后,这些状态往往分别保存在不同服务的数据库里,就很难再依赖一个本地事务同时提交。 + +这个时候,很多系统会采用事件驱动的一致性方式。也就是说,先让一个核心服务把自己的本地事务做成功,再通过事件把变更传播给其他服务,逐步把整个系统收敛到一致状态。 + +这就是我们说的“最终一致性”。如果某个下游暂时失败,也可以通过补偿、重试或者 Saga 机制继续推进,而不是要求所有服务在同一时刻一步到位。 + +这里补充解释一下 Outbox。 +在分布式系统里,一个很典型的问题叫做双写不一致。比如支付服务处理“支付成功”时,一方面要把自己的数据库状态改掉,另一方面又要把“支付成功”这个事件发到消息队列里。问题在于,这两个动作通常不在同一个原子事务里。 + +如果数据库已经提交成功了,但消息还没来得及发出去,服务就宕机了,那么下游系统就永远不知道这次支付成功,库存、通知、积分这些动作也就不会发生。 +反过来,如果消息先发出去了,但数据库事务最后回滚了,下游又会收到一个其实不应该存在的事件。 + +Outbox 模式就是为了解决这个问题。它的核心思路是:不直接把消息发到 MQ,而是先把“业务数据变更”和“待发送事件”一起写进同一个本地数据库事务里。 +也就是说,支付状态更新的同时,再往一张 Outbox 表里插入一条事件记录。只要本地事务提交成功,这两件事就一定同时成功;如果事务失败,这两件事就一起失败。 + +后面再由一个独立的 relay 程序,或者后台任务,去扫描 Outbox 表,把里面还没发送的事件可靠地投递到 MQ。等收到 Broker 的确认之后,再把这条 Outbox 记录标记为已发送。 + +所以 Outbox 的本质,就是把“数据库更新 + 发消息”这个难以保证原子性的过程,改造成“先本地原子落库,再异步可靠投递”。 +它解决的是事务与消息之间的衔接问题,是很多事件驱动架构里非常关键的一层。 + +不过也要注意,Outbox 解决的是“不丢消息、不漏事件”的问题,并不自动解决重复发送。所以即使用了 Outbox,下游消费者依然要做幂等。 + +### 2. 重试从哪里来? + +只要是分布式调用,就必须接受失败是常态。网络会抖动,Broker 会重启,消费者会超时,ACK 也可能丢失。 + +一旦某次处理“看起来失败”,系统通常就会重试或者重投。 + +所以重试不是一个偶发现象,而是分布式系统必须显式设计的机制。一个成熟的设计通常至少要有:重试次数上限、退避策略、重试队列和死信队列。 + +### 3. 幂等从哪里来? + +只要存在重试,就必须接受“同一条消息可能被处理多次”。 + +这意味着,我们不能假设消息只会来一次。工程上更可靠的思路是:默认它可能重复,然后通过 messageId、业务唯一键、去重表、唯一约束或者 upsert 让重复处理的结果保持不变。 + +所以可以把这三者连起来理解:**因为服务拆分,才需要最终一致性;因为最终一致性里一定会失败,才需要重试;因为有重试,才必须做幂等。** + +--- + +## 第 6 页:问题来源之三——顺序性、可靠投递 + +再往下看两个更偏系统语义的问题:顺序性和可靠投递。 + +### 1. 顺序性从哪里来? + +很多业务对象天然有顺序要求。比如同一个订单,应该先“创建订单”,再“支付成功”,最后才“发货”。 + +但分布式系统里,消息会分区、消费者会并发、失败后还会重试,这些都会打乱原本看似自然的顺序。 + +所以顺序不是默认就存在的,而是必须被明确定义出来。 + +工程上通常不会追求全局顺序,而是追求**局部顺序**。比如按订单 ID、账户 ID 这样的业务 key 来定义顺序范围,然后让相同 key 的消息进入同一分区、同一队列,或者由单线程、Single Active Consumer 这类方式串行消费。 + +### 2. 可靠投递从哪里来? + +可靠投递也不能只看 Broker 本身。因为消息可能在四个地方出问题: + +发送到 Broker 之前,Broker 持久化之前,Broker 投递给消费者之后,以及消费者处理完成但还没确认之前。 + +因此真正的可靠投递是一个端到端问题。 + +它通常包含几段机制:生产端要有 confirm 或 acks,Broker 侧要有持久化和副本,消费端要在本地事务完成后再 ACK,跨数据库与发消息之间还要通过 Outbox 或类似模式弥合空档。 + +所以工程上更常见的目标,不是抽象地追求“绝对恰好一次”,而是**至少一次 + 幂等 + 有边界的顺序**。 + +--- + +## 第 7 页:引入消息中间件后的纵览架构与数据流 + +理解了这些问题以后,我们再看为什么消息中间件会被引入。 + +引入 MQ 之后,整个系统一般会形成两条链路。 + +第一条是**同步核心链路**。也就是用户请求进入网关,再进入订单、支付这类必须同步完成的核心服务。 +第二条是**异步事件链路**。也就是核心服务本地事务成功后,把业务事件送入 MQ,再由库存、优惠券、通知、分析等下游独立消费。 + +这时候的典型数据流是: + +第一,用户请求进入订单或支付服务。 +第二,核心服务把本地业务数据和 Outbox 一起提交。 +第三,事件被 relay 或 producer 投递到 MQ。 +第四,下游服务按需扇出消费。 +第五,各个消费者本地提交成功后再 ACK。 +第六,如果失败,就进入 Retry 或 DLQ,后续再补偿或人工介入。 + +这就是为什么消息中间件会成为关键基础设施。它把服务之间原来那种直接、同步、脆弱的依赖,转成了带缓冲、可恢复、可扩展的事件协作。 + +--- + +## 第 8 页:为什么要引入 MQ——它到底解决了什么 + +如果要用一句更直接的话来总结 MQ 的作用,我会说: + +**它不是消灭问题,而是把问题从隐性的同步耦合,变成显性的系统机制。** + +没有 MQ 时,系统常常是点对点同步调用。新增一个下游,就新增一条依赖;主链路越拉越长,用户等待时间也越来越长;流量和故障会沿调用链不断传导。 + +引入 MQ 之后,系统获得了四种关键能力: + +第一,发布订阅,解决了解耦问题。 +第二,异步处理,让非关键任务离开主链路。 +第三,队列缓冲,把瞬时高峰转换成平滑消费。 +第四,配套 Outbox、ACK、重试和幂等之后,最终一致性和故障恢复开始变得可治理。 + +所以 MQ 的价值,并不只是“多了一个队列”,而是**重新定义了服务之间的协作方式**。 + +--- + +## 第 9 页:一个完整业务例子——支付成功后的事件流 + +这页我用一个支付成功的例子,把前面的概念串起来。 + +当支付服务确认支付成功时,它不会直接同步调用所有下游,而是先在本地事务里完成两件事:更新支付状态,同时写一条 Outbox 记录。 + +接着,relay 把这条 `PaymentSucceeded` 事件投递到 MQ。 +随后,库存服务会消费这条事件,去扣减库存;通知、优惠券、分析服务也可以并行地消费同一条事件。 + +如果库存服务处理成功,它就在本地提交完成之后再 ACK。 +如果库存服务失败,这条消息就进入 Retry 或 DLQ,等待后续重试或补偿。 +如果因为网络原因,这条消息又被投递了一次,那么库存服务也不应该被重复扣减,这就需要通过 `orderId + eventId` 这样的幂等键来兜底。 + +所以在这一条业务链路里,解耦、异步、最终一致性、重试、幂等、顺序和可靠投递,其实都会同时出现。 +这也是为什么消息中间件不是一个“点状技术”,而是整条业务链路的协作基础设施。 + +--- + +## 第 10 页:真正落地时,先想清楚的 4 件事 + +最后我想强调,落地时真正需要设计的,不只是 MQ 的集群怎么搭,而是下面这四件事。 + +第一,先划清边界。哪些动作必须同步完成,哪些动作可以异步收敛。 +第二,先定义事件。事件应该表达业务事实,而不是把 RPC 简单换个名字包成消息。 +第三,默认至少一次。也就是说,在设计上先接受重复,再用幂等和重试去消化它。 +第四,把事件流运营起来。持续监控 backlog、lag、重试率、死信队列和超时情况。 + +很多系统并不是死在 Broker 上,而是死在三个误区上: + +要么把 MQ 当成强一致事务的替代品; +要么无限重试,没有上限和告警; +要么只搭了消息中间件,却没有配套的监控、补偿和事件治理。 + +所以真正成熟的设计,不是“用了 MQ”,而是知道**哪些问题应该让 MQ 解决,哪些问题不应该强行交给 MQ**。 + +--- + +## 第 11 页:总结 + +最后我用一句话收束今天的汇报:**消息中间件不是为了让系统看起来更高级,而是为了把分布式系统里原本隐性的协作复杂度,变成一套可以设计、治理和监控的机制。** + +我的汇报就到这里,谢谢大家,欢迎交流。 + +---