跳到主要内容

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

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

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

读完本章,要建立三条认知——它们合起来回答一个问题:什么时候该拆服务,以及怎么拆。

  1. 拆服务是被逼出来的,不是设计出来的:三条信号——协作撞车、故障面扩大、数据墙撞墙——到了,才轮到"拆"。拆是演进循环的最后一站:不是某天早上拍脑袋的架构升级,而是单体内功再也撑不住时的自然结果;
  2. 边界是拆的全部秘密:业务边界等于数据边界,等于服务边界。服务拆得好不好,早在建模那天就定了型——建模时的边界感,是架构演进的准备金;
  3. 微服务不是标配:一致性、链路、运维——分布式的复杂度是实打实的债。"业务没到就别上",这条原则在编排那章拦过 K8s,本章它再拦一次微服务。

26.1 单体撑不住了:三条信号​

阶段 5,团队从 8 人长到 30 人,业务从"社区加交易"长成"社区加交易加拍卖加活动"。数字先摆出来:代码库 30 万行,部署一次全量回归要两天。单体架构的痛点,到这个阶段集中爆发。三个最疼的:发布互相踩——30 人改一个仓库,发布排队成了日常,合并冲突天天见;故障影响面全站——任何一个模块的 bug 都是全站事故,高可用机制再好,也架不住"发布即全站风险";数据库连接数告急——500 连接打满,加应用实例也白搭,因为连接全堵在数据库这一个口上。

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

把信号整理成清单,拆的正当性就建立在上面。协作信号:改代码互相踩,发布排队、合并冲突、上线要排期。故障面信号:一个模块出错全站遭殃,故障的爆炸半径管不住。数据信号:单库连接数告急,查询开始吃力——当年立过规矩,亿级行或单库连接数告急,就是该动结构的信号。还有一条补充,来自容量那一章的预告:垂直扩容到顶,机器换到最大还扛不住;水平扩容到顶,加实例反而把数据库连接打满。扩容的天花板,就是架构的天花板。四条信号,前三条是主角,第四条是佐证。

但在拆之前,要先公平地说一句单体的好。单体不是一无是处——阶段 1 到 4 全靠它活了下来:一个人能写全栈,一次部署全站更新,排障只看一个进程,事务天然跨表。单体的三个优点:简单、一致、好排障。而这些优点意味着什么?意味着拆的代价清单里每一项,都是单体白送的好处——后面会展开说,这些好处,拆了就没了。所以公平的结论是:不是单体变差了,是阶段 5 的规模,让单体的好不够用了。

再看阶段 4 到阶段 5 之间隔着什么。阶段 4 建的那套体系——工作流、流水线、容器化部署、高可用、可观测性——它们不是"阶段 4 的私产",是阶段 5 拆分的前提。没有流水线,拆了没法独立发布;没有可观测性,拆了没法排障;没有容器,拆了部署一团乱。回头一看,前面六章不是在修阶段 4 的锅,是在给阶段 5 预埋——每一轮演进都在为下一轮铺路。而当初部署章记下的演进条件,"先 Docker,后评估编排",本章就是那个评估时刻。

为什么是三条信号才算数?因为一条不够。连接数告急,加缓存、分库或许就解决了——那是局部修补。三条同时到,说明问题不在任何单个模块,而在"所有东西挤在一起"这件事本身。架构问题的特征正在于此:单点修补无效,必须动结构。原方案无法承受,才往前走——本章就是循环逼到头的那个时刻。

把这条线放回全书的演进脉络——循环又一次转到头:业务复杂度上升,原方案无法承受,新技术出现,新代价出现,再走一轮。案例中:性能逼出缓存和 CDN,交易逼出幂等和状态机,协作逼出流程和交付体系,规模逼出——服务化。每一轮都是同一个剧本,只是"扛不住"的主角在换:先是单机缓存扛不住,然后是同步调用,然后是发布靠人,现在是单仓库。架构演进的规律可以收进一句话:循环是永恒的,具体技术是暂时的——技术是决策的产物,决策跟着约束走,约束跟着业务走。

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

