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. 数据源轮询: 引擎以固定间隔(如 1 秒)从 Kafka、Kinesis 或文件源拉取新到达的数据。
  2. 批次规划: 将拉取到的数据封装为一个 DataFrame,并记录对应的 Offset 范围。
  3. 增量执行: 对该 DataFrame 执行用户定义的查询逻辑(过滤、聚合、与静态表 join 等)。
  4. 结果输出: 将计算结果写入目标 Sink(如 Redis、MySQL、Kafka)。
  5. 提交 Offset: 成功处理后,向 Checkpoint 目录提交 Offset,确保故障恢复时不会重复或丢失数据。
微批处理模式:Structured Streaming 默认执行流程 数据源 Kafka / Kinesis 批次规划器 间隔 1s 轮询 生成 DataFrame 增量执行 过滤 / 聚合 与静态表 Join 结果输出 Redis / MySQL Kafka 提交 Offset 到 Checkpoint 关键特性: • 每个批次独立调度,故障时从上一个已提交 Offset 的批次恢复,保证 Exactly-Once 语义。 • 延迟取决于批次间隔,通常为 500ms ~ 10s,适合大多数信用额度实时计算场景。
微批处理在额度定价中的典型代码片段: 以下示例展示如何从 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。
持续处理模式:事件驱动逐条处理 数据源 WAL 预写日志 持续处理引擎 逐条 select/filter 输出 Sink Kafka / Console Epoch 标记 Committer 限制说明: • 持续处理模式目前仅支持 Append 输出模式,不支持聚合操作(如 groupBy、window)。 • 在信用额度定价中,若需要毫秒级延迟且仅做简单过滤转发,可考虑此模式;否则推荐微批模式。

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 场景。