跳至主要內容

程序员小富大约 6 分钟

大家好,我是小富。

《十万个why》系列持续更新中

这两年"微服务"几乎成了架构升级的政治正确。团队一拍板:咱也搞微服务!然后花了半年把一个好好的单体应用拆成了十几个服务。

结果呢?接口响应时间从 50ms 涨到了 500ms,偶尔还飙到几秒。大家面面相觑:不是说微服务是先进架构吗?怎么越搞越慢了?

一次方法调用变成了一次网络请求

这是最直接的原因,也是最容易被低估的。

单体架构下,一个请求进来,Service A 调用 Service B,就是一次进程内的方法调用,耗时在纳秒到微秒级别。

拆成微服务后,同样的调用变成了一次 HTTP/RPC 请求:

单体:ServiceA.process() → ServiceB.calculate()
      进程内调用,< 1ms

微服务:ServiceA → [序列化] → [网络传输] → [反序列化] → ServiceB → [序列化] → [网络传输] → [反序列化] → ServiceA
      一来一回,3ms ~ 50ms(取决于网络状况和数据大小)

一次调用多 5ms 你可能觉得没什么。但一个业务请求往往是一条链路调用:

API Gateway → 用户服务 → 订单服务 → 商品服务 → 库存服务 → 优惠券服务 → 支付服务

6 次服务间调用,每次 5ms,光网络开销就加了 30ms。如果某些调用是串行的,还要算上序列化/反序列化的 CPU 时间。

更要命的是,这 5ms 只是理想情况。一旦遇上网络抖动、服务发现延迟、负载均衡切换,单次调用可能飙到几十甚至几百毫秒。

分布式事务拖慢了写操作

单体里一个 @Transactional 就搞定的事情,微服务里变成了分布式事务。

以下单为例:

单体:
@Transactional
public void createOrder() {
    orderDao.insert(order);       // 插入订单
    stockDao.deduct(sku, qty);    // 扣减库存
    couponDao.use(couponId);      // 使用优惠券
}
// 同一个数据库,同一个事务,几毫秒搞定

微服务:
public void createOrder() {
    // Saga 模式或 TCC 模式
    orderService.create(order);          // 调用订单服务
    stockService.deduct(sku, qty);       // 调用库存服务
    couponService.use(couponId);         // 调用优惠券服务
    // 任何一步失败,要触发补偿回滚...
}

不管你用 Seata 还是手写 Saga,分布式事务的性能开销都远大于本地事务:

  • TCC 模式:每个服务要执行 Try + Confirm/Cancel,调用次数翻倍
  • Saga 模式:要额外存储状态机的状态,每步操作后要记日志
  • Seata AT 模式:要记录前后镜像(undo log),还有全局锁的竞争

一个本地事务 5ms 能搞定的事情,分布式事务可能要 50 ~ 200ms。

数据查询从 JOIN 变成了多次 RPC

单体架构下,所有数据在一个库里,你可以随便 JOIN:

-- 一条 SQL 搞定
SELECT o.*, u.name, p.title 
FROM orders o
JOIN users u ON o.user_id = u.id  
JOIN products p ON o.product_id = p.id
WHERE o.id = 123;

微服务拆分后,用户表在用户服务、商品表在商品服务、订单表在订单服务。不能跨库 JOIN 了,只能这样:

// 先查订单
Order order = orderService.getById(123);
// 再查用户
User user = userService.getById(order.getUserId());
// 再查商品
Product product = productService.getById(order.getProductId());
// 手动组装
OrderVO vo = assembleOrderVO(order, user, product);

三次 RPC 调用代替了一条 SQL。即使你用 CompletableFuture 做并行调用,也至少要一次网络往返的时间。

如果是列表查询就更惨了。查 20 条订单,每条订单要查用户和商品,你可能需要批量调用接口或者做本地缓存来优化,复杂度蹭蹭往上涨。

服务治理本身也有开销

微服务架构下,你需要一套完整的基础设施:

组件开销
服务注册/发现(Nacos/Eureka)每次调用前要查询服务地址,虽然有本地缓存但也有刷新开销
负载均衡选择目标实例需要时间
熔断/限流(Sentinel/Hystrix)每次调用都要经过熔断器判断
链路追踪(Skywalking/Zipkin)每次调用都要上报 Trace 数据
API 网关请求多经过一层转发

这些组件每个单独看开销不大,但全部叠加在一起,每次 RPC 调用的额外开销可能就有 1-3ms。乘以调用链的长度,累计起来就很可观了。

那微服务的优势是什么?为什么还要拆?

微服务的优势从来不在"性能更好",而在:

  1. 独立部署:改了用户服务,不用重新部署订单服务
  2. 独立扩展:订单服务扛不住了,单独扩容它就行
  3. 技术栈灵活:不同服务可以用不同语言
  4. 团队自治:每个团队负责自己的服务,互不干扰
  5. 故障隔离:优惠券服务挂了,不影响下单的核心流程

这些都是组织和工程层面的优势,不是性能优势。

微服务本质上是用性能(网络开销)换取了可维护性和可扩展性。这个交换在大团队、大规模系统中是值得的,但在小团队、小系统中可能是亏的。

什么时候不该拆微服务

知乎上关于"微服务不如单体"的讨论越来越多,连亚马逊都公开发文说他们把某些微服务合回了单体。

以下情况建议别拆:

  • 团队不到 10 个人:微服务的运维复杂度需要足够的人手来消化
  • 业务还没稳定:需求天天变,边界都画不清,拆了等于白拆
  • 没有完善的基础设施:没有 CI/CD、没有监控、没有链路追踪,微服务就是灾难
  • 调用链很长但流量不大:所有服务加起来都跑在一台机器上能扛住,何必拆

一句话:如果你的问题不是微服务能解决的问题,那拆了只会多出问题。

总结

微服务拆分后系统变慢的根本原因:

进程内调用 → 进程间网络调用(慢 100 倍以上)
本地事务 → 分布式事务(慢 10 倍以上)
SQL JOIN → 多次 RPC 查询 + 手动组装
零治理开销 → 注册发现 + 负载均衡 + 熔断 + 追踪(每次调用叠加)

微服务不是架构的银弹,它是一种用性能换工程效率的交易。在团队够大、业务够复杂、基础设施够完善的前提下,这笔交易是划算的。否则,一个精心维护的单体可能比一堆乱拆的微服务好得多。


我是小富,下期见。

上次编辑于: