Skip to content

微服务架构与拆分原则 ​

S4-019建议学高级约 90 分钟JDK 21企业开发进阶 · 微服务

1. 本节定位 ​

《Dubbo 与 RPC 基础》讲了服务之间怎么通信;本节讲**"为什么要拆、拆到什么程度"**。

★ 微服务的收益众所周知,但它的代价很少被量化 ✓ ——本节补上这两笔账:

text
① 延迟账:一次业务从「1 次本地调用」变成「5 次远程调用」
② 可用性账:依赖 5 个 99.9% 的服务,整体变成多少

2. 学习目标 ​

  • 能说出微服务的收益与代价(各三条以上) ★★★;
  • 能算「串联依赖的可用性乘法」 ★★★;
  • 知道"跨服务调用次数"是微服务的第一性能指标 ★★★;
  • 能说出四种降低串联代价的手段 ★★★;
  • 知道按什么拆分(业务能力 + 变化率 + 团队边界),不按什么拆(技术分层) ★★★;
  • 能在"该不该拆"上给出务实判断 ★★★。

3. 前置知识检查 ​

4. 为什么需要这个知识 ​

先看一笔可用性账 ✓✓:

text
  假设每个服务的可用性是 99.9%(一年停机 8.8 小时)

  串联服务数   整体可用性    一年停机时长
  1           99.9000%     8.8 小时
  2           99.8001%     17.5 小时
  3           99.7003%     26.2 小时      ← 已低于「3 个 9」
  5           99.5001%     43.8 小时      ← 接近「2 个 9」,年停机近 2 天 ✗
  10          99.0045%     87.2 小时      ← 不可接受 ✗✗

★★ 这就是"微服务会降低可用性"的数学依据:串联的可靠性是乘法 ✓✓

★ 而且这还没算上网络故障与服务发现的开销 ⚠️

5. 核心概念 ​

5.1 延迟代价:多出来的"每一步"都要走网络 ★★★ ​

实测(5 步下单流程,每步真实工作 2 ms) ✓✓:

text
  ① 单体(5 次本地调用):   10030 微秒(10.03 ms)✓✓
  ② 微服务(5 次远程调用): 32406 微秒(32.41 ms)
  → 微服务版慢 **3.23 倍**,多花 22.38 ms(本机回环,最乐观的情况)✗

★ 关键认识 ✓✓:

每多一次远程调用,就要多花约 4.5 ms(本机) ✗

跨机房时,每次远程调用的网络往返是 5~50 ms —— 5 步串行就可能多出几十到几百毫秒 ✗✗

(呼应 《Dubbo 与 RPC 基础》:RPC 的固定成本约 0.8 ms,跨机房主要花在网络往返上)

★ 三种降低代价的手段 ✓✓:

手段做法代价
能并行的并行 ★★★5 步里 3 步无依赖 → 并发调用代码复杂度 ⚠️
合并边界 ★★把"总是一起调用"的服务合回去放弃一部分独立性
异步化 ★★★非核心步骤(通知)发消息异步做一致性变最终一致 ⚠️

★★ 最重要的一条 ✓✓:

「跨服务调用次数」是微服务架构的第一性能指标 ✓

设计时数一数「一次用户操作要跨几次服务」,超过 3~5 次就该重新考虑边界 ✓✓

5.2 可用性代价:串联依赖的乘法效应 ★★★ ​

见第 4 节的表 ✓——N 个 99.9% 的服务串联,整体 = 0.999^N ✓

★ 四种补偿手段(真实架构都在用) ✓✓:

手段做法效果
降级 ★★★非核心依赖失败时返回兜底把"依赖"变成"可选" ✓✓
超时 + 熔断 ★★★快速失败,不拖垮上游防止雪崩(《Sentinel 限流与熔断》)✓✓
冗余与多实例 ★★单实例可用性 → 集群可用性需要无状态 ✓
减少串联 ★★★能异步就异步、能合并就合并从源头减少乘法项 ✓✓

★★ 最容易被忽略的是第一行 ✓✓:

不是所有依赖都必须成功 ✓

「推荐服务挂了,商品页还是要能打开」→ 推荐位显示兜底内容 ✓✓

设计微服务时,给每个依赖标注「核心 / 非核心」是必做功课 ✓✓

★ 一个务实的做法 ✓✓:

java
// 核心链路:库存不足就返回失败(必须成功才能继续)
inventoryService.deduct(request);

// 非核心链路:包一层 try-catch + 超时,失败就记日志继续
try {
    pointsService.add(request);          // 加积分失败不影响下单
} catch (Exception e) {
    log.warn("加积分失败,已忽略", e);
}

5.3 拆分的正确依据 ★★★ ​

★ 什么该先拆 ✓✓:

信号判断动作
这块代码变更频率是其他的 10 倍 ★★★变化率差异大优先拆(拆分的第一驱动力)✓
两个模块的团队天天互相等发布 ★★★团队边界清晰优先拆(康威定律)✓
某模块要独立扩容(如秒杀) ★★★伸缩需求不同优先拆 ✓
共享同一张表、同一事务 ✗✗数据强耦合先别拆(拆了要处理分布式事务)✗
必须同步一致(余额与流水) ✗✗不能用最终一致先别拆或放同一服务 ✗
团队只有 3 个人 ✗✗没有团队边界别拆(只会增加沟通成本)✗✗

★★ 一句话原则 ✓✓:

按「业务能力 + 变化率 + 团队边界」拆,不按「技术分层」拆 ✓✓

★ 反例(很常见) ✗✗:

text
✗ 拆成「Controller 服务 / Service 服务 / DAO 服务」
→ 这是把「分层」变成了「分布式」
→ 等于给每次调用都加一次网络开销 ✗✗(呼应 5.1 的延迟账)

★ 拆分的正确顺序(务实版) ✓✓:

text
1. 先把单体做好模块化(清晰的包边界 + 接口,即 S4-002 的 DIP)✓✓
2. 找出「变化最频繁 + 要独立扩容」的那一块,先拆它一个 ✓
3. 建立可观测性(链路追踪、日志聚合)
   —— 没有它,拆完就查不出问题 ✗✗
4. 再考虑第二块、第三块……每一步都要能回退 ✓

5.4 微服务的收益与代价对照 ★★★ ​

收益代价
独立部署(改一个服务不用重发全部)★★★分布式复杂度(网络、事务、一致性)✗✗
独立扩容(只扩热点服务)★★★延迟增加(每步一次网络)✗
技术异构(不同服务用不同技术栈)★运维成本(N 个服务 × 部署/监控/告警)✗✗
团队自治(各团队有自己的发布节奏)★★★可用性下降(串联乘法)✗✗
故障隔离(一个服务挂了不拖垮全部)★★排障更难(要跨服务追踪)✗✗

★★ 一条务实的判断 ✓✓:

只有当"独立部署"和"团队自治"的收益超过了上面所有代价时,才该拆 ✓

而这两个收益都依赖同一个前提:团队规模 ✓✓

10 个人以下的团队,微服务几乎总是过早优化 ✗✗

5.5 「单体」不是贬义词 ★★★ ​

★ 三种架构的取舍 ✓:

架构适合典型
单体 ★★小团队、需求快速变化、边界还不清晰早期创业项目、内部系统 ✓
模块化单体 ★★★团队 10~30 人、边界开始清晰大多数团队的正确答案 ✓✓
微服务 ★★多团队、边界稳定、需要独立伸缩大型平台 ✓

★ "模块化单体"是被低估的选项 ✓✓:

它有清晰的模块边界(靠包和接口约束) ✓

但模块间是本地调用(没有网络开销、没有分布式事务) ✓✓

将来要拆时,因为接口已经清晰,拆出去也不难 ✓✓

★★ 最后一句忠告 ✓✓:

《软件工程与开发流程》 说的「复杂度、变化、沟通」才是要解决的问题 ✓

微服务只是其中一种手段,而且是有显著代价的那一种 ✓✓

6. 递进示例 ​

6.1 最小示例:并发化缩短链路 ​

java
// ✗ 串行:3 次远程调用,延迟相加
User user = userService.get(id);
Coupon coupon = couponService.get(id);
Points points = pointsService.get(id);

// ✓ 并行:延迟取最大值(用 CompletableFuture)
CompletableFuture<User> f1 = CompletableFuture.supplyAsync(() -> userService.get(id), pool);
CompletableFuture<Coupon> f2 = CompletableFuture.supplyAsync(() -> couponService.get(id), pool);
CompletableFuture<Points> f3 = CompletableFuture.supplyAsync(() -> pointsService.get(id), pool);
CompletableFuture.allOf(f1, f2, f3).join();

★ 3 次 50 ms 的调用:串行 150 ms → 并行 50 ms ✓✓

6.2 业务示例:给依赖标注核心 / 非核心 ​

见 5.2 ✓

6.3 反例:为了"技术解耦"而拆 ​

text
✗ "我们要做微服务,所以把用户服务拆成用户查询服务 + 用户写入服务"
→ 读写本来就要一致,拆开只会引入分布式事务 ✗✗

7. 完整可运行代码与实测 ​

代码:labs/distributed-lab/src/main/java/cn/kb/learn/dist/micro/MicroserviceCostLesson.java

运行:

powershell
cd labs
.\mvnw.cmd -pl distributed-lab compile
java "-Dstdout.encoding=UTF-8" -cp distributed-lab/target/classes cn.kb.learn.dist.micro.MicroserviceCostLesson

实测内容 ✓:

实验量出什么
单体 vs 微服务的延迟5 步流程本地调用 vs 5 次远程调用的差距 ✓
可用性乘法表1/2/3/5/10 个串联服务的整体可用性与年停机时长 ✓✓
拆分信号对照表六个信号分别对应"该拆/别拆" ✓