三条信号到了,但"拆"是以月计的大工程,必须正经理一遍完整的决策流程——这是决策模型的收官应用,也是全书"决策记录"的最后一次完整亮相:当年选型时预告过两次真实复查,一次复查了缓存,这一次,复查架构本身。

先立一个谱系,拆不是非黑即白。小拆:模块独立成包、独立进程,不动数据库,复杂度最低。中拆:按业务边界拆服务、拆库——本章的选择。大拆:微服务全家桶,服务网格、多机房、全面事件化——云原生的完全体。三档是一个谱系,从低到高逐步买,复杂度预算的原则在此适用:微服务不是唯一形态,拆有中间态,条件到了哪档,买到哪档。

这次决策还有一个新变量:参与人。最早的九步是一个人走的思维工具,这次是三十个人的团队决策——技术负责人主持,各小组代表参与,决策记录公开。重大技术决策要评审,架构决策是最大的评审。走到这里,决策模型完成了从个人工具到团队流程的蜕变:它服务过一个人,也服务三十个人。

使用决策模型。① 目标:让 30 人团队能独立演进、故障不扩散、数据不堵。② 约束:阶段 5 的现状——30 人团队、几十台机器、可观测性已就位、交易已稳定、扩容已到顶,容量告警常响。③ 问题:要不要拆?拆成什么?④ 候选:A,维持单体,继续加人加机器;B,按业务边界拆成几个服务;C,一步到位,微服务加全套基础设施。⑤ 维度:协作效率——发布还互相踩吗;故障隔离——爆炸半径多大;数据瓶颈——连接数堵不堵;分布式复杂度——一致性、链路、运维,拆的代价全在这。⑥ 打分:A 两条绝症——加人加剧拥挤,加机器解决不了"挤在一个仓库",协作和故障面只会继续恶化。C 的复杂度超出承受力——30 人团队运维服务网格加多机房,等于把高可用和可观测性的工作量放大十倍,当年玩不起的东西,现在也玩不起。B 均衡:按业务边界拆成三到四个服务。拆的顺序有讲究:先拆支付——最独立、最敏感,故事线里写着的原话;再拆内容。基础设施先不升级:K8s 的决策记录翻出来复查——几十台机器、几十容器,条件触发了一半,但专职运维还没有。增量重走一遍,结论:拆分优先,K8s 再等等。⑦ 选择:B,按边界拆三到四个服务。⑧ 收益:发布解耦——支付服务独立发布,"发布互相踩"从此只发生在自己的仓库;故障隔离——支付挂了内容还能跑,爆炸半径从全站缩到一个服务;数据拆开——连接数分到三个库。⑨ 代价:分布式复杂度全面到来——一致性、链路、部署、监控,后面几节全是它的偿还。

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

这次复查本身就是方法论的兑现。复查不是重走九步,是三件事:先看约束哪条变了——变了,30 人、几十台机器、连接告急;再看候选有没有新选项——没有,还是单体对微服务;最后对照上次的代价清单——当年记下的"拆分成本高",现在还成立吗?成立,而且三条信号证明不拆的成本更高。所以拆。决策记录的收官用法就在此:不是翻旧账,是照单复查——大部分复查走到第三步就结束,这次也一样。

最后一行,照例写进决策记录:决策:单体→按边界拆 3-4 服务;理由:协作/故障面/数据三条信号全到;代价:分布式复杂度;演进条件:K8s 等专职运维,全面微服务等团队 50+。为什么拆,为什么只拆到这一步——两个问题的答案都在这行字里。将来有人质疑,不必重吵,翻记录。

26.3 怎么拆:边界与数据​

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

秘密的依据是什么?业务边界等于数据边界,等于服务边界——服务是业务边界的投影。建模那章划过的边界——订单域、支付域、内容域、用户域——就是服务的边界。当年立过一句话:边界清晰是拆服务的地基,分层和拆服务都靠它。本章兑现。所以拆服务的第一步不是写代码,是回看当年的表:哪些表属于支付域,哪些属于内容域——边界,早就画好了。

