跳到主要内容

第 26 章 架构演进:什么时候该拆,怎么拆

副题:边界——业务边界 = 数据边界 = 服务边界;单体不是原罪,拆不是终点;"什么时候其实不需要微服务"

系统"看得见"了。业务也在增长——阶段 5 来了:日活新高、功能膨胀、8 人团队改一个仓库越来越挤、发布互相踩(第 20 章的老问题重演)、数据库连接数告急。本章回答:什么时候该拆?怎么拆?什么时候其实不用拆?

本章要建立的认知

  1. 拆服务是被逼出来的,不是设计出来的:三条信号(协作/故障面/数据)到了,才轮到"拆"——第 2 章演进循环的收官应用;
  2. 边界是拆的全部秘密:业务边界 = 数据边界 = 服务边界——第 6 章建模时的边界感,是架构演进的准备金;
  3. 微服务不是标配:分布式复杂度(一致性/链路/运维)是实打实的债——"业务没到就别上"(第 23 章 K8s 决策)的姊妹篇。

26.1 阶段 5:单体痛点集中爆发

阶段 5 的团队从 8 人长到 30 人,业务从"社区+交易"长成"社区+交易+拍卖+活动"(第 27 章的新功能预告)——代码库 30 万行,部署一次全量回归要 2 天(五阶段故事线阶段 5 数字)。单体架构的痛点开始集中爆发(五阶段故事线阶段 5 原文):发布互相踩(30 人改一个仓库——20 章的老问题重演,但规模翻了几倍:一次发布排队半天,合并冲突成了日常)、故障影响面全站(任何一个模块的 bug 都是全站事故——24 章的高可用机制再好,也架不住"发布即全站风险")、数据库连接数告急(14 章预告的信号:单库连接数打到顶——500 连接打满,加实例也白搭,因为连接都堵在数据库)。

阶段 5 的一周实景(它是"痛点"的具象):周一:支付组改了个字段,全站发布排队到周三(协作信号);周三:内容模块的 bug 让整个网站 502 了 20 分钟——包括和它毫无关系的支付(故障面信号);周五:晚高峰数据库连接数 480/500,值班的人盯着监控不敢睡(数据信号)——一周之内三条信号全到——老周问:这架构是不是不行了?——不是架构不行,是架构到了该长的时候(第 2 章:演进不是失败,是生长)。

三条信号线(它是"什么时候拆"的信号清单):

  • 协作信号:改代码互相踩(发布排队/合并冲突/上线要排期——20 章"发布互相踩"的升级版);
  • 故障面信号:一个模块出错全站遭殃(发布即全站风险——24 章"故障爆炸半径"管不住);
  • 数据信号:单库连接数告急/查询开始吃力(14 章说的"亿级行或单库连接数告急=阶段 5 的信号")。
  • 容量信号(补充):24 章说"扩容的尽头是架构问题"——垂直扩容到顶(机器换到最大还扛不住),水平扩容到顶(加实例连接数打满数据库)——扩容天花板=架构天花板(24 章预告的兑现)。

单体的"好"也要先说(它是"拆的对面"的公平):单体不是一无是处——它让阶段 1-4 活了下来:一个人能写全栈(12 章)、一次部署全站更新(22 章)、排障只看一个进程(25 章)、事务天然跨表(17 章)——单体的好:简单、一致、好排障——拆的代价清单里每一项,都是单体白送的好处(26.7 会说:这些好处拆了就没了)——公平地说:不是单体变差了,是阶段 5 的规模让单体的好不够用了

阶段 4 → 阶段 5 的过渡(它是"第 20-25 章准备了什么"的回看):阶段 4 建的体系(Git 工作流/CI/CD/部署容器化/高可用/可观测性)是阶段 5 拆分的前提——没有流水线,拆了没法独立发布(22 章);没有可观测性,拆了没法排障(25 章);没有容器,拆了部署混乱(23 章)——第 20-25 章不是"阶段 4 的事",是"阶段 5 的预埋"(第 2 章循环:每一轮都在为下一轮铺路——故事线原话:"部署容器化(先 Docker 后评估 K8s)"——本章就是那个"评估"时刻)。

为什么是"三条"(它是"拆的正当性"的检验):一条信号不够(连接数告急可能加缓存/分库就解决——16/14 章的活);三条同时到,说明问题不在某个模块,而在**"所有东西挤在一起"这件事本身**——架构问题的特征:单点修补无效,必须动结构(第 2 章演进循环:"原方案无法承受"才往前走——本章就是那个时刻)。

阶段 5 与第 2 章演进循环的对照(它是"循环"的第五次实例):第 2 章的循环——业务复杂度上升→原方案无法承受→新技术出现→新代价→…——案例走了五次:阶段 1→2(性能逼出缓存/CDN)、2→3(交易逼出机制)、3→4(协作逼出流程/交付体系)、4→5(规模逼出服务化)——每一次循环都是"原方案无法承受"(16 章单机缓存扛不住、17 章同步调用扛不住、20 章发布靠人扛不住、本章单仓库扛不住)——架构演进的规律:循环是永恒的,具体技术是暂时的(第 1 章:技术是决策的产物——决策跟着约束走,约束跟着业务走)。

第 1 章演进逻辑的正式收回(它是全书演进线的收官):第 1 章说"技术为什么变——不是潮流,是决策",第 2 章把它展开成演进循环(业务复杂度上升→原方案无法承受→新技术出现)——第 26 章是这条线的收官应用:单体从"最优解"(第 5 章)变成"无法承受",不是单体错了,是约束变了(第 5 章原话:"等到约束变了(第 26 章),它自然会动"——现在到了)。

