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 分区解决 ____,热点表现 ____
计费链路一致性:强一致环节 ____;最终一致 + 对账环节 ____
- 读完 全景串讲
- 能口述 30 秒版
- 填完上面三行产出
- 完成 Ch.5–9 跳读或读完 核心概念下钻
- 扫 Flink §4 Checkpoint / EOS
已有批流经验 · 捷径(约 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 改天补;对账差异先怀疑 **复制延迟 / 分区扫错**。分布式全景:历史脉络 → 挑战 → 解法(串讲)¶
一句话主线:数据与流量超过单机 → 复制 换可用与读扩展 → 分区 换写扩展与裁剪 → 跨系统要「像一台」却发现 分布式事务太贵 → 承认 网络/时钟/部分失败 → 用 一致性谱系 + 幂等 + 对账 换可运维,元数据用 共识 协调。
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 分区键、Kafkapartition、FlinkkeyBy。
// 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 事务性写入 + 幂等 sink → flink-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 offset、Flink JobManager、分区 leader 必须多节点 对同一份元数据达成一致。
- 业务 汇总金额 往往 不需要 线性一致;T+1 对账 接受分钟~小时窗口。
本专题关联
与本专题映射¶
| DDIA 概念 | 本专题文档 | 项目对应 |
|---|---|---|
| 复制 | ClickHouse | ReplicatedMergeTree、副本延迟 |
| 分区 | ClickHouse + Kafka | bill_date 分区、topic partition |
| 事务 / EOS | Flink | Checkpoint、Kafka 事务、幂等 sink |
| 最终一致 + 对账 | Ch.10、双指针 §二 | 日批 vs 实时指标 |
| 调度重试 | Airflow | 分区重跑、幂等覆盖 |
| 流乱序 | Ch.11 | Watermark、事件时间 |
建议下潜顺序:本节 → ClickHouse 副本/分区 → Flink §4 → Ch.10/11 对账视角
批 / 流 / 分布式三角¶
| 维度 | 批处理(Ch.10) | 流处理(Ch.11) | 分布式(本篇) |
|---|---|---|---|
| 核心问题 | 有界、可重跑 | 无界、低延迟 | 多机、故障、副本 |
| 扩展手段 | 分区并行 scan | partition + 并行度 | 复制 + 分片 |
| 正确性 | 幂等写分区 | Checkpoint、EOS | 复制延迟、quorum |
| 不一致窗口 | 重跑收敛 | 状态恢复、迟到事件 | 异步副本、CDC 延迟 |
| 收敛手段 | 日批 权威 | 对账修正 | 对账 + 监控 |
Flink EOS 需要的第二篇概念
- 幂等(Ch.7 思想)— sink 重复写同键覆盖
- 协调(Ch.9)— Checkpoint 与 Kafka 事务
- 分区对齐(Ch.6)—
keyBy与 Kafka partition - 承认延迟(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;正文挑 上表「建议读」 小节。
读完本节,你应该能口述¶
- 复制 解决可用/读扩展;异步复制的 延迟与丢数 风险。
- 分区 解决写扩展与裁剪;热点 与扫错分区的后果。
- 计费链路 哪段强一致、哪段最终一致,对账 补什么。
- 部分失败 为何不能只靠超时;流用 事件时间,批用 重跑。
- 最终一致 + 幂等 是数据管道默认;共识 留给 offset/leader。
- Flink EOS 与第二篇 三块 的对应(见 三角表)。
下潜顺序:本节 → Part 2 在框架中的位置 → Ch.11 / Flink
QA 疑问解答¶
格式:❌ 我的理解 + ✅ 纠正。关联正文锚点。
批量提问:复制 待解答 中BATCH_START…BATCH_END段。
待解答(批量问 Cursor)¶
(暂无 — 按下面格式追加)
待解答条目格式:
### Qn:一句话问题(YYYY-MM-DD)
**关联**:[§ Ch.5](#ch5-复制)
**❓ 我的疑问**
- …
**🤔 我目前的理解(可选)**
- …
(已解答条目移到此,<!-- ANSWERED_START --> … <!-- ANSWERED_END -->)
待沉淀¶
- 项目中 ClickHouse ReplicatedMergeTree 与复制延迟
- 计费表按
bill_date分区的实践与坑 - 计费链路:哪些环节强一致、哪些最终一致
- 监控清单:延迟、积压、失败率、数据新鲜度
- 「业务上可接受的不一致窗口」SLA 定义
- 流批指标对账方案(链 Ch.11 待沉淀)