我为什么从微服务退回单体架构
------一次"技术升级"的反思与回归
一、那个让我冲动的下午
三年前,我坐在工位上,刷着技术博客。满屏都是"微服务是未来的方向""单体架构是技术债的根源""Netflix 是如何用微服务支撑亿级流量的"。
当时我们系统的情况是这样的:一个运行了两年多的单体应用,代码大概 8 万行,部署一次需要 5 分钟,团队 6 个人。
说实话,系统跑得好好的。但架不住心里那股劲儿------"别人都在用微服务,我们还在单体,是不是技术落后了?"
于是我花了两周时间,画了一张服务拆分图,把原来的单体拆成了 9 个微服务。订单服务、用户服务、支付服务、商品服务、通知服务、日志服务、配置服务、网关服务、还有一个说不清干嘛的"公共服务"。
我至今记得第一次把整套系统跑起来的那个晚上。9 个服务,3 个数据库,2 个消息队列,1 个注册中心,1 个配置中心。屏幕上密密麻麻的终端窗口,像极了黑客帝国。
我当时的感觉是:终于像个正经架构师了。
二、然后现实开始打脸
第一个月:开发效率断崖式下跌
以前改一个功能,打开一个项目,改完,提交,部署。现在呢?
改一个"用户下单"的功能,涉及订单服务和用户服务。我得同时打开两个项目,改完用户服务,写个接口,改完订单服务,调接口。然后发现两个服务的 API 版本对不上,又得回去改。
一个简单的需求,以前半天搞定,现在要两天。
更崩溃的是本地调试。以前 npm run dev 或者 mvn spring-boot:run 一把梭,现在得先把注册中心跑起来,再把配置中心跑起来,然后依次启动各个服务。哪天某个服务的端口被占了,或者配置文件写错了,整个链路就跑不通。
我组里一个新来的同事,配了三天环境,最后跟我说:"哥,我想跑路。"
第三个月:运维噩梦
9 个服务,每个服务至少两个实例做高可用。加上中间件,我们服务器上跑了接近 30 个进程。
然后问题来了:
- 服务 A 调服务 B,B 超时了。 是 B 本身慢,还是网络问题,还是注册中心把 A 指向了一个不健康的 B 实例?排查一次至少半小时。
- 数据库分散了。 以前一个库,JOIN 一下完事。现在订单和用户在两个库,要查"某个用户的所有订单",得先查用户服务拿 ID,再拿 ID 去订单服务查。后来干脆上了 Elasticsearch 做聚合,又多维护一个组件。
- 日志分散了。 一个请求经过网关 → 用户服务 → 订单服务 → 支付服务,出了问题你得挨个翻 4 个服务的日志,靠一个 traceId 串联。我至今记得有天凌晨三点,我一边翻 Kibana 一边骂自己。
第六个月:团队开始分裂
6 个人的团队,每人负责 2-3 个服务。表面上"术业有专攻",实际上:
- 小张负责支付服务,对订单服务的逻辑一窍不通。订单服务要改个字段,得跟小李沟通半天,小李还不一定有空。
- Code Review 变成了走过场。没人能同时看懂所有服务的代码,review 的时候大家都在点头。
- 线上出问题,互相甩锅。"我这边接口返回没问题啊,是你调的方式不对吧。"
最讽刺的是:我们拆微服务,本意是让团队并行开发更快。结果变成了并行地互相阻塞。
三、我终于承认:我们不是 Netflix
回过头来看,微服务不是不好。微服务是一把手术刀,精准、强大,但前提是你得有手术室、麻醉师、护士团队,以及一台值得动手术的病人。
我们缺什么?
| 微服务的前提条件 | 我们当时的情况 |
|---|---|
| 团队规模 20+ 人 | 6 人 |
| 清晰的领域边界 | 业务还在快速变化,边界每月都在变 |
| 成熟的 DevOps 体系 | 一个半吊子运维 + 我这个兼职架构师 |
| 独立的数据库团队 | 没有,DBA 是隔壁组借的 |
| 完善的监控和链路追踪 | 没有,出问题靠人肉 |
| 服务治理经验 | 没有,全靠网上抄 |
**我们不是 Netflix,甚至连 Netflix 的 1% 都不到。** Netflix 有上千个工程师,有专门的基础设施团队,有自研的 Chaos Monkey 做混沌测试。他们用微服务是因为不得不这么做------单体根本装不下他们的复杂度。
而我们呢?我们的"复杂度"是 8 万行代码,6 个人,一个部署包。
这不是复杂度,这是矫情。
四、决定退回的那天
转折点是一个线上事故。
用户反馈下单失败。我排查了三个小时,发现是订单服务调用户服务时,因为网络抖动导致超时,但订单服务没有做好重试和降级,直接抛了异常给用户。
修复方案很简单:加个熔断和重试。但问题是,这个逻辑散落在 4 个地方------订单服务调用户服务、订单服务调库存服务、订单服务调支付服务、支付服务调通知服务。每个地方用的库版本还不一样,有的用 Hystrix,有的用 Resilience4j,有的干脆什么都没用。
我坐在那,突然觉得很荒谬。
一个单体应用里,这种问题根本不存在。方法调用失败了?加个 try-catch,完事。现在呢?我得在分布式系统里处理网络不可靠、服务不可用、消息丢失、数据不一致......
而这一切,只是为了让 6 个人能"独立开发"。
那天晚上我跟技术总监聊了很久。他说了一句话让我醍醐灌顶:
"架构是为业务服务的,不是为架构师的面子服务的。"
五、退回单体,但这次不一样
我们没有一夜之间把所有微服务合并回去。那太激进了。我们是分三步走的:
第一步:止血
先把最痛的几个问题解决:
- 把配置中心干掉,改回本地配置文件 + 环境变量
- 把消息队列干掉,改回应用内的事件驱动
- 把分布式事务干掉,改回数据库事务
这一步做完,运维复杂度直接砍掉一半。
第二步:合并
按"改动频率"和"业务耦合度"重新评估。发现 9 个服务里,真正需要独立的只有 2 个:支付服务(对接第三方,安全隔离需要)和通知服务(异步发送,可以独立)。
其余 7 个服务,合并成一个单体。不是简单地把代码堆一起,而是重新用模块化方式组织:
monolith/
├── modules/
│ ├── user/
│ ├── order/
│ ├── product/
│ ├── payment/ (只留接口定义,实现在独立服务)
│ └── notification/ (同上)
├── shared/
│ ├── common/
│ └── db/
└── api/
每个模块有明确的边界和接口,但部署时是一个包。
第三步:建立纪律
这是最关键的一步。单体架构最大的风险是"大泥球"------代码越来越乱,最后谁都不敢动。
所以我们立了几条规矩:
- 模块之间只能通过接口通信,不能直接访问对方的数据库表
- 每个模块有独立的测试套件,CI 里模块级别跑测试
- 定期做架构评审,当某个模块的复杂度或团队规模真的到了需要拆分的程度,再拆
- 监控和告警不能省,单体的监控比分布式简单得多,没理由不做
六、退回之后的变化
说几个数据:
| 指标 | 微服务时期 | 退回单体后 |
|---|---|---|
| 部署时间 | 20 分钟(全链路) | 3 分钟 |
| 新功能开发周期 | 平均 5 天 | 平均 2 天 |
| 线上故障排查时间 | 平均 2 小时 | 平均 15 分钟 |
| 服务器成本 | 12 台 | 3 台 |
| 团队满意度 | 低(新人离职率 40%) | 高(半年 0 离职) |
但比数据更重要的是心态的变化。
以前大家每天都在跟基础设施搏斗------服务注册不了、配置拉不到、链路追踪断了。现在大家终于可以把精力放在写业务代码上了。
那个差点跑路的新同事,现在是我们组最积极的 code reviewer。
七、我学到了什么
1. 康威定律是真实的
设计系统的架构,受制于产生这些设计的组织的沟通结构。
6 个人,拆成 9 个服务,本质上是在用技术架构强行模拟一个大团队的协作模式。结果就是沟通成本爆炸,每个人都成了"分布式系统里的一个节点",信息在传递中不断丢失。
团队规模决定了架构的复杂度上限。
2. 分布式系统的复杂度不是线性的
很多人以为:把单体拆成 N 个服务,复杂度除以 N。
错。复杂度是乘以 N。
因为你在单体里不需要处理的那些问题------网络分区、数据一致性、服务发现、熔断降级、分布式追踪------全部变成了你必须处理的问题。而且每一个都是深坑。
3. 模块化是微服务的"平替"
很多人把"单体"等同于"大泥球"。这是误解。
一个设计良好的模块化单体,完全可以做到:
- 模块边界清晰
- 团队可以并行开发不同模块
- 单个模块可以独立测试
- 将来需要时,把某个模块拆成独立服务(只需要改接口实现,不需要改调用方)
模块化单体 + 良好的架构纪律 = 80% 的微服务收益,10% 的复杂度。
4. 技术选型要问"为什么",而不是"为什么不用"
每次引入一个新东西,问自己:
"如果不引入这个,我最大的痛苦是什么?这个痛苦真的到了必须解决的程度吗?"
如果答案是"其实忍忍也能过",那就先忍忍。等技术真的痛了,你自然知道该用什么。
八、最后想说的话
这篇文章不是劝你不要用微服务。如果你的团队有 50 人,业务复杂到单体已经管不住了,那微服务可能是正确的选择。
这篇文章是写给那些和我当年一样的人:
- 团队不到 10 人
- 业务还在探索期,需求变化快
- 没有专职的运维和基础设施团队
- 系统跑得好好的,只是"感觉"该升级了
如果你符合以上大部分条件,请认真考虑:你的系统真的需要微服务吗?
有时候,最好的架构决策不是"加上什么",而是"忍住不加什么"。