4. 特征存储与计算分离:离线批处理引擎(Spark)、实时流处理引擎(Flink)、特征计算逻辑抽象

在金融级 Feature Store 架构中,“存储与计算分离”是支撑高并发、低延迟与大规模数据回溯的核心设计原则。本章深入剖析离线批处理引擎(Apache Spark)与实时流处理引擎(Apache Flink)如何与 Feature Store 协同工作,并重点讲解特征计算逻辑的抽象方法,确保同一套特征定义在离线和实时场景下输出一致的结果。

4.1 存储与计算分离架构总览

传统架构中,特征存储与计算紧密耦合,导致扩展性差、资源利用率低。金融 Feature Store 采用分离架构,将特征数据的持久化层与计算引擎解耦,实现弹性伸缩与独立运维。

核心优势:
  • 弹性扩展:计算集群可按需扩缩容,存储层使用对象存储或云原生数据库,不受计算节点限制。
  • 成本优化:离线批处理与实时流处理共享同一份特征数据,避免数据冗余与重复计算。
  • 逻辑统一:特征计算逻辑抽象为独立层,离线和实时引擎调用同一套代码,消除线上线下不一致(Training-Serving Skew)。

下图展示了金融 Feature Store 中存储与计算分离的典型架构,包含离线批处理、实时流处理以及统一特征逻辑抽象层。

金融 Feature Store 存储与计算分离架构图 金融 Feature Store 存储与计算分离架构 数据源层 交易流水(Kafka) | 用户行为日志(HDFS) | 外部征信数据(API) 计算引擎层(存储与计算分离) Spark 离线批处理 Flink 实时流处理 特征计算逻辑抽象层 统一特征存储层 离线特征库(Parquet/Delta Lake) | 实时特征库(Redis/内存表) | 特征元数据(MySQL) 特征服务 API(高并发读取、在线/离线一致性校验) 特征消费层 模型训练(XGBoost/DNN) | 实时推理(Triton/ONNX) | 特征监控与回溯

4.2 离线批处理引擎:Apache Spark

Spark 在金融 Feature Store 中承担大规模历史数据回溯、批量特征计算与离线训练数据集生成的任务。其核心优势在于内存计算与丰富的 ML 生态。

4.2.1 Spark 与 Feature Store 的集成模式

  • 批量特征计算:使用 Spark DataFrame API 读取离线存储层(如 HDFS/Delta Lake),执行窗口聚合、滑动统计、复杂 Join 等操作,结果写回特征库。
  • 特征回溯(Backfill):针对历史时间窗口重新计算特征,确保训练数据与实时特征的时间一致性。
  • 训练数据集生成:将特征与标签按时间对齐,输出为 Parquet/TFRecord 格式,供模型训练使用。
金融场景最佳实践:在信用评分模型训练中,使用 Spark 按“用户-天”粒度回溯过去 90 天的交易统计特征(如日均交易额、最大交易间隔),并确保回溯数据与实时 Flink 计算逻辑完全一致。

4.2.2 Spark 特征计算示例

// 使用 Spark 计算用户近 30 天交易金额滑动平均特征
import org.apache.spark.sql.expressions.Window
import org.apache.spark.sql.functions._

val df = spark.read.format("delta").load("/data/transactions")

val windowSpec = Window.partitionBy("user_id")
  .orderBy("transaction_date")
  .rowsBetween(-29, 0)

val featureDF = df.withColumn("avg_amount_30d", avg("amount").over(windowSpec))
  .withColumn("max_amount_30d", max("amount").over(windowSpec))
  .withColumn("transaction_count_30d", count("transaction_id").over(windowSpec))

// 写入离线特征库
featureDF.write.format("delta").mode("overwrite")
  .save("/feature_store/user_transaction_features")

4.3 实时流处理引擎:Apache Flink

Flink 在金融 Feature Store 中负责毫秒级特征计算,支撑风控、交易反欺诈、实时授信等场景。其事件时间语义与精确一次语义(Exactly-Once)是金融级可靠性的关键。

4.3.1 Flink 与 Feature Store 的集成模式

  • 实时特征计算:从 Kafka 消费交易事件,使用 Flink SQL 或 DataStream API 进行滑动窗口聚合、CEP 模式匹配,结果实时写入 Redis 或内存特征库。
  • 特征更新推送:通过 Flink 的 Async I/O 或 Side Output 将特征变更推送到特征服务,触发模型重算或规则引擎。
  • 状态一致性保障:利用 Flink Checkpoint 与 Kafka Exactly-Once 语义,确保特征计算在故障恢复后不丢不重。
注意:金融实时特征计算必须处理乱序事件。Flink 的事件时间水位线(Watermark)配置不当会导致特征延迟或错误聚合。建议设置允许的最大乱序时间为 5 秒,并配合周期性水位线生成策略。

4.3.2 Flink 特征计算示例

