微服务到底解决了什么,又制造了什么?

我见过一种很典型的项目。

团队三四个人,业务还没真正上线,仓库里已经有:

gateway
auth-service
user-service
asset-service
workflow-service
notification-service
audit-service

再配 PostgreSQL、Redis、消息队列、对象存储、配置中心、链路追踪。开发机启动一次,十几个容器一起亮起来,看着确实很有“大系统”的感觉。

然后改一个登录字段。

前端要改,网关要改,认证服务要改,用户服务要改,Proto/OpenAPI 要重新生成,两个服务要一起发版,测试环境还得把依赖全拉起来。

这时候就该问一句了:

我们到底为什么要拆微服务?

AWS 对单体与微服务的对比示意

图:AWS 官方对 Monolith 与 Microservices 的对比示意。

很多人第一次接触微服务,会把它理解成“把大项目拆小”。这个理解只说对了一小半。

如果只是代码太大,模块化单体、Package 边界、依赖倒置、DDD 分层都能解决相当一部分问题,而且便宜得多。

微服务真正值钱的地方,是独立性

一个服务最好能由一支明确的团队负责,拥有自己的业务边界、代码、数据和发布节奏。它可以单独修改、单独测试、单独上线、单独扩容,出了问题也尽可能把影响限制在自己的边界里。

微软当前的 Azure Architecture Center 也把“独立部署”“独立扩缩容”“数据隔离”“小而聚焦的团队”列在微服务的核心收益里,同时明确提醒:微服务会引入服务发现、跨服务通信、数据一致性、事务管理和运维复杂度。

换句话说,微服务本质上是一笔交易。

你主动接受一部分分布式系统的麻烦,换取系统和团队在某些地方真正能够独立演进。

最实际的收益:发布边界终于可以缩小

假设一个大型单体里有订单、搜索、通知、用户、报表五块业务。

通知模块改了一行代码,理论上你可能仍然需要重新构建整个应用,然后把一个包含所有业务的新版本部署出去。

微服务拆得合理时,通知服务可以自己发布。

这件事对五个人团队未必值很多钱,但对几十支团队并行开发的大型组织,价值会非常明显。

真正的微服务通常伴随着一个现实组织问题:

我不希望 A 团队上线一个功能,还必须等 B、C、D 三个团队一起约窗口。

AWS 的官方指导甚至直接给出了“service per team”的模式:一个微服务由一支团队负责,团队能够独立开发、测试、部署和扩缩容,跨团队主要通过稳定 API 协商。

所以很多时候,微服务首先解决的是组织扩张以后产生的协作成本。

如果整个后端只有两个人,而且两个人每天坐在一起改同一个业务,先把系统拆成 18 个进程,收益往往没想象中大。

某一块特别吃资源时,终于不用全体陪跑

这也是微服务非常实在的价值。

比如你的系统里:

  • 普通 REST API 只需要 2 核 4G;
  • 文档解析非常吃 CPU;
  • AI 推理需要 GPU;
  • 文件转码需要大量临时内存;
  • 消息消费可以异步慢慢处理。

如果它们全部塞在一个进程里,扩容经常只能把整个应用一起复制。

拆开以后,谁忙扩谁。

这也是 AWS 和微软都反复强调的 independent scaling。

这种场景下,服务边界有非常明确的资源意义,不是为了架构图更漂亮。

故障也可以有边界

设计得好的情况下,推荐系统挂了,用户仍然能登录;报表服务挂了,核心交易仍然能进行;邮件服务暂时不可用,订单可以先进入队列,晚点再通知。

这叫降级。

但注意前提:设计得好。

如果登录要同步调用用户服务,用户服务同步调用组织服务,组织服务同步调用权限服务,权限服务再同步查另一个服务,那么任何一个节点超时都可能把整条请求链拖死。

AWS Well-Architected Framework 甚至给这种东西起了一个很形象的名字:Microservice Death Star

服务看起来很多,依赖线密密麻麻,任何一处变化都会沿着网络传播。

最后既没有单体简单,也没有微服务独立。

然后,账单来了

微服务最容易被忽略的一点,就是它把很多以前由进程内部保证的东西,交给了网络。

以前:

func A() -> func B()

现在:

service A
   |
 HTTP/gRPC
   |
service B

图上只多了一条箭头。

工程上多出来的东西可不少:

  • DNS 会不会解析失败;
  • 连接会不会超时;
  • 对方返回 500 怎么办;
  • 请求已经执行成功,但响应丢了怎么办;
  • 重试会不会造成重复写入;
  • API 如何版本兼容;
  • TLS 和服务身份怎么做;
  • 链路怎么追踪;
  • 对方发布新版本时自己会不会被带崩。

一次普通函数调用变成网络调用以后,“失败”从异常情况变成了日常设计的一部分。

这也是为什么微服务系统里 timeout、retry、circuit breaker、idempotency、trace ID 这些词出现得那么频繁。