边界划得对不对,用三条检验验收。独立演进:这个服务的改动,需不需要别的服务配合发版?需要,边界没划对。故障隔离:它挂了,别的服务受不受影响?受影响,没划对。变更频率:它是不是经常变、独立变的模块?是,就该拆——拆的优先级有个公式:变更频率乘以独立性。两个标准都占的,排最前。

边界定了,动手顺序是"先拆表,后拆代码"。为什么?因为数据是服务最难拆的部分——代码一天能改完,数据迁移以天计。具体怎么走?第一步,划数据边界:哪些表跟支付走。第二步,数据独立:支付表从单体库迁到支付库。第三步,代码再拆:支付模块独立成服务。次序不能反——库没拆,代码拆了也是假拆分:两个服务共用一个数据库,等于没拆。

拆表本身是个迁移工程,步骤要稳:建新库;双写,新旧库都写,过渡期;校验,两库数据一致吗——对账思想在这里用上;切换读,读走新库;停旧写。每一步都可回退,数据迁移要能回——当年说"改表贵",拆表是它的放大版。迁移期间还有一个新问题冒出来:跨库事务没了。支付表和订单表分属两个库,原来的一个事务,变成两个库的操作——一致性从"一个事务"变成"事件串联加对账兜底"。这是拆的代价里最大的一笔,后面专门有一节偿还它。

拆代码之前,要有保险。拆分承诺"行为不变"——拆前什么样,拆后还得什么样。这个承诺靠什么证明?核心链路的测试。拆前全绿,拆完再全绿,行为没变,拆错了测试当场拦住。没有测试兜底的拆分是盲拆:拆完不知道哪里变了,上了线才知道错在哪。

拆不只是拆系统,也是拆人。服务边界往往对应团队边界——支付服务由支付小组负责,这是康威定律的通俗版:系统结构复制沟通结构。架构也是团队的函数:30 人分成三个小组,各自负责一个服务,发布、评审、复盘都在组内闭环。拆的收益因此有一半是组织的:自治。技术管隔离,组织管自治,各得一半。

边界的价值还不止于此,它是贯穿全书的一把尺子。建模时划边界,分层时按边界分职责,拆服务时按边界分系统——下一章新需求来了,第一个问题还是它:这个功能落在哪个边界里?拍卖属于交易域,就放进订单和支付服务的边界。边界清晰的系统,新需求放得进去;边界模糊的,每次新功能都是一次架构事故。同一把尺子,量完建模量分层,量完分层量服务,量完服务量迭代。

拆的路上有三个坑,提前亮出来。假拆分:库没拆代码拆了,两个服务共用一个库,等于没拆。过度拆分:一个服务五百行也拆——服务有运维成本,粒度越小成本越高,复杂度预算的老规矩。拆完不管:拆了没有对应的部署、监控、链路配套——动手术之前先配眼镜的教训,对每个新服务都要重来一遍。三个坑的共性一句话:拆是结构动作,配套必须跟上——前面五章建的那套体系,每个服务各领一份。

先拆谁?支付。三个理由。最独立:支付只和订单说话,接口面最小。最敏感:钱不能错,拆它的时候机制最全——事务、幂等、对账全部在岗,最谨慎的活先做扎实。最有说服力:拆完立刻见效,支付发布从此不再惊动全站。选第一个服务的标准由此立住:风险最高、收益最明显的那一个。按风险排,不按热门程度排——这个排序原则从安全投入一路用到架构拆分,到此是它的最远射程。

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

服务拆开了,第一个迎面而来的问题:服务之间怎么沟通?这个问题早就露过面——第 12 章的中间件编排,在拆分后放大成了"服务间协作"。

先从最熟的谈起:联调——服务间联调,就是前后端联调的更大规模版本。核心还是契约那件事:A 以为 B 的接口返回 {status: 1},B 悄悄改成了 {status: "ok"}。联调三问在这里照样用,断在哪一段、卡在哪一层、数据卡在哪个环节。再加一件武器:契约测试——接口测试的服务间版本,消费方和提供方都跑同一份契约。"契约"这个词,从前后端的约定升级为服务间的约定。联调的经验没有浪费:服务越多,规模越大,越需要契约。