-- 使用 Flink SQL 计算用户近 5 分钟交易金额总和与次数
CREATE TABLE transactions (
  user_id STRING,
  amount DOUBLE,
  event_time TIMESTAMP(3),
  WATERMARK FOR event_time AS event_time - INTERVAL '5' SECOND
) WITH (
  'connector' = 'kafka',
  'topic' = 'transactions',
  'properties.bootstrap.servers' = 'kafka:9092',
  'format' = 'json'
);

CREATE TABLE feature_sink (
  user_id STRING,
  sum_amount_5min DOUBLE,
  count_txn_5min BIGINT,
  window_start TIMESTAMP(3),
  window_end TIMESTAMP(3)
) WITH (
  'connector' = 'redis',
  'format' = 'json',
  'redis.mode' = 'single',
  'redis.host' = 'redis-server'
);

INSERT INTO feature_sink
SELECT
  user_id,
  SUM(amount) AS sum_amount_5min,
  COUNT(*) AS count_txn_5min,
  TUMBLE_START(event_time, INTERVAL '5' MINUTE) AS window_start,
  TUMBLE_END(event_time, INTERVAL '5' MINUTE) AS window_end
FROM transactions
GROUP BY
  TUMBLE(event_time, INTERVAL '5' MINUTE),
  user_id;

4.4 特征计算逻辑抽象

特征计算逻辑抽象是解决“训练-推理不一致”的核心手段。通过将特征定义与具体计算引擎解耦,同一份特征逻辑可在 Spark 和 Flink 上透明执行。

4.4.1 抽象层次设计

抽象层次 描述 金融场景示例
特征定义层 使用 DSL 或 YAML 声明特征名称、类型、聚合逻辑、时间窗口 “用户近 30 天最大交易金额”定义为 max(amount) over 30d
逻辑计划层 将特征定义转化为与引擎无关的逻辑执行计划(如 Calcite RelNode) 统一表示窗口聚合、时间范围过滤、分组键
物理执行层 根据运行时环境(批/流)将逻辑计划翻译为 Spark DataFrame 或 Flink DataStream 离线使用 Spark Window,实时使用 Flink TumblingWindow

4.4.2 统一特征定义示例

以下 YAML 定义了一个跨引擎兼容的特征,Feature Store 会自动将其编译为 Spark 或 Flink 代码。

# feature_definitions/user_features.yaml
features:
  - name: user_avg_amount_30d
    type: double
    description: "用户近30天平均交易金额"
    source: transactions
    aggregation:
      function: avg
      column: amount
      window:
        type: sliding
        size: 30d
        slide: 1d
    group_by: user_id
    timestamp_column: event_time

  - name: user_txn_count_1h
    type: bigint
    description: "用户近1小时交易次数"
    source: transactions
    aggregation:
      function: count
      column: transaction_id
      window:
        type: tumbling
        size: 1h
    group_by: user_id
    timestamp_column: event_time

4.4.3 抽象执行流程

下图展示了特征计算逻辑从定义到多引擎执行的完整抽象流程。

特征计算逻辑抽象执行流程 特征计算逻辑抽象执行流程 特征定义(YAML/DSL) 逻辑计划(引擎无关) Spark 物理计划(批处理) Flink 物理计划(流处理) 离线特征库(Delta/Parquet) 实时特征库(Redis/内存)

4.5 离在线一致性保障

特征计算逻辑抽象的核心目标是确保离线训练与在线推理使用完全相同的特征值。以下是金融 Feature Store 中保障一致性的关键措施:

维度 离线(Spark) 实时(Flink) 一致性保障手段
时间窗口 处理时间 / 事件时间 事件时间 + Watermark 统一使用事件时间,离线回溯时模拟 Watermark 语义
聚合函数 Spark SQL 内置函数 Flink SQL 内置函数 抽象层统一函数签名,如 avg、count、max
空值处理 默认忽略 NULL 默认忽略 NULL 抽象层显式声明 NULL 策略(忽略/填充默认值)
数据源 HDFS / Delta Lake Kafka 离线数据由实时数据落盘生成,确保同源
金融级验证方法:在每日离线批处理完成后,随机抽取 10 万用户,对比离线特征库与实时特征库中同一时间点的特征值。允许误差范围:数值型特征相对误差 < 0.1%,枚举型特征完全一致。若偏差超限,触发告警并重新校准特征逻辑。

4.6 本章小结

  • 存储与计算分离架构是金融 Feature Store 实现弹性、低成本、高一致性的基础。
  • Spark 负责大规模离线回溯与批量特征计算,Flink 负责毫秒级实时特征更新。
  • 特征计算逻辑抽象通过“定义层 → 逻辑计划层 → 物理执行层”三层架构,实现离在线逻辑统一。
  • 离在线一致性保障需要从时间窗口、聚合函数、空值处理、数据源四个维度进行系统性验证。