一条数据库事务,也可能变成一篇论文

单体时代有个很好用的东西:数据库事务。

BEGIN;
UPDATE orders ...;
UPDATE inventory ...;
INSERT INTO payment_records ...;
COMMIT;

只要这些数据在同一个数据库里,很多一致性问题可以交给数据库处理。

拆成三个服务以后,情况可能变成:

Order Service -> Payment Service -> Inventory Service

三个服务各有自己的数据库。

这时已经没有一个简单的本地 COMMIT 能把三边一起提交。

你开始接触:

  • Saga;
  • 补偿事务;
  • Outbox;
  • 幂等消费;
  • 最终一致性;
  • 事件重复;
  • 事件乱序;
  • 消息投递失败。

微软官方文档说得很直接:多个微服务共同持久化一项业务变更时,完整操作通常很难继续保持传统意义上的 ACID 事务,需要接受和设计最终一致性。

这是分布式系统的价格。

Microsoft Azure 微服务示例架构

图:Microsoft Azure Architecture Center 的微服务示例。注意服务之外还有消息总线、独立存储和 Kubernetes。真正的复杂度往往就在这些“服务以外”的东西里。

日志也不再是一份日志

单体出错时,运气好的话:

grep ERROR app.log

就能找到堆栈。

微服务里,一个用户请求可能经过:

Gateway
 -> Auth
 -> User
 -> Permission
 -> Workflow
 -> NATS
 -> Worker

你看到 Gateway 返回 500,只能说明 Gateway 最后没拿到正确结果。

真正的问题可能发生在六跳之外。

于是你需要:

  • 结构化日志;
  • 全局 trace ID;
  • Metrics;
  • Distributed Tracing;
  • OpenTelemetry;
  • 服务依赖图;
  • 告警和 SLO。

微软最新的微服务 readiness assessment 把这些东西直接放进了采用微服务前后的评估清单:请求的 Trace Context 是否能跨同步和异步边界传播,日志、指标、Trace 能否按 trace ID 关联,依赖是否被监控。

如果这些基础能力完全没有,却先拆了几十个服务,事故发生以后通常会出现一种非常有仪式感的排障方式:

六个人同时打开六个终端,各自 docker logs -f

测试会突然变贵

以前测试一个业务:

go test ./...

现在可能要先启动:

PostgreSQL
Redis
NATS
MinIO
Gateway
Auth
User
Workflow
Worker
...

然后你会发现服务 A 的单测全过,服务 B 的单测也全过,A 调 B 还是炸了。

原因可以是:

  • 请求字段含义不同;
  • API 版本不同;
  • 错误码约定不同;
  • protobuf 没同步;
  • 超时策略不同;
  • 数据已经发生 schema 漂移。

所以微服务通常会把 Contract Test、Integration Test、E2E、真实依赖测试的重要性一起拉高。

这也解释了为什么大型微服务项目的 verify 很容易越来越长。之前站里专门写过一篇:《项目写到后来,为什么跑一次 verify 要半小时》

最危险的东西:分布式单体

我觉得这是小团队采用微服务时最值得警惕的结果。

表面上:

7 repositories
7 Docker images
7 services

实际上:

  • 大家共享同一个数据库;
  • 一个字段变更五个服务一起改;
  • 一个服务不能脱离另外三个服务启动;
  • 每次上线所有服务一起发;
  • 测试必须完整启动全套环境;
  • 一个公共库升级,全仓一起升级;
  • 服务之间大量同步调用。

这时候你拥有的只是单体的耦合 + 分布式系统的故障模式

代码边界切开了,业务边界没切开。

这种架构最大的成就,大概是把一次函数调用变成了一次网络请求。

如果两个服务总是一起修改、一起部署、一起扩容,那么它们为什么一定要是两个服务?

这个问题比“每个服务多少行代码”重要得多。

服务应该多小?

我不太喜欢用代码行数回答这个问题。

微软的文档也明确强调,比服务物理大小更重要的是内聚性和独立性。

更实用的判断方式是看几件事:

它有明确的业务能力吗?

“用户管理”可能有。

“UserControllerService”和“UserDatabaseService”通常只是把技术分层硬切成网络边界。

它能独立发布吗?

如果每次修改都必须协调三个服务同时发,边界很可能切错了。

它拥有自己的数据吗?

如果所有服务都直接读写同一组表,那么所谓服务自治很容易只剩 API 这一层。

它真的需要独立扩容或隔离故障吗?

如果答案长期都是“不需要”,进程边界带来的收益就值得重新算账。

有明确的人负责吗?

创建 notification-service 很容易。

两年以后还有谁负责升级它的依赖、处理漏洞、修告警,这才决定它是不是一个真正的服务。

这和上一篇 《我们是不是正在制造越来越多没人维护的软件?》 其实是同一个问题的另一面。

小团队,我更喜欢“模块化单体优先”

