“要收官了。”
《1999点科技树》写到第十一、十二期,这系列终于要画上句号了。说实话,这期的稿子我改了三遍,起初只想讲微服务拆分,但写拆的过程时我发现一个很别扭的事:所有人都在教你怎么把一个系统拆成微服务,很少有人告诉你拆完之后怎么把日子过下去。所以第十一和十二合集的标题我改成了“纠偏与收官”,前面十期已经点过不少科技树,这一期不急着种新树,反而要回头把歪的枝丫修剪掉。
1999年的机房里还跑着Windows NT、Unix和刚刚流行的Linux,和今天大家到处搜“微服务架构图”的时代差着二十多年。但“点科技树”这件事的本质不是配置多新的机器,而是我提前记住了一些规律:比如服务拆分的热搜词这几年越来越密,从“微服务架构”到“微服务拆分”,再到“Spring Cloud微服务开源项目”“若依微服务plus”,工具越来越多,翻车案例也越来越生动。这篇大结局不打算再列什么框架清单,我只想以这十几年的从业经验,把微服务这条路上最容易被误解的东西讲清楚,再聊聊未来我们到底该往哪里看。
1. 纠偏一:单体不是羞辱,模块化单体才是安全的起点
1.1 我见过最脆弱的多服务系统什么样
先讲一个让我印象很深的项目。那是一个电商后台,团队把订单、支付、购物车、优惠券、积分、库存、对账全部拆成了独立服务,听上去特别符合“微服务架构”的标准。结果上线半年,整个系统最痛的地方反而是以前单体时代根本不会遇到的问题:购物车列表一次请求要聚合六个服务的数据,平均响应时间从单体时代的180毫秒涨到1.2秒;一旦积分服务抖动,订单确认页也跟着打不开。更离谱的是,订单服务超时之后会重试,重试又把库存服务打挂,库存服务一挂,商品详情页也跟着出问题。
这个案例我记了很多年,因为问题不在代码质量,而在于我们当时太相信“拆”这个动作本身了。技术圈总有一种风气,一提起单体就露出嫌弃的表情,好像单体等于技术债,等于团队不行。实际上很多系统根本不应该一上来就拆成微服务,尤其是在对业务边界还没有清晰认知的时候。
我这些年看过不少团队做过类似的“壮举”:一百人以内的研发团队,硬要拆出三十个微服务,每个服务平均一个半人维护,公共代码靠复制粘贴,接口文档三天不更新就彻底失联。最后线上出了问题,光查请求是从哪个服务出去的就得查半天。我以为这叫微服务实践,结果我们只是把单体时代的复杂度转移到了网络、部署和运维环节,一笔也没省。
1.2 拆服务的门牌号:一切以业务模块和团队边界为支点
那到底什么时候拆?我现在的回答比较朴素:先做模块化单体,也就是在同一个应用进程里把模块边界划清楚,包结构和数据库schema完全按领域隔离,但依然一起编译、一起部署。等到下面这些条件凑齐了,再考虑把某一个模块真正独立成服务。
第一,团队已经大到同一个仓库里的协作成本明显超过部署成本。比如代码合并冲突频繁,发版要照顾十几条业务线的节奏,这时候独立部署能换来效率。
第二,某个模块有独立的弹性伸缩需求。比如搜索、秒杀这类模块的流量曲线和其他功能相差很大,单独扩容比整体扩容划算得多。
第三,生命周期不同步。有的模块一个月发三次版,有的半年才动一次,把它们绑死在同一个发布流程里,只会互相拖累。
第四,数据归属明确。一个服务不允许跨过边界去写别人的表,所有跨服务的数据访问都应该通过对外接口完成。如果两个模块的数据耦合太深,它们天然就不适合拆开。
拆完之后千万不要把“服务数量”当KPI。我见过不少公司技术复盘里写“重构为微服务架构”,但指标只列了一条:服务数从五个变成二十个。服务多不等于架构好,服务边界清晰、故障隔离有效、发布节奏变快,这才算数。这个道理听起来简单,但只要你经历过一次靠微服务翻车的过程,就知道它值多少钱。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 纠偏二:分布式事务里的“账”,要在开工前就算完
2.1 订单、库存、积分的故事,每个微服务项目都要重演一遍
拆成微服务之后,第一个撞上来的墙往往不是性能,而是事务。
还是拿电商举例。一个用户下单这个动作,会同时触发扣库存、生成订单、冻结优惠券、加积分、记录对账单等一连串变化。在一个单体系统里,这些动作可以在同一个数据库事务里完成,要么全部成功,要么全部回滚,大脑想都不用想。可在微服务架构下,订单数据在订单库,库存数据在库存库,积分数在积分库,数据库事务根本覆盖不到不同库,甚至都不一定用的是同一种数据库。
这时候你必须回答一个问题:扣库存成功了,但加积分失败了,用户会怎么样?大多数人的直觉是尝试“分布式事务中心”这种方案,让所有参与的服务都去一个全局协调器上报状态,做两阶段提交。理论上是通的,但如果你真的在生产环境里用过苛刻的分布式事务,你会发现它的锁定开销、协调复杂度、故障恢复难度都比想象中大得多,特别是高并发下很容易变成性能瓶颈。
我现在的选择很简单:能不强一致的业务尽量不强一致,让系统走到“最终一致”。方法也很多,本地消息表加消息队列是最容易落地的一种。
2.2 本地消息表加幂等,比一个分布式事务中心更可靠
具体做法用大白话讲就是这样:
用户下单请求进入订单服务后,程序一边把订单状态写入业务表,一边在同一次数据库事务里写一条“待发送”的消息表记录。这个本地事务成功之后,后台起一个定时任务,把消息表里“待发送”的记录扫出来,投递到消息队列。库存服务、积分服务各自订阅自己关心的消息,处理各自的数据更新。消息队列负责把投递可靠性先兜住,本地消息表又保证了“业务成功”和“消息产生”这两个动作不会产生一半成功一半失败。
这套方案我从很早之前就在用了,直到今天依然觉得它是最适合互联网业务节奏的事务落地方案。它不要求所有服务实时一致,却能在秒级甚至毫秒级之内让数据回到正确的状态。
但有一件事必须同时做,那就是“幂等”。因为消息重投是常态,网络闪断、消费超时、重启都会让同一事件被消费两次。如果消费端不处理重复,用户下单一次结果积分加了两次,这就闹大了。幂等其实不难,比如在消息表里放一个唯一业务ID,消费端建一个唯一索引,重复的消息直接落库失败;或者用状态机校验,只有待处理状态下才允许处理,重复消息进来发现状态已经变了,直接丢弃。
我踩过一次很典型的坑。某个活动模块处理用户参与记录,消费端本地判断“如果该用户已参与则跳过”时报错退出,生产者以为消费失败就不断重试,结果并发场景下两次重试同时查到了“未参与”,两边一起插入,最终重复数据还是进了库。后来我们把“用户+活动”设成数据库唯一键,这个问题才彻底消失。所以说,所有分布式方案都要配合唯一约束来兜底,别把希望全压在业务代码的判断上。
既然聊到一致性,我也想说说“不做的事”。账务余额、支付结果这种与钱直接相关的数据,不能拿异步最终一致去糊弄,该用强一致方案的地方一定要硬起来。比如支付回调对账,要保证接口的幂等性和上下游状态的一致性,该加锁的地方加锁,该对账的地方对账。设计上千万不要一对多、一蹴而就,要具体业务具体分析。
3. 纠偏三:必须先治理再拆分,微服务才不是一团乱麻
3.1 我见过拆完才发现没有“运维大脑”的项目
大概七八年前,我带过一个服务化改造项目。第一周我们就把几个模块拆了出去,代码编译通过,接口也通了,但越往后走越不对劲:服务A要让服务B调用自己,服务B的IP变了怎么办?新加一台机器之后,上游怎么知道?配置改了,几十个服务怎么同时改?
这些问题的答案放进微服务里,就是注册中心、配置中心、网关和可观测性。这个“治理层”才是微服务的真正重头戏,很多人把代价看得太轻了。
我后来的习惯是先搭治理框架,再动手拆服务。在公司内部先把微服务架构图画出来,每个服务叫什么、依赖哪些服务、谁对外提供API,全部画清楚。画的顺序不是功能清单,而是调用关系,因为微服务最容易失控的地方就是你不知道流量到底怎么走。
3.2 注册中心、配置中心、网关:一个都不能少
这三样要是缺了其中一样,后续基本会踩大坑。
注册中心可以用一句话理解,就是电话本。服务启动的时候给注册中心报个到,说自己叫什么、地址在哪,服务要调用别人时直接去电话本里查。它解决的问题是“动态发现”,不然你真得靠运维手工维护一个IP白名单,服务一扩容就手忙脚乱。
配置中心解决的是“动态变更”。以前单体时代改配置要重启应用,微服务时代几十个实例如果还靠手动一台台改,那根本没法上线。把配置抽出来放中心化存储,改一次推送所有节点,而且还能带版本管理,出问题可以迅速回滚。
网关更像一个总门卫。所有外部请求先进网关,由网关做鉴权、限流、灰度路由,再转发给后面的服务。很多没有网关的微服务项目到最后都会发现,安全策略东一处西一处,限流逻辑散落个不同服务里,根本管不住。早期就算你没把网关做得多复杂,也至少要把统一入口立住,后续加功能才有抓手。
3.3 链路追踪做得好,线上故障才能顺藤摸瓜
在做微服务治理的时候,我最想喊的其实是另一个东西:链路追踪。单体应用报错,我们看一个堆栈就能解决;微服务报错,用户说“我下单失败了”,你在订单服务里找不到问题,因为问题可能发生在三跳之外的库存服务里。
链路追踪的原理并不复杂,就是为每一个请求生成一个全局唯一的traceId,从网关开始透传下去,每个服务在处理请求时都把traceId记到日志里。当收到多个服务的日志后,用traceId把散开的日志串起来,就能看到一次请求跨了哪些服务、每一步消耗了多少时间。故障排查从“大海捞针”变成“按图索骥”,效率天差地别。
我们当时做了两件具体的事:一是所有日志框架集成MDC,把traceId和spanId自动塞到日志输出里;二是服务间调用时,在HTTP头里把traceId传下去,绝不把它存在线程变量里,因为一旦经过消息队列就会丢失。这个看似微小的细节,后来救了很多次线上故障。有一次用户反馈下单偶发超时,我们顺着traceId发现是积分服务某个实例FullGC导致偶发两秒停顿,五分钟定位到根因,修复后整个链路恢复平稳。
服务治理这件事没有太多炫技的空间,全是地基活。注册中心、配置中心、链路追踪、日志采集、监控告警,这些都是微服务能不能长期跑下去的关键。很多人上来就问“微服务怎么拆”,其实他们应该先问“拆完以后我拿什么去看它、管它”。
4. 收官复盘:我是怎样把十六个服务收敛成八个的
4.1 不太体面的翻车案例
既然叫“纠偏与收官”,我就拿自己过去的项目做标本。那是一个互联网业务系统,最开始的时候我把它拆成了十六个服务。拆的理由看起来都很正当:独立部署、各自伸缩、职责清晰。可运行半年之后,一些指标越来越难看:一个普通查询经常跨五个服务,部署一次要手动改八个不同仓库,线上告警倒是天天有,但大量告警来自服务间依赖超时,而不是真正的业务异常。
我是被一次大规模故障打醒的。某个底层基础服务做了个兼容性调整,结果影响到了上游六个服务,最终导致核心流程不可用。复盘的时候,我们画完依赖图才意识到:很多上下游之间的关系根本不应该存在,有些服务之间本来就共享同一个数据库表,只是换了一种访问方式,反而徒增了网络开销和接口维护成本。
4.2 收敛的路线
后面我们做了为期三个月的收敛,核心思路是“高内聚优先,能合则合”。
第一步,把共享同一套数据表的服务尽量合并。比如订单、支付、优惠券这三个模块虽然业务上会相互调用,但它们的表都还在同一个数据库里,代码层面又存在大量跨服务单查与回写。我们把它们收敛成一个“订单域服务”,保留模块边界,但共享数据库事务能力,回归单体内聚。
第二步,把所有不需要独立伸缩能力的模块回调。比如消息通知、操作日志这类几乎没什么量级波动的模块,收进各自主域服务里,不再单独做成微服务。
第三步,建立依赖规范。新代码一律禁止服务间直接读写对方数据库;跨服务调用必须走统一网关或消息;服务间最多允许三级调用深度,超过三级必须引入中间层或消息解耦。
收敛之后,十六个服务变成了八个,跨服务调用从平均五次降到了两次,部署时间从三十分钟缩到了八分钟。最明显的变化是故障恢复速度,以前平均一个小时起步,现在遇到核心模块问题二十分钟内就能定位和恢复。虽然服务数少了,但系统的稳定性反而大幅提升,研发团队的效率也上来了。
4.3 微服务项目的验收指标不该是服务数量
这次收敛之后,我再也不敢把“服务数量”当成项目成果了。衡量微服务收益的维度应该是这几个:
- 需求迭代平均耗时是否缩短了
- 线上故障定位时间是否变快了
- 系统能否独立扩缩容最热门的模块
- 一个服务出问题,是否真能把爆炸半径控制住
如果这些指标都没有变好,那么你拆得越多,亏得越多。
很多开源微服务项目之所以让你觉得复杂,不是因为微服务本身复杂,而是为了保证这些指标,它们带上了注册中心、配置中心、网关、认证、消息队列、分布式事务、可观测性这一整套家当。比如中文社区里应用很广的若依微服务plus这类架构,本身就是把治理层给你配全了,你业务没到那个量级,硬要往这套体系里套,反而被运维复杂度拖死。技术选型首先要看团队,要看业务的需要,不能光看架构大不大。
5. 未来之光:微服务不会消失,而会被消化进更厚的平台层
5.1 别再只看那张微服务架构图了
最近几年总有人问我,微服务的未来到底是什么。我反而不太爱回答这个问题,因为真正的未来往往不是想象出来的,而是被现实逼出来的。我亲眼看到很多团队在微服务这条路上折腾了一大圈,最后又回到一个接近命名的模型:模块化单体加分布式能力外置。
我觉得这不是倒退,而是一种成熟的消化。当Kubernetes这类容器编排平台把部署、伸缩、流量调度这些问题接走之后,应用层的服务数不需要再刻意拆到无限小。反过来说,因为容器平台天然提供了进程级别的隔离,你有可能会发现“微服务”的那部分工作很多被平台层替代了。以后工程师可能不再需要亲手维护那一堆注册中心、熔断器、网关配置,而是直接用平台能力和服务网格把这些点点滴滴地埋到底座里。
然而底座的智慧并不能替代业务上的判断。该拆的边界依然要拆,该做的领域建模依然要做。一张微服务架构图好画,但这些框框之间的关系随时都可能变成白费功夫的烂账,关键是你是否清楚每一个依赖为什么存在、是否值得跨网络去调。
5.2 我眼中微服务留下来的三样东西
如果非要从这些年里提炼出即使未来也不过时的东西,我会讲三个。
第一是边界的意识。无论是单体里的模块边界还是微服务之间的服务边界,本质都是让团队能够独立演进。这是组织问题,也是业务问题,架构只是它的映射。
第二是故障隔离的思想。互联网业务的抖动总是难免的,你要做到的是不让一个进程把整个系统拖垮。限流、熔断、降级、隔离这些能力,价值和具体技术名字没有关系,它们只跟你能否保全核心链路有关。
第三是可观测的习惯。想看透一个复杂的系统,你必须在每个关键节点留下可检查的痕迹,包括日志、指标、链路信息。只要这个习惯还在,不管架构换成什么,出了问题都有办法抓出来。我最怕的不是架构复杂,而是系统里到处都是黑盒子,出了故障连第一步的判断都没法做。
5.3 在1999年点上最后一格
早年我总喜欢把技术想成一个向上爬的阶梯,好像每一次升级都离“最佳实践”更近一点。但写完这十一期、十二期的内容,回头整理思路,我更愿意把技术理解成一片森林。工具会换,风口会变,线永远是被踩出来的。微服务这套东西曾让无数人兴奋,也让无数人痛苦,它的价值不应该是让架构图更好看,而应该是让团队跑得更快、让系统更稳。
我还记得我最早接触到微服务这个概念时,满脑子都是“我要把所有功能拆成自己能控制的小服务”。如今我的想法变了,我会先问自己:你真的需要拆吗?拆给谁看?拆完怎么管?这些问题的价值,超过所有的框架对比和架构图收藏。
你在网上搜“微服务拆分”“微服务架构”,搜到的永远是方案、代码、开源项目,但搜不到一个团队的审慎思考。希望这个大结局能补上这节课:点科技树不是越点越多,而是在每一个分岔口知道什么时候该停下来,把眼前的树照顾好。
这也是我在1999年,给自己点的最后一格。后面没有续集了。
