2. 离线特征生产流程:离线ETL、特征存储与回溯计算

离线特征生产是特征工程体系中最成熟、最稳定的环节。它依赖批处理引擎(如 Hive/Spark)对全量历史数据进行清洗、加工和存储,为模型训练和离线评估提供“标准答案”。本章深入拆解离线ETL、特征存储(Hive/离线表)以及特征回溯与批量计算的核心原理。

2.1 离线ETL:从原始日志到特征宽表

离线ETL(Extract-Transform-Load)是特征生产的第一道工序。其目标是将分散在多个数据源(业务DB、埋点日志、第三方API)的原始数据,转换为结构化的特征宽表。

核心原则: 离线ETL必须保证“可重入性”(Idempotent),即同一份数据在任意时间点重新执行,结果完全一致。这是特征回溯的基石。

典型的离线ETL流程包含以下步骤:

  1. 数据抽取(Extract): 从HDFS、Kafka、RDBMS等源读取T-1或T-N的全量/增量数据。常用工具:Sqoop、Flume、DataX。
  2. 数据清洗(Clean): 处理缺失值(填充/剔除)、异常值(3σ/IQR)、格式统一(时间戳标准化、枚举映射)。
  3. 特征计算(Transform): 执行聚合(SUM/COUNT/AVG)、滑动窗口(最近7天点击量)、交叉组合(用户×商品类目)、分箱(年龄分段)等逻辑。
  4. 数据加载(Load): 将计算结果写入Hive分区表或ClickHouse等OLAP引擎,形成“特征宽表”。
ETL阶段 典型操作 常见陷阱
Extract 增量读取、全量快照、CDC捕获 数据漂移(时间戳不一致)、重复读取
Clean NULL填充、离群点截断、类型转换 填充策略与线上不一致(如均值填充 vs 0填充)
Transform JOIN宽表、窗口函数、UDF计算 数据倾斜、Shuffle爆炸、时区未对齐
Load 动态分区写入、ORC/Parquet压缩 小文件过多、分区字段选择不当

2.2 特征存储:Hive/离线表的设计模式

离线特征通常存储在Hive分区表中,以“日期分区 + 实体ID”作为主键。设计良好的特征存储结构,直接影响回溯效率和查询性能。

最佳实践: 采用“分层存储”策略——ODS层(原始日志)、DWD层(明细特征)、DWS层(聚合特征)、ADS层(应用特征宽表)。每层独立分区,便于回溯时按需读取。

以下是一个典型的离线特征表DDL示例:

CREATE TABLE dws.user_daily_features (
    user_id      STRING COMMENT '用户ID',
    feature_date STRING COMMENT '特征日期(yyyy-MM-dd)',
    -- 基础统计特征
    login_cnt_7d    INT COMMENT '近7天登录次数',
    order_amt_30d   DOUBLE COMMENT '近30天订单金额',
    avg_click_7d    DOUBLE COMMENT '近7天平均点击量',
    -- 复杂衍生特征
    category_entropy_7d DOUBLE COMMENT '近7天浏览类目熵',
    last_active_hour    INT COMMENT '最后活跃小时',
    -- 元信息
    etl_time TIMESTAMP COMMENT 'ETL写入时间'
)
PARTITIONED BY (dt STRING)  -- 按日期分区
STORED AS ORC
TBLPROPERTIES ('orc.compress'='SNAPPY');

离线特征存储的核心设计要点:

  • 分区策略: 按 dt(日期)分区,支持 WHERE dt='2025-01-15' 快速裁剪。避免使用多级分区(如年/月/日),防止目录过深。
  • 文件格式: 优先使用ORC或Parquet列式存储,压缩比高(Snappy/Zstd),支持谓词下推和投影下推。
  • 分桶优化: 对高频JOIN的实体ID(如user_id)进行分桶(Bucketing),减少SMB Join的Shuffle开销。
  • 版本管理: 特征表应保留至少30天历史分区,支持“时间旅行”回溯。生产环境建议保留90天。
