Skip to content

分布式系统从零到生产级掌握

分布式不是“把项目拆成很多服务”这么简单。真正的分布式系统要面对网络失败、调用超时、重复请求、数据不一致、服务扩缩容、注册发现、限流熔断、链路追踪、补偿对账和线上排查。

一句话建立主线:

分布式系统 = 多个进程/服务/数据库通过网络协作完成一个业务目标;它用复杂度换容量、扩展性、容灾能力和团队协作效率。

如果只学框架名,会出现一种很危险的状态:知道 Nacos、Dubbo、Seata、RocketMQ、Redis 锁,但一问“超时后到底成功还是失败”“重复消费怎么保证不重复扣款”“CAP 为什么不是三选二”“扩容为什么没有效果”就答不上来。

如果你想检查自己是否真的从零基础学懂到能面试、能落地、能排查,按 分布式系统从零到精通验收清单 逐项验收。它把远程调用不确定性、CAP/BASE、幂等、事务、锁、缓存一致性、ES 同步、MQ、Dubbo、ZooKeeper、限流和生产排查拆成可检查的问题。

学习目标

学完这一页,你要能做到:

  1. 解释为什么单机问题拆到分布式后会变难。
  2. 解释远程调用为什么必须有超时、重试、幂等和熔断。
  3. 解释注册中心做什么,为什么它不转发业务流量。
  4. 解释 CAP、BASE、强一致、最终一致、读写一致的区别。
  5. 解释 Paxos、Raft、ZAB 这类共识/一致性协议解决什么问题。
  6. 解释分布式事务为什么不等于 Seata。
  7. 解释 XA/JTA/2PC、TCC、Saga、可靠消息、本地消息表、事务消息、Seata 的适用场景。
  8. 解释分布式 ID、幂等、限流、熔断、降级、分布式锁分别解决什么问题。
  9. 解释缓存一致性、ES 同步一致性、MQ 堆积和补偿对账怎么落地。
  10. 能按订单、支付、库存、医疗采集、搜索同步等商业场景设计分布式方案。
  11. 能从日志、指标、链路追踪、消息堆积、锁等待、数据库状态排查线上问题。

学习路线

mermaid
flowchart TD
    A["单体系统<br/>本地调用、本地事务"] --> B["远程调用<br/>网络、超时、重试"]
    B --> C["服务治理<br/>注册发现、负载均衡"]
    C --> D["可靠性保护<br/>限流、熔断、降级"]
    D --> E["一致性理论<br/>CAP、BASE、PACELC"]
    E --> F["数据一致性<br/>幂等、状态机、补偿、对账"]
    F --> G["分布式事务<br/>XA、TCC、Saga、消息、Seata"]
    G --> H["协调与锁<br/>ZooKeeper、Redis锁、Redlock"]
    H --> I["生产排查<br/>日志、指标、Tracing、告警"]

这条路线不能反过来。还没理解超时和幂等就学分布式事务,很容易把所有问题都交给框架;还没理解 CAP/BASE 就背 Seata 模式,会不知道为什么有些业务宁愿最终一致;还没理解可观测性,上线后只能靠猜。

第一步:为什么单机变分布式后会变难

单体应用里,下单可能是一个进程内调用:

mermaid
flowchart TD
    A["用户下单"] --> B["订单模块"]
    B --> C["库存模块"]
    C --> D["支付模块"]
    D --> E["同一个数据库事务"]

这种情况下,方法调用成功就是成功,异常就是失败,同一个数据库事务可以统一提交或回滚。

拆成分布式后:

mermaid
flowchart TD
    A["用户下单"] --> B["订单服务<br/>订单库"]
    B --> C["库存服务<br/>库存库"]
    C --> D["支付服务<br/>支付库"]
    D --> E["优惠券服务<br/>优惠券库"]
    C --> F{"网络超时"}
    D --> G{"服务失败"}
    E --> H{"状态不一致"}

问题变多了:

