DDIA Part 3: 衍生数据

本文是 DDIA 第三部分的完整读书笔记,涵盖第 10-12 章:批处理、流处理、数据系统的未来。


第10章:批处理

10.1 系统类型对比

类型 特点 示例
在线服务 请求-响应模式,低延迟 Web 服务、API
批处理系统 处理大量数据,高吞吐 MapReduce、Spark
流处理系统 实时处理数据流 Kafka Streams、Flink
1
2
3
4
5
6
7
8
9
在线服务: 用户请求 ──> [服务] ──> 响应 (毫秒级)

批处理:
┌────────────────┐ ┌────────────────┐
│ 大量输入数据 │ ──> │ 批处理作业 │ ──> 输出结果
│ (TB级别) │ │ (运行数小时) │
└────────────────┘ └────────────────┘

流处理: 事件流 ──> [处理] ──> 输出流 (持续进行)

10.2 Unix 工具的批处理

1
2
3
4
5
6
7
# 找出访问量最高的 URL
cat access.log |
awk '{print $7}' | # 提取 URL 字段
sort | # 排序
uniq -c | # 计数
sort -rn | # 按计数降序排序
head -n 10 # 取前10

Unix 哲学

  • 每个程序做好一件事
  • 输出可以成为另一个程序的输入
  • 快速原型开发

10.3 MapReduce

1
2
3
4
5
6
7
8
9
10
11
12
13
14
MapReduce 流程:

输入数据 Map 阶段 Shuffle Reduce 阶段 输出
┌─────┐ ┌─────────┐ ┌──────────┐
│分片1│ ──────>│ Map 1 │ ─┐ ┌─>│ Reduce 1 │ ──> 结果1
└─────┘ └─────────┘ │ │ └──────────┘
│ ┌───────────┐ │
┌─────┐ ┌─────────┐ └>│ 按键分组 │ ─┤
│分片2│ ──────>│ Map 2 │ ───│ Shuffle │ │
└─────┘ └─────────┘ ┌─>│ │ ─┤
│ └───────────┘ │ ┌──────────┐
┌─────┐ ┌─────────┐ │ └>│ Reduce 2 │ ──> 结果2
│分片3│ ──────>│ Map 3 │ ─┘ └──────────┘
└─────┘ └─────────┘

词频统计示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Map 函数
def map(document):
for word in document.split():
emit(word, 1)

# Reduce 函数
def reduce(word, counts):
emit(word, sum(counts))

# 执行流程:
# 输入: "hello world hello"
# Map 输出: ("hello", 1), ("world", 1), ("hello", 1)
# Shuffle 后: "hello": [1, 1], "world": [1]
# Reduce 输出: ("hello", 2), ("world", 1)

10.4 Join 策略

策略 特点 适用场景
排序-合并 Join 两边都排序后合并 Reduce 端 Join
广播 Join 小表广播到所有节点 一边数据量小
分区 Join 按相同键分区 两边都很大

10.5 现代批处理框架

框架 特点
Spark 内存计算,比 MapReduce 快 10-100x
Flink 统一批流处理
Presto/Trino 交互式 SQL 查询

第11章:流处理

11.1 批处理 vs 流处理

方面 批处理 流处理
数据 有界,静态 无界,持续到达
延迟 分钟~小时 毫秒~秒
触发 定时/手动 事件驱动
结果 一次性输出 持续更新

11.2 消息系统

传统消息队列

代表:RabbitMQ, ActiveMQ, Amazon SQS

1
2
3
4
5
6
7
8
9
10
┌─────────────────────────────┐
│ 队列 │
├─────────────────────────────┤
│ msg1 │ msg2 │ msg3 │ msg4 │
└──┬───┴──┬───┴──┬───┴──┬────┘
▼ ▼ ▼ ▼
消费1 消费2 消费1 消费2

每条消息只被一个消费者处理
处理后消息被删除

日志型消息系统(Kafka)

1
2
3
4
5
6
7
8
分区0: [msg0] [msg3] [msg6] [msg9]  ──> 消费者A
分区1: [msg1] [msg4] [msg7] [msg10] ──> 消费者B
分区2: [msg2] [msg5] [msg8] [msg11] ──> 消费者C

特点:
- 消息持久化,可重放
- 保序(分区内)
- 多消费者组可独立消费

11.3 Kafka 架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────────────────┐
│ Kafka Cluster │
├─────────────────────────────────────────────────────┤
│ Topic: orders │
│ ┌────────────────┐ ┌────────────────┐ │
│ │ Partition 0 │ │ Partition 1 │ │
│ │ [0][1][2][3]...│ │ [0][1][2][3]...│ │
│ │ Leader: B1 │ │ Leader: B2 │ │
│ │ Replica: B2 │ │ Replica: B1 │ │
│ └────────────────┘ └────────────────┘ │
│ │
│ Broker 1 Broker 2 │
└─────────────────────────────────────────────────────┘

