跳转至

DDIA 第二篇:分布式数据系统 — 复习专题

梳理自《Designing Data-Intensive Applications》第 2 版 Ch.5–9(第二篇)
Ch.5 复制 · Ch.6 分区 · Ch.7 事务 · Ch.8 分布式系统的麻烦 · Ch.9 一致性与共识
定位:历史脉络 → 挑战 → 解法,先框架后下钻;完整第二篇约 2–3h(150–180min)
配对:Ch.10 批处理 · Ch.11 流处理

← 返回 DDIA 框架 | 专题索引 | ↑ 总脑图 L3 分布式 · 对账场景

组件深潜ClickHouse OLAP · Flink 实时计算 · Airflow 调度


完整第二篇复习清单(150–180min)

节奏:每 50 分钟学习 + 10 分钟休息;建议分 两次 90min一次 3h 块 完成。
先读 全景串讲,再按章下钻。

时段 做什么 锚点
0–30 min 全景串讲 + 30 秒版 + 架构图 Ch.5 复制
30–55 min § Ch.5 复制 + § Ch.6 分区 + DDIA 书 Ch.5–6 跳读
55–90 min § Ch.7 事务 + MVP 休息 10min
90–110 min § Ch.8 分布式麻烦
110–150 min § Ch.9 一致性与共识 + 批流分布式三角 不抠 Raft 证明
150–180 min § 与本专题映射 + Flink EOS 扫 15min + 填空产出

读法

  • 第二篇 不必逐字翻译;每章 开头动机 + 章末 Summary + 图 优先。
  • Ch.9 共识算法实现细节可跳;带走 一致性谱系、Quorum、协调服务角色 即可。
  • 已有京东 Flink/批流 经验:可走 捷径(约 60min)

必须搞懂的点

概念 问自己 下文
复制 多副本为了解决什么?异步复制会丢什么? § Ch.5
分区 和「分库分表」一样吗?热点从哪来? § Ch.6
分布式事务 业务库→Kafka→数仓为啥很少 2PC? § Ch.7
部分失败 超时后请求到底成功没? § Ch.8
最终一致 T+1 对账在补什么洞? § Ch.9
EOS Flink 精确一次依赖哪几块分布式语义? § 三角表

写下来(必做)

复制一句话:Leader/Follower 解决 ____,异步复制风险 ____

分区一句话:按 bill_date 分区解决 ____,热点表现 ____

计费链路一致性:强一致环节 ____;最终一致 + 对账环节 ____