26.2 拆不拆:★单体 vs 微服务——决策模型完整示例

三条信号到了,但"拆"是大工程(以月计),必须走一遍完整的九步——这是决策模型第 18 次应用,也是全书"决策记录"的收官示例(第 5 章说过:"第 16 章和第 26 章,你会看到案例的两次真实复查"——16 章复查了缓存,本章复查架构)。

拆分的"规模分级"(它是"拆多少"的谱系):小拆(模块独立成包/独立进程——不动数据库,复杂度最低);中拆(按业务边界拆服务+拆库——本章的选择);大拆(微服务全家桶+网格+多机房——云原生)——从低到高是一个谱系,不是非黑即白("微服务"不是唯一形态——"拆"有中间态:先小拆/中拆,条件到了再大拆(第 4 章复杂度预算:从低到高逐步买)。

这次决策的"参与人"(它是"30 人团队怎么决策"的提示):阶段 1 的九步是一个人走(5 章),本章的九步是团队走——技术负责人主持、各小组代表参与、决策记录公开(20 章协作轨道:重大技术决策要评审——架构决策是最大的评审)——九步模型从"个人思维工具"变成"团队决策流程"(第 5 章模型的收官:它服务过 1 个人,也服务 30 个人)。

★ 九步完整示例① 目标:让 30 人团队能独立演进、故障不扩散、数据不堵;② 约束:阶段 5——30 人团队、几十台机器(23 章 K8s 的演进条件已触发)、可观测性已就位(25 章)、交易已稳定(17 章)、扩容已到顶(24 章容量告警常响);③ 问题:要不要拆?拆成什么?④ 候选:A 维持单体(继续加人加机器);B 拆成几个服务(按业务边界);C 一步到位微服务+全套(服务网格/多机房/全面事件化);⑤ 维度:协作效率(发布互相踩)、故障隔离(爆炸半径)、数据瓶颈(连接数)、分布式复杂度(一致性/链路/运维——拆的代价);⑥ 打分:A 的协作和故障面继续恶化(加人加剧拥挤,加机器解决不了"挤在一个仓库");C 的复杂度超过阶段 5 的承受力(30 人团队运维服务网格+多机房,等于把 24/25 章的活放大十倍);B 均衡——按业务边界拆成 3-4 个服务(先拆支付:最独立、最敏感(故事线原话:先拆支付,再拆内容);再拆内容),基础设施先不升级(K8s 的决策记录翻出来:条件触发了吗?——几十台机器+几十容器,触发了,但"专职运维"还没有——再评估一次,结论:拆分优先,K8s 再等等(23 章决策记录的"增量重走"——第 5 章说大部分复查到第三步就能结束,这次也是);⑦ 选择B(按边界拆 3-4 个服务)⑧ 收益:发布解耦(支付服务独立发布——20 章的"发布互相踩"只发生在自己的仓库)、故障隔离(支付挂了内容还能跑——24 章爆炸半径从全站缩到一个服务)、数据拆开(连接数分到 3 个库);⑨ 代价:分布式复杂度全面到来(一致性/链路/部署/监控——26.4-26.6 全是它的偿还)。

图26-1 图稿占位
单体 vs 微服务
两种形态的协作与故障对比

这次决策与第 5 章"增量重走"的呼应(它是"复查"的方法论兑现):第 5 章说复查不是重走九步,是"先看约束哪条变了→再看候选有没有新选项→对照上次代价清单"——本章的复查实景:约束变了(30 人/几十机器/连接告急)、候选没变(还是单体 vs 微服务)、上次的代价清单("拆分成本高")——第三步:当年付的代价,现在还成立吗?——成立,所以拆——决策记录的收官用法:不是翻旧账,是照单复查

"拆"的决策记录(它是第 5 章那张表的收官行):决策:单体→按边界拆 3-4 服务;理由:协作/故障面/数据三条信号全到;代价:分布式复杂度;演进条件:K8s 等专职运维,全面微服务等团队 50+——这一行写进决策记录——"为什么拆"和"为什么只拆到这一步",都有答案(23 章"决策记录=将来质疑时的答案")。

26.3 怎么拆:边界与数据

拆的决策定了,怎么拆是更难的工程。核心一句话:边界是拆的全部秘密

业务边界 = 数据边界 = 服务边界(它是"拆的依据"):服务是业务边界的投影——第 6 章建模时划的边界(订单域/支付域/内容域/用户域),就是服务的边界——6 章说"边界清晰是拆服务的地基"(原话:"第 12 章分层、第 26 章拆服务都靠它")——本章兑现:拆服务的第一步不是写代码,是看第 6 章的表——哪些表属于支付域、哪些属于内容域,边界早已画好。

拆表的"迁移工程"(它是"以天计"的实景):支付表从单体库迁到支付库——步骤:① 建新库(支付库)② 双写(新旧库都写——过渡期)③ 校验(两库数据一致吗——17 章对账思想)④ 切换读(读走新库)⑤ 停旧——每一步都可回退(23 章回滚思想:数据迁移要能回——6 章说过"改表贵",拆表是它的放大版)——迁移期间的新问题:跨库事务没了(支付表和订单表分属两库——原来的一个事务变成两个库的操作——这正是 26.4 消息/事件要解决的问题(订单创建和支付扣款的一致性,从"一个事务"变成"事件串联+对账兜底"——17 章防线的迁移)。

先拆表,后拆代码(它是"拆的次序"):数据是服务最难拆的部分(代码可以一天改完,数据迁移以天计——6 章说过"拆服务的第一步往往就是拆表,以天计的工程")——拆的次序:① 先划数据边界(哪些表跟支付走)→ ② 数据独立(支付库分出来)→ ③ 代码再拆(支付模块独立成服务)→ 拆表在前,拆代码在后(数据拆不开,代码拆了也是假拆分——两个服务共用一个库,等于没拆)。

拆分与团队组织的关系(它是"拆服务也是拆人"的预告):服务边界往往对应团队边界——支付服务由支付小组负责——20 章说"流程是团队的函数",架构也是团队的函数:服务边界=团队边界,是最省心的分工(康威定律的通俗版:系统结构复制沟通结构)——拆服务不只为技术,也为组织:30 人分成 3 个小组各自负责一个服务,发布/评审/复盘都在组内闭环(20 章轨道的服务版)——"拆"的收益一半是技术(隔离),一半是组织(自治)

边界与第 27 章业务迭代的关系(它是"边界"的将来):27 章新需求(二手拍卖)来了——先问:它落在哪个边界里?(拍卖属于交易域——订单/支付服务的边界)——边界清晰的系统,新需求"放得进去"(6 章"演进有依据"的收官:建模时的边界,27 章的新功能,26 章的架构,三件事在同一张边界图上)——边界是全书的主线之一(6 章建模→12 章分层→26 章服务→27 章迭代:同一把尺子)。

服务边界的三条检验(它是"边界划对没有"的验收):独立演进(这个服务的改动,不需要别的服务配合发版吗?——需要,边界没划对);故障隔离(它挂了,别的服务受影响吗?——受影响,边界没划对);发布频率(它是不是"经常变、独立变"的模块?——是,就该拆——拆的优先级 = 变更频率 × 独立性:支付(独立+敏感)第一,内容(高频变更)第二)。

拆分与 18 章测试的关系(它是"拆了怎么验证"):拆之前要有"行为基线"——核心链路的测试(18 章金字塔)是拆分的保险:拆前全绿,拆后全绿=行为没变(22 章"最快失败点"思想:测试把"拆错了"挡在发布前)——没有测试兜底的拆分是盲拆(拆完不知道哪里变了——18 章"测试是承诺":拆分承诺"行为不变",测试证明它)。

拆的三个坑(它是"怎么拆"的注意事项):假拆分(库没拆,代码拆了——两个服务共用一个库,等于没拆(26.3 说过);过度拆分(一个服务 500 行也拆——服务有运维成本,粒度越小成本越高——第 4 章复杂度预算);拆完不管(拆了没有对应的部署/监控/链路配套——可观测性"先有眼睛再动手术")——坑的共性:拆是结构动作,配套必须跟上(21-25 章的全部体系,每个服务一份)。

拆的顺序:先支付(它是"最难的先动"):先拆支付(故事线原话)——为什么?最独立(支付只和订单说话)、最敏感(17 章的钱不能错——拆它的时候最谨慎,机制最全:事务/幂等/对账都在)、最有说服力(拆完立刻见效:支付发布不再影响全站)——"第一个服务"的选择标准:风险最高+收益最明显的那一个(19 章"按风险排,不按热门程度排"的架构版)。

26.4 服务间通信:从调用到消息

服务拆开了,服务之间怎么说话?这是拆分后第一个迎面而来的问题(第 12 章预告过:业务层编排角色在服务拆分后放大为"服务调用")。

服务间联调(15 章兑现——"联调的另一个名字叫集成"):15 章说联调=集成,服务间联调是同一问题的更大规模版本——服务间对接的"契约"问题:A 以为 B 的接口返回 {status: 1},B 改成了 {status: "ok"}——联调三问(15 章:断在哪一段/卡在哪一层/数据卡在哪个环节)服务间照样用,加上契约测试(18 章的接口测试,服务间版本:消费方和提供方都跑同一份契约——"契约"从前后端约定(7 章)升级为服务间约定)——15 章的联调经验没有浪费:服务越多,联调的规模越大,越需要契约

同步调用:服务间接口(7 章兑现):服务之间的调用,就是第 7 章的接口——服务 A 调服务 B 的 REST 接口(7 章说"服务之间的调用走的就是这些接口"——兑现)——接口的一切规矩都适用:版本化(向后兼容规则表:只加不改不删——7 章原话,拆分时接口改造还要用一遍)、request_id 贯穿(7 章)、超时(24 章)——"接口设计"的读者,从"前端"扩展到"另一个服务"(7 章原话:接口是系统里被读得最多的"代码")。

RPC 的演进条件(7 章兑现——"服务间高频强类型调用"):服务间调用多了以后,REST 的"语义表达力有限"开始痛(7 章记录过:REST vs RPC 的演进条件=服务间高频强类型调用)——阶段 5 的答案:REST 先用(3-4 个服务,调用量还没到"高频强类型"),演进条件记着(服务再多再密,评估 gRPC)——REST vs RPC 不是"谁更好",是"调用密度到了没有"(7 章决策记录的再确认)。

服务发现的"问题"(它是"服务多了之后的下一环",一句):服务多了以后,"服务 A 的地址是什么"成了问题(机器会变、会加)——服务发现(service discovery):服务注册自己的地址,调用方按名字查——阶段 5 的答案:轻量处理(配置+网关统一入口——地址变化只在网关一处改),服务注册中心是服务再多之后的工具(演进条件:服务超过 10 个再评估——第 4 章复杂度预算的又一次)。

网关(浅提)(它是"入口"的演进):服务多了以后,入口也要变——原来浏览器直接调单体(一个域名),现在调 3-4 个服务——网关(API Gateway):统一入口,把请求路由到对应服务(12 章中间件的"服务版"——鉴权/限流/日志在网关做一次,所有服务共享)——阶段 5 的答案:轻量网关(路由+鉴权+限流——12/24 章机制的入口聚合),网关本身也是服务(它挂了全挂——24 章:网关要冗余)——架构演进中"入口"的演进:单体接口 → 网关路由(7 章接口设计的地图,入口换了。

无状态 = 服务设计原则(10 章兑现——"以'服务设计原则'的身份再出现"):10 章埋的伏笔——"无状态是服务拆分的前提"——拆了之后,每个服务的接口都必须是无状态的(请求自带完整信息,不依赖"服务器记着我")——有状态的接口一拆就乱(会话粘在某个服务实例上=扩展卡住——10 章原话)——第 13 章 Session 入 Redis 的决策,在服务化时代收成"服务设计铁律"(Session 共享存储让服务实例全部无状态)。

JWT 一次性服务凭证(13 章兑现——"JWT 的舒适区"):服务 A 调服务 B,怎么证明"我是服务 A"?——JWT 的用武之地到了(13 章说:服务间调用用 JWT 做一次性凭证——凭证短命、不需要吊销,正是 JWT 的舒适区)——13 章"不上 JWT"的决策记录翻出来:演进条件(多服务)触发了——不是"JWT 更先进",是"场景到了"(13 章决策的收官)。

服务间同步调用的"防线"(它是"调用变成接口"后的新规矩):服务 A 调服务 B——超时必须有(24 章:没有超时的调用=线程被占满)、重试要克制(24 章:重试是故障放大器——熔断配合)、降级要想好(B 挂了 A 给什么——24 章)——24 章的高可用机制,从"系统内部"变成"服务之间"——拆分的代价:原来一个进程内的函数调用(挂了整个进程一起挂,反而简单),现在变成网络调用(要防网络的一切不可靠——10 章"网络不可靠"的正式展开)

异步链回收:从调用到消息(17 章认知链的完整回收——本章的另一个主线):第 17 章埋的链——单体→同步调用→问题出现→异步→消息→服务拆分→事件驱动——前四环在第 17 章走完(支付回调不得不异步→任务表→阶段 3 末引入消息队列),本章走完最后三环:服务拆分(26.2-26.3)→ 服务间通信(本章)→ 事件驱动(26.5)——服务拆分的必然结果:同步调用变慢依赖,慢依赖逼出消息——17 章说"异步化的代价清单里排查复杂度最容易被低估"——25 章的链路追踪已经把这笔账还了(17 章说"异步化之后必须有可观测性兜底"——兑现)。

异步链的"17 章 → 26 章"对照(它是"回收"的实感):17 章:任务表(订单服务的表,定时扫描)、回调不得不异步、同步→异步的认知——26 章:消息队列(服务之间的事件通道)、MQ 替代任务表(17 章预告"阶段 3 末引入消息队列"——兑现)、异步→消息→事件驱动的完整链——17 章是"同一个进程里怎么异步",26 章是"跨服务怎么异步"——认知链回收的含义:17 章的问题没消失,只是升级了规模(同一套答案,更大的棋盘)。

★ 消息队列——九步决策第 19 次(决策记录又添一行):① 目标:服务间解耦 + 削峰(支付回调/通知/扣库存这些"不着急但费时"的动作);② 约束:3-4 个服务、已有任务表(17 章)、链路已通(25 章);③ 问题:服务间"慢动作"用什么传?④ 候选:A 同步调用(继续 REST);B 消息队列(MQ);C 维持任务表;⑤ 维度:解耦度、削峰能力、一致性代价、运维成本;⑥ 打分:A 的同步调用在"回调/通知"场景会拖垮调用方(24 章超时/熔断只能止损不能根治);C 的任务表撑过日订单千级(17 章),但服务化之后"任务表放哪个服务"成了问题(订单服务的任务表调支付服务=表还是耦合的);B 的 MQ 让"发消息"和"消费消息"完全解耦(订单服务只管发事件,支付/通知服务各自消费——削峰:高峰期消息排队慢慢消费);⑦ 选择B(引入消息队列)⑧ 收益:解耦+削峰+17 章异步链的最后一块拼图;⑨ 代价:最终一致性(17 章异步的代价,现在放大了:消息可能丢/重复/乱序——17 章的防线全要搬到消息层:幂等消费/重试/对账)。

MQ 与 17 章任务表的"交接"(它是"迁移"的实景):阶段 3 的任务表(订单服务的表)怎么退役?——新逻辑走 MQ(发事件),任务表只处理存量任务——双跑过渡(任务表和新 MQ 同时跑,对账一致后再停任务表——17 章对账思想)——"老机制退役"的流程和"新机制上线"一样要谨慎(22 章回滚思想:退役也要可回退)。

消息队列的"落地形态"(它是"MQ 长什么样"的一瞥):生产端(服务把事件发到主题——一行代码);队列(消息暂存——高峰期堆积,消费端慢慢处理——削峰);消费端(订阅主题的服务拉消息处理——处理失败重试/进死信队列(重试 N 次还失败的消息,人工处理))——三个角色,对应 17 章三个问题:生产端=任务表(17 章)的升级、队列=削峰(16 章缓存同款哲学)、消费端=任务执行(17 章 worker)——MQ 不是新世界,是任务表的工程化(17 章认知链的实物形态)。

26.5 事件驱动:状态与事件的解耦

消息队列带来一个新视角:服务之间传的不再是"请求",是"事件"——事件驱动(event-driven)是异步链的终点。

图26-2 图稿占位
事件驱动
服务间通过事件解耦

事件驱动的"案例实景"(它是"事件长什么样"的业务侧):下单流程的事件链——用户下单 → 订单服务发 order.created → 支付服务收(等支付)→ 用户支付 → 支付服务发 order.paid → 通知服务收(发短信)、库存服务收(扣库存)、统计服务收(记数据)——每个服务只听自己关心的(通知服务不关心库存)——加一个新服务(推荐/风控):订阅事件即可,不用改任何已有服务——这就是 26.4 决策的落地形态(MQ 的实物)。

事件与"命令"的对比再深一层(它是"为什么事件能解耦"的机制):命令请支付订单 123)——调用方要知道"谁来做"(支付服务)、"怎么做"(接口是什么);事件订单 123 已创建)——发布方只管"发生了什么"——命令耦合两件事(对象+动作),事件只耦合一件事(事实本身)——这就是"加功能不加耦合"的来源:新服务只需要"听得懂事实",不需要"被任何人记住"。

事件 = 事实(它是"事件驱动"的核心):事件是"已经发生的事"——order.created(订单创建了)、order.paid(订单付款了)——服务发布事件,不关心谁在听(支付服务发了 order.paid,通知服务在听、库存服务在听、统计服务也在听——发的人不知道也不关心)——与同步调用的本质区别:同步是"叫别人做事"(命令),事件是"宣告发生了什么"(事实)——命令耦合"谁来做",事实只描述"发生了什么"。

状态变化 = 事件(17 章状态机的呼应):第 17 章的订单状态机(CREATED→PAID→SHIPPED…)——每一次状态转移,就是一个事件——状态机是事件驱动的内部视角(一个服务内部用状态机管自己的状态,状态变化对外发布成事件——其他服务通过事件感知它的状态)——17 章的状态机,26 章升级成"事件源"(状态是结果,事件是过程——事件流可以重建状态:把 order.created/paid 重放一遍,订单状态就回来了——对账(17 章)也是这么做的)。

事件的"命名"与"格式"(它是"事件也是接口"的提示):事件要命名规范order.paid——主体.动作,像接口一样有约定——7 章接口设计思想的事件版);格式要版本化(事件字段会变——消费者按旧格式解析会崩——7 章"只加不改不删"的规则表,事件同样适用)——事件是服务之间的新"接口"(7 章接口的设计纪律,在事件层原样继承)。

发布订阅(它是"谁在听"的机制):事件不发给"某个服务",发给"主题"(order 主题)——订阅者自己决定听不听——新增一个服务(统计/推荐),不用改任何已有服务,订阅主题即可——"加功能不加耦合"是事件驱动的最大红利(26.3 独立演进标准的极致版:新服务零成本接入)。

事件流与 25 章可观测性的关系(它是"事件=事实"的运维价值):事件流本身就是最干净的审计日志(19 章审计:谁对订单做了什么——事件流回答)——事件是业务的可观测性(25 章的日志是系统的,事件是业务的)——排障时重放事件流=回放业务历史(25 章"回放能力"的业务版:25 章回放请求,本章回放业务)。

最终一致性(它是"事件驱动的代价"——17 章异步代价的收官):事件是异步的,状态最终一致——下单后订单显示"待支付",支付事件到了才变"已支付"——用户看到的短暂不一致是设计的一部分(17 章说过的代价,现在成为整个系统的默认)——兜底机制(17 章防线全套搬家)幂等消费(同一个事件消费两次结果一样——17 章幂等)、重试(消费失败重试——17 章重试)、对账(定期核对事件流与数据库——17 章对账)——"事件驱动的系统,防线和交易系统一样厚"(17 章的五道防线,在消息层原样重建)。

事件驱动的"对账"深化(它是"防线重建"的最后一笔):事件驱动系统里,对账(17 章)升级为"事件对账"——定期把事件流和数据库状态对比(这个订单发出了 paid 事件,但数据库里还是待支付?——事件丢了)——对账从"交易系统的防线"变成"整个分布式系统的日常"(17 章说对账是最后一道防线——26 章它是整个架构的底线)——"分布式系统的所有防线,最终都落到对账"(人工兜底永远在——17 章到 26 章,不变的最后一环)。

事件驱动与 26.3 边界的关系(它是"边界"引导词的收官):26.3 说边界=数据边界——事件驱动是"数据边界的桥梁":数据拆开了(分库),但业务需要跨库的一致性——事件把"拆开的数据"在逻辑上重新连起来(订单库的 paid 事件,让支付库和内容库各自更新——物理上分开,逻辑上串联)——边界的两面:拆的时候靠边界(26.3),连的时候也靠边界(事件的主题=边界的对外接口)——"边界"引导词在这里闭环。

事件驱动与"状态到处飞"(它是"为什么要事件"的业务侧):单体时代状态在数据库里(一张订单表);拆了之后状态分散在 3 个库里(订单库/支付库/内容库)——"状态到处飞"的解法不是"再合并",是"用事件把状态串起来"——每个服务的状态变化都发事件,全系统的状态由事件流串联(骨架原话:"状态到处飞→事件驱动")。

26.6 拆分后的系统:架构演进全景

拆完 3-4 个服务后,把整个演进过程收成一张全景图(图 26-3,一级图 #8 首现):

阶段 1:单体(一台机器,全部代码)——第 4-15 章
→ 代价:流量大了扛不住(16 章)
阶段 2-3:单体 + 多实例/缓存/交易机制——第 16-19 章
→ 代价:8 人改一个仓库、发布互相踩、故障面全站(20 章)
阶段 4:交付体系 + 高可用 + 可观测性——第 20-25 章
→ 代价:数据库连接数告急、拆的时机成熟
阶段 5:按边界拆服务 + 消息 + 事件驱动——本章
图26-3 一级#8首现 图稿占位
演进图放大
单体→服务化→云原生

图 26-3 与第 3 章总架构的关系(它是"同一张地图"的具象):第 3 章画的总架构(浏览器/网络/服务端/数据层)——拿图 26-3 的四格对照:每一格都是第 3 章那张图,变的只是"服务端"一格内部的画法(一个方块→几个方块)——第 1 章说"不断放大同一张地图"——架构演进就是这张地图被放大的四次(第 8 章浏览器放大、第 12 章服务端放大、第 14 章数据层放大、第 26 章服务端再放大)。

演进是同一张地图的放大,不是推倒重来(它是图 26-3 的 takeaway):第 3 章的总架构图(浏览器/网络/服务端/数据层)——阶段 5 的架构还是这张图,只是"服务端"一格从"一个方块"变成"几个方块"——每一章的技术都还在:单体里的分层(12 章)变成服务内的分层;接口(7 章)变成服务间接口;缓存/限流/熔断(16/24 章)每个服务一份——演进不丢资产,只改结构(第 2 章演进循环:不是革命,是重组)。

拆的收益兑现(它是"为什么拆"的回看):拆完第一个服务(支付),三个立刻可见的变化——发布解耦(支付小组自己发版,不再排队——20 章的发布互相踩少了一大半);故障隔离(支付服务挂了,内容社区照常——24 章爆炸半径的实感);数据分库(连接数告急缓解——14 章信号消失)——三条信号(26.1)各有一个对应答案(协作→发布解耦、故障面→故障隔离、数据→分库)——拆的正当性用结果检验:信号消失=拆对了;信号还在=边界划错了(26.3 的检验标准)。

拆分与 24 章高可用的关系(它是"隔离与冗余"的服务版):24 章的机制(冗余/限流/熔断)每个服务一份——但服务化带来新维度:故障隔离(支付挂了内容照常——24 章"爆炸半径"的架构级实现)和级联故障(服务 A 调 B,B 挂了 A 也危险——24 章熔断降级的服务版)——拆分的收益(隔离)和代价(级联)是同一枚硬币(24 章机制在服务之间重新上岗:每个服务有自己的熔断,链路上一环断了不拖垮全链)。

拆分与可观测性的关系("先有眼睛再动手术"的兑现):拆之前——25 章的体系(日志/监控/链路)必须在:拆了之后,一次下单跨 3 个服务——没有链路,排障回到 26.1 的"黑盒"(第一次值班的场景,跨服务版)——拆分清单里的第一项不是代码,是观测(链路打通/告警分服务/日志集中——可观测性的活 × 服务数)——"拆"的验收标准之一:拆完当天,跨服务排障是通的(25 章"看得见"在服务化时代的含义)。

拆分与 21 章制品的呼应("每个服务自己的制品"兑现):21 章预告"内容服务一个制品、订单服务一个制品"——兑现:3-4 个服务 = 3-4 条构建链(22 章流水线 ×3-4)——制品的"原子单位"从"应用"变成"服务"(21 章说"制品是部署的原子单位"——服务化时代,原子单位是服务——第 21 章的"构建一次处处运行",在微服务时代变成"每个服务各自构建、各自部署"(21 章原话兑现)。

拆分与 23 章部署的呼应("部署形态跟着架构长"):21-23 章建的交付体系(制品/流水线/部署)每个服务一套——"部署"从"发布一个应用"变成"编排几个服务"(发布顺序/兼容性/回滚联动——23 章发布单的服务版)——架构演进的上游是代码,下游是交付(21-25 章体系是拆分的"基础设施"——26.1 说过:20-25 章是 26 章的预埋)。

拆分后的新问题(它是"拆的代价"清单——全部在前面章节有答案):

  • 构建与部署(21/22/23 章兑现):每个服务有自己的制品和构建链(21 章预告"每个服务各自构建、各自部署"——兑现)——流水线从一条变成 3-4 条(22 章);部署从"一个制品"变成"几个制品的编排"(23 章——发布单从"部署一个"变成"协调几个");
  • 排障(25 章兑现):拆服务的前提是可观测性就位(25.7 说过"先有眼睛再动手术")——跨服务的请求必须靠链路追踪(25 章 trace 跨进程——17 章 trace_id 贯穿的完整版);
  • 配置管理(23 章兑现):服务多了,环境变量散在几个服务里——配置中心(23.5 说的"配置多了再上")的时机到了
  • 发布方式的升级(23 章兑现——"第 26 章灰度会深化"):23 章说金丝雀是"高风险发布"的选项,第 26 章深化——服务化时代发布更频繁(每个服务独立发),灰度成为标配:新版本先放 1% 流量 → 观察监控(25 章:错误率/延迟)→ 逐步放量 → 全量——23 章的"健康了才放上去",服务化时代变成"健康曲线才放量"(发布与监控的闭环:23 章发布、25 章看着、本章放量决策)。

K8s(23 章兑现):几十个容器+机器到规模——23 章决策记录的演进条件(十几台/几十容器)触发了——但"专职运维"还没有——增量重走(5 章):结论还是"再等等"(K8s 的价值=编排自动化,30 人团队没专职运维,编排的自动化收益>学习运维成本之前,Docker 够用——23 章的"业务没到就别上",在规模到了之后依然成立:不是"规模到了就上",是"规模到了+能力到了才上")。

数据层的演进(14 章兑现——"读写分离/分库分表是 SQL 体系内的扩容"):拆分时数据层同步演进——读写分离(14 章预告的主从,24 章已用:读多写少摊读);分库(26.3 拆表:连接数摊开);分表(数据量更大的表按维度拆——14 章说"比直接换 NoSQL 常见得多"——兑现)——数据层演进的原则:先在 SQL 体系内扩容(读分离→分库→分表),换 NoSQL 是最后手段(14 章"数据量没到别折腾"第三次检验:到了再说)——14 章的数据量直觉,在阶段 5 兑现成数据层演进路线图

云原生(浅提)(它是演进链的"远处"):容器+编排+微服务+DevOps 全套,是云原生(cloud native)——阶段 5 只走到"服务化+消息",云原生是"团队 50+ 之后"的地图(决策记录里的演进条件)——知道它存在,知道它在哪一环(第 1 章"名词地图"的反面:名词都知道,位置才重要)。

26.7 什么时候其实不需要微服务

本章的边界章:拆的对面——不拆。微服务的名声很大(第 1 章"名词地图"的巅峰),但它是有真实代价的架构(骨架原话:"什么时候其实不需要微服务")。

微服务的"名声"从哪来(它是"为什么大家想拆"的心理侧):微服务的名声来自大厂的宣传(Netflix/Amazon 的故事——但那是万人团队的地图);名词地图的陷阱(第 1 章:只记住了名词没记住条件)——"大厂用微服务"和"我们该用微服务"之间,差着团队规模和业务复杂度两个变量(第 5 章"看到还在用单体的公司先别急着笑"——同一句话的镜像版:看到用微服务的也别急着羡慕)。

微服务的真实代价(它是"不拆的正当性"):分布式一致性(26.5 的最终一致性——所有交易逻辑(17 章)都要在消息层重建防线);链路与排障(25 章链路是必需品不是可选项——观测成本 × 服务数);运维复杂度(3 个服务=3 套部署/日志/监控/告警——24/25 章的活 ×3);团队心智(30 人团队每人要懂"跨服务"的思维方式)——每一项都是第 4 章复杂度预算的花费(微服务=复杂度预算的大额支出)。

微服务"成功案例"的真相(它是"别被宣传骗"的清醒剂):大厂能微服务,是因为有几百人团队和专职平台组(容器平台/监控平台/发布平台——23/24/25 章的东西各有一个团队维护)——30 人团队没有平台组:微服务的运维债只能自己扛——"微服务成功"的案例里,成功的是"组织+平台+流程",不只是架构(第 20 章"流程是团队的函数"——微服务是"组织能力"的函数)。

架构演进与工程师成长的关系(它是"为什么学架构"的答案):1-3 年工程师常见的困惑:没做过微服务,是不是落后了?——本章的答案:你经历过"单体→拆"的完整过程,比直接跳进微服务的人更懂架构——因为你知道"为什么拆"(26.1 信号)、"怎么拆"(26.3 边界)、"拆的代价"(26.7 复杂度)——直接学微服务的人只有名词,你有决策(第 1 章:名词地图 vs 决策地图——本章是决策地图的巅峰实例)——架构能力 = 拆过 + 知道不拆的边界

判断标准(它是"拆不拆"的体检表):团队规模(<10 人:拆了没人维护——20 章"流程是团队的函数",架构也是团队的函数);业务复杂度(业务边界清晰吗?——边界都不清,拆了更乱);发布频率(一周一次发版都难?先解决流程(22 章),不是架构);数据瓶颈(单库真的到顶了吗?——14 章数据量直觉:亿级行或连接数告急才是信号)——四问全是"否",就还不该拆(第 4 章"复杂度没到别上"的收官)。

微服务与"团队 8 人"的回顾(它是"没到就别上"的实例):23 章 8 人团队不上 K8s(决策 0024)——如果阶段 4 就拆服务会怎样?——8 人分 3 个服务=每个服务 2-3 人=没人有空做 24/25 章的运维(告警/演练/值班)——拆早了:机制空转(有服务没有配套,拆=负收益)——26 章拆的时机:团队 30 人+配套已建(20-25 章全就位)——"拆"和"上 K8s"一样,都是"条件到了才动"(第 4 章复杂度预算:不是"能拆"就拆,是"拆了有人维护"才拆)。

"先单后拆"与 23 章"能承受就停"的呼应(它是"不上系列"的收官):23 章说"业务没到就别上"(K8s)、14 章说"数据量没到别折腾"(NoSQL)、13 章说"场景没到不上"(JWT)——本章说"问题没到别拆"(微服务)——"不上"系列的最后一块拼图——本书的架构观一句话:让问题决定技术,不让名词决定技术(第 1 章地图的收官宣言)。

"先单后拆"(它是本书架构观的一句话总结):单体不是耻辱,是起点——绝大多数系统的正确路径是"先单体跑通,被问题逼着拆"——主动拆(没到就拆)和被动拆(到了不拆)一样危险——架构演进是"问题驱动的",不是"名词驱动的"(第 1 章地图的收官:技术是决策的产物,不是潮流)。

还债的"顺手"原则(它是"还债不翻车"的技巧):拆分时顺手还债,但别在拆的时候大改特改(拆是结构动作,改是行为动作——一起做=事故加倍)——还债的次序:先拆后改(结构稳了再优化行为——22 章"失败前置"思想)——每笔债的"还法"不一样:一坨代码(12 章)在拆的时候顺手分层;缺的测试(18 章)在服务独立后补(测试跟着服务走);没写的文档(20 章)在交接时补——拆一次,还一批;不是拆一次,改一切(第 4 章复杂度预算:拆的复杂度预算,不包含大改)。

"拆"的心理学(它是"为什么团队不敢拆"的现实):拆是大工程(按月计),团队常见的两种病——拆的拖延症(信号到了不敢拆:怕分布式复杂度——26.7 的代价清单看着吓人)和拆的冲动症(信号没到就拆:追逐微服务名词——26.7 的名声陷阱)——九步决策(26.2)就是治这两种病的药:强迫你把信号(26.1)、代价(26.7)、条件(② 约束)摆到桌面上——"决策模型"的最终价值:让"拆不拆"从情绪变成论证(第 5 章模型的收官宣言)。

"重构"与"演进"的边界(它是"拆"和"改"的区分):重构(refactoring):不改行为只改结构(12 章分层的活);演进(architecture evolution):改结构也改边界(本章的活)——重构是演进的准备动作(拆之前先重构:把支付相关代码聚到一起——"为拆分做的重构")——拆分流程:先重构聚拢(几周)→ 再拆服务(几周)→ 后迁移数据(几周)——急不得:架构演进是按月计的事,按周计就会翻车(第 4 章"改表贵"的架构版:改架构更贵)。

技术债的还债日(20 章兑现——"第 26 章架构演进,很大一部分是在还第 4-20 章欠的债"):拆分时你会遇到:早期为了快写的一坨代码(12 章)、为省事绕过的边界(6 章)、没补的测试(18 章)、没写的文档(20 章)——拆分是"顺手还债"的最佳时机(代码要动、结构要变——债是演进的一部分,不是失败——20 章原话:"技术债的积累是演进的前奏,不是失败")。

26.8 本章对应表

业务诉求技术选择为什么代价/取舍
30 人改一个仓库发布互相踩★拆服务(决策第 18 次)三条信号全到:协作/故障面/数据分布式复杂度全面到来
数据库连接数告急拆库(数据边界先行)6 章边界=拆表依据;先拆表后拆代码跨库事务没了(17 章防线重建)
服务之间怎么说话同步 API → 消息队列(★决策第 19 次)慢依赖逼出消息;削峰解耦最终一致性(幂等/重试/对账兜底)
状态分散在多个库事件驱动(图 26-2)状态变化=事件;发布订阅零耦合事件防线要重建(17 章全套)
服务间认证JWT 一次性凭证13 章演进条件触发(舒适区)凭证短命(不需要吊销)
拆了怎么排障链路追踪 + 每服务制品/流水线可观测性就位是拆分前提(25 章);21/22 章兑现观测/部署成本 × 服务数
什么时候不拆边界四问(团队/复杂度/频率/数据)微服务是复杂度预算的大额支出"业务没到就别上"收官

本章对两类读者的收益(与前几章同款):后端读者——拆分/消息/事件是后端架构的主场;前端读者——服务化时代前端的接口更多(网关/多服务),前端框架(第 9 章)和接口设计(第 7 章)的功底更值钱——两类读者共同的收获:架构演进第一次有了完整的决策链(信号→决策→边界→通信→事件→边界),也第一次看清"大厂架构"的适用条件(26.7)——"架构"从名词(微服务/K8s/云原生)变成决策(什么时候、为什么、凭什么)(第 1 章地图的收官)。

每一行都在本章正文里有完整论证:信号在 26.1,决策在 26.2,边界在 26.3,通信在 26.4,事件在 26.5,全景在 26.6,边界在 26.7。先看"业务诉求"列——这一章的每一行,都是"挤在一起"的代价逼出来的

本章小结

架构演进速查(第六部分回指本章时翻回这里):

  1. 三条信号线:协作(发布互相踩)/故障面(全站风险)/数据(连接数告急)——三条全到才拆;
  2. ★单体 vs 微服务(决策第 18 次):按边界拆 3-4 服务(先支付再内容);K8s 再等等(23 章记录翻出);
  3. 边界=拆的全部秘密:业务边界=数据边界=服务边界(6 章兑现);先拆表后拆代码;拆的优先级=变更频率×独立性;
  4. 服务间通信:REST(7 章)→ RPC 演进条件;无状态=服务铁律(10 章);JWT 服务凭证(13 章);消息队列(★决策第 19 次);
  5. 事件驱动(图 26-2):事件=事实;状态变化=事件(17 章状态机升级);发布订阅零耦合;最终一致性(17 章防线重建:幂等/重试/对账);
  6. 演进全景(图 26-3 一级#8):同一张地图的放大,不是推倒重来——每章技术都还在;
  7. 什么时候不拆:四问体检表(团队/复杂度/频率/数据);"先单后拆";技术债还债日(20 章)。 (七条速查对应本章结构:1 是"信号",2-3 是"决策与边界",4-5 是"通信与事件",6 是"全景",7 是"边界"——第 27 章业务迭代回指时重点翻 3(边界)和 7(不拆的标准);第 28 章成长路径回指时重点翻 7(架构能力=拆过+知道不拆)。)

  • 拆是被逼出来的,不是设计出来的:三条信号(协作/故障面/数据)全到,才轮到拆(第 2 章循环收官);
  • 边界是拆的全部秘密:业务边界=数据边界=服务边界(6 章的准备金,本章兑现);
  • 异步链完整回收:单体→同步→异步→消息→服务拆分→事件驱动(17 章引出,本章走完);
  • 微服务不是标配:四问体检表——团队/复杂度/频率/数据;"先单后拆";"业务没到就别上"收官
  • 演进是同一张地图的放大:不丢资产,只改结构(第 1 章地图的收官)。

下一章

架构演进不是终点——业务还在继续。阶段 5 之后,新需求来了:社区要加"二手拍卖"。不是从零开始,而是在这个已经长成 3-4 个服务的系统上叠加——需求分析、技术决策、设计、开发、测试、发布、监控……第 4-25 章的全部动作,再来一遍。

第 27 章,业务迭代的循环:在已有系统上叠加新需求——全书地图走第二遍(对照第 1 章地图),也是"演进"的最后一块拼图。