┌───────────────┼───────────────┐
▼ ▼ ▼
Consumer 1 Consumer 2 Consumer 3
Group A Group A Group B

11.4 变更数据捕获(CDC)

捕获数据库的变更,将其转换为事件流

1
2
3
4
5
6
7
8
9
10
┌─────────────┐      ┌─────────────┐      ┌─────────────┐
│ 应用程序 │ ──> │ 数据库 │ ──> │ CDC 工具 │
└─────────────┘ └─────────────┘ │ (Debezium) │
│ └─────────────┘
变更日志 │
│ 事件流到 Kafka
▼ │
┌───────────┐ ┌───────────┐
│ binlog │ │ Kafka │
└───────────┘ └───────────┘

应用场景

  • 同步搜索索引、缓存
  • 微服务间数据同步
  • 实时 ETL

11.5 事件溯源(Event Sourcing)

1
2
3
4
5
6
7
8
9
10
11
传统方式:直接修改状态
账户余额: 100 ──> 90 ──> 140 ──> 110

事件溯源:存储事件
事件日志:
1. 初始存款 100
2. 取款 10
3. 存款 50
4. 取款 30

重放事件 ──> 计算当前状态

11.6 时间语义

时间类型 定义 用途
事件时间 事件实际发生的时间 业务逻辑
处理时间 事件到达处理系统的时间 系统监控
摄入时间 事件进入流处理系统的时间 折中方案

11.7 窗口操作

窗口类型 特点
滚动窗口 固定大小,不重叠
滑动窗口 固定大小,可重叠
会话窗口 按活动间隙分割
1
2
3
4
5
6
7
滚动窗口 (5分钟):
[00:00-05:00] [05:00-10:00] [10:00-15:00]

滑动窗口 (5分钟窗口, 1分钟滑动):
[00:00-05:00]
[01:00-06:00]
[02:00-07:00]

第12章:数据系统的未来

12.1 数据集成

将多个系统的数据统一管理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
┌─────────────────────────────────────────────────────┐
│ 数据集成架构 │
├─────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 数据库 │ │ 缓存 │ │ 搜索引擎 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
│ └──────────────┼──────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────┐ │
│ │ 事件日志 │ │
│ │ (Kafka) │ │
│ └──────────────┘ │
│ │ │
│ ┌──────────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ 分析系统 │ │ ML平台 │ │ 监控系统 │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────┘

12.2 Lambda 架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Lambda 架构:
输入数据

┌─────────┴─────────┐
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 批处理层 │ │ 速度层 │
│ (全量计算) │ │ (增量计算) │
│ │ │ │
│ MapReduce │ │ Storm/Flink │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ 批处理视图 │ │ 实时视图 │
└──────┬───────┘ └──────┬───────┘
│ │
└─────────┬─────────┘

┌──────────────┐
│ 服务层 │
│ (合并结果) │
└──────────────┘

问题:需要维护两套代码

12.3 Kappa 架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Kappa 架构:
输入数据


┌──────────────┐
│ 日志存储 │
│ (Kafka) │
└──────┬───────┘


┌──────────────┐
│ 流处理 │
│ (Flink) │
└──────┬───────┘


┌──────────────┐
│ 服务层 │
└──────────────┘

统一批流处理:重放日志即可重新计算

12.4 解绑数据库

将数据库的各个功能分解为独立组件

功能 传统数据库 解绑后
存储 B-Tree 分布式文件系统
事务 内置 独立事务管理器
索引 内置 外部搜索引擎
缓存 内置 Redis

12.5 端到端论证

某些功能只能在端到端层面正确实现

示例:exactly-once 消息传递

  • 中间件可能声称 exactly-once
  • 但网络可能中断
  • 真正的 exactly-once 需要应用层去重

12.6 数据流系统设计原则

  1. 不可变性:日志是追加的,事件不可修改
  2. 可重放性:可以从日志重建状态
  3. 分离计算和存储:计算层无状态
  4. 模式演化:支持模式向前/向后兼容

本章关键要点

  1. 批处理适合高吞吐的离线计算
  2. MapReduce 是分布式批处理的基础范式
  3. 流处理适合低延迟的实时计算
  4. Kafka 是现代流处理的核心组件
  5. CDC 连接传统数据库和事件流
  6. 事件溯源将状态变更作为事件序列存储
  7. 时间语义在流处理中至关重要
  8. Lambda/Kappa 架构是批流融合的尝试
  9. 解绑数据库提供更大的灵活性

延伸阅读

  • 《Streaming Systems》:流处理权威指南
  • 《Kafka: The Definitive Guide》:Kafka 权威指南
  • 《Designing Data-Intensive Applications》原书