服务之间的同步调用,就是接口那章讲过的接口——REST。接口的一切规矩原样适用:版本化,只加不改不删;request_id 贯穿全链路;超时必须有。变的只是读者:接口这份"被读得最多的代码",读者从浏览器扩展到了另一个服务。那 RPC 呢?它的演进条件当年记过——服务间高频强类型调用。阶段 5 的答案:REST 先用,三四个服务,调用密度还没到。演进条件记着:服务再多再密,评估 gRPC。REST 对 RPC,比的是调用密度到了没有,不是谁更好——这是当年决策记录的再确认,不是新决策。

同步调用立起来,立刻要配一组防线——高可用那章的机制,从系统内部搬到了服务之间。超时必须有:没有超时的调用,线程被占满。重试要克制:重试是故障放大器,要和熔断配合。降级要想好:B 挂了,A 给用户什么。这条代价值得单独说透:原来进程内的函数调用,挂了整个进程一起挂,反而简单;现在变成了网络调用,网络的不可靠全部都要防——"网络不可靠"那句话,在这里从认知变成工程。

服务设计还有两条铁律,都是旧决策的兑现。第一条,无状态。每个服务的接口必须无状态:请求自带完整信息,不依赖"服务器记着我"。有状态的接口一拆就乱——会话粘在某个实例上,扩展立刻卡死。当年把会话挪进共享存储的决策,在服务化时代收成为铁律:实例全部无状态,来了都能接。第二条,服务身份。服务 A 调服务 B,怎么证明"我是服务 A"?一次性服务凭证出场——短命、不需要吊销,这正是它的舒适区。当年"不上 JWT"的决策记录翻出来:演进条件,多服务,触发了。不是 JWT 更先进,是场景到了。

服务多了,两个小问题跟着来,各用轻量方案接住。服务的地址会变、会加,"服务 A 的地址是什么"成了问题——服务发现本该登场,阶段 5 的答案:配置加网关统一入口,地址变化只在网关改一处;注册中心留给服务超过十个那天。入口本身也要变:原来浏览器对着一个域名,现在对着三四个服务——网关出场:统一入口,按路径路由到对应服务,鉴权、限流、日志在网关做一次、所有服务共享。注意两件事:网关是中间件机制的服务版;网关自己也是服务,它挂了全挂——冗余的老规矩对它同样适用。

现在走到本章的另一个主线:异步链的完整回收。这条链埋在交易那章——同步调用出问题,逼出异步,异步用任务表起步,预告过阶段 3 末引入消息队列。前四环当年走完了,本章走完后三环:服务拆分,服务间通信,事件驱动。链条的因果很清楚:服务拆分的必然结果,是同步调用变成慢依赖;慢依赖,逼出消息。当年还记过异步的代价——排查复杂度最容易被低估——链路追踪已经替这笔账付了款:异步化之后必须有可观测性兜底,兑现了。要说 17 章和本章的差别,一句话:当年是同一个进程里怎么异步,现在是跨服务怎么异步——问题没消失,只是升级了规模;同一套答案,更大的棋盘。

于是消息队列的决策上台,照例走九步。① 目标:服务间解耦加削峰——支付回调、通知、扣库存,这些"不着急但费时"的动作该有新通道。② 约束:三四个服务,任务表已在运行,链路已通。③ 问题:服务间的慢动作用什么传?④ 候选:A,继续同步调用;B,消息队列;C,维持任务表。⑤ 维度:解耦度、削峰能力、一致性代价、运维成本。⑥ 打分:A 在回调通知场景会拖垮调用方——超时熔断只能止损,治不了本。C 的任务表撑过了日订单千级的日子,但服务化之后,"任务表放哪个服务"成了新问题——放在订单服务的表,调支付服务的接口,表还是耦合的。B 的解耦最彻底:订单服务只管发事件,支付、通知各自消费;高峰期消息排队,消费端慢慢处理,削峰天然成立。⑦ 选择:B,引入消息队列。⑧ 收益:解耦、削峰,外加异步链的最后一块拼图。⑨ 代价:最终一致性——消息可能丢、可能重复、可能乱序,交易那章的防线要全部搬到消息层:幂等消费、重试、对账。

