软件架构模式:把“我的页面“拆成 5 个微服务后,一次渲染要并发等 5 次 RPC

📅 2026/8/18 6:27:52
软件架构模式:把“我的页面“拆成 5 个微服务后,一次渲染要并发等 5 次 RPC
title: 软件架构模式把我的页面拆成 5 个微服务后一次渲染要并发等 5 次 RPCtags: [软件架构, 微服务, serverless, 单体架构, 服务拆分]categories: [后端, 架构]我们曾经是微服务原教旨主义者用户中心按功能切成 profile、auth、preference、address、points 五个服务理由是每个服务独立部署、独立扩容。直到我的页面上线前端一次渲染并发调这 5 个服务任何一个慢 200ms整页就慢 200ms大促时 points 服务抖动连带把个人主页一起拖垮。那次我们才明白架构模式没有先进落后只有贴合不贴合你的团队规模和调用关系。这篇把单体、SOA、微服务、Serverless 的真实代价摆出来重点讲那个拆过头的坑以及 Serverless 冷启动在什么场景下会反咬你一口。事故现场一次渲染打 5 个服务我的页面要展示昵称、登录状态、偏好设置、收货地址、积分。我们按服务拆分把它们分到了 5 个微服务前端BFF 层这么聚合// 错误示范强耦合的我的页面被拆成 5 个服务并发调用脆弱且慢 public MyPageVO buildMyPage(long userId) { CompletableFutureProfile p1 svc.profileAsync(userId); // 昵称 CompletableFutureAuth p2 svc.authAsync(userId); // 登录态 CompletableFuturePref p3 svc.prefAsync(userId); // 偏好 CompletableFutureAddress p4 svc.addressAsync(userId); // 地址 CompletableFuturePoints p5 svc.pointsAsync(userId); // 积分 CompletableFuture.allOf(p1, p2, p3, p4, p5).join(); // 全部等到最慢的那个 return new MyPageVO(p1.join(), p2.join(), p3.join(), p4.join(), p5.join()); }逐行看第 3-7 行发起 5 个异步调用第 8 行allOf(...).join()的意思是等最慢的那个完成。这 5 个服务里profile/auth/pref/address 的数据其实来自同一个用户库、同一个用户生命周期彼此强耦合——用户改了昵称profile 和 pref 大概率一起变。把它们拆开只换来部署独立代价是每次渲染多 5 次网络往返、多 5 个故障点。points 服务一旦抖动整页 P99 被它拽高。正确的切分按演进速度而非功能名词我们后来重切了一刀profile、auth、pref、address 这 4 个数据同源、变更同步、调用同频的合并回一个用户服务只有 points积分有独立的活动运营节奏、独立的数据量、独立扩容诉求单独留成服务。聚合层从 5 调变 2 调// 正确示范强耦合的合并成一个服务只把演进速度不同的留作独立服务 public MyPageVO buildMyPage(long userId) { // 一次本地/同机房调用拿齐昵称、登录态、偏好、地址 UserAggregate u userService.loadAggregate(userId); // 只有积分是真正独立演进的服务单独调 Points pts pointsClient.get(userId); return new MyPageVO(u, pts); // 2 次调用故障点减半 }逐行看第 4 行userService.loadAggregate在合并后的用户服务里一次查出 4 块数据甚至可以是一次 SQL 多表 JOIN 或同库多次查都在一个进程内无网络往返第 6 行只单独调积分服务。网络往返从 5 次降到 2 次最慢依赖从5 个里最慢变成积分服务一个整页 P99 明显下降。我的页面这种读多写少、数据强耦合的场景单体/聚合服务明显比拆碎了更划算。Serverless 的冷启动坑同步调用被拖垮另一个被模式光环坑到的地方是 Serverless。我们把图片缩略图处理写成函数以 AWS Lambda Java 运行时为例期望按需执行、不用养机器。但图片上传是同步链路的一环——用户传完头像要立刻看到缩略图// Serverless 函数Java 运行时做缩略图冷启动代价高 public class ThumbnailHandler implements RequestHandlerS3Event, String { // 静态初始化里加载了 ImageIO 缩略图库冷启动时一次性加载 ~1.2s private static final Thumbnailer THUMB new Thumbnailer(); Override public String handleRequest(S3Event event, Context ctx) { String key event.getRecords().get(0).getS3().getObject().getKey(); return THUMB.resize(key, 200, 200); // 业务逻辑本身只要 80ms } }逐行看第 4 行静态初始化Thumbnailer在冷启动时加载JVM 图片库初始化约 1.2 秒第 8 行真正的缩略图逻辑只要 80ms。问题在于这个函数是同步被上传链路调用的——用户传完图上传服务同步 invoke 这个函数等结果。函数长时间没流量被回收下次调用先经历 1.2 秒冷启动上传接口的 P99 直接从 200ms 飙到 1.5 秒。Serverless 适合异步、可延迟的任务比如把缩略图改成上传完成后发消息函数异步消费不适合卡在同步主链路里。SOA 的 ESB 陷阱一个总线成了全公司的单点聊架构模式绕不开 SOA因为很多人是从单体直接跳微服务踩坑后才发现中间还有 SOA 这层。我们早年在 SOA 阶段用企业服务总线ESB把所有系统打通订单、库存、用户、支付都通过 ESB 转发报文。听起来解耦了实际上 ESB 成了全公司的同步单点。一次大促ESB 上某个 XSLT 报文转换规则写得重一个订单报文要过 6 次 XPath 抽取再拼装单条转换 30ms峰值 8000 TPS 时 ESB 节点的 8 核 CPU 打满所有走总线的交易一起变慢。更糟的是 ESB 是同步转发前面订单系统超时了请求还在 ESB 队列里排队雪崩顺着总线传到库存、支付。那次之后我们把重转换挪到各业务系统内部做ESB 只做轻量路由并给总线加了异步队列隔离。SOA 的教训和微服务的教训是同一句话的反面总线式集中治理在流量小的时候很省心流量一大就变成瓶颈而微服务式去中心化把治理成本摊到每个服务灵活但运维重。选 SOA 还是微服务本质是在集中治理的瓶颈风险和去中心化的运维成本之间 trade-off不是谁淘汰谁。四种模式到底怎么选模式适用团队/规模拆得越细越好典型代价单体小团队10 人、单一业务适合早期别急着拆代码膨胀、部署耦合SOA多系统集成的企业按企业总线解耦ESB 易成瓶颈、治理重微服务中大型、团队按域划分否按演进速度拆网络延迟、分布式事务、运维复杂Serverless事件驱动、流量波动大、异步为主函数粒度看场景冷启动、调试难、 vendor 绑定我们现在的判断是微服务不是把服务拆小是按业务域和演进速度划边界。一个服务该不该独立看三件事——①数据是否同源同生命周期②发布节奏是否不同③是否需要独立扩缩容。三条里只满足一条就先别拆。复盘真实数字我的页面拆 5 服务时渲染 P99 为420ms其中网络往返占约 210ms大促时 points 服务抖动整页错误率一度4.7%。合并回 2 调用后渲染 P99 降到190ms网络往返占比降到约 60ms整页错误率和 points 服务解耦points 抖动不再影响主页。缩略图函数冷启动1.2s导致上传接口 P99 从 200ms 飙到1.5s改成异步消费后上传接口 P99 回到210ms缩略图在 1 秒内最终一致生成用户无感。我们审计了一遍服务边界把 23 个微服务合并成 14 个运维工单跨服务链路排查同比下降约 40%。SOA 阶段那次 ESB 瓶颈单条报文转换30ms峰值8000 TPS把 8 核 ESB 节点 CPU 打满连带拖垮订单/库存/支付三条链路把重转换下沉到业务系统、ESB 改纯路由 异步队列后总线 CPU 峰值从 100% 降到35%交易超时率归零。这轮拆分合并下来单机到微服务的演进我们前后推翻了两次边界团队从盲目拆变成按数据生命周期和发布节奏划界新需求平均交付周期反而缩短了一天半。我的取舍判断第一别用功能名词拆服务用演进速度拆。profile/auth/pref/address 拆成 5 个是教科书式的过度拆分它们同一份数据、同一次变更、同一节奏拆开只增加网络跳数和故障点。真正值得独立的是 points 这种有自己运营活动、自己数据量曲线的。第二Serverless 是给异步任务用的别塞进同步主链路。它的冷启动在 Java 运行时尤其明显JVM 启动 依赖加载放进用户传完图立刻看结果这种同步路径就是给 P99 埋雷。改成事件触发、异步消费才发挥它的弹性长处。第三架构模式要回头看别一次定终身。我们从单体到微服务拆过头再合并不是走回头路是让边界贴合真实调用关系。合并不等于架构失败拆也不等于先进。我更倾向于每季度审计一次服务边界数据同源的该合就合。第四单体不是临时方案是多数项目的最优解。我们见过太多团队一上来就微服务结果 3 个人维护 8 个服务一半精力花在搭链路追踪和网关上。我的经验线团队不到 10 人、业务域没清晰分裂前单体顶多加模块化分包的迭代速度远超微服务。等真出现某模块要独立扩容或发布节奏明显冲突的信号再拆不迟。架构跟着组织和业务走别反过来。思考题你现在负责的服务里有没有数据同源、却拆成两个服务、每次调用都要跨一次 RPC的情况如果把它合并你预计会省掉哪些代价、又会失去哪些好处欢迎评论区说说你的取舍。架构没有银弹只有取舍。这一轮 Batch 7 的 5 篇配置中心 / Session / 存储 / 混沌 / 架构就到这里下一批我们收尾 Round 3 的 Batch 8。