从单体拆分为微服务后,我们的响应时间反而变长了?

发布时间:2026/9/13 11:04:39
从单体拆分为微服务后,我们的响应时间反而变长了?
去年我们把一个运营了五年的单体应用拆成了十二个微服务。拆分前下单接口平均响应180毫秒拆分后同样的接口P99直接飙到1.2秒。团队一度怀疑是不是拆错了。复盘了两个月终于把响应时间压回200毫秒以内。这篇文章把踩过的坑和优化过程完整还原如果你也正被微服务性能问题困扰应该能少走不少弯路。第一个坑把本地方法调用变成了网络调用单体时代订单服务调用库存服务就是一行inventoryService.deduct()纳秒级。拆成微服务后这行代码变成了HTTP请求即使在内网一次往返也要5-10毫秒。更致命的是一个下单流程原本有七八个内部调用全部变成远程调用后光是网络开销就叠加上百毫秒。解法合并粗粒度接口。原来查订单要调用户、商品、库存三个服务现在订单服务直接聚合对外只暴露一个接口。跨服务的调用次数从七次降到两次响应时间立刻砍掉一半。第二个坑序列化和反序列化的隐形开销单体时代对象在JVM内直接传递没有序列化。微服务之间用JSON传输一个订单对象序列化要1毫秒反序列化再1毫秒十次调用就是20毫秒。如果对象嵌套深、字段多开销更大。解法换用Protobuf或Avro。我们后来把核心链路改成gRPCProtobuf序列化开销降到原来的十分之一。非核心链路继续用JSON但严格控制字段数量禁止返回大对象。第三个坑数据库拆分后的跨库查询原来一个库订单和用户表JOIN一下就行。拆开后用户数据在用户服务订单服务只能先查订单再调用户服务拿用户信息。一次JOIN变成了两次查询加一次网络调用。更糟的是如果还要查商品信息又得多调一次。解法数据冗余。在订单表里冗余用户名和商品快照下单时一次性写入。查询时不需要跨服务直接从订单表读。代价是数据一致性但订单场景下快照本身就是业务需求冗余反而更合理。第四个坑服务发现和负载均衡的额外跳转微服务架构下每次调用都要经过服务发现如Nacos、Consul拿到实例列表再经过负载均衡选一个节点。如果用了API网关还多一层转发。这些跳转在单体时代完全不存在。解法客户端负载均衡本地缓存。用Ribbon或Spring Cloud LoadBalancer实例列表缓存在本地避免每次调用都查注册中心。网关只做鉴权和路由不做业务聚合减少一跳。第五个坑分布式事务拖慢响应拆分后下单要同时扣库存、创建订单、扣优惠券三个服务需要保证一致性。我们一开始用了Seata的AT模式结果每次下单要等全局锁响应时间直接翻倍。解法改最终一致性。下单先写本地订单表发消息到MQ库存和优惠券异步消费。前端先返回“下单处理中”几秒后刷新状态。用户体验没有明显下降但响应时间从800毫秒降到了120毫秒。第六个坑链路追踪和日志拖后腿拆成微服务后每个服务都打日志、上报监控。如果日志是同步写磁盘或者链路追踪采样率过高都会拖慢响应。我们曾经因为Zipkin全量采样导致每个请求多出30毫秒。解法日志改异步Appender链路追踪采样率降到5%错误请求100%采集。生产环境关闭DEBUG级别只保留WARN和ERROR。总结微服务拆分后响应变长本质是把单体内部的“免费”调用变成了“付费”的网络调用。每一跳都有成本叠加起来就非常可观。优化的核心思路只有一条减少远程调用次数能聚合就聚合能异步就异步能冗余就冗余。微服务不是性能银弹它用响应时间换取了可扩展性和团队自治。如果业务量不大、团队规模小单体反而是更优解。拆分之前先算清楚这笔账。