问题单机里分布式里
方法调用内存调用,失败明确网络调用,可能超时、丢包、重复
事务一个数据库事务多个服务、多个数据库
JVM 锁或数据库锁多实例,本地锁失效
状态集中在一个库分散到多个库和缓存
日志看一个应用日志多服务、多实例、多链路
扩容加机器较简单注册发现、负载均衡、数据一致性都受影响

所以分布式系统第一原则是:

不要假设远程调用一定成功,不要假设失败响应代表没有执行,不要假设重试不会造成重复结果。

第二步:远程调用的三种不确定状态

调用方请求下游服务,可能得到:

结果含义
成功响应下游明确执行成功
失败响应下游明确拒绝或处理失败
超时/断连调用方不知道下游到底有没有执行

最难的是第三种。

mermaid
flowchart TD
    A["订单服务发送创建支付单请求"] --> B["支付服务可能未收到、执行中或已提交"]
    B --> C["响应超时或在网络中丢失"]
    C --> D["订单服务只能确认没有按时拿到结果"]
    D --> E["按业务流水查询事实并幂等恢复"]

如果调用方看到超时就直接重试,而下游没有幂等,就可能创建两笔支付单。

因此远程调用最少需要:

防线解决什么
超时避免调用线程无限等待
重试处理短暂网络抖动
幂等重试不会造成重复业务结果
查询确认超时后能查真实状态
熔断下游持续异常时快速失败
降级非核心能力失败不影响主流程
traceId跨服务定位一次请求

第三步:远程调用基础 Demo

下面不是完整框架,而是为了让初学者看到“分布式调用不能裸奔”。

java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.UUID;

public class PaymentClient {
    public String createPayment(String orderNo, long amount) throws Exception {
        String traceId = UUID.randomUUID().toString();
        String idempotentKey = "payment:create:" + orderNo;
        String body = String.format(
                "{\"orderNo\":\"%s\",\"amount\":%d}", orderNo, amount);

        HttpURLConnection connection = (HttpURLConnection) new URL(
                "http://payment-service/payments").openConnection();
        connection.setRequestMethod("POST");
        connection.setConnectTimeout(2000);
        connection.setReadTimeout(3000);
        connection.setDoOutput(true);
        connection.setRequestProperty("Content-Type", "application/json");
        connection.setRequestProperty("X-Trace-Id", traceId);
        connection.setRequestProperty("Idempotent-Key", idempotentKey);

        byte[] bytes = body.getBytes(StandardCharsets.UTF_8);
        try (OutputStream output = connection.getOutputStream()) {
            output.write(bytes);
        }

        try (BufferedReader reader = new BufferedReader(new InputStreamReader(
                connection.getInputStream(), StandardCharsets.UTF_8))) {
            StringBuilder response = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) response.append(line);
            return response.toString();
        } finally {
            connection.disconnect();
        }
    }
}

这段代码体现了三件事:

  1. connectTimeout 保护连接建立。
  2. readTimeout 保护等待响应。
  3. Idempotent-Key 让下游可以去重。

该Demo兼容JDK 8。Java 11+/17+可以使用java.net.http.HttpClient,但仍要保留连接超时、响应超时、幂等和结果确认。

真实项目还要配合 OpenFeign、Dubbo、Resilience4j、Sentinel、日志和链路追踪。

第四步:注册中心到底做什么

很多初学者误以为注册中心会转发请求,这是错误的。

注册中心的职责是保存服务地址,并通知调用方地址变化。

mermaid
flowchart TD
    A["Provider 启动"] --> B["向注册中心注册地址"]
    C["Consumer 启动"] --> D["从注册中心订阅地址"]
    D --> E["本地缓存 Provider 列表"]
    E --> F["负载均衡选择一个 Provider"]
    F --> G["Consumer 直接调用 Provider"]
    B --> H["Provider 宕机或下线"]
    H --> I["注册中心推送变更"]
    I --> E

注册中心不转发业务请求。真正业务流量通常是:

text
Consumer -> Provider

不是:

text
Consumer -> 注册中心 -> Provider

常见注册中心:

注册中心特点
EurekaAP 思路,老 Spring Cloud 常见
Nacos注册发现 + 配置中心,国内微服务常见
ZooKeeper偏 CP,临时节点和 Watcher 适合协调
Consul服务发现、健康检查、KV
etcdRaft 共识,K8s 底层常用

注册中心挂了是否服务全挂?

  1. 如果 Consumer 已经缓存地址,短时间内可能还能调用。
  2. 新实例注册、下线感知、动态配置、治理规则会受影响。
  3. 如果本地缓存为空或地址全部失效,调用会失败。

所以服务治理不能只靠注册中心,还要有本地缓存、健康检查、超时、熔断和灰度策略。

第五步:负载均衡和扩容为什么不总有效

负载均衡是在多个可用实例中选择一个。

常见策略:

策略原理风险
轮询逐个实例分配不考虑机器差异
随机随机选择样本小时不均匀
权重高配置实例更多流量权重配置不当会倾斜
最少活跃选当前请求少的实例统计不准会误判
一致性 Hash同 key 尽量到同实例热 key 会压垮单实例

扩容不总有效,原因可能是:

  1. 下游数据库已经是瓶颈。
  2. MQ 分区或队列数限制并行度。
  3. 热点 key 导致流量集中到少数实例。
  4. 服务有全局锁或单线程瓶颈。
  5. 注册中心推送或客户端缓存还没刷新。
  6. 扩容触发 Rebalance,短期反而抖动。

这也解释了 MQ 里“扩容消费者后短暂有效,后面又堆积”的问题:可能整体瓶颈不在消费者数量,而在分区并行度、下游写入、慢消息、热点 key 或重试风暴。

第六步:CAP 不是三选二

CAP:

字母含义
CConsistency,一致性,读到最新正确数据
AAvailability,可用性,每个请求都能得到非错误响应
PPartition Tolerance,分区容错,网络分区时系统仍能应对

最常见误解是“CAP 三选二”。更准确:

分布式系统必须考虑网络分区;当分区真的发生时,只能在一致性 C 和可用性 A 之间取舍。

mermaid
flowchart TD
    A["网络分区发生"] --> B["节点之间无法通信"]
    B --> C{"继续对外服务吗"}
    C -- "继续服务" --> D["可用性更好<br/>可能读到旧数据"]
    C -- "拒绝部分请求" --> E["一致性更安全<br/>可用性下降"]

举例:

系统倾向原因
ZooKeeperCP少数派不可写,保证一致性
EurekaAP注册信息可短暂不一致,保证服务发现可用
Redis 主从取决于配置异步复制时可能丢失最新写
金融核心账务更偏 C宁愿拒绝也不能错账
商品评论数更偏 A短暂不一致可接受

第七步:BASE 和最终一致不是不管一致性

BASE:

概念含义
Basically Available基本可用,核心链路尽量能用
Soft State中间状态可存在
Eventually Consistent最终一致,经过重试补偿后达到正确结果

最终一致不是“随便错”。成熟最终一致方案必须有:

  1. 本地事务保证本服务数据正确。
  2. 消息或任务驱动下游变化。
  3. 消费端幂等。
  4. 失败重试。
  5. 长期失败进入死信或异常表。
  6. 定时补偿。
  7. 对账和告警。
mermaid
flowchart TD
    A["本地事务提交"] --> B["记录消息或发送事务消息"]
    B --> C["消费者处理"]
    C --> D{"处理成功吗"}
    D -- "成功" --> E["最终一致"]
    D -- "失败" --> F["重试"]
    F --> G{"超过阈值吗"}
    G -- "否" --> C
    G -- "是" --> H["死信/异常表/告警"]
    H --> I["补偿任务或人工处理"]

第八步:一致性协议解决什么

Paxos、Raft、ZAB 这类协议不是给普通业务开发每天手写的,而是支撑协调系统、注册中心、元数据系统、分布式存储的底层能力。

它们解决的问题是:

多个节点在可能宕机、网络延迟、消息乱序的情况下,如何对某个值或某批日志达成一致。

简单对比:

协议常见系统学习重点
Paxos理论基础,很多系统变体提案、接受、多数派
Raftetcd、Consul、很多教学实现Leader、日志复制、任期、提交
ZABZooKeeperLeader、zxid、原子广播、崩溃恢复

Raft 简化流程

mermaid
flowchart TD
    A["客户端写请求"] --> B["Leader 接收"]
    B --> C["追加到 Leader 日志"]
    C --> D["复制日志给 Follower"]
    D --> E{"多数派确认"}
    E -- "是" --> F["Leader 提交"]
    F --> G["通知 Follower 提交"]
    G --> H["返回客户端成功"]
    E -- "否" --> I["等待或失败"]

为什么要多数派?

  1. 两个网络分区不可能同时拥有多数派。
  2. 已提交日志不容易被另一个分区覆盖。
  3. Leader 切换时能从多数派中恢复已提交状态。

业务开发不一定要实现 Raft,但要理解:ZooKeeper、etcd 这类协调组件为什么通常牺牲部分可用性来保证一致性。

继续深入:Paxos、Raft、ZAB 与多数派 · etcd/raft 内部状态机、Ready 持久化、线性读、快照与成员变更 · 独立面试题

第九步:幂等是分布式系统的地基

幂等是指同一个业务请求执行一次和执行多次,最终业务结果一致。

需要幂等的场景:

场景为什么会重复
支付回调支付平台可能多次通知
MQ 消费至少一次投递,ACK 失败会重投
接口重试超时后调用方重试
补偿任务定时任务反复扫描
用户重复点击前端或网关重复请求

常见幂等方案:

方案适合
唯一索引订单号、支付流水、消息 ID
幂等表外部请求号、回调流水
状态机订单只能按合法状态流转
乐观锁更新时带版本号
分布式锁短时间互斥,但不能替代幂等
Token防重复提交

幂等表 Demo

sql
create table idempotent_record (
  id bigint primary key auto_increment,
  biz_type varchar(64) not null,
  biz_key varchar(128) not null,
  status varchar(32) not null,
  created_at datetime not null,
  unique key uk_biz (biz_type, biz_key)
);

Java 伪代码:

java
public void handlePaymentCallback(String payNo, String orderNo) {
    boolean first = idempotentRepository.tryInsert("PAY_CALLBACK", payNo);
    if (!first) {
        return;
    }

    orderService.markPaid(orderNo, payNo);
}

重点:不能先查再插,因为并发下两个线程可能都查不到。更稳的是插入唯一键,谁插入成功谁处理。

第十步:分布式 ID 为什么需要专门设计

单库单表可以用自增主键。分库分表、多服务、多机房后,自增 ID 会遇到:

  1. 多库自增冲突。
  2. 暴露业务规模。
  3. 不利于全局排序。
  4. 合并数据困难。

常见方案:

方案优点缺点
UUID简单,本地生成太长,无序,索引不友好
数据库号段趋势递增,易落地依赖数据库,号段服务要高可用
Redis INCR简单递增依赖 Redis,高可用和持久性要考虑
Snowflake本地生成,趋势递增时钟回拨、机器 ID 管理
Leaf/UidGenerator工程化封装需要部署和治理

Snowflake 典型结构:

text
符号位 | 时间戳 | 机器ID | 序列号

不这样设计会怎样?

  1. ID 冲突导致唯一键异常。
  2. ID 无序导致 B+Tree 频繁分裂。
  3. 时钟回拨导致重复 ID。
  4. 机器 ID 配错导致多实例冲突。

第十一步:限流、熔断、降级分别做什么

这三个经常被混在一起。

能力解决什么例子
限流请求太多,保护自己每秒最多 1000 次下单
熔断下游持续失败,快速失败支付查询连续超时后短暂熔断
降级非核心能力不可用时走兜底推荐服务挂了返回默认推荐
mermaid
flowchart TD
    A["请求进入系统"] --> B{"是否超过限流阈值"}
    B -- "是" --> C["拒绝或排队"]
    B -- "否" --> D{"下游是否熔断"}
    D -- "是" --> E["快速失败或兜底"]
    D -- "否" --> F["调用下游"]
    F --> G{"下游成功吗"}
    G -- "成功" --> H["返回结果"]
    G -- "失败" --> I["记录失败并可能触发熔断"]

