微服务拆了五年,该收一收了
过去十年,微服务架构几乎成了互联网行业的技术共识。从单体应用中剥离出独立的服务单元,让每个团队专注于自己的领域,让每个服务独立部署、独立演进——这套叙事在 PPT 上永远漂亮。于是,一家中型电商公司拆出了三百多个微服务,一个社交平台的后台跑着上千个容器,一个支付系统的调用链路里跳了十七跳。
然而,当拆分的红利逐渐被运维的复杂度吞噬,当新人入职需要理解八十多个仓库才能改一行配置,当一次线上故障的排查要穿越十几个服务边界——行业开始冷静下来,重新审视那个被过度简化的命题:微服务不是越多越好,拆出去的东西,有时候还得收回来。
这不是倒退,而是成熟。就像一座城市不能无限摊大饼,到了一定规模就需要做功能归拢、片区整合。微服务治理的下半场,核心命题已经从"怎么拆"变成了"怎么收"。
本文将从合并编译和服务精简两个维度出发,系统讨论大规模微服务的归拢治理实践,涵盖技术手段、架构策略、方法论判断和组织配套。
过度拆分的三大症状
在讨论"怎么收"之前,先搞清楚"为什么该收"。过度拆分的微服务架构,通常会呈现出三类典型症状。
症状一:运维成本指数级膨胀
微服务的运维成本不是线性增长的。当服务数量从 50 增长到 500,需要管理的不再是 10 倍的关系,而是呈指数级放大的复杂度:
| 运维维度 | 50 个服务 | 500 个服务 | 复杂度倍数 |
|---|---|---|---|
| 服务间调用关系 | ~200 条 | ~5000 条 | 25x |
| CI/CD 流水线维护 | 50 套配置 | 500 套配置(含分支策略) | 10x+ |
| 监控告警规则 | ~500 条 | ~8000 条 | 16x |
| 发布协调窗口 | 每周 2 次 | 每天持续发布仍排不完 | — |
| 基础设施资源开销 | 可控 | 大量低利用率 Pod | 3x~5x |
一个真实的行业案例:某出行平台在微服务高峰期维护着超过 4000 个服务实例,其中近三成服务的 QPS 长期低于个位数,但每个服务都需要独立的监控、日志、告警、发布流水线。这些"僵尸微服务"吞噬了大量运维资源,却不产生任何业务价值。
症状二:调用链路深不见底
当一个简单的"查询用户订单"请求需要经过网关 → 用户服务 → 鉴权服务 → 订单服务 → 库存服务 → 优惠服务 → 支付服务 → 通知服务……八个跳点,任何一个环节的抖动都会导致整条链路超时。
这带来的问题不仅是性能损耗:
- 排查困难:一次请求失败,需要在八个服务的日志中大海捞针
- 数据一致性脆弱:跨服务的事务需要分布式事务框架兜底,复杂度远超单体
- 测试覆盖困难:端到端测试需要拉起全部依赖,环境搭建本身就是一个工程
- 变更影响面不可控:改一个底层服务的字段,上游十几个服务都可能受影响
症状三:团队协作反而变慢
微服务的一个核心承诺是"让团队独立工作、独立交付"。但现实往往是:
- 一个业务需求涉及五六个服务的改动,需要五个团队排期协调
- 服务间接口频繁变更,联调成本居高不下
- 公共库的版本冲突让多个服务被迫同步升级
- 新人入职需要 clone 几十个仓库,理解复杂的依赖拓扑
当"拆"本身成了效率的瓶颈,就说明已经越过了拆分的最优边界。
合并编译:技术层面的归拢手段
在动手合并服务之前,有一个更轻量、更安全的归拢手段可以先做起来——合并编译。它的核心思路是:不改运行时架构,先在开发和构建层面把散落的服务归拢到一起。
什么是合并编译
合并编译,是指将多个原本独立仓库、独立构建的微服务代码,合入到一个统一的代码仓库(通常是 Monorepo)中,共享同一套 CI/CD 流水线和依赖管理体系。
它不要求改变服务的运行时部署形态——各个服务仍然可以独立部署、独立扩缩容。但在代码管理、构建流程、依赖治理层面,它们被"归拢"到了一个统一的技术底座上。
| 维度 | 分散编译(多仓库) | 合并编译(Monorepo) |
|---|---|---|
| 代码仓库 | 每个服务一个 Git 仓库 | 多个服务共享一个仓库 |
| CI/CD | 每服务独立流水线 | 统一流水线 + 增量构建 |
| 依赖管理 | 各自维护版本号 | 统一版本锁定 |
| 接口变更 | 跨仓库改接口需要发版协调 | 同仓库内原子提交 |
| 代码复用 | 通过 SDK/NPM 包传递 | 直接引用内部模块 |
Monorepo 不等于回到单体
这是最常见的误解。Monorepo 是代码组织策略,单体是运行时架构,两者正交。一个 Monorepo 里可以有几十个独立部署的微服务,也可以有共享的库和工具。关键区别在于:
- 单体架构:所有代码编译成一个制品,部署为一个进程,共享一个数据库
- Monorepo + 微服务:同一仓库中包含多个服务的代码,但每个服务独立编译、独立部署、独立拥有数据存储
Google、Meta 等公司早已验证了 Monorepo 在超大规模工程中的可行性。国内多家大型互联网公司也在过去几年完成了从多仓到单仓的迁移。
合并编译的实施路径
第一步:仓库归拢
将分散的 Git 仓库合并到一个 Monorepo 中。常见的目录结构如下:
| |
归拢过程中需要注意:
- Git 历史保留:使用
git subtree或git filter-repo保留各仓库的提交历史 - 目录规范先行:在搬代码之前,先定好目录规范和命名约定
- 分批迁移:不要一次性搬完,按业务域分批,每批验证 CI 流水线跑通后再迁移下一批
第二步:统一 CI/CD 流水线
这是合并编译的核心价值所在。在分散仓库时代,每个服务都有自己的 Jenkinsfile 或 GitHub Actions 配置,格式不一、标准各异。归拢之后:
- 一套流水线模板:所有服务共享同一套构建、测试、部署模板
- 增量构建:通过代码变更分析(如 Bazel 的依赖图分析),只构建受影响的服务
- 统一质量门禁:代码扫描、单元测试覆盖率、安全审计等规则统一管理
- 原子变更:跨服务的接口修改可以在一个 commit 中完成,避免版本不一致
增量构建是关键。一个包含 200 个服务的 Monorepo,如果每次提交都全量编译,构建时间会让人崩溃。成熟的增量构建系统可以将平均构建时间从小时级降低到分钟级。
第三步:依赖治理统一化
多仓库时代最头疼的问题之一是依赖版本冲突。服务 A 用 protobuf 3.19,服务 B 用 protobuf 3.21,当它们通过公共库产生间接依赖时,版本冲突让人抓狂。
合并编译后,依赖管理变得简单:
- 统一版本锁定文件:在仓库根目录维护一份全局的依赖版本清单
- 自动依赖更新:通过 Dependabot 或 Renovate 统一管理依赖升级
- 消除幽灵依赖:在统一构建系统中,每个模块必须显式声明依赖,杜绝隐式传递
- 共享基础库:公共工具类、中间件封装等直接作为仓库内模块引用,告别"发版-更新-兼容"的循环
合并编译的收益量化
根据行业实践经验,完成合并编译后,典型的收益指标如下:
| 指标 | 改善幅度 | 说明 |
|---|---|---|
| 跨服务接口变更周期 | 缩短 60%~80% | 从"发版协调"变为"同仓库原子提交" |
| CI 配置维护成本 | 降低 70%+ | 一套模板替代几百套独立配置 |
| 依赖冲突排查时间 | 降低 90%+ | 统一版本锁定消除绝大多数冲突 |
| 新人上手时间 | 缩短 40%~50% | 一个仓库看到全貌,不用到处找代码 |
| 构建资源利用率 | 提升 2~3 倍 | 增量构建避免重复编译 |
服务精简:架构层面的归拢策略
合并编译解决了"开发态"的碎片化问题,但运行时的服务数量仍然庞大。接下来需要在架构层面做更深层的归拢——服务精简。
领域边界的重新划分
微服务拆分之初,团队往往对领域驱动设计(DDD)的理解不够深入,导致边界划分过细。常见的过度拆分模式包括:
按数据表拆分:每张表一个服务——用户表是用户服务,地址表是地址服务,登录记录表是登录服务。这不是微服务,这是"分布式数据库访问层"。
按功能点拆分:发送短信是一个服务,发送邮件是一个服务,发送站内信又是一个服务。三个服务的代码量加在一起可能不到两千行。
按技术组件拆分:缓存管理服务、文件上传服务、验证码服务……这些本应是中间件能力,不应该以独立微服务的形态存在。
重新划分领域边界的思路:
- 从业务流程出发,而非从数据模型出发。一个完整的业务用例(如"下单")所涉及的操作,如果变更频率相似、数据关联紧密,就应该归属到同一个限界上下文
- 用变更频率做聚类。如果两个服务总是在同一个需求中被同时修改,它们很可能不该是两个服务
- 用团队归属做验证。如果两个服务永远由同一组人维护,拆分没有带来任何组织上的独立性收益
功能合并的实操策略
识别出可以合并的服务之后,合并过程需要稳妥推进:
合并前的评估清单
- 两个服务的 QPS 量级是否相近?(避免高流量服务被低流量服务的慢查询拖累)
- 两个服务的数据模型是否有强关联?(合并后是否可以消除分布式事务)
- 两个服务的变更频率是否趋同?(合并后不会造成发布冲突加剧)
- 合并后的代码量是否在可控范围内?(通常单个服务不超过 5 万行核心代码)
- 合并是否能消除至少一条跨服务调用链路?
合并的技术步骤
- 代码层合并:将两个服务的代码移入同一个模块,保留原有的包结构
- 接口兼容:对外的 API 保持不变,内部从 RPC 调用变为本地方法调用
- 数据迁移:如果两个服务各有独立数据库,评估是否需要合并数据库(通常先不合并,后续渐进)
- 流量切换:通过网关或注册中心将流量切换到合并后的新服务
- 旧服务下线:确认无流量后,下线旧服务,清理相关配置和监控
合并过程中最重要的原则是对外接口不变。上游调用方不需要感知底层的服务合并,这大幅降低了变更风险。
废弃服务的清理
除了合并,还有一类重要的归拢动作:清理那些已经不再有业务价值的"僵尸服务"。
僵尸服务的典型特征:
- 流量归零:连续 90 天没有任何线上流量
- 无主服务:原始开发者已离职,没有任何团队认领维护
- 功能冗余:其功能已被其他服务覆盖,但旧服务忘记下线
- 实验遗留:A/B 测试或临时活动产生的服务,活动结束后没有清理
清理步骤建议:
- 盘点阶段:通过服务注册中心和流量监控,自动识别低流量/无流量服务
- 公示阶段:在内部公告中列出拟下线服务清单,留出 2~4 周的缓冲期
- 降级阶段:将服务标记为"即将下线",在网关层添加废弃提示头
- 摘流阶段:从注册中心注销,停止接收新请求
- 下线阶段:停止容器、清理 CI 流水线、归档代码仓库
- 清理阶段:删除监控面板、告警规则、文档中的相关条目
一家大型视频平台曾做过一次集中清理,一次性下线了 230 多个僵尸服务,释放了超过 1500 个 CPU 核心的集群资源,同时减少了近 3000 条无效告警规则。运维团队反馈"第一次觉得告警通道安静了"。
治理方法论:从拆分到归拢的判断标准
“什么时候该拆、什么时候该合”——这是微服务治理中最核心也最模糊的问题。下面提供一套可操作的判断框架。
拆分合理性的四维评估
在决定是否保留一个微服务的独立存在时,从四个维度进行评估:
维度一:独立变更需求
该服务是否有独立的发布节奏?如果它总是和其他服务一起发布,独立部署的价值就很低。
维度二:独立扩缩容需求
该服务的资源需求模式是否独特?如果一个服务的 CPU 利用率始终在 5% 以下,它不需要独立的资源配额,合并到更大的服务中更经济。
维度三:团队独立性
是否有专门的团队负责这个服务?如果一个三人小组同时维护着十五个微服务,这些服务应该合并到更少的单元中。
维度四:故障隔离需求
该服务是否需要独立的容错边界?如果它的故障不会级联影响其他服务(比如一个内部工具),独立部署的故障隔离收益有限。
归拢决策矩阵
将上述四个维度做成一个决策矩阵,帮助团队做出客观判断:
| 评估维度 | 高分(保留独立) | 低分(考虑合并) |
|---|---|---|
| 变更频率 | 每周多次独立发布 | 月级发布,且总与其他服务同步 |
| 资源模型 | 有独特的 CPU/内存需求模式 | 资源利用率极低,无明显特征 |
| 团队归属 | 有专属团队,独立 on-call | 与其他服务共享维护者 |
| 故障域 | 高可用要求,故障会级联 | 故障影响面小,无级联风险 |
经验法则:如果一个服务在四个维度上有两个以上得"低分",就应该认真考虑将它与其他服务合并。
归拢的优先级排序
不是所有服务都值得同等关注。建议按照以下优先级排序归拢工作:
- 僵尸服务清理(最高优先级):零流量、无主服务直接下线,投入产出比最高
- 微型服务合并:代码量少于 2000 行、QPS 低于 10 的服务,优先合并到相邻服务中
- 高频联动服务合并:总是在同一需求中一起改动的服务对,合并后可以大幅减少协调成本
- 技术组件下沉:将"缓存管理服务"“文件上传服务"等技术组件下沉为中间件,而非独立微服务
- 领域边界重新划分(最低优先级):这需要更深层的架构调整,放在最后处理
组织配套:康威定律的另一面
康威定律告诉我们,系统的架构会映射组织的沟通结构。反过来说,当我们要调整系统架构时,组织结构也必须同步变化。
从"每个服务一个团队"到"每个域一个团队”
在微服务拆分的高峰期,很多公司推崇"两个披萨团队"模式——每个小团队负责一到两个微服务。这在早期确实带来了灵活性和速度。但当服务数量膨胀到数百个,这种模式的问题暴露了:
- 团队数量过多,跨团队沟通成本急剧上升
- 每个团队都在造自己的轮子(日志框架、监控方案、部署脚本)
- 团队之间的接口约定不一致,联调效率低下
归拢之后的组织模式应该是:每个业务域由一个中等规模的团队(8~15 人)负责,域内可以包含多个微服务,但团队对域内的所有服务承担端到端的责任。
这种模式的优势:
| 方面 | 改善 |
|---|---|
| 沟通成本 | 域内沟通替代跨团队沟通,减少 50%+ 的协调会议 |
| 技术一致性 | 同一团队维护的服务天然风格统一 |
| 上下文切换 | 工程师对域内所有服务都有上下文,减少知识孤岛 |
| On-call 效率 | 域内统一 on-call,不需要同时呼叫多个团队 |
建立服务治理委员会
归拢工作涉及多个团队的利益调整,需要有专门的组织来推动和仲裁。服务治理委员会(或架构治理委员会)的职责包括:
- 审批新服务创建:任何团队要创建新的微服务,必须经过评审,论证拆分的必要性
- 推动服务合并:识别应当合并的服务对,协调相关团队推进
- 维护服务目录:建立全公司的服务目录和元数据管理,让每个服务的状态、归属、流量都清晰可见
- 制定服务标准:定义服务的接入规范、下线流程、监控基线等标准
治理委员会不是要成为审批瓶颈,而是要成为"服务卫生"的守护者。就像城市需要规划局来防止无序扩张,微服务生态也需要治理机制来防止无序拆分。
工程师文化的转变
归拢治理不仅是技术决策,还涉及工程师文化的转变:
从"我的服务"到"我们的域":打破服务所有权的壁垒,鼓励工程师关注整个业务域而非单个服务。
从"追求拆分"到"追求简洁":在技术评审中,不再以"服务拆得多细"作为技术能力的衡量标准,而是以"用最少的服务解决问题"为目标。
从"各自为战"到"平台赋能":鼓励基础能力下沉到平台层,业务团队聚焦在业务逻辑上,而非重复建设基础设施。
实战案例:从 600 到 80 的归拢之路
以下综合多家大型互联网公司的公开实践,描述一个典型的归拢治理案例。
背景
某大型互联网公司的交易平台,经过四年的微服务演进,服务数量膨胀到 600 余个。团队面临的核心痛点:
- 一次大促前的全链路压测,需要协调 40+ 个团队同步准备环境
- 一个"退款"功能的改动需要修改 7 个服务的代码
- 超过 150 个服务的日均 QPS 低于 5,但仍然占用独立的计算资源
- 新人入职平均需要 3 周才能跑通本地开发环境
阶段一:合并编译(耗时 3 个月)
将分散在 600+ 个 Git 仓库中的代码,按业务域归拢到 12 个 Monorepo 中。
关键动作:
- 统一使用 Bazel 作为构建系统,实现增量编译
- 建立统一的 CI 模板库,所有服务共享同一套流水线
- 依赖版本统一锁定,消灭了 80% 以上的依赖冲突问题
效果:跨服务接口变更的平均交付周期从 5 天缩短到 0.5 天。
阶段二:僵尸服务清理(耗时 2 个月)
通过自动化脚本扫描服务注册中心和流量监控平台,识别出 180 个僵尸服务(90 天零流量或流量极低)。
关键动作:
- 建立"服务健康度评分"模型,综合流量、变更频率、团队归属等维度打分
- 分批公示下线计划,留出缓冲期让相关方确认
- 开发一键下线工具,自动化完成容器回收、配置清理、监控归档
效果:释放了约 2000 个 CPU 核心的资源,清理了 4000+ 条告警规则。
阶段三:服务合并(耗时 6 个月)
对剩余的活跃服务进行领域重新划分和功能合并。
关键动作:
- 将原来 14 个"通知类"微服务(短信、邮件、站内信、Push、WebSocket 等)合并为 2 个:消息路由服务和消息投递服务
- 将 8 个"用户画像类"微服务合并为 1 个用户标签服务
- 将分散在各业务线中的 20+ 个"配置管理"相关服务,统一收拢到平台层的配置中心
效果:活跃服务数量从 420 个精简到 80 个。
阶段四:长效机制建设(持续)
- 建立新服务创建的审批流程:必须论证四个维度中至少两个达到"高分"
- 每季度做一次服务健康度审计,识别新的归拢机会
- 将服务数量纳入团队的技术债务考核指标
归拢成果总结
| 指标 | 归拢前 | 归拢后 | 变化 |
|---|---|---|---|
| 服务总数 | 600+ | 80 | -87% |
| 代码仓库数 | 600+ | 12 | -98% |
| 平均发布协调涉及团队数 | 5.3 | 1.8 | -66% |
| 新人环境搭建时间 | 3 周 | 2 天 | -90% |
| 基础设施成本 | 基线 | -40% | 显著降低 |
| 跨服务分布式事务数量 | ~200 个/天 | ~30 个/天 | -85% |
归拢过程中的常见陷阱
归拢虽好,但操作不当也会踩坑。以下是实践中最常见的几个陷阱:
陷阱一:归拢过头,回到单体
归拢的目标是"合理的微服务数量",不是"一个服务"。如果一个合并后的服务代码量超过 10 万行、构建时间超过 30 分钟、每次发布都需要全量回归测试,说明合并过头了。
陷阱二:只合代码不合数据
两个服务合并了代码,但数据库仍然各自独立,结果每次数据操作还是需要跨库查询,复杂度不降反升。合并服务时,数据层的整合方案必须同步设计。
陷阱三:忽视组织配套
技术上合并了服务,但组织架构没调整,原来负责两个服务的两个团队仍然各自维护"自己的那部分代码",结果在同一个服务内部产生了隐性的"代码割据"。
陷阱四:一次性大爆炸式合并
试图在一个月内完成所有服务的归拢,结果引入了大量 bug,团队疲于应付,项目烂尾。正确的做法是分批渐进,每次合并 2~3 个服务,验证稳定后再推进下一批。
陷阱五:没有建立防反弹机制
辛辛苦苦精简到 80 个服务,半年后又膨胀到 200 个——因为没有建立新服务创建的审批机制。归拢的成果需要通过治理流程来守护。
落地建议:从哪里开始
对于正在考虑微服务归拢的团队,建议按以下步骤推进:
第一步:摸底盘点(1~2 周)
- 拉出全量服务清单,标注每个服务的流量、代码量、维护团队、最近变更时间
- 识别僵尸服务(90 天零流量)和高频联动服务对
- 输出一份《微服务健康度报告》
第二步:先做合并编译(1~3 个月)
- 选择一个业务域作为试点,将该域内的服务迁入 Monorepo
- 搭建统一的 CI/CD 流水线模板
- 验证增量构建和依赖治理的效果
第三步:清理僵尸服务(1~2 个月)
- 按照前述的清理六步法,分批下线僵尸服务
- 建立自动化的服务健康度监控,持续发现新的僵尸服务
第四步:推进服务合并(3~6 个月)
- 按照优先级排序,从微型服务合并开始,逐步推进到领域重新划分
- 每批合并后做稳定性验证,确保没有引入新的问题
第五步:建立长效机制(持续)
- 建立新服务创建审批流程
- 定期(每季度)进行服务健康度审计
- 将服务治理指标纳入技术管理看板
小结
微服务治理是一个持续的动态平衡过程。行业走过了"无脑拆分"的狂热期,正在进入"理性归拢"的成熟期。合并编译和服务精简是两把关键的归拢工具:前者在开发态将碎片化的代码和构建流程重新聚拢,后者在运行态将过度拆分的架构重新收敛到合理的粒度。
归拢不是要否定微服务,而是要让微服务回到它真正擅长的位置——在合理的粒度上实现独立部署和独立演进,而非为了拆分而拆分。
最终的衡量标准很简单:团队是否更高效了?系统是否更稳定了?运维是否更轻松了?如果三个问题的答案都是"是",那归拢就走在正确的路上。
