名称: 流处理
描述: 用于设计实时数据处理系统、选择流处理框架或实现事件驱动架构。涵盖Kafka、Flink和流处理模式。
允许工具: Read, Glob, Grep
流处理
用于实时数据处理、事件流和流分析的模式和技术。
何时使用此技能
- 设计实时数据管道
- 选择流处理框架
- 实现事件驱动架构
- 构建实时分析
- 理解流处理与批处理的权衡
批处理 vs 流处理
比较
| 方面 |
批处理 |
流处理 |
| 延迟 |
分钟到小时 |
毫秒到秒 |
| 数据 |
有界(有限) |
无界(无限) |
| 处理 |
一次性处理所有 |
数据到达时处理 |
| 状态 |
每次运行重新计算 |
持续维护 |
| 复杂度 |
较低 |
较高 |
| 成本 |
通常较低 |
通常较高 |
何时使用流处理
使用流处理当:
- 需要实时响应(<1分钟)
- 事件需要立即行动(欺诈、警报)
- 数据持续到达
- 用户期望实时更新
- 时间敏感的业务决策
使用批处理当:
- 每日/每小时报告足够
- 需要复杂转换
- 成本优化优先
- 历史分析
- 一次性处理
流处理概念
事件时间 vs 处理时间
事件时间:事件实际发生的时间
处理时间:事件被处理的时间
示例:
┌─────────────────────────────────────────────────────────┐
│ 事件:10:00:00的购买(事件时间) │
│ 网络延迟:5秒 │
│ 处理时间:10:00:05(处理时间) │
└─────────────────────────────────────────────────────────┘
为什么重要:
- 需要处理迟到事件
- 顺序不保证
- 水印跟踪进度
水印
水印 = "此时间之前的所有事件都已到达"
事件流:
──[10:01]──[10:02]──[10:00]──[10:03]──[水印: 10:00]──
允许系统:
- 知道窗口何时完成
- 处理迟到事件
- 平衡延迟与完整性
窗口
翻滚窗口(固定,不重叠):
|─────|─────|─────|
0 5 10 15(秒)
滑动窗口(固定,重叠):
|─────|
|─────|
|─────|
大小:5秒,滑动:2秒
会话窗口(基于活动):
|──────| |───────────| |───|
用户活动间隙定义窗口
计数窗口:
每N个事件处理一次
状态管理
有状态操作需要维护状态:
- 聚合(和、计数、平均)
- 流之间的连接
- 模式检测
- 去重
状态后端:
- 内存中(快速,有限)
- RocksDB(较大,持久)
- 外部(Redis、数据库)
流处理框架
Apache Kafka Streams
特点:
- 库(非集群)
- 精确一次语义
- Kafka原生
- Java/Scala
最佳用于:
- Kafka中心架构
- 简单转换
- 微服务
示例拓扑:
源 → 过滤 → 映射 → 聚合 → 汇
Apache Flink
特点:
- 分布式集群
- 真正流处理(非微批)
- 高级状态管理
- SQL支持
最佳用于:
- 复杂事件处理
- 大规模流处理
- 低延迟需求
示例:
DataStream<Event> events = env.addSource(kafkaSource);
events
.keyBy(e -> e.getUserId())
.window(TumblingEventTimeWindows.of(Time.minutes(5)))
.aggregate(new CountAggregator())
.addSink(sink);
Apache Spark Streaming
特点:
- 微批处理
- 统一批处理 + 流处理API
- 广泛生态系统
- Python, Scala, Java, R
最佳用于:
- 有Spark经验的团队
- 批处理 + 流处理统一
- 机器学习集成
延迟:秒(微批)
Kafka Streams vs Flink vs Spark
| 因素 |
Kafka Streams |
Flink |
Spark Streaming |
| 部署 |
库 |
集群 |
集群 |
| 延迟 |
低 |
最低 |
中等 |
| 状态 |
好 |
优秀 |
好 |
| 精确一次 |
是 |
是 |
是 |
| 复杂度 |
低 |
高 |
中等 |
| 扩展 |
随Kafka |
独立 |
独立 |
| SQL |
有限 |
是 |
是 |
| ML集成 |
有限 |
有限 |
优秀 |
流处理模式
过滤
输入:所有事件
输出:匹配条件的事件
示例:仅处理金额 > 1000的事件
映射/转换
输入:事件类型A
输出:事件类型B
示例:用客户数据丰富订单事件
聚合
输入:多个事件
输出:单个聚合结果
示例:
- 每个窗口的事件计数
- 每个用户的金额总和
- 每个端点的平均延迟
连接模式
流-流连接:
┌─────────────┐ ┌─────────────┐
│ 订单 │ ──► │ 连接 │
└─────────────┘ │ (按订单号 │
┌─────────────┐ │ 在窗口中) │
│ 发货 │ ──► │ │
└─────────────┘ └─────────────┘
流-表连接(丰富):
┌─────────────┐ ┌─────────────┐
│ 事件 │ ──► │ 连接 │
└─────────────┘ │ (按客户查找 │
┌─────────────┐ │ ) │
│ 客户 │ ──► │ │
│ 表 │ └─────────────┘
└─────────────┘
去重
问题:至少一次交付的重复事件
解决方案:
1. 在状态中跟踪已见ID(带TTL)
2. 如果已见,丢弃
3. 如果新,处理并存储ID
状态:{事件ID: 时间戳}
TTL:基于预期重复窗口
事件交付保证
最多一次
可能丢失事件,从不重复
处理 → 提交 → (如果失败,事件丢失)
使用当:可接受丢失,偏好简单性
至少一次
从不丢失,可能有重复
提交 → 处理 → (如果失败,重新处理)
使用当:不允许丢失,下游处理重复
精确一次
从不丢失,从不重复
要求:
- 幂等操作,或
- 事务处理
工作原理:
1. 从事务性源读取
2. 处理并更新状态
3. 一起写入输出和提交
Flink:检查点 + 两阶段提交
Kafka Streams:事务生产者 + EOS
迟到事件处理
策略
1. 丢弃迟到事件
简单,可能丢失数据
2. 允许迟到事件(允许延迟)
如果在延迟阈值内,处理
3. 侧输出迟到事件
主流处理准时事件
侧流单独处理迟到事件
4. 重新处理历史
批处理作业修复迟到数据影响
水印策略
有界乱序:
水印 = 最大事件时间 - 最大延迟
示例:
最大事件时间 = 10:00:00
最大延迟 = 5秒
水印 = 09:59:55
09:59:55之前的事件视为完成
可扩展性模式
分区
按键分区以并行处理:
┌─────────────────────────────────────────────────────┐
│ Kafka主题(3个分区) │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────┐│
│ │ 分区0 │ │ 分区1 │ │ 分区2 ││
│ │ 用户a, b │ │ 用户c, d │ │ 用户e, f ││
│ └─────────────┘ └─────────────┘ └─────────────────┘│
└─────────────────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│工人0 │ │工人1 │ │工人2 │
└─────────┘ └─────────┘ └─────────┘
背压
当下游无法跟上时:
1. 缓冲(风险:内存溢出)
2. 丢弃(风险:数据丢失)
3. 背压(减慢源)
Flink:背压自动传播
Kafka:消费者滞后指示背压
监控流处理应用
关键指标
吞吐量:
- 每秒事件数
- 每秒字节数
延迟:
- 处理延迟
- 端到端延迟
健康度:
- 消费者滞后
- 检查点时长
- 背压率
- 错误率
消费者滞后
滞后 = 最新偏移量 - 消费者偏移量
高滞后指示:
- 处理太慢
- 需要更多并行度
- 下游瓶颈
监控:设置警报阈值
最佳实践
1. 需要时设计为精确一次
2. 明确处理迟到事件
3. 使用事件时间,而非处理时间
4. 密切监控消费者滞后
5. 规划状态恢复
6. 用实际数据量测试
7. 实现背压处理
8. 尽可能保持处理幂等
相关技能
消息队列 - 消息模式
数据架构 - 数据平台设计
ETL-ELT模式 - 数据管道模式