任务表怎么退役?和老机制告别,要和新机制上线同样谨慎。新逻辑走消息队列,任务表只处理存量;双跑过渡——两边同时跑,对账一致后再停;停了也要可回退。退役的流程,就是回滚思想的镜像。

最后看一眼消息队列长什么样,三个角色。生产端:服务把事件发到主题,一行代码——这是任务表的升级版。队列:消息暂存,高峰期堆积,消费端慢慢处理——削峰的容器,缓存哲学的同款。消费端:订阅主题,拉消息处理,失败重试,重试到底还失败的进死信队列,等人工处理——这是当年 worker 的工程化。三个角色各对应一个老熟人:MQ 不是新世界,是任务表的工程化完整形态。

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

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

先立核心定义。事件,是"已经发生的事"——订单创建了、订单付款了。服务发布事件,不关心谁在听:支付服务发出"订单已付款",通知服务在听,库存服务在听,统计服务也在听——发的人不知道,也不关心。这和同步调用是本质区别:同步是"叫别人做事",命令,调用方得知道谁来做、怎么做;事件是"宣告发生了什么",事实,发布方只管陈述。命令耦合两件事——对象加动作;事件只耦合一件事——事实本身。这层机制解释了事件驱动最大的红利:新服务只需要听得懂事实,不需要被任何人记住——加功能,不加耦合。

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

看一个业务实景。用户下单,订单服务发"订单已创建";支付服务收到,等用户付钱。用户支付,支付服务发"订单已付款";通知服务收到,发短信;库存服务收到,扣库存;统计服务收到,记数据。每个服务只听自己关心的——通知服务不关心库存。后来要加一个推荐服务?订阅主题即可,不改任何已有服务。发布订阅的机制就是这么朴素:事件不发给某个服务,发给主题;订阅者自己决定听不听。"加功能不加耦合"这六个字,是它的全部广告。

事件和业务内核还有一个呼应:状态机。交易那章的订单状态机——已创建、已付款、已发货——每一次状态转移,就是一个事件。状态机是事件驱动的内部视角:一个服务内部用状态机管自己的状态,状态一变,对外发布成事件,其他服务通过事件感知它的状态。而且事件流可以重建状态:把已创建、已付款重放一遍,订单状态就回来了——对账干的正是这件事。状态是结果,事件是过程;过程留全了,结果随时可以重算。

事件也是接口,纪律原样继承。命名要规范:主体加点动作,订单已付款写作 order.paid——接口设计思想的事件版。格式要版本化:事件字段会变,消费者按旧格式解析会崩——只加不改不删的规则表,事件同样适用。接口是系统里被读得最多的代码;事件是服务间被读得最多的消息。同一套设计纪律,换了一个载体。

代价也要认全。事件是异步的,状态最终一致:下单后订单显示"待支付",支付事件到了才变"已支付"——用户看到的短暂不一致,是设计的一部分,不是 bug。这是异步的代价从单个功能升级成整个系统的默认。兜底机制是交易防线的全套搬家:幂等消费,同一个事件消费两次,结果一样;重试,消费失败再来;对账,定期核对事件流和数据库。事件驱动的系统,防线和交易系统一样厚。

对账在这里再深一层。事件驱动系统里,对账升级为事件对账:定期把事件流和数据库状态对比——这个订单发出了已付款事件,库里怎么还是待支付?事件丢了。交易时代,对账是最后一道防线;分布式时代,它是整个架构的底线。可以说,分布式系统的所有防线,最终都落到对账——人工兜底永远在,从交易那一章到这里,这是不变的一环。

事件还有一层运维价值:事件流本身就是最干净的审计日志——谁对订单做了什么,事件流逐条回答。日志那章说,系统的日志管系统排障;事件则是业务的可观测性:排障时重放事件流,就是回放业务历史。回放请求是系统侧的能力,回放业务是业务侧的能力,两者合起来,排障的眼界才完整。

