3. Spark与流式计算:Spark Streaming与Structured Streaming简介、微批处理与持续处理模式
在信用额度定价的实时决策场景中,客户行为事件(如刷卡消费、还款、额度使用率变化)需要被秒级或分钟级捕获并重新计算风险评分与额度。Spark 提供了两代流处理引擎来支撑这类需求:Spark Streaming(DStream) 和 Structured Streaming。理解它们的架构差异与处理模式,是构建流式额度定价链路的基础。
3.1 两代流引擎:从 DStream 到 Structured Streaming
Spark 的流处理能力经历了从 RDD-based DStream 到 DataFrame/Dataset-based Structured Streaming 的演进。下表从核心抽象、一致性保证、适用场景三个维度进行对比:
| 对比维度 | Spark Streaming (DStream) | Structured Streaming |
|---|---|---|
| 核心抽象 | DStream(离散化流),本质是 RDD 序列 | 无界表(Unbounded Table),基于 DataFrame/Dataset |
| 处理语义 | Exactly-Once 需手动实现(结合事务输出) | 内置 End-to-End Exactly-Once(通过 Offset + 幂等写入) |
| 时间语义 | 仅支持处理时间(Processing Time) | 支持事件时间(Event Time)与水位线(Watermark) |
| API 风格 | 基于 RDD 的低级算子(map, reduceByKey) | 声明式 SQL / DataFrame 算子(groupBy, agg, join) |
| 延迟与吞吐 | 微批延迟较高(通常 1-10 秒) | 微批延迟可低至 500ms;持续处理模式可达毫秒级 |
| 推荐场景 | 遗留系统维护、简单计数或累加 | 新项目首选,尤其需要事件时间、状态管理、SQL 分析的场景 |
架构选型建议: 在信用额度定价课程中,我们统一采用 Structured Streaming。它天然支持事件时间(客户交易发生时间)与水位线,能更准确地处理乱序到达的还款或消费事件,避免因数据延迟导致额度误判。
3.2 微批处理模式(Micro-Batch Processing)
微批处理是 Structured Streaming 的默认执行模式。它将连续的数据流切分成一系列小批次(Batch),每个批次作为一个 DataFrame 提交给 Spark 引擎执行。其核心流程如下:
- 数据源轮询: 引擎以固定间隔(如 1 秒)从 Kafka、Kinesis 或文件源拉取新到达的数据。
- 批次规划: 将拉取到的数据封装为一个 DataFrame,并记录对应的 Offset 范围。
- 增量执行: 对该 DataFrame 执行用户定义的查询逻辑(过滤、聚合、与静态表 join 等)。
- 结果输出: 将计算结果写入目标 Sink(如 Redis、MySQL、Kafka)。
- 提交 Offset: 成功处理后,向 Checkpoint 目录提交 Offset,确保故障恢复时不会重复或丢失数据。
微批处理在额度定价中的典型代码片段: 以下示例展示如何从 Kafka 读取交易流,按客户 ID 聚合近 5 分钟的交易金额,并更新额度使用率。
import org.apache.spark.sql.streaming.Trigger
val transactionStream = spark
.readStream
.format("kafka")
.option("kafka.bootstrap.servers", "host:9092")
.option("subscribe", "credit_transactions")
.load()
.selectExpr("CAST(value AS STRING) as json")
.select(from_json($"json", schema).as("data"))
.select("data.customer_id", "data.amount", "data.event_time")
val usageRateQuery = transactionStream
.withWatermark("event_time", "2 minutes")
.groupBy(
window($"event_time", "5 minutes"),
$"customer_id"
)
.agg(sum($"amount").as("total_amount"))
.writeStream
.outputMode("update")
.trigger(Trigger.ProcessingTime("5 seconds")) // 微批间隔 5 秒
.option("checkpointLocation", "/tmp/checkpoint/usage_rate")
.foreachBatch { (batchDF, batchId) =>
// 将批次结果写入 Redis 或 MySQL,用于额度定价引擎读取
batchDF.write.mode("overwrite").jdbc(url, "usage_rate", props)
}
.start()
3.3 持续处理模式(Continuous Processing)
持续处理模式是 Structured Streaming 在 Spark 2.3 引入的实验性功能,旨在提供毫秒级端到端延迟。与微批不同,持续处理不再等待批次边界,而是以事件驱动的方式逐条处理记录。
3.3.1 核心差异对比
| 特性 | 微批处理模式 | 持续处理模式 |
|---|---|---|
| 调度方式 | 定时触发(如每 5 秒) | 事件到达即触发 |
| 典型延迟 | 500ms ~ 10s | ~1ms(实验性) |
| 吞吐量 | 高(批次合并优化) | 中等(逐条处理开销) |
| 容错机制 | 基于 Offset 的批次级重放 | 基于预写日志(WAL)的记录级重放 |
| 支持的操作 | 所有 DataFrame 操作(join, agg, window) | 仅支持简单 select / filter / map |
| 生产就绪度 | GA(生产推荐) | 实验性(不推荐生产使用) |
3.3.2 持续处理模式架构
持续处理模式通过以下组件实现低延迟:
- Continuous Reader: 持续从数据源拉取记录,不等待批次边界。
- Epoch 标记: 引擎定期插入 Epoch 标记到数据流中,用于故障恢复时确定一致性快照。
- WAL(Write-Ahead Log): 每条记录在处理前先写入预写日志,确保故障后可以精确重放。
- Committer: 异步提交已处理的 Epoch,更新 Offset 到 Checkpoint。
3.4 模式选择决策树
在信用额度定价系统中,选择哪种处理模式取决于延迟要求与计算复杂度:
- 需要复杂聚合(窗口、join、状态管理)→ 微批处理模式
- 例如:计算客户近 30 天滚动平均额度使用率、与客户画像静态表 join。
- 仅需简单过滤或字段映射,且要求毫秒级延迟 → 持续处理模式(实验性)
- 例如:将交易事件原样转发到另一个 Kafka Topic 供下游实时风控使用。
- 生产环境默认选择 → 微批处理模式
- 成熟稳定,支持 Exactly-Once,延迟 1-5 秒已能满足大部分额度定价场景。
生产环境警告: 持续处理模式在 Spark 3.x 中仍标记为实验性(Experimental),不支持背压(Backpressure)和动态资源分配。在信用额度定价这类对数据一致性要求极高的金融场景中,除非有明确的毫秒级 SLA 且业务可容忍实验性风险,否则应优先使用微批处理模式。
3.5 小结
本章介绍了 Spark 流式计算的两代引擎,重点剖析了 Structured Streaming 的两种处理模式:
- 微批处理模式 是生产首选,通过批次切分与 Offset 管理实现高吞吐与 Exactly-Once 语义,适合大多数额度实时计算需求。
- 持续处理模式 提供更低延迟,但功能受限,仅适用于简单 ETL 场景。