分布式系统组件全景与学习路线
基础理论面试闭环:网络失败、CAP、一致性、共识与幂等标准回答。详细原理仍保留在各知识页,避免面试答案覆盖推导过程。
共识算法专题:Paxos、Raft、ZAB 与多数派主线 · etcd/raft 状态机、持久化、线性读与故障恢复 · 独立面试题。专题从论文不变量追到 v3.7.0 源码,并用 v3.6.0 真实三节点测试验证 PreVote、分区、冲突覆盖、恢复和 ReadIndex。
新增专题:Kubernetes服务发现:Service、EndpointSlice、CoreDNS与流量传播原理 · 面试标准回答。该专题从微服务调用视角解释地址快照、连接级负载、滚动摘流,以及Java 8/Boot 2与Java 17+/Boot 3接入边界;包级网络细节仍归入DevOps Kubernetes网络专栏。
Service Mesh 专题:Envoy、xDS、mTLS 与流量治理主线 · Envoy 运行时、xDS 收敛与生产治理 · 独立面试题。专题把请求还原到 Listener、HCM、Route、Cluster、Endpoint 和连接池,并覆盖 Warming、Panic、Outlier、SDS、Ambient、商业配置与 Runbook。
gRPC专题:协议与生产主线 · Java内部原理与生产治理 · 独立面试题。专题把Stub调用还原为Resolver、Picker、Subchannel、物理Attempt、HTTP/2 Stream、服务端Executor、业务事务与Trailers,并覆盖流式背压和长连接倾斜。
新增专题:微服务稳定性治理:超时、重试、隔离、熔断、过载保护与故障演练 · 面试标准回答。该专题从一次慢依赖的事故传播出发,解释每个保护器的位置、原理、误用后果与恢复过程。
新增专题:微服务容量规划:SLO、压测、瓶颈识别、扩缩容与容量闭环 · 面试标准回答。专题把 QPS、RT、P95/P99、Little 定律、线程池、连接池、DB、Redis、MQ、HPA 和扩容无效排查串成生产容量闭环。
新增专题:故障演练与混沌工程:故障注入、爆炸半径、稳态指标与恢复验证 · 面试标准回答。专题把超时、重试、熔断、限流、降级、幂等、补偿、MQ堆积和灾备验证串成可执行演练闭环。
新增专题:微服务可观测性:OpenTelemetry、Trace传播、采样、指标基数与SLO · 面试标准回答。主线从应用埋点追踪到Collector和后端,并解释异步因果、遥测丢失窗口与SLO告警。
新增专题:多机房与异地多活:单元化、RPO/RTO、故障切换、脑裂与回切 · 面试标准回答。主线围绕业务写所有权和切换状态机,而不是把两个机房误写成双活。
新增专题:API与事件契约治理:REST、OpenAPI、Protobuf、Schema Registry与兼容发布 · 面试标准回答。专题围绕新旧Reader/Writer共存、历史消息、灾备与回滚窗口建立可执行契约。
新增专题:微服务组件选型与能力归属:REST、Dubbo、gRPC、MQ、Gateway、Spring Cloud与Mesh · 面试标准回答。专题重点消除双层负载、多层重试、超时倒挂和安全职责混淆。
新增专题:微服务网关治理:路由、认证授权、限流、灰度、错误语义与排查 · 面试标准回答。专题从统一入口讲到 OAuth2/OIDC、JWT、下游授权、限流 key、
lb://负载均衡和 401/403/404/429/503/504 排查。
新增专题:分布式会话与状态管理:Session、JWT、Redis、粘性会话与无状态服务 · 面试标准回答。专题讲清登录态跨实例恢复、JWT撤销、权限版本、Redis Session、WebSocket连接状态和用户串号排查。
新增专题:微服务配置治理:配置中心、动态刷新、灰度、回滚、密钥与排查 · 面试标准回答。专题把配置中心放回控制面语境,讲清启动加载、运行期刷新、资源切换、版本收敛和配置事故 Runbook。
新增专题:CDC与Outbox:MySQL到MQ、ES、Redis的可靠同步与重放 · 面试标准回答。专题把本地消息表、binlog/WAL、MQ、ES、缓存、位点、重复、乱序、死信和重建索引串成数据收敛链路。
新增专题:多租户隔离:身份、数据权限、资源配额、缓存、MQ与数据库治理 · 面试标准回答。专题覆盖可信租户身份、Header清洗、Service数据权限、SQL兜底、热租户隔离、缓存Key、MQ和大租户独立化。
一、学完本专栏要真正会什么
分布式系统不是“用了微服务、Redis、MQ 就算分布式”。只要一次业务操作跨越进程、机器、数据库或网络,就要面对单机程序没有的事实:消息会延迟,节点会宕机,响应会丢失,时钟会漂移,同一请求可能执行多次,不同节点可能同时认为自己正确。
本专栏的目标不是背组件,而是让你能够完成下面这些工作:
- 从一次远程调用判断连接、发送、执行、提交、响应各阶段的失败窗口。
- 解释超时为什么只能证明“没有按时得到答案”,不能证明下游没执行。
- 根据业务语义设计幂等键、状态机、唯一约束、补偿和对账。
- 解释 CAP、BASE、线性一致性、最终一致性、Quorum、Raft、ZAB 的适用层次。
- 区分注册中心、配置中心、协调服务、RPC、MQ、分布式事务、锁和限流。
- 对 XA、TCC、Saga、事务消息、Seata 作有前提的选型,而不是问“哪个最好”。
- 解释 Redis、ZooKeeper、etcd 锁的锁所有权、租约、会话和 Fencing Token。
- 设计网关、服务、租户和下游资源的多层限流预算。
- 用 Trace、Metrics、Logs、业务流水、协调器状态和中间件指标定位线上故障。
- 清楚每个方案“不这样做会怎样”,并能写出最小可运行 Demo。
二、什么才叫分布式系统
一个系统的组件位于不同故障域,彼此通过不可靠通信协作,并共同维护业务结果,就构成了分布式系统。这里的“不同故障域”很重要:同一 JVM 内的方法通常一起存活;两个服务进程可能一个成功、一个宕机;两个机房可能因专线故障互相不可见但仍各自运行。
flowchart TD
A["一个业务操作"] --> B["订单服务与订单库"]
A --> C["库存服务与库存库"]
A --> D["支付服务与支付渠道"]
A --> E["消息队列与异步消费者"]
B --> F["各节点可独立成功、失败或超时"]
C --> F
D --> F
E --> F
F --> G["系统必须把部分结果收敛为可解释的最终状态"]分布式的收益与代价必须一起看:
| 收益 | 为什么有价值 | 同时增加的代价 |
|---|---|---|
| 横向扩容 | 增加实例处理更多流量 | 负载均衡、状态共享、热点和容量规划 |
| 故障隔离 | 一个服务故障不必拖垮全部系统 | 超时、熔断、降级和依赖治理 |
| 独立发布 | 团队可按业务边界演进 | 接口兼容、灰度、版本和数据迁移 |
| 数据分治 | 不同模型选择不同存储 | 跨库一致性、对账、搜索与缓存同步 |
| 异地容灾 | 单机房故障仍能服务 | 网络分区、复制延迟、冲突解决 |
如果业务规模和团队规模没有这些需求,单体或模块化单体往往更可靠。微服务不是技术成熟度勋章,而是用额外复杂度换取明确收益。
三、分布式系统的八个硬事实
3.1 网络有三种结果,不是两种
本地方法常被理解成成功或抛异常;远程调用至少有:
- 明确成功:收到可验证的成功响应。
- 明确失败:收到业务拒绝或协议明确失败。
- 结果未知:超时、断连、进程重启,调用方不知道服务端是否已经提交。
flowchart TD
A["调用方发送扣款请求"] --> B{"请求是否到达服务端"}
B -- "未到达" --> C["服务端未执行"]
B -- "到达" --> D{"本地事务是否提交"}
D -- "未提交" --> E["业务未生效"]
D -- "已提交" --> F{"响应是否到达调用方"}
F -- "到达" --> G["调用方知道成功"]
F -- "丢失或超时" --> H["调用方只知道结果未知"]因此,SocketTimeoutException 不能直接映射成“扣款失败”。正确做法通常是使用同一业务幂等键查询事实状态,必要时安全重试。
3.2 节点故障和网络分区难以区分
节点没有回应,可能是进程死了、GC 停顿、CPU 打满、网络拥塞、交换机故障或只是响应迟到。故障检测器只能在超时时间后“怀疑”节点,不能瞬间证明节点永久死亡。
深入学习:分布式故障检测:心跳、Phi Accrual、SWIM 与健康探针 · 独立面试题
3.3 请求会重复
客户端重试、网关重试、MQ 重投、消费者 Rebalance、定时任务补跑、支付回调和协调器恢复都会产生重复。至少一次投递是可靠系统的常见现实,因此所有有副作用的入口都要设计幂等。
3.4 状态会短暂分叉
订单库已提交、缓存未删除、ES 未更新、消息仍在路上时,不同读取路径会看到不同结果。系统必须定义允许多长时间、谁是事实源、怎样修复,而不是只说“最终会一致”。
3.5 时钟不是绝对可靠的全局顺序
机器时钟可能漂移或被 NTP 调整。System.currentTimeMillis() 适合记录近似现实时间,但不能单独证明跨节点事件的因果顺序,也不适合直接当全局唯一 ID。租约和超时还要考虑时钟单调性及服务端裁决。
深入学习:分布式时间与时钟:NTP、Deadline、租约、Fencing 与回拨 · 独立面试题
3.6 局部成功不等于全局成功
三个步骤中两个提交、一个失败,不能用一个布尔值表达。要用状态机记录过程,例如 CREATED → STOCK_RESERVED → PAYING → PAID,并允许进入 COMPENSATING、MANUAL_REVIEW。
3.7 重试会放大故障
A 调 B 最多 3 次,B 调 C 最多 3 次,C 最坏收到 9 次。若下游越慢上游越重试,会形成正反馈雪崩。重试必须有总时间预算、次数上限、退避、抖动、幂等和熔断。
3.8 任何协调机制也有失败边界
注册中心会不可用,Redis 锁会过期,ZooKeeper Session 会失效,事务协调器会恢复,MQ 会重复投递。组件减少某类不确定性,却不会取消分布式事实。
四、分布式系统六层知识地图
flowchart TD
A["01 通信层<br/>HTTP、RPC、MQ、协议、序列化"] --> B["02 寻址与协调层<br/>Nacos、Consul、ZooKeeper、etcd"]
B --> C["03 流量治理层<br/>Gateway、负载均衡、限流、熔断、隔离"]
C --> D["04 状态一致性层<br/>幂等、事务、缓存、CDC、状态机"]
D --> E["05 复制与共识层<br/>Quorum、Paxos、Raft、ZAB"]
E --> F["06 运行保障层<br/>发布、容灾、观测、补偿、对账"]这六层不是严格网络协议栈,而是学习顺序。先不理解通信失败,就无法理解幂等;先不理解一致性,就无法正确选择事务;先不理解故障证据,就无法做生产排查。
五、常见组件全景与职责边界
5.1 服务寻址和配置
| 组件 | 核心职责 | 典型数据 | 不应该承担 |
|---|---|---|---|
| Nacos | 注册发现、配置管理 | 服务实例、Namespace、Group、DataId | 业务请求转发和大量业务数据 |
| Eureka | 经典客户端注册发现 | 服务名、实例、状态、元数据 | 配置管理和强一致协调 |
| Consul | 服务发现、健康检查、KV | Catalog、Health、KV | 高频业务数据库 |
| Kubernetes Service/EndpointSlice | 集群内服务寻址 | Pod 地址、端口、就绪状态 | 应用级业务事务 |
| Spring Cloud Config/Apollo | 配置治理 | 版本化应用配置 | 订单、库存等实时业务状态 |
注册中心通常位于控制面:客户端订阅或拉取实例并形成本地快照,业务请求由 HTTP/RPC 客户端直达提供者。每次业务请求同步查注册中心会把控制面变成数据面瓶颈。
5.2 协调与共识
| 组件/算法 | 解决的问题 | 核心机制 | 典型用途 |
|---|---|---|---|
| ZooKeeper | 分布式协调 | ZAB、Session、临时/顺序节点、Watcher | 选主、锁、元数据协调 |
| etcd | 强一致 KV 与协调 | Raft、Revision、Lease、Watch、事务比较 | Kubernetes 状态、配置、选主、锁 |
| Consul | 服务治理和 KV | Raft、Agent、健康检查、Gossip、Connect | 多语言服务发现和基础设施治理 |
| Raft | 复制状态机共识 | Leader、Term、日志复制、多数派 | etcd、控制面元数据 |
| Paxos/Multi-Paxos | 共识理论与实现族 | Proposal、Promise、Accept、多数派 | 分布式数据库和协调系统基础 |
| ZAB | ZooKeeper 原子广播 | epoch、zxid、Proposal、ACK、COMMIT | ZooKeeper 写入和恢复 |
共识解决“多个副本在故障下如何对一串状态变化达成一致”,不直接解决订单和支付跨服务的业务事务。业务事务可能借助共识系统保存协调状态,但两者层次不同。
5.3 服务通信
| 方式 | 优点 | 代价 | 合适场景 |
|---|---|---|---|
| HTTP/REST | 通用、跨语言、易调试 | 文本协议开销、契约约束需治理 | 对外 API、通用微服务调用 |
| OpenFeign | 声明式 HTTP、Spring 集成好 | 容易误以为是本地方法 | Spring 服务内部同步调用 |
| Dubbo | 完整 RPC、协议与治理扩展 | 框架和契约治理复杂 | Java 内部高频 RPC |
| gRPC | Protobuf 契约、流式、跨语言 | 浏览器/调试和网关适配成本 | 高性能跨语言内部调用 |
| MQ | 异步、削峰、解耦、可重放 | 最终一致、重复、顺序、积压 | 事件传播和非即时步骤 |
同步调用提供即时结果,但把可用性和延迟绑定到下游;异步消息隔离峰值,却要求状态机、幂等、重试、死信和对账。
5.4 流量治理
| 能力 | 保护对象 | 常见实现 | 与其他能力的区别 |
|---|---|---|---|
| 负载均衡 | 多个健康实例 | LoadBalancer、Dubbo、Nginx、Envoy | 选择目标,不限制总流量 |
| 限流 | 自身容量和公平配额 | Sentinel、Gateway、Redis Lua、Envoy | 超过预算时主动拒绝或排队 |
| 并发隔离 | 线程、连接、信号量 | Bulkhead、线程池、信号量 | 限制同时占用资源数量 |
| 熔断 | 不健康下游 | Sentinel、Resilience4j、Envoy | 根据失败/慢调用暂时阻断 |
| 降级 | 非核心能力 | fallback、静态数据、异步化 | 选择较弱但可接受的业务结果 |
| 超时 | 单次等待预算 | HTTP/RPC/DB/MQ 客户端配置 | 防止一次调用无限占用资源 |
这些能力必须按时间预算组合。例如入口总预算 1 秒,下游不能每个都配置 1 秒再叠加两次重试。
5.5 数据一致性和事务
| 方案 | 一致性思路 | 适用 | 主要风险 |
|---|---|---|---|
| XA/JTA/2PC | 参与者准备后统一提交 | 资源支持 XA、短事务、强一致要求 | 持锁、阻塞、协调器恢复、in-doubt |
| TCC | 业务预留资源后 Confirm/Cancel | 高价值短流程、可设计预留 | 侵入、空回滚、悬挂、幂等 |
| Saga | 正向步骤和业务补偿 | 长流程、允许中间状态 | 补偿失败、补偿非物理回滚 |
| 本地消息表/Outbox | 业务状态和待发事件同库提交 | 大多数最终一致事件 | 投递延迟、重复、表清理 |
| MQ 事务消息 | 协调本地事务和消息可见性 | Broker 支持且语义匹配 | 回查、重复消费、消费者一致性 |
| Seata | AT/XA/TCC/Saga 集成协调 | Java 生态的明确场景 | 模式约束、热点、协调器和恢复 |
| CDC | 从数据库日志捕获变化 | 数据集成、ES/数仓同步 | 顺序、Schema 演进、重复、回放 |
没有一种方案能同时做到零侵入、无限吞吐、任意资源、强一致和永不阻塞。选型必须从业务不变量、可接受中间状态、吞吐、延迟和补偿能力出发。
5.6 锁、租约和 Fencing Token
分布式锁不是“把 synchronized 放到 Redis”。锁服务要回答:
- 谁获得了锁,所有权如何标识。
- 锁何时过期,客户端暂停时怎样处理。
- 释放时如何防止删除别人的锁。
- 网络分区后旧持有者是否仍能写资源。
- 下游资源如何拒绝过期持有者。
flowchart TD
A["客户端 A 获得锁与 token=10"] --> B["A 长时间 GC,租约过期"]
B --> C["客户端 B 获得锁与 token=11"]
C --> D["A 恢复并继续提交旧操作"]
D --> E{"资源是否校验递增 token"}
E -- "不校验" --> F["旧持有者可能覆盖新结果"]
E -- "校验" --> G["拒绝 token=10 的过期写入"]Fencing Token 是单调递增的世代号。锁服务只保证互斥窗口并不总能阻止暂停后的旧客户端继续操作;真正的数据资源若能校验 Token,才能拒绝僵尸持有者。
六、一次商业请求的完整分布式生命周期
以“提交订单”为例:
flowchart TD
A["客户端生成业务幂等键"] --> B["Gateway 认证、限流、路由"]
B --> C["订单服务原子占用幂等键"]
C --> D["订单库事务创建状态机记录和 Outbox"]
D --> E["库存服务按订单号预占库存"]
E --> F["订单事务推进到 STOCK_RESERVED"]
F --> G["Outbox 发布 OrderCreated 事件"]
G --> H["支付、履约、通知、ES 各自幂等消费"]
H --> I["对账任务检查长时间未收敛状态"]每一步都要回答四个问题:
- 请求重复会不会多执行一次副作用?
- 超时后事实源在哪里查询?
- 当前状态允许哪些合法迁移?
- 自动修复失败后怎样告警和人工处理?
七、超时、重试和总时间预算
一次调用通常包含:连接池等待、DNS、连接、TLS、请求发送、服务端排队、业务执行和响应读取。只设置一个模糊的 timeout=3s,排查时无法知道时间花在哪里。
入口剩余预算
= 连接池等待
+ 建连/TLS
+ 服务端排队与执行
+ 响应传输
+ 重试退避
+ 上游处理和安全余量如果入口 SLA 是 1000ms,可以按业务实测分配预算,例如订单自身 150ms、库存 250ms、支付单 300ms、网络与序列化 100ms、余量 200ms。数字必须来自压测和生产分位数,不是通用固定值。
适合重试的条件通常同时包括:
- 错误被明确分类为瞬时错误。
- 操作幂等,或服务端支持同一幂等键结果复用。
- 总时间预算仍充足。
- 使用退避和随机抖动,避免客户端同时重试。
- 只在一个经过设计的层负责重试。
参数校验失败、余额不足、库存不足、权限拒绝不应重试。结果未知的写请求应先查询事实状态,而不是换一个请求号重新执行。
八、幂等、状态机、唯一约束为什么要组合
幂等键标识“这是同一次业务意图”;唯一约束负责在并发下原子拒绝重复;状态机负责限制当前状态能执行的动作;结果表负责让重复请求复用第一次结果。
flowchart TD
A["收到 idempotencyKey"] --> B["原子插入请求记录"]
B --> C{"是否首次获得处理权"}
C -- "是" --> D["校验当前业务状态并执行"]
D --> E["同一事务写业务结果和成功状态"]
C -- "否" --> F{"已有状态"}
F -- "SUCCEEDED" --> G["返回第一次结果"]
F -- "PROCESSING" --> H["返回处理中或查询事实源"]
F -- "FAILED_RETRYABLE" --> I["按租约和 owner token 接管"]只做“先查数据库是否存在,再插入”会有并发竞态;只加分布式锁但没有唯一约束,锁故障时仍可能重复;只用唯一约束却不保存响应,重复请求无法得到一致结果。
深入学习:请求、消息、任务与状态机幂等
九、从 CAP 到业务最终一致的逻辑链
CAP 讨论网络分区发生时,一致性与可用性如何取舍;它不是数据库产品广告标签,也不是所有业务的“三选二”。BASE 描述允许中间状态并通过后续过程收敛的工程思路。
业务最终一致必须具备可验证闭环:
flowchart TD
A["事实源事务提交"] --> B["可靠记录待传播事件"]
B --> C["异步投递并允许重试"]
C --> D["消费者幂等更新派生状态"]
D --> E["失败进入重试、死信或补偿"]
E --> F["对账比较事实源与派生状态"]
F --> G["自动修复或人工处理"]
G --> H["指标证明在承诺时间内收敛"]如果没有重试、死信、对账、修复、告警和收敛指标,“最终一致”只是希望,不是设计。
十、共识、复制和业务事务不能混为一谈
复制是把状态保存到多个副本;共识是在节点失败和消息延迟下,对操作顺序或值达成一致;业务事务是维护订单、库存、支付等业务不变量。
| 问题 | 典型答案 |
|---|---|
| etcd 三副本怎样确定一次写提交 | Raft Leader 将日志复制到多数派并满足提交规则 |
| ZooKeeper 写请求怎样形成一致顺序 | Leader 通过 ZAB Proposal、ACK、COMMIT 广播事务 |
| 订单和库存两个数据库怎样一致 | TCC、Saga、可靠消息、Seata或业务补偿 |
| 为什么注册中心能高可用 | 服务端复制、健康模型和客户端本地缓存共同作用 |
Raft 不能直接替你撤销一笔支付;Saga 也不能替数据库副本选 Leader。理解层次后,组件边界才不会混乱。
深入学习:Paxos、Raft、ZAB 与多数派共识 · Raft内部原理与生产治理 · 独立面试题
十一、数据库、缓存、ES 与 CDC 的一致性链路
数据库通常是交易事实源,Redis 是性能视图,ES 是搜索视图。一次商品更新不可能用普通本地事务原子提交三个异构系统,因此要设计传播和修复:
flowchart TD
A["MySQL 提交商品版本 version=8"] --> B["Outbox 或 Binlog CDC 捕获变化"]
B --> C["发布带主键和版本号的事件"]
C --> D["Redis 消费者删除或重建缓存"]
C --> E["ES 消费者按版本更新文档"]
D --> F["失败重试与缓存自然过期"]
E --> G["失败重试、死信和索引重建"]
F --> H["对账任务比较事实源版本"]
G --> H事件必须携带业务主键和版本。否则旧事件晚到时可能覆盖新数据。ES 更新失败不能只打印日志,要保留可重试事件、监控 Lag、进入死信,并支持按数据库事实源重放或重建索引。
十二、分布式 ID 为什么不是简单自增
分库分表后单库自增只能保证库内唯一。常见 ID 方案:
| 方案 | 原理 | 优点 | 风险 |
|---|---|---|---|
| UUID | 随机/时间等构造 128 位标识 | 无中心、生成方便 | 长、索引局部性差、不同版本语义不同 |
| 数据库号段 | 中心表批量分配区间 | 趋势递增、吞吐高 | 号段服务和步长治理 |
| Snowflake 类 | 时间戳 + 节点号 + 序列 | 本地高吞吐、趋势递增 | 时钟回拨、节点号冲突、位宽寿命 |
| Redis INCR | 单线程原子递增 | 简单递增 | 可用性、持久化和跨地域延迟 |
Snowflake 遇到时钟回拨时,不能无条件继续按较小时间戳生成,否则可能与历史 ID 冲突。常见处理包括短回拨等待、维护逻辑时间、切换备用节点位、长回拨拒绝服务并告警。
深入学习:分布式 ID:UUID、数据库号段、Snowflake 与生产治理 · 独立面试题
数据规模继续增长时,还需要把 Key 映射到稳定逻辑分区并安全迁移。深入学习:数据分片与一致性哈希 · 独立面试题
服务实例扩缩容还需要理解连接级与请求级选择。深入学习:微服务负载均衡:P2C、EWMA、Maglev 与长连接治理 · 独立面试题
十三、Service Mesh 把什么下沉到了基础设施
Istio、Linkerd、Envoy 等可以把部分服务发现、mTLS、路由、重试、熔断、指标采集下沉到 Sidecar 或节点代理。它减少语言 SDK 差异,但不会替代业务幂等、状态机、事务和错误语义。
flowchart TD
A["Service A 业务进程"] --> B["本地代理"]
B --> C["mTLS、路由、负载、重试、指标"]
C --> D["Service B 代理"]
D --> E["Service B 业务进程"]
F["控制面下发配置"] --> B
F --> DMesh 自动重试写请求同样可能重复副作用。代理看到的是 HTTP/gRPC 状态码,不一定理解“扣款已提交但响应丢失”的业务事实。
深入学习:Service Mesh 主线 · Envoy 运行时、xDS 收敛与生产治理 · 独立面试题
十四、商业常用场景与方案组合
| 场景 | 核心不变量 | 推荐组合 | 必须防范 |
|---|---|---|---|
| 下单扣库存 | 同一订单不能重复占库存 | 状态机、幂等键、条件更新、TCC或Saga | 超卖、空回滚、超时未知 |
| 支付回调 | 同一渠道流水只入账一次 | 签名、唯一索引、状态条件更新、对账 | 重复回调、伪造、金额不一致 |
| 商品同步 ES | 搜索最终反映数据库版本 | Outbox/CDC、版本事件、重试、重建索引 | 旧事件覆盖新文档、更新丢失 |
| 多租户 API | 一个租户不能占满系统 | 租户令牌桶、全局并发限制、公平队列 | 高基数 key、Redis 热点、误伤 |
| 医疗采集 | 断点续传且数据不重不漏到可接受范围 | 任务分片、采集批次、幂等落库、补偿 | 上游重复、顺序乱、敏感日志 |
| 定时结算 | 多实例只能处理一次业务批次 | 调度分片、业务唯一键、租约、Fencing | 锁过期后双写、任务悬挂 |
| 秒杀 | 库存不超卖且系统不崩 | CDN、网关限流、令牌、队列削峰、库存预扣 | 热点、超卖、重复下单 |
十五、分布式故障排查总 Runbook
15.1 先定义故障而不是先重启
记录开始时间、影响租户/接口/地域、成功率、P95/P99、错误码和最近变更。重启可能暂时释放资源,却会丢失现场、触发重连风暴或 MQ Rebalance。
15.2 按链路分层取证
flowchart TD
A["入口成功率与 Trace 样本"] --> B["Gateway 路由、限流、连接池"]
B --> C["调用方线程池、超时、重试、实例选择"]
C --> D["提供者 CPU、GC、线程、队列和业务错误"]
D --> E["MySQL锁与慢SQL、Redis延迟、MQ Lag"]
E --> F["协调器、注册中心、网络和基础设施"]
F --> G["业务流水、幂等记录、事务/消息状态收口"]15.3 常见症状对应证据
| 症状 | 优先证据 | 不要只做什么 |
|---|---|---|
| 无可用实例 | 客户端本地快照、Namespace/Group、实例健康和注册 IP | 只看注册中心控制台 |
| 大量超时 | Trace 分段耗时、连接池等待、线程池队列、下游 P99 | 只把超时调大 |
| 重复订单 | 幂等表、唯一键冲突、网关/客户端重试、MQ 投递次数 | 只怪用户重复点击 |
| 消息堆积 | Queue/Partition Lag 分布、消费耗时、失败重试、下游容量 | 只扩容消费者 |
| 数据不一致 | 事实源版本、Outbox/CDC 位点、消费记录、死信、对账差异 | 手工直接改多个库 |
| 锁失效双写 | 锁 token、租约、GC 停顿、资源端版本校验 | 只看 Redis key 是否存在 |
| 事务悬挂 | XID、全局/分支状态、协调器日志、锁等待、补偿记录 | 直接删协调记录 |
十六、JDK 8 Demo:结果未知状态机
下面用纯 JDK 8 展示为什么超时不能直接标记失败。第一次请求在服务端提交后“响应丢失”;调用方进入 UNKNOWN,使用同一幂等键查询后才确认成功。
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class UnknownResultStateDemo {
enum ClientState { NEW, UNKNOWN, SUCCEEDED, FAILED_FINAL }
static final class PaymentService {
private final Map<String, String> results =
new ConcurrentHashMap<String, String>();
String pay(String idempotencyKey, boolean loseResponse) {
String old = results.putIfAbsent(idempotencyKey, "PAID");
String result = old == null ? "PAID" : old;
if (loseResponse) {
throw new RuntimeException("response lost after commit");
}
return result;
}
String query(String idempotencyKey) {
return results.get(idempotencyKey);
}
}
public static void main(String[] args) {
PaymentService service = new PaymentService();
String key = "pay:ORDER-9001";
ClientState state = ClientState.NEW;
try {
service.pay(key, true);
state = ClientState.SUCCEEDED;
} catch (RuntimeException timeoutOrDisconnect) {
state = ClientState.UNKNOWN;
}
System.out.println("afterCall=" + state);
String fact = service.query(key);
if ("PAID".equals(fact)) {
state = ClientState.SUCCEEDED;
}
System.out.println("fact=" + fact + ", final=" + state);
}
}运行结果:
afterCall=UNKNOWN
fact=PAID, final=SUCCEEDED真实系统的查询可能来自支付流水表、渠道查询接口或消息状态。关键不是捕获某一种异常,而是把“不确定”作为合法状态,并能根据事实源收敛。
十七、常见反模式与后果
| 反模式 | 为什么错误 | 线上后果 |
|---|---|---|
| 把远程接口当本地方法 | 忽略网络、序列化和部分失败 | 线程堆积、重复副作用、错误误判 |
| 所有层都自动重试 | 尝试次数乘法放大 | 下游雪崩、重复写 |
| 用 traceId 当幂等键 | 每次重试可能产生新 Trace | 同一业务被执行多次 |
| 最终一致不做对账 | 无法证明最终收敛 | 静默脏数据长期存在 |
| 分布式锁没有唯一约束 | 锁过期或网络异常仍可并发 | 重复记录、超卖 |
| MQ 消费成功后再单独记幂等 | 业务提交与去重记录不原子 | 重复消费时再次执行 |
| 配置中心保存业务状态 | 配置系统不适合高频事务 | 并发覆盖、审计和一致性混乱 |
| 只看平均耗时 | 尾延迟被掩盖 | 少量慢请求拖垮线程池 |
| 故障先重启再取证 | 现场丢失且可能扩大抖动 | 根因反复出现 |
十八、按依赖关系学习,不按组件热度学习
- 分布式系统基础:远程调用、超时、重试、状态机和可观测性。
- CAP、BASE 与一致性理论:理解可用性、一致性和网络分区的约束。
- 数据一致性设计与幂等全过程:掌握业务收敛基本功。
- Paxos、Raft、ZAB 与多数派:理解协调系统和复制的底层依据。
- ZooKeeper与Dubbo:学习协调组件和 RPC 完整调用链。
- 分布式事务:比较 XA、TCC、Saga、可靠消息和 Seata。
- CDC与Outbox数据同步:MySQL 到 MQ、ES、Redis 的事件传播、位点、重放和对账。
- 缓存一致性与数据库、ES、CDC 传播链路。
- 分布式锁与 Fencing Token,再学习 Redlock 的争议和边界。
- 网关治理、认证授权与灰度:入口路由、OAuth2/JWT、下游授权、限流、灰度和状态码排查。
- 分布式会话与状态管理:Session、Redis、JWT、粘性会话、权限版本、WebSocket和用户串号排查。
- 分布式限流:算法、多实例共享状态和多层容量治理。
- 多租户隔离与资源配额:身份、数据权限、SQL、缓存、MQ、热租户和大租户独立化。
- 容量规划、压测与扩缩容:SLO、P95/P99、Little 定律、容量拐点、HPA 和扩容无效排查。
- 故障演练与混沌工程:故障注入、爆炸半径、稳态指标、停止条件、恢复验证和复盘闭环。
- 配置治理、动态刷新与回滚:控制面、版本快照、灰度发布、密钥、回滚和配置事故排查。
- 商业场景训练营、生产级主线和验收清单。
- 最后使用分布式面试页复习标准回答,并跳回精确原理锚点。
十九、面试标准回答
分布式系统把状态和计算放在多个独立故障域,通过网络协作。它的核心难点不是组件数量,而是网络不可靠、结果可能未知、请求可能重复、状态会短暂分叉、节点时钟和故障判断不完美。工程上要通过超时预算、有限重试、幂等键、唯一约束、状态机、限流熔断、事务或可靠消息、补偿对账以及 Trace/Metrics/Logs 让系统在部分失败时仍能得到可解释并可修复的结果。注册中心、RPC、MQ、共识、锁和分布式事务分别解决不同层次的问题,不能互相替代。
本章小结
学习分布式真正的主线是:先承认不确定性,再定义业务不变量,然后选择能提供证据、限制损失并最终收敛的机制。组件只是这些机制的实现。后续每个专栏都应该能够回答“内部怎么执行、在哪一步失败、失败后事实在哪里、怎样恢复”。