最后两笔,收在"边界"这个引导词上。第一笔:事件是数据边界的桥梁。数据拆了库,业务却需要跨库的一致性——事件把拆开的数据在逻辑上重新连起来:订单库的已付款事件,让支付库和内容库各自更新。物理上分开,逻辑上串联。边界这个词的两面性到此完整:拆的时候靠边界,连的时候也靠边界——事件的主题,就是边界的对外接口。第二笔:单体时代,状态安安稳稳躺在一张订单表里;拆了之后,状态分散在三个库。状态到处飞,解法不是再合并回去,是用事件把状态串起来——每个服务的状态变化都发事件,全系统的状态由事件流串联。这,就是异步链的终点,也是拆服务的终点。

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

拆完三四个服务,把整个演进过程收成一张全景图——这也是一级总图的第一次完整出场:

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

这张图怎么读?拿它和全书最早那张总架构图对照:总架构画的是浏览器、网络、服务端、数据层——四格演进图的每一格都是那张图,变的只是"服务端"一格内部的画法:一个方块,变成几个方块。这是"不断放大同一张地图"的铁律走到今天的样子:浏览器被放大过,服务端被放大过,数据层被放大过,现在轮到服务端再放大一次。而且注意,演进不丢资产:单体里的分层,变成服务内的分层;接口,变成服务间接口;缓存、限流、熔断,每个服务各领一份。不是革命,是重组——每一章的技术都还在,只是结构换了。

拆的收益,用结果检验。拆完第一个服务——支付——三个变化立刻可见。发布解耦:支付小组自己发版,不再排队,"发布互相踩"少了一大半。故障隔离:支付服务挂了,内容社区照常,爆炸半径的实感第一次摸到。数据分库:连接数告急缓解,那个信号消失了。回头看,三条信号各有一个对应答案:协作撞车对上发布解耦,故障面全站对上故障隔离,连接告急对上数据分库。信号消失,等于拆对了;信号还在,等于边界划错了——这是拆的验收逻辑。

代价清单也要摆全,好消息是:每一项在前面章节都有答案。构建与部署:制品的原子单位从"应用"变成"服务",三四个服务就是三四条构建链,流水线从一条变成几条,发布单从"部署一个"变成"协调几个"。排障:拆的前提是可观测性就位,跨服务请求靠链路追踪贯穿——拆完当天,跨服务排障必须已经打通,这是验收标准之一。配置管理:环境变量散在几个服务里,配置中心到了该上的时候。发布方式:服务化时代发布更频繁,灰度从"高风险选项"变成标配——新版本放 1% 流量,盯着错误率和延迟,健康曲线达标再放量。当年那句"健康了才放上去",现在变成"健康曲线才放量"。隔离的收益和级联的风险是同一枚硬币:每个服务有自己的熔断,链路上一环断了,不拖垮全链——高可用机制在服务之间重新上岗。

K8s 的演进条件也到了复查的时候。几十台机器、几十个容器,规模那条线过了;但专职运维还没有。增量重走一遍,结论还是再等等——编排的自动化收益,要超过学习和运维的成本才值得上。那句"业务没到就别上",在规模到了之后依然成立:不是规模到了就上,是规模和能力都到了才上。

数据层同步演进。读写分离早已上岗;分库在本章拆表时完成;数据量更大的表按维度拆分,比换 NoSQL 常见得多。原则不变:先在 SQL 体系内扩容——读分离、分库、分表,一步步走;换 NoSQL 是最后手段,数据量没到别折腾——这套原则在本章再次检验。

最后是远处。容器加编排加微服务加交付体系的全套,有个名字叫云原生。阶段 5 只走到"服务化加消息",云原生的完整形态,留给团队五十人以上的那天——决策记录的演进条件里写着。名词都知道,位置才重要:知道它存在,知道它在哪一环,就够。

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

拆的对面,是不拆。微服务的名声很大——名词清单里最典型的一例——但它是有真实代价的架构。什么时候其实不需要微服务,这个问题和"怎么拆"同样重要。

