4348 字
约 14 分钟
7
S18-A04 · 高可用分布式架构设计 + 从需求到架构的评审清单

适用读者:要在评审会上回答"你的架构为什么这么设计、为什么能扛住"的人;准备系统设计面试的资深/架构岗。
前置:S17(分布式基础:注册发现、网关、消息、事务、ID)、S18-A01(边界)、S18-A02(结构)。
覆盖旧文:A-2412(290 点,含 2 份重复导出 → duplicate_ref)、A-652(187 点)、A-173(228 点)——合计 705 点。
扩展点:E-S18-021(容量估算)、E-S18-022(故障域与熔断/超时参数)、E-S18-023(ADR 模板)、E-S18-024(五步评审清单)、E-S18-025(幂等与外部回调)、E-S18-026(长任务异步化)。
上一篇:S18-A03 · MVC 到 DDD 的重构与迁移
下一篇:S18-X01 · 23 种设计模式速查表。


1. 一句话结论

"高可用"不是形容词,是四个可算的东西:容量(能接多少)、故障域(坏一块死多少)、一致性(能容忍多旧)、幂等(重试会不会重复)——四个都给出数字和归属,架构才算设计过。

A-2412 的开场就把这类问题的性质说清了:它考察"整体架构设计能力、技术广度、严谨程度","不太会强求……给出一个完美的答复",只要"一个听上去行止可行,逻辑自洽的回答,基本上就可以得到高分了"。换句话说:评审看的不是"你有没有堆组件",而是"你的推理链是否闭合"。

1.1 第一步永远是"把问题具体化"

原文给的做法是先圈范围:面向用户的高可用应用(电商商品体系、社区)/通用服务(网关、缓存、消息推送)/对 CAP 敏感的注册中心——不同定位,考虑的点完全不同。然后在评审时先把这三类特点摆出来,表明"我关注的不只是通用方案,还关注实际场景"。

这一步的价值在于:没有范围的"高可用"无法验收。所以本文后面每一节都要求产出"数字 + 归属"。


2. 容量估算:从日单量到实例数(E-S18-021)

A-2412 的实例题是"日均千万量级的高可用订单履约系统",它给的第一步估算非常朴素,也非常正确:

1kw / 1d = 10,000,000 / 86,400 ≈ 115.74 单/s → 按 116 单/s 设计
峰值按 200 单/s(原文口径:瞬时最大消费能力大于平均值)

复核(本文用 Python 重算,2026-11-06):10,000,000 / 86,400 = 115.74,取 116 的误差 0.22%。这个精度对容量设计完全够用——容量估算要的是量级正确,不是小数点。

2.1 一张可复用的容量表

量 公式 本例 说明
平均 QPS 日量 / 86400 116 单/s 只用于基线
峰值 QPS 平均 × 峰均比(3–10) 200–1160 电商大促取 10,内部系统取 3
单实例能力 压测得出(不是猜) 假设 50 单/s/实例 必须实测,否则后面全是空中楼阁
实例数 ⌈峰值 / (单实例 × 0.7)⌉ ⌈200/35⌉ = 6 0.7 是安全水位
存储/天 条数 × 单条字节 5000 万 × 1KB ≈ 47.7 GB/天 日志/明细类
保留天数 可用容量 / 每天增量 1.8 TB / 47.7 GB ≈ 37.7 天 决定分区与归档策略
连接数 实例数 × 每实例池大小 6 × 20 = 120 要 ≤ DB max_connections × 0.8

这张表最容易被跳过的是"单实例能力"。没有压测数据时,正确做法是写"待压测,暂按 X 估算"并标注为风险项,而不是假装知道。

2.2 从容量推出架构(原文的三层拆法)

A-2412 的实例设计有三层,每层都有容量理由:

层 设计 容量/可用性理由
收单服务 独立集群 + MQ 异步拉单 削峰:让下游按 200 tps 消费,不必关心零点瞬时洪峰;故障后可回放消息
履约服务 拆成多个独立服务 + 统一状态机编排 各服务按自身能力弹性伸缩;状态机负责限流、背压、重试、流量分配
存储 按订单 ID 分库分表(+ 冷热分离) 千万/天必然单表撑不住;冷热分离降成本

状态机编排这一段是全文最有价值的架构判断,原文的推理链值得完整保留:先画了"串行流式消费"的方案,随即自己指出"这个方案和高可用存在冲突——一个服务中断会影响整条链路",然后改成状态机调度,收益是:

  • 减少服务间直接依赖,可做流量控制、速度调节、异常重试;
  • 每个服务只关心自己的逻辑,通知状态机可同步可异步;
  • 可按各服务承载能力动态分配流量;
  • 部分场景可跳过某服务,也可并行调度。

这就是"从方案 A 到方案 B"的取舍展示——评审时最有说服力的不是"我选了 B",而是"我先画了 A,自己发现了 A 的缺陷,改成了 B"。


3. 故障域与爆炸半径(E-S18-022)

A-2412 给的高可用清单有 11 条,本质是四类动作:冗余、隔离、保护、观测。本文把它重排成可执行的参数表:

动作 具体手段 可配置参数(经验值,推断,需压测校准) 归属
冗余 多实例、多可用区、主备/主从 副本数 ≥2;跨 AZ 至少 1 份 运维
隔离 按来源路由(天猫/京东各自集群)、按服务拆、按机房分 单集群流量上限 = 总容量 × 0.6 架构
超时 每个外部调用都要有超时 timeout = 下游 P99 × 2,且 ≤ 上游剩余预算 调用方
重试 只对幂等操作重试,带退避 最多 1–2 次,指数退避 + 抖动 调用方
熔断 错误率/慢调用比例触发 10s 窗口错误率 > 50% 打开;半开放 5 个探测 调用方
限流 入口限流 + 出口限流 入口 = 峰值 × 1.2;单实例按水位 70% 网关/服务
背压 队列满则拒绝而非无限堆积 队列长度 = 单实例 1s 处理量 × 3 服务
降级 非核心功能可关 至少 3 个开关:推荐、积分、评价 产品 + 架构
观测 监控 + 关键日志 + 预警 四个黄金信号:延迟/流量/错误/饱和度 运维
少依赖 尽量少引入第三方组件 每引入一个中间件,多一个故障域 架构
少交互 能异步不同步,能本地不远程 同步调用链 ≤ 3 跳 架构

"尽量少引入第三方组件"(原文 2.6)是这份清单里最反直觉、也最该被引用的一条:多数人谈高可用是"加组件",原文的结论是"外部依赖越少,稳定性越高"。这与 S18-A01 的"拆分过头=分布式单体"是同一个判断在两个层面的表达。

爆炸半径的算法(本文补充的量化方式):

爆炸半径 = 受影响请求占比 × 受影响时长
         = (单故障域承载流量 / 总流量) × 故障恢复时间(MTTR)

例:按来源路由后,单集群承载 30% 流量,MTTR 10 分钟 → 影响 = 3 分钟等效全站不可用。把这个数字写进 ADR,比写"我们做了隔离"有力得多。


4. 一致性与幂等(E-S18-025)

A-2412 要求在回答高可用时"务必谈一下一致性的问题",并给了两个具体故障源:

  • 时钟不一致:各机器时钟不同步 → 基于当前时间的业务逻辑出错 → 统一时钟源(NTP);
  • 资源竞争:多节点争同一资源 → ABA 等问题 → 分布式锁 / DB 排他锁;
  • 网络分区:尽量同机房内网通信,但"这其实牺牲了一定的可用性"(一个机房全崩则全不可用)——这就是 CAP 的取舍现场。

A-652 则从业务侧给了同一个问题的实例:支付回调幂等。两边合起来,幂等有四层手段:

层 手段 幂等键 失效场景
接口层 幂等 Token(客户端生成,服务端去重) requestId 客户端重试时换了 token
业务层 状态机 + 唯一约束(where status=CREATED) 业务单号 状态机被绕过
存储层 唯一索引 / 乐观锁 version 主键/业务键 分库后唯一性只在单库
消息层 消费去重表 + 至少一次投递 msgId 去重表未落库就消费

判据:幂等键必须来自业务,不能来自请求。requestId 只能防"同一次请求重发",防不住"用户点了两次"。真正可靠的键是业务单号 + 状态前置条件。

支付回调的正确写法(伪代码,标注推断——未在真实支付网关验证):

// 1) 先按业务单号加行锁/乐观锁,2) 判断状态前置,3) 再改状态
int n = jdbc.update("update orders set status='PAID' where order_no=? and status='CREATED'", no);
if (n == 0) { /* 已处理过或状态不符:直接返回成功,不报错 */ return "SUCCESS"; }

第二行 if (n == 0) return "SUCCESS" 是重点:重复回调要返回成功,否则支付方会一直重试。


5. 长任务异步化与工作流(E-S18-026)

A-652 的项目(AI 求职信息聚合)给了三个真实技术问题,都是"需求→架构"的典型:

问题 方案 判据
耗时较长的 AI 任务(采集+抽取几十秒) 提交任务 → 返回 taskId → 异步执行 → 轮询/推送结果 同步接口 P99 > 3s 就该异步
复杂的 AI 工作流(多步、可分支) 状态机/工作流引擎编排,每步可重试、可观测 步骤 ≥4 或需要人工介入
动态业务配置(运营改规则) 配置中心 + 版本化 + 灰度 改配置不该发版

异步化的四个必答项(少一个就会在生产出事):

  1. 任务状态机:PENDING → RUNNING → SUCCESS/FAILED,且 FAILED 可重试;
  2. 超时与看门狗:RUNNING 超过 N 分钟 → 标 TIMEOUT 并告警(否则任务永远"运行中");
  3. 幂等:同一 taskId 重复执行结果一致;
  4. 可观测:每步的耗时、输入摘要、失败原因都能查(对应 J05 的"失败留现场")。

A-2412 的状态机编排与这里的"工作流编排"是同一个模式在两个尺度的应用:把"谁调谁"换成"谁向编排者报告",代价是多一个组件,收益是流量控制、重试、可观测都集中到一处。


6. ADR 与五步评审清单(E-S18-023 / E-S18-024)

6.1 ADR 模板(Architecture Decision Record)

格式参考 adr.github.io (Michael Nygard 的原始提案),最小可用版本:

# ADR-0007 订单履约采用状态机编排而非串行调用

## 状态
已接受(2026-11-06)| 取代:无 | 被取代:—

## 背景(Context)
日均订单 1kw,峰值 200 单/s。履约链路由 4 个服务串行推进,
任一服务故障会导致整条链路停摆(MTTR 实测 18 分钟,影响 100% 订单)。

## 决策(Decision)
引入状态机作为编排者:各服务向状态机上报状态,状态机负责流量分配、
重试与背压。服务间不再直接串行调用。

## 备选方案(Alternatives)
A. 保持串行 + 增加重试 —— 拒绝:故障仍会阻塞链路
B. 纯 MQ 事件驱动 —— 拒绝:缺全局状态视图,补偿逻辑分散
C. Saga 编排 —— 保留:与状态机方案接近,待复杂跨域事务出现时再评估

## 后果(Consequences)
+ 故障隔离;可限流/背压/重试;可观测集中
− 多一个组件(状态机)成为新的单点 → 必须集群化 + MQ 异步交互
− 状态机成为"上帝对象"的风险 → 只做路由与状态流转,不含业务规则

