跳到内容

第 7 卷 / 第 6 章 / 第 1 课

6.1 微服务是故障拓扑,不是目录拆分 ​

一个持续增长的应用仍把账户、分析、搜索和报告装在同一个部署单元里。它是否真的需要拆分?若拆,部署与数据边界应沿哪些变化方向划开?切目录很容易,切出可独立承担故障的边界才是难题。

把模块放进不同进程后,本地调用变成网络调用,本地事务变成跨服务流程,函数参数变成长期契约。微服务的收益来自独立部署、扩缩、隔离和团队所有权,成本来自分布式故障。

第 6 卷已经讲过服务提取、网关、韧性和 Saga。本章从分布式系统角度检查:拆分是否真的形成可控故障域。

服务边界必须同时满足多种边界 ​

一个健康服务通常同时拥有:

  • 业务能力与统一语言;
  • 公开 API/事件契约;
  • 数据写入权;
  • 独立构建、部署和回滚;
  • 容量、SLO、告警和值班责任。

只有独立代码仓库,没有独立数据和发布权,只是分布式单体;只有独立数据库,却每次功能都要求五个团队同步发布,也没有获得自治。

必需依赖共同决定可用性 ​

若一次请求串行依赖 A、B、C,任何一个超时都会使整体失败。只有在失败相互独立、统计口径相同等假设下,才可把成功概率相乘:三个各为 99.9% 的必需依赖,联合成功率约为 0.999³ = 99.7003%。共享数据库、机房或证书造成的相关失败不能这样计算;更不能直接相乘几份产品月度 SLA 当作自己的 SLO。串行等待还会累加延迟,尾部要用真实调用分布评估。

text
Gateway → Order → Inventory → Pricing → Promotion → Payment

应区分:

  • 必需依赖:失败就不能诚实完成;
  • 可选依赖:可以返回明确降级;
  • 可异步后置:不阻塞前台结果;
  • 可预计算/缓存:用新鲜度换取隔离。

把同步改异步不是自动解耦。消息契约、积压、幂等和最终结果责任仍然存在。

远程接口应粗粒度 ​

本地对象可以频繁调用 getter,远程 API 不应模拟细粒度对象图。每个 RPC 都有序列化、排队、网络、鉴权和失败成本。

围绕用例设计接口,例如 QuoteOrder 一次返回定价所需结果,而不是让客户端逐商品调用 getPrice、getDiscount、getTax。

QuoteOrder 还可以返回报价版本和有效期,让提交订单时能够说明采用了哪套价格。把几十次 getter 合成一次调用只是起点;如果结果里的价格和优惠来自不兼容的版本,省下网络往返也没有省下对账。契约仍要有清晰责任、大小上限和版本策略。

数据所有权限制耦合 ​

服务应通过所有者契约修改数据。多个服务直接写同一批表,会绕过彼此业务规则,并把 schema 迁移变成共同协调。逻辑所有权不要求每个服务立刻独占一台数据库:同一实例上的独立 schema、账号权限和明确写入权可以作为边界,但仍共享容量与故障域。同理,独立发布不要求一定拆成多个 Git 仓库。

跨服务查询可通过 API composition、CQRS 投影、数据产品或分析仓库完成。选择取决于新鲜度、延迟和一致性,不能让在线服务直接 JOIN 所有生产库。

故障隔离需要资源边界 ​

独立进程并不等于故障隔离。多个服务仍可能共享:

  • 同一下游连接数或并发配额,即使各进程的线程池独立;
  • 同一数据库或消息分区;
  • 同一节点、zone 或账户配额;
  • 同一网关和身份服务;
  • 同一发布流水线或证书。

例如 50 个服务都把自己的数据库连接池上限设为 100,理论需求就到 5000,而共享数据库只允许 300 个连接。每个池单独看都有上限,合起来仍可把下游挤满。配置模板同时出错还会造成相关故障,但它与共享一份运行时资源是两种问题。架构图和容量、故障实验要覆盖这些实际影响范围。

自治不等于任意异构 ​

团队可以选择最适合的技术,但每种语言、数据库和框架都会增加补丁、观测、值班和人才成本。组织应提供少量受支持的 paved roads,特殊选择要有可量化收益和长期所有者。

是否值得拆分 ​

  • 独立扩缩或故障隔离是否有真实指标?
  • 团队能否独立交付并运行?
  • 核心不变量是否会被切成分布式事务?
  • 调用和数据依赖是否已在单体内理清?
  • 平台是否能提供发现、身份、观测、发布和恢复?
  • 新增网络跳数是否仍满足 SLO?

下一课给出微服务平台的最小生产基线,而不是再列一遍中间件品牌。

参考资料 ​

Built with VitePress | Software Systems Atlas