离线特征存储分层架构 离线特征存储分层架构 ODS 原始数据层 埋点日志 / 业务DB快照 / 第三方API原始响应 DWD 明细特征层 清洗后单实体特征 / 时间戳对齐 / 缺失值填充 DWS 聚合特征层 滑动窗口聚合 / 多实体交叉统计 / 复杂UDF计算 ADS 应用特征宽表层 模型训练宽表 / 离线评估样本 / 特征回溯输出

2.3 特征回溯与批量计算原理

特征回溯(Feature Backfill)是指使用历史时间点的数据,重新计算并生成对应时间点的特征值。这是离线训练与在线推理对齐的关键环节。

核心挑战: 特征回溯必须严格遵循“时间一致性”原则——计算某日特征时,只能使用该日之前的数据,不能“偷看”未来信息。否则会导致训练-推理偏差(Train-Serving Skew)。

2.3.1 回溯计算模式

批量计算引擎(Hive/Spark)通过以下两种模式实现特征回溯:

回溯模式 实现方式 适用场景 性能特点
全量回溯 对每个回溯日期,读取该日期之前的所有历史数据,重新计算全部特征 首次构建特征库、特征逻辑大版本变更 计算量大,但逻辑简单,无状态依赖
增量回溯 基于上一个日期的中间结果,仅计算新增/变更部分,合并生成新特征 日常T+1更新、小范围特征修正 计算量小,但需维护中间状态,易出错
快照回溯 利用Hive分区快照,直接读取对应日期的ODS/DWD分区,按日期独立计算 特征数量少、回溯周期短(<30天) IO密集,无Shuffle,适合小规模回溯

2.3.2 批量计算引擎执行流程

以Spark SQL为例,一次典型的特征回溯作业包含以下阶段:

  1. 解析与优化: Spark Catalyst优化器将SQL解析为逻辑计划,进行谓词下推、列剪枝、分区裁剪。
  2. 任务调度: DAG Scheduler将物理计划拆分为Stage,每个Stage包含一组可并行执行的Task。
  3. 数据读取: 从Hive分区读取ORC文件,利用Projection Pushdown只读取需要的列,减少IO。
  4. 计算执行: 在Executor上执行Filter、Map、Aggregate、Join等算子。关键优化点:
    • Broadcast Join: 小表(<10MB)自动广播,避免Shuffle。
    • Sort Merge Join: 大表JOIN时按分桶键排序,减少数据倾斜。
    • AQE(Adaptive Query Execution): 动态调整分区数、Join策略、倾斜处理。
  5. 结果写入: 按 dt 动态分区写入Hive表,控制每个分区文件大小(建议256MB~1GB),避免小文件问题。
特征回溯批量计算流程 特征回溯批量计算流程(Spark SQL) 回溯请求 (dt=2025-01-01~01-15) Hive分区裁剪 (dt IN (...)) 列剪枝 + 谓词下推 (ORC Projection) Spark Executor 计算 (Agg/Join/UDF) 动态分区写入 Hive (ORC/Snappy)

2.3.3 回溯一致性校验

特征回溯完成后,必须进行一致性校验,确保离线特征与在线实时特征在相同时间点对齐。常用校验方法:

  • 行数校验: 回溯结果的行数是否等于预期(如用户数 × 天数)。
  • 分布校验: 对连续特征计算均值、方差、分位数,与在线特征的历史分布对比(KS检验)。
  • 抽样比对: 随机抽取1000个样本,在离线表和在线服务中分别计算特征值,误差应小于1e-6。
  • 时间戳边界: 确认回溯特征的时间戳严格小于等于回溯日期(无未来数据泄露)。
工程经验: 建议将回溯校验逻辑封装为自动化CI/CD流水线。每次特征逻辑变更后,自动触发全量回溯+校验,通过后才允许上线。这能将特征不一致问题扼杀在开发阶段。