第7章 分布策略

抵抗透明分布诱惑,依据延迟、失败和一致性选择粗粒度远程边界。

学习目标

  • 能解释网络延迟、序列化、超时和部分失败为何改变本地接口的设计边界
  • 能用 TypeScript DTO 与 Remote Facade 把细粒度领域调用收敛为可观测的远程契约
  • 能根据责任归属、调用粒度、失败恢复和部署约束,判断何时保持本地以及何时分布

为什么第7章分布策略值得单独学习

第7章分布策略的核心不是记住某个 RPC 框架的调用方式,而是承认网络不是一条透明的进程内函数边界。订单服务请求结算服务时,延迟、序列化、超时、重复请求和版本不兼容都会成为业务设计的一部分;如果只把本地接口搬到网络上,调用者就会失去对成本和失败的控制。

需要同时描述通信成本、责任所有者和恢复路径。本页以 2024 年中文版公开目录限定第7章的范围,并依据 Martin Fowler 的作者图书页和模式目录独立重写;目录只决定要讲什么,案例、实验、判断题和答案均为本课程原创。

先建立直觉:问题、机制与代价

分布对象最容易制造一种错觉:远程对象看起来仍然像本地对象,于是调用者会继续逐字段读取、逐方法修改,却看不到每次往返都可能跨越网络。真正的设计要把一次远程操作看成有成本的消息交换,明确载荷、超时、重试、幂等键和兼容版本。

不能被包装成普通的布尔返回值。对订单与结算跨进程调用,优先比较保持本地的简单性与跨进程部署的独立性,再用调用次数、载荷大小和失败恢复成本证明分布值得承担。

目录单元到教学证据

第7章 分布策略

第7章分布策略要求把“是否拆开”变成可观察的架构决定:先标出状态所有者、通信方向、调用粒度和恢复责任,再用延迟、错误率与重试结果验证决定。只有当这些证据能在订单与结算跨进程调用中复现,分布边界才不是部署图上的装饰。

7.1 分布对象的诱惑

7.1 分布对象的诱惑提醒我们,远程对象不能假装拥有本地对象的低成本引用语义。若客户端逐个读取名称、地址、余额和订单,就会把网络往返隐藏在看似自然的属性访问里;应先统计往返次数,再判断是否需要批量查询或本地快照。

7.2 远程接口和本地接口

7.2 远程接口和本地接口要求契约显式表达消息边界,而不是让远程实现泄漏领域对象的全部方法。远程接口应围绕一次业务意图组织输入和输出,明确 DTO 字段、错误类型、超时语义与版本兼容规则,让调用者能够估算一次操作的成本。

7.3 必须使用分布的情况

7.3 必须使用分布的情况关注组织、合规、独立扩展和故障隔离等真实约束,而不是把微服务数量当成目标。只有当职责必须跨进程、数据必须由不同所有者管理,或独立部署确实带来收益时,才接受网络延迟与部分失败的额外责任。

7.4 关于分布边界

7.4 关于分布边界要求把高频且强一致的协作尽量留在同一边界,把稳定的业务意图和可恢复的状态转换放在远程边界上。边界太细会产生聊天式调用,边界太粗又会形成难以独立演化的巨型接口,选择必须由调用画像和失败恢复证据推动。

7.5 分布接口

7.5 分布接口强调远程契约应当粗粒度、可演化并可观测。接口需要记录请求标识、幂等键、超时和结果状态,返回 DTO 而不是远程领域对象;这样重试、降级和版本迁移才有明确的落点,调用者也不会依赖远端内部结构。

专属代码案例:订单结算的远程外观

把订单结算跨进程调用切成“本地订单摘要 → DTO → Remote Facade → 结算端口 → 可恢复结果”五个观察点。第7章分布策略的设计草案必须写出谁拥有状态、谁作出业务决定、失败怎样传播,以及网络往返、序列化、部分失败、边界粒度和幂等键中哪个指标最先提示当前方案不再适用。

设计记录采用五个可换行字段:单元键为 poeaa24-chapter-07-distribution-strategies;模式族为 distribution;裁决是“保持本地除非部署、所有权或隔离证据迫使分布”;观测项包括网络往返、序列化、部分失败、边界粒度、幂等键;拒绝条件是“把远端领域对象的细粒度方法原样暴露给调用者”。

先预测:如果把 settleOrder 改成四次远程 getter,延迟和结果未知的处理路径会如何变化?先写下往返次数、可能重复的副作用和恢复动作,再阅读下面的实现;验证时应能说明一次粗粒度 DTO 调用减少了什么成本,以及它没有消除什么失败。

type OrderSummary = {
  orderId: string;
  totalCents: number;
  status: "open" | "paid";
};
 
type SettlementResult = {
  ok: boolean;
  settlementId?: string;
  reason?: "timeout" | "rejected" | "unknown";
};
 
interface SettlementPort {
  settle(
    orderId: string,
    totalCents: number,
    idempotencyKey: string,
  ): SettlementResult;
}
 
class SettlementRemoteFacade {
  constructor(private readonly gateway: SettlementPort) {}
 