先看名声从哪来。两个来源:大厂的宣传——Netflix 和亚马逊的故事,但那是万人团队的地图;名词清单的陷阱——只记住了名词,没记住条件。"大厂用微服务"和"我们该用微服务"之间,差着团队规模和业务复杂度两个变量。当年说过,看到还在用单体的公司先别急着笑;镜像版同样成立:看到用微服务的,也别急着羡慕。大厂的真相还要再戳破一层:他们能微服务,是因为有几百人的团队和专职的平台组——容器平台、监控平台、发布平台,各有一个团队维护。微服务的成功案例里,成功的是"组织加平台加流程",不只是架构本身。三十人的团队没有平台组,运维债只能自己扛。

那真实代价是什么?列出来。分布式一致性:交易那章的所有防线,要在消息层全部重建。链路与排障:链路追踪从可选项变成必需品,观测成本乘以服务数。运维复杂度:三个服务,三套部署、三套日志、三套监控、三套告警——高可用和可观测性的全部工作量乘以三。团队心智:每个人都要掌握跨服务的思维方式。每一项,都是复杂度预算的大额支出。

所以给一张体检表:四问,全是"否",就还不该拆。团队规模:十个人以下,拆了没人维护——架构是团队的函数,和流程一样。业务复杂度:业务边界清晰吗?边界都不清,拆了只会更乱。发布频率:一周一次发版都还困难?那先解决流程,不是动架构。数据瓶颈:单库真的到顶了吗——亿级行、连接数告急,才是该动结构的信号,别把"查询慢"当成拆的理由。四问过完,还有个反事实检验:假设阶段 4 就拆,会怎样?8 个人分三个服务,每个服务两三个人——没人有空做告警、演练、值班这些配套的活。有服务没配套,机制空转,拆等于负收益。拆的时机是团队 30 人加配套就位,两个条件缺一不可——不是"能拆"就拆,是"拆了有人维护"才拆。

由此,"不上"系列的最后一块拼图落位。缓存场景没到不上,一次性凭证场景没到不上,编排规模没到不上——本章补上最后一块:微服务,问题没到,别拆。本书的架构观可以收成一句话:让问题决定技术,不让名词决定技术。单体不是耻辱,是起点。绝大多数系统的正确路径,是先单体跑通,被问题逼着拆——主动拆和被动拆一样危险,架构演进是问题驱动的,不是名词驱动的。

但还有两种心理病要治。一种是拆的拖延症:信号到了不敢拆,被代价清单吓住。另一种是拆的冲动症:信号没到就要拆,追逐名词。治这两种病的药是同一个:正经理一遍决策流程,把信号、代价、约束全部摆上桌面——让"拆不拆"从情绪变成论证,这是决策模型的最终价值。

动手之前,还有一条技术边界要划清:重构和演进不是一回事。重构,行为不变、结构变——分层那种活;演进,结构变、边界也变——本章的活。而且重构是演进的准备动作:拆之前,要先把支付相关的代码重构成聚拢的一坨,这一步单独做,用周计;然后才拆服务,用月计;最后迁移数据,还是按周计。急不得——改表很贵,改架构更贵,按周计就会翻车。

最后说还债。拆分时你会遇见所有的旧账:早期图快写的一坨代码,当年图省事绕过的边界,没补的测试,没写的文档。拆分是顺手还债的最佳时机——代码反正要动,结构反正要变。但还债也要讲次序,别在拆的时候大改特改:拆是结构动作,改是行为动作,一起做,事故加倍。正确顺序:拆和还配合着走——代码坨在拆的时候顺手分层;缺的测试在服务独立后补,测试跟着服务走;没写的文档在交接时补。拆一次,还一批;不是拆一次,改一切——拆的复杂度预算里,不包含大改。而债本身不必羞愧:技术债的积累是演进的前奏,不是失败——拆分这一天,就是还债日,债是演进的一部分,到了该还的日子,说明路走对了。

26.8 本章对应表​

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

本章小结​

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

下一章​

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

第 27 章,业务迭代的循环:在已有系统上叠加新需求,也是"演进"的最后一块拼图。