Structured Streaming:把流当成无界表
上一篇解决了数据源 API 和谓词下推。这一篇进入流处理——Structured Streaming。
Spark 的流处理经历了两代:DStream(基于 RDD 的离散化流)和 Structured Streaming(基于 DataFrame/Dataset 的结构化流)。DStream 已经停止演进,Structured Streaming 是当前唯一推荐的流处理 API。
Structured Streaming 的核心模型可以用一句话概括:把流数据看成一张不断追加新行的无界表,复用 Spark SQL 的 Catalyst 优化器和 Tungsten 执行引擎来处理。
本文只抓一个问题:无界表模型如何工作,以及 MicroBatch 执行引擎如何把连续的流切成离散的批次。
无界表模型
1 | |
用户写的查询代码和批处理完全相同:
1 | |
这段代码和批处理的唯一区别是 readStream/writeStream 替代了 read/write。groupBy.count 的逻辑完全相同。Structured Streaming 引擎负责把这个批查询"增量化"——每次只处理新到的数据,而不是重新扫描全部历史数据。
三种输出模式
输出模式(Output Mode)决定了每个 micro-batch 写入输出表的行为:
1 | |
选择输出模式的经验法则:无聚合查询用 Append,全局聚合用 Complete,有 watermark 的窗口聚合用 Append 或 Update。
MicroBatch 执行引擎
Structured Streaming 的默认执行引擎是 MicroBatch。它把连续的输入流切成离散的小批次(micro-batch),每个批次作为一个独立的 Spark 作业执行:
1 | |
触发间隔(Trigger)控制批次的生成频率:
1 | |
MicroBatch 的延迟下限由批次间隔和执行时间决定。如果一个 batch 的执行时间是 2 秒,触发间隔设为 1 秒,实际延迟至少 2 秒(batch 执行完才能输出结果)。
Source 和 Sink 抽象
Source 负责提供数据并跟踪读取进度。每个 Source 必须能报告当前的 offset(标记读到了哪里),并能重放从某个 offset 开始的数据。
1 | |
Sink 负责接收输出并保证 Exactly-Once 语义(配合 checkpoint)。每个 Sink 必须是幂等的——如果 batch 重试,写入相同的数据不会产生重复。
1 | |
Checkpoint 与 Exactly-Once
Structured Streaming 通过 checkpoint 目录保证端到端的 Exactly-Once 语义:
1 | |
恢复流程:查询重启后,从 checkpoint 读取最后一个已提交的 batch ID 和对应的 source offset,从该 offset 之后继续处理。如果上一个 batch 执行了但没提交(Executor 故障),重新执行该 batch(Source 重放 offset 范围内的数据),配合幂等 Sink 保证不重复。
实验:Socket 流式 Wordcount
终端 1:启动 netcat 服务
1 | |
终端 2:在 spark-shell 中执行
1 | |
在 netcat 终端输入文本,每 5 秒可以在 spark-shell 的控制台看到更新后的 wordcount 结果。在 Spark UI 的 Structured Streaming 页面可以看到每个 batch 的输入行数、处理时间和延迟。
模式提炼
Structured Streaming 体现了批流统一的设计模式:用同一套 API 和引擎处理有界数据(批)和无界数据(流),流是批的推广,批是流的特例。
这个模式的优势是减少了概念和代码的重复。用户不需要为批处理和流处理各写一套逻辑,不需要维护两套引擎的配置和调优经验。
Flink 从另一个方向达到了同样的目标:Flink 把批看成有界流,Spark 把流看成追加的批。两种视角在实践中都工作得很好,区别在于默认的延迟特性——Flink 的逐条处理天然低延迟,Spark 的 micro-batch 延迟取决于批次间隔。
工程迁移表
| Spark 概念 | Flink 对应 | Kafka Streams 对应 | 数据库对应 |
|---|---|---|---|
| Structured Streaming | Flink SQL / DataStream | KStream / KTable | 物化视图增量刷新 |
| 无界表模型 | Dynamic Table | Changelog Stream | 持续查询(Continuous Query) |
| MicroBatch 引擎 | 无(逐条/mini-batch) | 无(逐条拉取) | 批量刷新物化视图 |
| Source offset | Checkpoint barrier | Consumer offset | WAL LSN |
| Output Mode (Append/Complete/Update) | Changelog Mode (Insert/Upsert/Retract) | 无直接对应 | 全量/增量物化视图 |
| Checkpoint 目录 | Checkpoint(Chandy-Lamport) | Changelog topic | 物化视图的检查点 |
常见误解
误解一:“Structured Streaming 是实时处理”。MicroBatch 的延迟下限是一个 batch 的执行时间(通常几百毫秒到几秒),不是真正的逐条实时处理。Continuous Processing 模式(实验性,Spark 2.3+)可以做到毫秒级延迟,但功能受限,不支持聚合和多数复杂操作。
误解二:“readStream 和 read 的查询可以互换”。大部分查询逻辑可以互换,但流查询对某些操作有限制:不支持没有 watermark 的无界聚合(内存会无限增长)、不支持 sort(无界数据无法排序)、不支持 limit(流没有终点)。
误解三:“Exactly-Once 意味着每条数据只被处理一次”。Exactly-Once 保证的是输出效果等价于每条数据只被处理一次,不是执行过程中的每条数据物理上只被处理一次。Batch 重试时数据可能被重新处理,但通过幂等 Sink 和 offset 管理保证最终输出不重复。
练习
-
修改上面的 Socket Wordcount 示例,把 outputMode 从 “complete” 改为 “update”,观察输出行为的差异。再改为 “append”,观察是否报错以及原因。
-
给 Kafka Source 的 Structured Streaming 查询配置 checkpoint 目录,运行后 kill 并重启查询。验证是否从 checkpoint 恢复,是否出现重复计数。
-
在 Spark UI 的 Structured Streaming 页面中,找到 “Input Rate”、“Processing Rate” 和 “Batch Duration” 图表。如果 Processing Rate 持续低于 Input Rate,说明什么问题?应该如何应对?
系列导航
| 序号 | 主题 | 状态 |
|---|---|---|
| 00 | 导读:为什么 Spark 的核心是一张 DAG | 已完成 |
| 01 | RDD:弹性分布式数据集的五大属性 | 已完成 |
| 02 | 宽依赖与窄依赖:Stage 是怎样划分出来的 | 已完成 |
| 03 | DAG Scheduler 与 Task Scheduler:从逻辑计划到物理执行 | 已完成 |
| 04 | Shuffle 机制:数据跨分区交换的代价与优化 | 已完成 |
| 05 | 内存管理与 Tungsten:堆外内存、序列化与代码生成 | 已完成 |
| 06 | 存储体系:Block Manager、广播变量与累加器 | 已完成 |
| 07 | 容错机制:Lineage、Checkpoint 与推测执行 | 已完成 |
| 08 | Spark SQL 与 Catalyst:从 SQL 文本到物理算子树 | 已完成 |
| 09 | DataFrame 与 Dataset:类型安全与性能的折中 | 已完成 |
| 10 | Adaptive Query Execution:运行时改写执行计划 | 已完成 |
| 11 | 数据源 API 与谓词下推:让存储层帮忙过滤 | 已完成 |
| 12 | Structured Streaming:把流当成无界表 | 本篇 |
| 13 | 水位线与状态管理:流处理的时间语义 | 下一篇 |
| 14 | Spark on YARN / K8s:资源管理与部署模式 | |
| 15 | 动态资源分配与作业调度:多租户集群的资源博弈 | |
| 16 | 性能诊断:Spark UI、Event Log 与调优思路 | |
| 17 | Spark 与 Flink:两种计算引擎的设计选择 |
参考资料
- Apache Spark 官方文档:Structured Streaming Programming Guide. https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html
- Michael Armbrust et al. Structured Streaming: A Declarative API for Real-Time Applications in Apache Spark. SIGMOD 2018.
- Databricks Blog: Continuous Processing in Structured Streaming. https://www.databricks.com/blog/2018-03-20/low-latency-continuous-processing-mode-in-structured-streaming-in-apache-spark-2-3-0.html
- Apache Spark 源码:
sql/core/src/main/scala/org/apache/spark/sql/execution/streaming/https://github.com/apache/spark