常见限流算法:

算法原理特点
固定窗口一个窗口内限制总数窗口边界可能突刺
滑动窗口把窗口切小格统计更平滑
漏桶固定速度流出平滑但可能排队
令牌桶固定速度生成令牌允许一定突发

第十二步:分布式锁能做什么,不能做什么

分布式锁解决的是多实例之间的互斥。

适合:

  1. 防止多个实例同时重建热点缓存。
  2. 防止多实例定时任务重复执行。
  3. 低频关键资源互斥。

不适合:

  1. 替代数据库事务。
  2. 替代幂等。
  3. 长时间包住复杂远程调用。
  4. 作为资金库存唯一正确性保障。

Redis 锁正确释放必须校验 token:

lua
if redis.call("get", KEYS[1]) == ARGV[1] then
  return redis.call("del", KEYS[1])
else
  return 0
end

为什么?

  1. 线程 A 加锁成功但执行太久,锁过期。
  2. 线程 B 获得同一把锁。
  3. A 执行完如果直接 del,会误删 B 的锁。
  4. 校验 token 可以避免删除别人的锁。

Redlock 是多 Redis 节点多数派加锁方案,但它不是强一致共识算法。关键业务仍然要靠数据库约束、状态机、幂等和补偿兜底。

第十三步:分布式事务怎么选

分布式事务不是一种技术,而是一类方案。

方案一致性倾向适合代价
XA/JTA/2PC强一致短事务、资源支持 XA锁重、性能低、可用性差
TCC较强一致余额、库存、券预留业务侵入大
Saga最终一致长流程、多步骤业务需要补偿设计
本地消息表最终一致订单后置通知、积分、ES 同步有延迟,需要投递任务
MQ 事务消息最终一致本地事务和发消息一致消费仍需幂等
Seata AT框架协调SQL 兼容场景全局锁、undo_log、限制较多
最大努力通知最终一致支付回调、通知类依赖对方查询和补偿

选型流程:

mermaid
flowchart TD
    A["跨服务一致性问题"] --> B{"能否避免跨服务事务"}
    B -- "能" --> C["调整建模或合并事务边界"]
    B -- "不能" --> D{"是否必须立即强一致"}
    D -- "是" --> E{"是否能资源预留"}
    E -- "能" --> F["TCC"]
    E -- "不能" --> G["XA/JTA/2PC 或 Seata XA/AT"]
    D -- "否" --> H{"是否长流程"}
    H -- "是" --> I["Saga"]
    H -- "否" --> J["本地消息表 / 事务消息"]
    J --> K["幂等、重试、补偿、对账"]
    I --> K
    F --> K
    G --> K

面试重点不是背方案名,而是能说明:

  1. 业务是否真的需要强一致。
  2. 失败后怎么重试。
  3. 重复执行怎么幂等。
  4. 一直失败怎么补偿。
  5. 最终状态怎么对账。
  6. 用户看到什么状态。

第十四步:缓存一致性怎么放进分布式主线

缓存一致性本质是事实源和副本之间的一致性。

mermaid
flowchart TD
    A["写请求"] --> B["更新数据库事实源"]
    B --> C["事务提交"]
    C --> D["删除 Redis 缓存"]
    D --> E{"删除成功吗"}
    E -- "成功" --> F["后续读回源重建缓存"]
    E -- "失败" --> G["记录重试任务"]
    G --> H["补偿删除或 CDC 修正"]

常见原则:

  1. 数据库是事实源。
  2. Redis 是可重建副本。
  3. 普通缓存追求最终一致,不追求每一毫秒强一致。
  4. 删除缓存失败必须重试或补偿。
  5. 关键交易状态不要只信缓存。

第十五步:ES 同步一致性怎么放进分布式主线

MySQL 和 ES 也是事实源和搜索视图的关系:

mermaid
flowchart TD
    A["MySQL 业务数据变更"] --> B["Binlog CDC 或 MQ"]
    B --> C["同步消费者"]
    C --> D["写入 Elasticsearch"]
    D --> E{"写入成功吗"}
    E -- "成功" --> F["搜索视图更新"]
    E -- "失败" --> G["重试 / 死信 / 补偿任务"]
    G --> C

如果 ES 更新失败怎么办?

  1. 不要静默丢弃。
  2. 记录失败事件。
  3. 可重试错误自动重试。
  4. 不可重试错误进入死信并告警。
  5. 定时按 MySQL 事实源补偿。
  6. 严重时通过别名重建索引。

第十六步:生产排查总流程

mermaid
flowchart TD
    A["分布式线上问题"] --> B{"表现是什么"}
    B -- "接口慢" --> C["看链路追踪和各段耗时"]
    B -- "错误率高" --> D["看异常、熔断、下游健康"]
    B -- "数据不一致" --> E["查事实源、消息、补偿、对账"]
    B -- "消息堆积" --> F["看生产TPS、消费TPS、Lag分布"]
    B -- "服务找不到" --> G["查注册中心、版本、分组、本地缓存"]
    C --> H["定位慢服务/慢SQL/慢下游"]
    D --> I["限流、降级、熔断或回滚"]
    E --> J["补偿修复并补幂等/告警"]
    F --> K["扩容、限流、修慢消息、处理热点"]
    G --> L["修注册、网络、配置和发布版本"]

排查不能只看一个服务日志。要形成证据链:

证据看什么
traceId一次请求跨服务经过哪里
指标QPS、RT、错误率、线程池、连接池
日志参数、业务状态、异常栈
MQLag、重试、死信、消费耗时
数据库慢 SQL、锁等待、事务、主从延迟
缓存命中率、大 key、热 key、超时
注册中心实例是否存在、健康状态、版本分组
配置中心开关、限流阈值、灰度规则

商业场景一:下单扣库存

问题

下单需要:

  1. 创建订单。
  2. 扣减或锁定库存。
  3. 使用优惠券。
  4. 发支付单。

如果这些在不同服务,任意一步失败都会导致状态不一致。

推荐设计

mermaid
flowchart TD
    A["创建订单<br/>本地事务"] --> B["订单状态: 待支付"]
    B --> C["发送库存锁定消息"]
    C --> D["库存服务幂等锁库存"]
    D --> E{"库存成功吗"}
    E -- "成功" --> F["订单可支付"]
    E -- "失败" --> G["订单关闭或等待补偿"]
    F --> H["支付成功回调"]
    H --> I["幂等更新订单为已支付"]

落地要点:

  1. 订单号唯一。
  2. 库存锁定有幂等号。
  3. 支付回调幂等。
  4. 订单状态机限制状态跳转。
  5. 消息失败有重试和死信。
  6. 定时任务扫描超时订单释放库存。

商业场景二:医疗数据采集平台

问题

医疗数据采集平台常见链路:

  1. 调度采集任务。
  2. 调用医院接口或读取文件。
  3. 清洗转换。
  4. 写入数据库。
  5. 同步搜索索引。
  6. 记录异常和资产变更。

分布式风险:

风险后果
任务重复调度重复采集、重复入库
接口超时不知道医院系统是否处理
消息堆积数据同步延迟
ES 写入失败搜索结果不更新
Redis 缓存旧数据页面显示旧资产状态

推荐设计

  1. 采集任务有 taskId + sourceId + batchNo 幂等键。
  2. 原始数据先落库或落对象存储,避免处理中断丢数据。
  3. 清洗入库使用唯一约束防重复。
  4. 资产变更通过 MQ 同步 ES。
  5. ES 失败进入重试和死信。
  6. Redis 缓存由数据库变更后删除,失败补偿。
  7. XXL-JOB 分片执行任务,任务本身仍要幂等。
  8. 全链路 traceId 贯穿调度、采集、入库、同步。

常见误区