★ 三条可落地的结论 ✓:

#结论依据
①串联依赖的可用性是乘法5 个 99.9% → 99.5%(年停机近 2 天)✗
②必须给依赖标注核心/非核心,并配降级否则一个非核心服务拖垮全站 ✗✗
③小团队优先做"模块化单体"收益/代价比最优 ✓✓

8. Debug 观察点 ​

  1. 数一次核心业务的"跨服务调用次数":超过 5 次就要重新审视边界 ★★★;
  2. 画出核心链路的依赖图:找出"所有请求都必经"的服务(它是可用性短板)★;
  3. 给每个依赖分类核心/非核心:非核心的必须有降级 ✓✓;
  4. 算一次你系统的可用性(各服务可用性相乘):看结果是否可接受 ★★;
  5. 问团队"上一次发布等了别人多久":这是拆分的真实信号 ✓。

9. 常见错误 ​

#错误说明
1按技术分层拆(Controller/Service/DAO) ✗✗等于给每次调用加网络开销 ✓✓
2小团队上微服务 ✗✗没有团队边界,只有沟通成本 ✓
3所有依赖都当核心 ✗✗一个非核心服务挂掉拖垮全站 ✓
4不配超时与熔断 ✗✗雪崩(《Sentinel 限流与熔断》)✓
5拆完才发现没有链路追踪 ✗✗出问题查不出来 ✓
6共享数据库却说是"微服务" ✗✗数据耦合没解决,只是改了调用方式 ✓
7一次大爆炸式拆分 ✗✗无法回退 ✓✓

10. 动手实验 ​

目标:算清自己系统的两笔账。

步骤:

  1. 跑 MicroserviceCostLesson,记录延迟与可用性数据 ✓;
  2. 列出你系统的所有服务间调用,标出"核心/非核心" ★★★;
  3. 把 3 个串行的非核心调用改成并行,量 P99 改善 ✓;
  4. 给一个非核心依赖加降级,人为让它失败,验证主流程不受影响 ★★★;
  5. 算一次整体可用性,并写出最该改进的那一项 ✓。

验收方式:能说出"你的系统拆得对不对、最该补的是什么"。

扩展任务:写一份"该不该拆服务 X"的决策记录(ADR):

  • 列出拆分的收益(独立部署/扩容/团队) ✓;
  • 列出代价(延迟/可用性/运维/事务) ✓;
  • 给出结论与回退方案 ✓;
  • 说明:为什么"写决策记录"比"讨论一上午"更有效(提示:把隐性判断显性化)★★

11. 练习题 ​

  1. 判断:微服务架构能提高系统的可用性。(对 / 错)
  2. 填空:3 个 99.9% 的服务串联,整体可用性是 ______(保留 4 位小数)。
  3. 简答:为什么"按技术分层拆"是错的?
  4. (场景判断)
text
团队 5 人,一个单体应用 8 万行代码,
订单、用户、商品三个模块共享同一张订单表。

该拆微服务吗?为什么? 5. (设计)给"商品详情页"设计依赖方案:要查商品、库存、评价、推荐,要求推荐服务挂了页面也能打开。

参考答案(要点)
  1. 错。串联依赖使可用性相乘下降 ✗ ——3 个 99.9% 串联 → 99.7% ✓★
  2. 99.7003% ✓
  3. 因为分层(Controller/Service/DAO)不是"业务边界" ✗ —— 它会把原本的本地方法调用变成远程调用,只增加开销不增加价值 ✓✓
  4. 不该拆 ✗✗ ——理由:① 共享同一张订单表(数据强耦合,拆了要处理分布式事务); ② 只有 5 个人(没有团队边界);③ 8 万行还不算大到无法维护。 正确的第一步是"模块化单体":理清包边界与接口 ✓✓
  5. 要点:商品与库存是核心(必须成功,超时要短 + 熔断)✓; 评价可降级为"暂无评价" ✓;推荐可降级为空或兜底内容 ✓✓; 四个调用能并行就并行,总延迟取最大值 ✓

12. 掌握标准 ​

  • [ ] 能说出微服务的收益与代价(各三条以上) ★★★;
  • [ ] 能算串联依赖的可用性 ★★★;
  • [ ] 知道"跨服务调用次数"是第一性能指标 ★★★;
  • [ ] 能说出四种降低串联代价的手段 ★★★;
  • [ ] 知道按什么拆、不按什么拆 ★★★;
  • [ ] 知道"模块化单体"是大多数团队的正确答案 ★★★;
  • [ ] 知道所有依赖都要标注核心/非核心 ★★★。

13. 扩展资源 ​

14. 下一步学习 ​

《Spring Cloud 核心组件》:网关、负载均衡、熔断、配置。

进度只保存在本机浏览器,可随时清除

个人学习项目:AI 生成内容均经人工复核,第三方资源仅作索引