已有批流经验 · 捷径(约 60min) 若 [Ch.10](./ddia-ch10-batch-processing.md) / [Ch.11](./ddia-ch11-stream-processing.md) 已熟,**不必走完整 3h**: | 时段 | 做什么 | |------|--------| | 0–15 min | [全景串讲](#分布式全景历史脉络--挑战--解法串讲) + [30 秒版](#30-秒版) | | 15–25 min | [架构图 · 计费链路](#应用视角计费与对账链路) | | 25–40 min | [§ Ch.7 事务](#ch7-事务) + [§ Ch.9](#ch9-一致性与共识)(最终一致 + Quorum) | | 40–50 min | [批流分布式三角](#批--流--分布式三角) + [Flink EOS](./flink-streaming.md#43-exactly-once-端到端) | | 50–60 min | 填空产出 + 1 条 QA | Ch.5–6、Ch.8 改天补;对账差异先怀疑 **复制延迟 / 分区扫错**。

分布式全景:历史脉络 → 挑战 → 解法(串讲)

用法:先读这一节建立时间线,再 下钻。疑问记入 QA

一句话主线:数据与流量超过单机 → 复制 换可用与读扩展 → 分区 换写扩展与裁剪 → 跨系统要「像一台」却发现 分布式事务太贵 → 承认 网络/时钟/部分失败 → 用 一致性谱系 + 幂等 + 对账 换可运维,元数据用 共识 协调。


0. 起点:单机数据库不够

场景 计费明细、广告事件:磁盘、连接数、单点故障顶不住
挑战 ① 容量;② 可用性(机器会挂);③ 读 QPS
第一反应 买更大机器(垂直扩展)
天花板 成本曲线陡;仍 单点;备份恢复慢

需要 多机分担数据与流量,且一台挂了别全挂 → 复制 + 分区。


1. 复制(Replication):多份数据

挑战 读多写少、要 7×24、故障切换
解法 Leader/Follower、Multi-Leader、Leaderless + Quorum
得到 高可用、读扩展、就近读
新代价 异步复制延迟;故障切换 脑裂;读从库 读己之写 不保证

本栈:ClickHouse ReplicatedMergeTree;Kafka ISR 副本。


2. 分区(Partitioning):拆成多片

挑战 单表/单节点写不下;全表扫描太贵
解法 按 key 哈希、按时间/ID 范围、复合策略
得到 水平扩展;查询 分区裁剪
新代价 热点分区;加节点 再平衡;跨分区查询 scatter-gather

本栈PARTITION BY toYYYYMM(bill_date);Kafka partition ↔ Flink keyBy


3. 事务(Transactions):跨行/跨表仍在一库

挑战 转账、库存扣减要 原子;并发要隔离
解法 单机 ACID;隔离级别
得到 业务在 一个数据库 里好推理
新代价 跨库、跨服务 的「全局事务」→ 2PC/XA 重、慢、运维难

数据工程现实:业务库 → Kafka → Flink → ClickHouse 很少强一致分布式事务;常见 At-least-once + 幂等 + 日批对账


4. 分布式麻烦(Ch.8):不可靠的基础设施

挑战 网络丢包/延迟;时钟漂移;部分失败(一半成功)
解法 超时、重试、幂等键、事件时间(非信任处理时间)
和流的关系 Ch.11 Watermark 对付乱序
和批的关系 Airflow 重试、分区 幂等覆盖

拜占庭故障(恶意节点)一般 业务数据管道不直接处理,知道存在即可。


5. 一致性与共识(Ch.9):多副本「听谁的」

挑战 多副本、多消费者,什么叫「已经写成功」?
解法谱系 线性一致 → 顺序 → 因果 → 最终一致(数仓最常见)
机制 Leader 选举、Quorum 读写、Raft/Paxos(ZK/etcd)
工程取舍 元数据/offset 要强协调;报表 接受短暂不一致 + 对账

明确跳过:Raft 日志证明、Paxos 细节 — 改天需要再抠。


6. 时间线(复习用)

timeline
  title 分布式数据系统演化(复习用)
  section 扩展
    垂直扩展 : 更大单机
    主从复制 : MySQL 主从 读扩展
    分片分区 : 分库分表 时间分区
  section 语义
    单机ACID : OLTP 事务
    最终一致 : 消息队列 幂等消费
    协调服务 : ZK etcd 选主 offset
  section 数据工程
    T+1对账 : 批权威修正流
    FlinkEOS : Checkpoint 两阶段提交

30 秒版

单机放不下 / 会挂
  → 复制(高可用 + 读扩展,注意异步延迟)
  → 分区(写扩展 + 裁剪,注意热点)
  → 跨系统少用 2PC → 幂等 + 最终一致 + 日批对账
  → 承认网络/时钟/部分失败 → 重试 + 事件时间 + 监控
  → 强语义留给协调层(Kafka offset、Flink Checkpoint)

架构图(双视角)

拓扑视角:Leader / Follower + 分区路由

flowchart TB
  subgraph clients [客户端]
    Writer[写入]
    Reader[查询]
  end

  subgraph shard0 [分区0]
    L0[Leader]
    F0a[Follower]
    F0b[Follower]
    L0 --> F0a
    L0 --> F0b
  end

  subgraph shard1 [分区1]
    L1[Leader]
    F1[Follower]
    L1 --> F1
  end

  Writer -->|路由key| L0
  Writer -->|路由key| L1
  Reader -->|可读从库| F0a
  Reader -->|读主| L1
元素 含义
Leader 单写者(或主写路径),避免写冲突
Follower 异步/同步复制,读扩展
分区 bill_date / campaign_id 等路由到不同 shard

应用视角:计费与对账链路

标出 最终一致窗口 — 对账要覆盖的区间。

flowchart LR
  subgraph oltp [业务侧]
    PG[(业务库 Postgres)]
  end

  subgraph pipe [管道]
    Kafka[(Kafka)]
    Flink[Flink]
  end

  subgraph olap [分析侧]
    CH[(ClickHouse)]
    Dbt[dbt 日批]
  end

  subgraph reconcile [收敛]
    Daily[日批对账作业]
  end

  PG -->|CDC 或 导出| Kafka
  Kafka --> Flink
  Flink -->|分钟级 可能滞后| CH
  PG -->|T+1 有界| Dbt
  Dbt --> CH
  CH --> Daily
  Flink -.->|流指标| Daily
典型一致性 风险
PG 单库事务 强一致(单库 ACID)
PG → Kafka 至少一次 / 最终一致 重复、延迟
Flink → CH EOS 或 至少一次 + 幂等 sink 重复写、状态恢复
日批 dbt 有界重跑 分区扫错、从库延迟
Daily 对账 权威收敛 定义「可接受不一致窗口」

MVP 示例代码(Java):分区与复制延迟

教学用伪代码:分区路由 + 写主读从的可见性延迟;对应 拓扑视角
生产对照:ClickHouse 分区键、Kafka partition、Flink keyBy

// PartitionAndReplicaDemo.java (pseudocode)

import java.util.*;

/** 按 key 哈希到固定分区(简化 Kafka / CH shard) */
class PartitionRouter {
    private final int numPartitions;

    PartitionRouter(int numPartitions) {
        this.numPartitions = numPartitions;
    }

    int route(String key) {
        return Math.floorMod(key.hashCode(), numPartitions);
    }
}

/** 模拟:写 Leader 后,Follower 异步落后 replicationLagMs */
class AsyncReplicaLag {
    private final Map<String, String> leader = new HashMap<>();
    private final Map<String, String> follower = new HashMap<>();
    private final long replicationLagMs;

    AsyncReplicaLag(long replicationLagMs) {
        this.replicationLagMs = replicationLagMs;
    }

    void write(String key, String value) {
        leader.put(key, value);
        // 异步复制:follower 延迟才可见
        schedule(() -> follower.put(key, value), replicationLagMs);
    }

    String readFromLeader(String key) {
        return leader.get(key);
    }

    String readFromFollower(String key) {
        return follower.get(key); // 可能 null → 「读己之写」失败
    }

    private void schedule(Runnable r, long delayMs) { /* 线程池 / 定时 */ }
}

/** Quorum 读示意:R+W > N 则读可能碰到最新写(DDIA Ch.9 直觉) */
class QuorumRead {
    private final int n; // 副本数
    private final int w; // 写确认数
    private final int r; // 读确认数

    QuorumRead(int n, int w, int r) {
        if (w + r <= n) throw new IllegalArgumentException("need w + r > n");
        this.n = n; this.w = w; this.r = r;
    }
    // 生产:Dynamo 风格;Kafka ISR 是另一套语义,此处仅建立直觉
}

class Demo {
    public static void main(String[] args) {
        PartitionRouter router = new PartitionRouter(8);
        int p = router.route("campaign_42"); // → 某 partition

        AsyncReplicaLag store = new AsyncReplicaLag(200);
        store.write("user:1", "balance=100");
        System.out.println(store.readFromLeader("user:1"));   // balance=100
        System.out.println(store.readFromFollower("user:1")); // 可能 null → 对账/读主
    }
}
教什么
PartitionRouter Ch.6 路由;与 Flink key 对齐
AsyncReplicaLag Ch.5 异步延迟 → 对账差异来源
QuorumRead Ch.9 读写 quorum 直觉(不代替 Kafka ISR 公式)

核心概念下钻

Ch.5 复制

目的:高可用 + 读扩展
├── 主从复制(Leader / Follower)     ← 最常见
├── 多主复制(Multi-Leader)          ← 多数据中心写,冲突难
└── 无主复制(Leaderless + Quorum)   ← Dynamo 风格
概念 要搞懂
同步 vs 异步复制 同步:丢数风险低、延迟高;异步:快、可能 丢最近写
故障切换 脑裂:旧主仍写、新主也写
读己之写 用户刚写后读从库,可能 看不见 → 读主或会话粘滞
单调读 不会先读新再读旧(时间倒流)

本专题关联

  • ClickHouse ReplicatedMergeTree:副本间合并、延迟监控
  • Kafka ISR:副本集合与 leader 选举
  • 对账差异:有时来自 从库/副本延迟,不是业务公式错

Ch.6 分区

目的:水平扩展 + 裁剪扫描
├── 按 key 哈希分区      → 均匀、难范围查询
├── 按范围分区(时间)   → 计费 bill_date 常见
└── 复合策略
问题 说明
热点分区 某 key 过大(大广告主、默认分区)
再平衡 加节点迁数据,带宽与一致性窗口
跨分区查询 scatter-gather,最慢分区拖尾
二级索引 分区内的局部索引,不是全局 B+Tree

本专题关联

  • ClickHouse PARTITION BY toYYYYMM(date)clickhouse-deep-dive
  • Flink keyBy 与 Kafka partition 对齐 → 同 key 进同并行度,状态本地
  • 扫错 dt 分区 = 批处理对账全错(Ch.10 有界输入)

Ch.7 事务

层次 例子
单机 ACID Postgres 单条 INSERT + UPDATE
分布式事务 2PC、XA — 重、锁久、协调器单点
最终一致 消息 + 幂等消费 + 日批对账
隔离级别 读未提交 → 可串行化;OLAP 常更松

数据工程现实

做法 何时用
单库事务 订单、账户 仍在 OLTP
Saga / 补偿 多服务长流程,可接受中间态
不用 2PC 管道型:CDC → Kafka → Flink → CH
幂等 + 对账 默认推荐 计费/广告汇总

与 Flink:Exactly-Once = Checkpoint + Kafka 事务性写入 + 幂等 sinkflink-streaming §4


Ch.8 分布式系统的麻烦(精读要点)

故障类型 表现 工程应对
网络丢包/延迟 超时、重试、重复请求 幂等键、去重表
时钟漂移 事件时间乱序 Watermark(Ch.11)
部分失败 写 CH 成功、写 Kafka 失败? 两阶段、对账、告警
垃圾回收停顿 「进程活着但无响应」 心跳、超时、K8s 重启

本专题关联

  • Flink:事件时间 不信任处理时间
  • Airflow:任务 重试、SLA、失败分区 重跑
  • 监控:延迟、积压、新鲜度、失败率(待沉淀清单)

Ch.9 一致性与共识

一致性谱系(从强到弱)
├── 线性一致性(最强,代价高)
├── 顺序一致性
├── 因果一致性
└── 最终一致性(数仓链路最常见)
机制 用途
Leader 选举 单写者,避免写冲突
Quorum 读写 R+W>N 时读可能见最新写
共识算法 Raft/Paxos — ZK、etcd、Kafka 控制器

为何需要共识(不抠证明)

  • Consumer offsetFlink JobManager分区 leader 必须多节点 对同一份元数据达成一致
  • 业务 汇总金额 往往 不需要 线性一致;T+1 对账 接受分钟~小时窗口。

本专题关联

  • 数仓 T+1 = 接受 短暂不一致,用批 收敛
  • Ch.10 批 权威 vs Ch.11 流 近似

与本专题映射

DDIA 概念 本专题文档 项目对应
复制 ClickHouse ReplicatedMergeTree、副本延迟
分区 ClickHouse + Kafka bill_date 分区、topic partition
事务 / EOS Flink Checkpoint、Kafka 事务、幂等 sink
最终一致 + 对账 Ch.10双指针 §二 日批 vs 实时指标
调度重试 Airflow 分区重跑、幂等覆盖
流乱序 Ch.11 Watermark、事件时间

建议下潜顺序:本节 → ClickHouse 副本/分区Flink §4Ch.10/11 对账视角


批 / 流 / 分布式三角

维度 批处理(Ch.10) 流处理(Ch.11) 分布式(本篇)
核心问题 有界、可重跑 无界、低延迟 多机、故障、副本
扩展手段 分区并行 scan partition + 并行度 复制 + 分片
正确性 幂等写分区 Checkpoint、EOS 复制延迟、quorum
不一致窗口 重跑收敛 状态恢复、迟到事件 异步副本、CDC 延迟
收敛手段 日批 权威 对账修正 对账 + 监控

Flink EOS 需要的第二篇概念

  1. 幂等(Ch.7 思想)— sink 重复写同键覆盖
  2. 协调(Ch.9)— Checkpoint 与 Kafka 事务
  3. 分区对齐(Ch.6)— keyBy 与 Kafka partition
  4. 承认延迟(Ch.5)— 端到端仍可能有 对账窗口

DDIA Ch.5–9 书内读法对照

建议读 可跳或扫读
Ch.5 复制 Leader/Follower、同步异步、故障切换 多主细节(若无多活)
Ch.6 分区 分区策略、热点、再平衡 个别 DB 专有实现
Ch.7 事务 ACID、隔离级别、为何少用 2PC 可串行化实现细节
Ch.8 麻烦 不可靠网络、部分失败、时钟 拜占庭长篇
Ch.9 一致 一致性谱系、Quorum、共识用途 Raft/Paxos 证明

统一读法:每章 先读开头 + Summary;正文挑 上表「建议读」 小节。


读完本节,你应该能口述

  1. 复制 解决可用/读扩展;异步复制的 延迟与丢数 风险。
  2. 分区 解决写扩展与裁剪;热点 与扫错分区的后果。
  3. 计费链路 哪段强一致、哪段最终一致对账 补什么。
  4. 部分失败 为何不能只靠超时;流用 事件时间,批用 重跑
  5. 最终一致 + 幂等 是数据管道默认;共识 留给 offset/leader。
  6. Flink EOS 与第二篇 三块 的对应(见 三角表)。

下潜顺序:本节 → Part 2 在框架中的位置Ch.11 / Flink


QA 疑问解答

格式:❌ 我的理解 + ✅ 纠正。关联正文锚点。
批量提问:复制 待解答BATCH_STARTBATCH_END 段。

待解答(批量问 Cursor)

(暂无 — 按下面格式追加)

待解答条目格式:

### Qn:一句话问题(YYYY-MM-DD)

**关联**:[§ Ch.5](#ch5-复制)

**❓ 我的疑问**
- **🤔 我目前的理解(可选)**
- 

(已解答条目移到此,<!-- ANSWERED_START --><!-- ANSWERED_END -->


待沉淀

  • 项目中 ClickHouse ReplicatedMergeTree 与复制延迟
  • 计费表按 bill_date 分区的实践与坑
  • 计费链路:哪些环节强一致、哪些最终一致
  • 监控清单:延迟、积压、失败率、数据新鲜度
  • 「业务上可接受的不一致窗口」SLA 定义
  • 流批指标对账方案(链 Ch.11 待沉淀