Structured Streaming 详解
(Spark 的声明式流处理引擎)
Structured Streaming 是 Spark 2.0+ 引入的基于 DataFrame/Dataset API 的流处理引擎,核心思想是将实时数据流视为一张持续追加的表(“无界表”),实现批流统一的编程模型。
一、核心设计理念
-
“流即动态表”(Stream as Table)
- 输入流 → 动态输入表
- 查询操作 → 在动态表上连续执行
- 结果输出 → 动态结果表 → 写入外部存储
-
端到端精确一次语义(Exactly-once)
通过以下机制保证:- Source 偏移量跟踪(如 Kafka offset)
- 容错状态存储(HDFS 检查点)
- 幂等输出接收器
二、编程模型
1. 基础代码结构
import org.apache.spark.sql.SparkSession
val spark = SparkSession.builder()
.appName("StructuredStreaming")
.getOrCreate()
// 1. 定义输入源(Source)
val inputStream = spark.readStream
.format("kafka") // 支持 kafka, socket, file, rate 等
.option("kafka.bootstrap.servers", "localhost:9092")
.option("subscribe", "topic1")
.load()
// 2. 数据处理(与批处理 API 完全一致)
val processed = inputStream
.selectExpr("CAST(value AS STRING) as text")
.withColumn("words", split($"text", " "))
.groupBy($"words")
.count()
// 3. 定义输出接收器(Sink)
val query = processed.writeStream
.outputMode("complete") // 输出模式
.format("console") // 输出位置
.option("checkpointLocation", "/path/to/checkpoint") // 容错必需
.start()
query.awaitTermination()
三、关键组件详解
1. 输入源(Sources)
类型 | 描述 | 是否容错 |
---|---|---|
File Source | 监控目录下的新文件(CSV/JSON/Parquet) | ✓ |
Kafka Source | 从 Kafka 读取数据(0.10+) | ✓ |
Socket Source | 测试用(从 Socket 读文本) | ✗ |
Rate Source | 生成测试数据(固定速率) | ✓ |
2. 输出接收器(Sinks)
类型 | 描述 |
---|---|
Console Sink | 控制台输出(调试用) |
File Sink | 写入文件(Parquet/CSV) |
Kafka Sink | 输出到 Kafka Topic |
Foreach Sink | 自定义写入逻辑(如 JDBC/NoSQL) |
Memory Sink | 结果存入内存表(供 spark.sql 查询) |
3. 输出模式(Output Modes)
模式 | 适用场景 | 要求 |
---|---|---|
Append | 只追加新结果(默认) | 查询不包含聚合 |
Update | 输出变化的行(类似 CDC) | 支持聚合+Watermark |
Complete | 全量输出结果(每次触发更新整个结果表) | 需存储全量状态(如聚合) |
四、时间概念与窗口操作
1. 时间类型
- 事件时间(Event Time):数据自带的时间戳(
timestamp
类型列) - 处理时间(Processing Time):数据到达 Spark 的时间
2. 水印(Watermark)
处理延迟数据的核心机制:
val withEventTime = inputStream
.withColumn("eventTime", $"timestamp".cast("timestamp"))
val watermarked = withEventTime
.withWatermark("eventTime", "10 minutes") // 最大延迟时间
3. 窗口聚合(Tumbling/Sliding)
import org.apache.spark.sql.functions._
import org.apache.spark.sql.expressions.Window
// 滚动窗口(5分钟)
val tumblingWindow = watermarked
.groupBy(
window($"eventTime", "5 minutes"),
$"deviceId"
)
.count()
// 滑动窗口(10分钟窗口,5分钟滑动)
val slidingWindow = watermarked
.groupBy(
window($"eventTime", "10 minutes", "5 minutes"),
$"deviceId"
)
.avg("value")
五、容错与检查点
-
检查点目录(Checkpoint)
- 存储 source 偏移量
- 保存聚合查询的中间状态
- 必需设置:
.option("checkpointLocation", "/path")
-
恢复流程
重启时 → 读取检查点 → 恢复偏移量 → 重建状态 → 继续处理
六、事件时间处理示例
// 定义 Schema(事件时间字段)
case class Event(deviceId: String, timestamp: Long, value: Double)
// 从 Kafka 读取
val events = spark.readStream
.format("kafka")
.option(...)
.load()
.select(from_json($"value".cast("string"), schema).as[Event])
// 启用水印 + 窗口聚合
val results = events
.withWatermark("timestamp", "1 hour") // 允许1小时延迟
.groupBy(
window($"timestamp", "15 minutes"),
$"deviceId"
)
.agg(avg("value").as("avg_value"))
// 输出到 Kafka
results.writeStream
.format("kafka")
.outputMode("update") // 只输出变化行
.option("topic", "output-topic")
.option("checkpointLocation", "/checkpoint")
.start()
七、性能优化技巧
-
触发间隔(Trigger)
.trigger(Trigger.ProcessingTime("30 seconds")) // 固定间隔 .trigger(Trigger.Once()) // 类似批处理 .trigger(Trigger.Continuous("1 second")) // 低延迟模式(实验性)
-
状态管理
- 使用水印清除旧状态
- 避免无限状态(如无界 GroupBy)
-
水位敏感参数
spark.conf.set("spark.sql.shuffle.partitions", "200") // 调大 shuffle 并行度
八、与 Spark Streaming(DStream)对比
特性 | Structured Streaming | Spark Streaming (DStream) |
---|---|---|
编程模型 | 声明式 (DataFrame API) | 过程式 (RDD 操作) |
API 一致性 | 与批处理完全统一 | 单独 API |
事件时间支持 | 原生支持 | 需手动实现 |
延迟 | 毫秒~秒级 | 秒级 |
端到端精确一次 | 原生支持 | 需手动实现 |
动态表语义 | 有 | 无 |
最佳实践:
- 始终设置检查点以实现容错
- 优先选择事件时间而非处理时间
- 合理配置水印平衡延迟与状态大小
- 生产环境推荐 Kafka → Structured Streaming → Kafka/Delta Lake 架构
通过 Structured Streaming,Spark 实现了 “批流一体” ,允许开发者用同一套代码处理实时流和离线批数据,大幅简化流处理系统的开发和维护。