  settleOrder(order: OrderSummary, requestId: string): SettlementResult {
    const idempotencyKey = `settle:${order.orderId}:${requestId}`;
    return this.gateway.settle(order.orderId, order.totalCents, idempotencyKey);
  }
}

这段代码把远程边界收敛为一次有业务含义的调用:订单端只发送必要 DTO,结算端口负责幂等键和结果契约。实际适配器还应设置超时、记录请求标识并区分“明确拒绝”和“结果未知”;调用者不能因为超时就盲目再次扣款。

远程调用的代价与优化

分布的第一原则是远程调用比本地调用慢得多且随时可能失败,因此必须减少网络往返,每次调用传递完成一个业务意图所需的信息。下面的对比图把本地引用、远程序列化、细粒度往返和 DTO 优化放在同一张证据图中。

分布策略:远程调用的代价与优化本地调用(同进程)延迟:~100 纳秒失败:几乎不会(除非 OOM)传参:直接传引用 / 指针调用粒度:可以很细(getter/setter)远程调用(跨网络)延迟:~1-100 毫秒(×10000)失败:超时、丢包、连接断开传参:序列化 → 传输 → 反序列化调用粒度:必须粗(否则延迟爆炸)VS优化:Remote Facade + DTO 减少网络往返优化前(N 次远程调用):getName()getAddress()getOrders()getBalance()4 次网络往返 × 10ms = 40ms 延迟优化后(1 次远程调用):getCustomerDTO() → 一次返回全部数据1 次网络往返 × 10ms = 10ms 延迟分布的第一原则:减少远程调用次数,每次调用传递更多信息
远程调用比本地调用慢万倍且随时可能失败。分布策略的核心是减少网络往返: Remote Facade 提供粗粒度接口,DTO 把多个字段打包成一次传输。

Remote Facade 提供粗粒度接口,DTO 把多个字段打包成一次传输;它不是为了隐藏失败,而是为了让失败、超时和兼容版本集中在一个可测试的边界上。

选择与拒绝矩阵

评审问题保持本地或选择分布的证据应拒绝当前方案的信号
责任状态所有者、业务决定者和恢复者清晰两个服务都能直接改写同一份领域状态
粒度一次远程调用完成一个稳定业务意图客户端必须逐字段读取或逐方法修改
失败超时、明确拒绝、结果未知和重试可分别观测把网络异常包装成普通空值或布尔值
演化DTO、版本和幂等键有兼容策略远程接口暴露内部对象结构并随字段变化

分布边界决策图

选择分布前先问部署独立性、所有权和故障隔离是否构成硬约束;若没有,保持本地通常能减少延迟与失败面。若必须分布,则用 DTO、Remote Facade、超时和幂等键把网络责任集中起来。

分布边界:先证明约束,再承担网络成本没有硬约束保持本地少一次网络失败保留清晰端口确有分布约束DTO + Remote Facade超时 + 幂等键可观测恢复拒绝聊天式调用逐字段 getter透明重试扣款泄漏远端对象评审证据:所有权 × 部署独立性 × 故障隔离 × 调用画像先证明必须分布,再把网络责任集中在粗粒度契约
分布不是默认的演化步骤:没有真实约束就保持本地,必须分布时集中处理 DTO、超时、幂等和恢复。

常见误区

可验证练习

练习

本组练习覆盖 7.1 分布对象的诱惑、7.2 远程接口和本地接口、7.3 必须使用分布的情况、7.4 关于分布边界和 7.5 分布接口。

问题 1:改写接口。 订单页面需要显示客户摘要、余额和最近订单,现有实现对远端对象逐个调用 getter。应如何改造?

问题 2:处理未知结果。 结算请求超时,但服务端可能已经完成扣款,客户端应该直接重试吗?

问题 3:判断分布边界。 两个模块部署在同一应用、共享事务且调用频率很高,团队只是因为“以后可能拆服务”就想加一层远程接口,是否应接受?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

分布策略

把职责拆到不同进程、节点或服务,并为网络延迟与部分失败显式设计契约的架构选择。

部分失败

请求结果未知或仅部分参与者完成时,需要由超时、重试和恢复流程共同处理的状态。

粗粒度远程边界

以一次稳定业务意图组织请求和响应,减少细粒度网络往返并集中失败处理的接口边界。

幂等键

标识同一业务操作的稳定键,使服务端能够把安全重试识别为同一次请求而不是新动作。

本章小结

掌握第7章分布策略的标志不是背下 Remote Facade 或 DTO 的名称,而是能在订单结算跨进程调用中解释“本地摘要 → DTO → 远程外观 → 结算端口 → 可恢复结果”的责任链。学习者应利用网络往返、序列化、部分失败、边界粒度和幂等键作出可证伪的选择,并在没有真实部署或所有权约束时明确拒绝无谓分布。

前后导航

来源与改写范围

资料与写作方式声明

本章以Martin Fowler《企业应用架构模式》与公开模式目录权威目录界定学习范围,并结合正文列出的技术资料独立重写;不宣称复现原书正文,也不沿用原作表述。

原作版权归作者与出版社所有;本站原创教学结构与表述仅供学习交流。

讨论

评论区加载中…