误区为什么错正确理解
微服务一定比单体高级分布式复杂度很高业务规模和团队需要时再拆
注册中心转发请求注册中心只保存地址Consumer 直连 Provider
超时就是失败下游可能已执行成功要查询确认和幂等
重试能解决所有问题重试会放大流量和重复执行必须配合幂等和退避
分布式事务就是 SeataSeata 只是框架还有 XA、TCC、Saga、消息
最终一致就是不保证一致最终一致要重试、补偿、对账只是接受短暂中间状态
分布式锁能保证正确性锁会过期、误删、脑裂还要数据库约束和状态机
CAP 三选二P 是必须面对的现实分区时在 C 和 A 间取舍

面试标准回答

分布式系统怎么从零学到生产级

text
我会先理解分布式系统的本质:多个服务通过网络协作,网络可能失败、超时和重复,状态分散在不同服务和数据库中。然后按远程调用、注册发现、负载均衡、限流熔断、CAP/BASE、一致性设计、幂等、分布式事务、分布式锁、缓存一致性、消息可靠性和可观测性这条线学习。生产落地时重点不是用了哪个框架,而是超时后如何确认状态,重复请求如何幂等,失败如何补偿,数据如何对账,线上如何通过 traceId、指标、日志和消息状态定位问题。

CAP 怎么理解

text
CAP 不是简单三选二。分布式系统必须考虑网络分区 P;当分区发生时,系统只能在一致性 C 和可用性 A 之间取舍。比如 ZooKeeper 更偏 CP,少数派不可写以保证一致性;Eureka 更偏 AP,允许注册信息短暂不一致以保证服务发现可用。业务选型要看数据风险,资金账务更偏一致性,评论数、浏览量可以接受最终一致。

分布式事务怎么选

text
分布式事务要先看业务是否能避免跨服务事务,能合并边界就不要拆。必须跨服务时,如果需要强一致且事务短,可以考虑 XA/JTA/2PC 或 Seata XA/AT;如果业务能做资源预留,适合 TCC;长流程适合 Saga;订单后置通知、积分、ES 同步等更适合本地消息表或事务消息。无论哪种方案,都必须配套幂等、重试、补偿、对账和告警。

为什么分布式系统必须做幂等

text
因为远程调用、MQ 消费、支付回调、补偿任务都可能重复执行。超时后调用方不知道下游是否成功,通常会重试;MQ 至少一次投递也可能重复消费。如果没有幂等,就可能重复扣款、重复发券、重复写入。常用方案有唯一索引、幂等表、状态机、乐观锁和业务流水号。

关联知识点

知识点继续学习
从零到精通验收清单分布式系统从零到精通验收清单
商业场景训练营分布式系统商业场景训练营
分布式系统基础基础架构
CAP、BASE 与一致性理论CAP与BASE
数据一致性设计数据一致性
幂等设计幂等设计
分布式事务总览分布式事务
XA/JTA/2PCXA/JTA/2PC
TCCTCC
SagaSaga
可靠消息与本地消息表可靠消息
SeataSeata
分布式事务选型选型与排查
共识算法主线Paxos、Raft、ZAB与多数派
Raft内部原理etcd/raft状态机、线性读与故障恢复
共识与Raft面试独立面试题
ZooKeeperZooKeeper
DubboDubbo
Service Mesh 基础主线Envoy、xDS、mTLS 与流量治理
Service Mesh 内部原理Envoy 运行时、xDS 收敛与生产治理
Redisson 分布式锁Redisson 锁
Redis 红锁Redlock
缓存一致性Redis 与数据库缓存一致性
MQ 堆积消息堆积与背压
ES 一致性MySQL 与 ES 一致性

本章小结

分布式系统不是靠某个框架变可靠,而是靠一组工程原则变可靠:超时、重试、幂等、状态机、注册发现、限流、熔断、降级、锁、事务、消息、补偿、对账、监控和追踪。学习时不要从框架名开始,而要从“网络不可靠、状态会分散、一致性有代价”开始。只要这条主线建立起来,Spring Cloud、Dubbo、ZooKeeper、RocketMQ、Redis 锁、Seata、ES 同步这些技术就能串成一个完整体系。