如果是一个新项目:

  • 开发者 2~8 人;
  • 业务边界还在不断变化;
  • 用户规模还没真正验证;
  • 发布频率没有形成团队瓶颈;
  • 没有明显的独立扩缩容需求;
  • 运维平台能力还不成熟;

我通常更倾向先把一个单体写干净。

注意,是模块化单体,不是所有代码塞进一个 service.go

目录和依赖关系仍然可以按照领域切:

internal/
  identity/
  organization/
  workflow/
  asset/
  notification/

模块之间通过清晰接口交互,数据库表也划好所有权。

等哪一天你真的发现:

notification 的流量和主业务差异很大;

workflow 已经有独立团队;

AI worker 必须跑 GPU;

某个模块发布频率严重拖累其他模块;

再把那个边界抽出去。

这时候拆服务是在解决已经存在的问题。

而不是提前解决一个想象中的“未来十亿用户”。

AWS 的 Well-Architected 指导里有一句很实际的意思:新产品冲首发和一个从第一天就明确要大规模扩展的系统,架构选择本来就不该一样。更小的服务能带来敏捷、扩展和故障隔离,同时也会增加延迟、调试难度和运维负担。

这个 trade-off 不会因为 Kubernetes 很流行就消失。

Kubernetes 也救不了错误的服务边界

Kubernetes 很强。

它可以帮你做:

  • Deployment;
  • Service Discovery;
  • Load Balancing;
  • Health Probe;
  • Rolling Update;
  • Autoscaling;
  • Desired State Reconciliation。

但 Kubernetes 不知道:

order-service

payment-service

到底应该不应该拆开。

它也不知道两个服务为什么每天互相调用 50 次。

平台能管理分布式系统的运行,没法替你修正错误的领域边界。

把一个耦合严重的系统装进 Kubernetes,只会得到一个被自动编排得非常专业的耦合系统

AI 又把这个问题放大了一次

现在生成一个新服务越来越便宜。

让 Agent 创建:

cmd/
internal/
Dockerfile
Helm Chart
OpenAPI
health endpoint
metrics
CI

可能几分钟就出来了。

于是架构图很容易迅速膨胀。

但真正昂贵的东西依然没变:

  • 这个边界五年后是不是还合理;
  • API 谁负责兼容;
  • 数据归谁;
  • 故障怎么恢复;
  • 谁值班;
  • 谁理解跨服务事务;
  • 谁在一次线上故障后把调用链完整还原出来。

AI 降低的是创建服务的成本,没有同步降低拥有一个服务的长期成本。

这跟之前写的 《AI 把写代码变便宜以后,谁来替这些代码还债?》 很像。

我现在怎么判断一个项目要不要上微服务

如果让我看一个项目,我不会先问:

用不用 Kubernetes?

我通常先问下面这些:

1. 现在有多少支能够独立交付的团队?
2. 哪些业务模块真的需要独立发布?
3. 哪些模块资源模型明显不同?
4. 哪些故障必须被隔离?
5. 数据边界能不能清楚划分?
6. 跨服务事务到底有多少?
7. CI/CD、日志、Tracing、监控成熟了吗?
8. 谁拥有每一个服务?

如果前四个问题都答不上来,但已经准备部署 30 个微服务,我会建议先等等。

如果一个系统已经有十几支团队,一个单体每次发版要排队,某一块流量是其他模块的一百倍,不同业务需要完全不同的可靠性等级,那么继续坚持“一个程序最简单”,同样可能是在制造问题。

微服务没有天然的高级感。

单体也没有天然的落后感。

真正应该优化的是系统面对的现实约束。

最后

我越来越觉得,一个项目开始讨论微服务时,最有价值的问题不是:

“我们应该拆成几个服务?”

更应该先问:

“我们现在到底遇到了什么问题,值得为它引入一个分布式系统?”

如果这个问题没有答案,先别急着画那张满是六边形和箭头的架构图。

毕竟把一个简单问题做成分布式系统很容易。

把分布式系统长期维护好,完全是另一件事。


参考资料

  • Microsoft Azure Architecture Center:Microservices architecture style
    https://learn.microsoft.com/en-us/azure/architecture/guide/architecture-styles/microservices
  • Microsoft Azure Architecture Center:Microservices Assessment and Readiness
    https://learn.microsoft.com/en-us/azure/architecture/guide/technology-choices/microservices-assessment
  • AWS:What are Microservices?
    https://aws.amazon.com/microservices/
  • AWS Well-Architected Framework:Choose how to segment your workload
    https://docs.aws.amazon.com/wellarchitected/latest/framework/rel_service_architecture_monolith_soa_microservice.html
  • AWS Prescriptive Guidance:Service per team pattern
    https://docs.aws.amazon.com/prescriptive-guidance/latest/modernization-decomposing-monoliths/service-per-team.html
  • Kubernetes:Services, Load Balancing, and Networking
    https://kubernetes.io/docs/concepts/services-networking/