## 度量(如何知道它对了)
履约链路 MTTR ≤ 5 分钟;单服务故障影响 ≤ 25% 订单;
状态机自身可用性 ≥ 99.95%。

关键在最后两节:"备选方案"证明你比较过;"度量"证明它可以被验收。没有这两节的 ADR 只是"技术决定通知"。

A-173 的三份 Markdown 可以看作 ADR 的"运行时版本":AGENTS.md=宪法(约束与红线,等价于 ADR 的 Decision)、STATE.md=记忆(当前进度与待决项,等价于 ADR 的 Status + 后续)、SKILL.md=能力单元(可复用的操作契约,等价于 Consequences 里的执行细则)。三者合起来,就是"决策 + 状态 + 执行"的闭环。这个类比的价值:ADR 不是写完就归档的文档,它应该像 STATE.md 一样被持续更新。

6.2 五步评审清单(E-S18-024)

A-652 把"从需求到架构"拆成五步,本文把它变成可勾选的评审表:

步 动作 产出物 评审问法(答不出就退回)
1 业务需求 → 业务流程 2–3 条核心流程(用户流、数据流) "这条流程里哪些步骤必须同步?"
2 按职责拆模块 模块清单 + 职责一句话 "这个模块的边界用一句话说得清吗?"(对应 SRP)
3 设计分层 分层图 + 依赖方向 "依赖箭头有没有朝外的?"
4 解决核心技术问题 每问题一个方案 + 取舍 "如果这个方案失效,退路是什么?"
5 说明架构取舍 3 组"选 A 不选 B + 理由 + 代价" "代价你打算怎么付?"

为什么"取舍"必须放最后且必须有 3 组:因为前四步都是"加法"(加模块、加层、加组件),只有第五步是"减法"。评审时最容易被追问的也正是这一步——A-652 原文的一句话是:"核心原则:先明确业务,再划分模块,最后选择技术方案",顺序反了就会变成"用技术找需求"。


7. 常见误区

误区 真相
"高可用 = 堆机器/堆组件" 外部依赖越少越稳;每加一个组件多一个故障域(原文 2.6)
"容量靠估算就行" 单实例能力必须压测;没有压测就写"待压测"并列为风险
"重试能解决超时" 非幂等重试会造重复数据;重试必须有预算与退避
"分布式锁能解决一切竞态" 锁的失效、续期、脑裂都要处理;能用唯一约束/状态前置就别上锁
"同机房部署更可靠" 它提升了延迟与分区容忍,但牺牲了机房级容灾
"架构做完再写文档" ADR 要在决策当时写;事后补的 ADR 只剩结论没有推理

8. 自测

  1. 日均 300 万单,峰均比 8,单实例压测 40 单/s,安全水位 70%,需要几个实例?
  2. 你的同步调用链有几跳?超过 3 跳时,最先该改哪一段?
  3. 支付回调重复到达,你的代码返回什么?为什么不是报错?
  4. 状态机编排引入的新单点怎么消除?
  5. 写一条你自己项目的 ADR,必须含"备选方案"和"度量"两节。
  6. 爆炸半径怎么算?给出你系统的当前数值。

下一篇

S18-X01 · 23 种设计模式速查表

正文 10 篇结束。附录两篇负责"查":X01 把 23 种模式压成"意图 / 场景 / 反例 / 易混淆"四列,X02 给 DDD 术语与建模模板。


本篇属于系列《S18 · 架构方法与领域建模》。旧文来源:A-2412(含 2 份重复导出)、A-652、A-173;扩展点 E-S18-021/022/023/024/025/026。容量数字为 Python 复核(116 单/s,误差 0.22%);熔断/超时参数为经验值(推断,需压测校准);ADR 格式参考 adr.github.io;CAP 与分区取舍为原文表述。

S18-A04 · 高可用分布式架构设计 + 从需求到架构的评审清单
https://www.clxhxhhr.top/posts/5444/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。