本文是 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
| cat access.log | awk '{print $7}' | sort | uniq -c | sort -rn | head -n 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
| def map(document): for word in document.split(): emit(word, 1)
def reduce(word, counts): emit(word, sum(counts))
|
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 数据流系统设计原则
- 不可变性:日志是追加的,事件不可修改
- 可重放性:可以从日志重建状态
- 分离计算和存储:计算层无状态
- 模式演化:支持模式向前/向后兼容
本章关键要点
- 批处理适合高吞吐的离线计算
- MapReduce 是分布式批处理的基础范式
- 流处理适合低延迟的实时计算
- Kafka 是现代流处理的核心组件
- CDC 连接传统数据库和事件流
- 事件溯源将状态变更作为事件序列存储
- 时间语义在流处理中至关重要
- Lambda/Kappa 架构是批流融合的尝试
- 解绑数据库提供更大的灵活性
延伸阅读
- 《Streaming Systems》:流处理权威指南
- 《Kafka: The Definitive Guide》:Kafka 权威指南
- 《Designing Data-Intensive Applications》原书