2. 离线特征生产流程:离线ETL、特征存储与回溯计算
离线特征生产是特征工程体系中最成熟、最稳定的环节。它依赖批处理引擎(如 Hive/Spark)对全量历史数据进行清洗、加工和存储,为模型训练和离线评估提供“标准答案”。本章深入拆解离线ETL、特征存储(Hive/离线表)以及特征回溯与批量计算的核心原理。
2.1 离线ETL:从原始日志到特征宽表
离线ETL(Extract-Transform-Load)是特征生产的第一道工序。其目标是将分散在多个数据源(业务DB、埋点日志、第三方API)的原始数据,转换为结构化的特征宽表。
核心原则: 离线ETL必须保证“可重入性”(Idempotent),即同一份数据在任意时间点重新执行,结果完全一致。这是特征回溯的基石。
典型的离线ETL流程包含以下步骤:
- 数据抽取(Extract): 从HDFS、Kafka、RDBMS等源读取T-1或T-N的全量/增量数据。常用工具:Sqoop、Flume、DataX。
- 数据清洗(Clean): 处理缺失值(填充/剔除)、异常值(3σ/IQR)、格式统一(时间戳标准化、枚举映射)。
- 特征计算(Transform): 执行聚合(SUM/COUNT/AVG)、滑动窗口(最近7天点击量)、交叉组合(用户×商品类目)、分箱(年龄分段)等逻辑。
- 数据加载(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天。
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为例,一次典型的特征回溯作业包含以下阶段:
- 解析与优化: Spark Catalyst优化器将SQL解析为逻辑计划,进行谓词下推、列剪枝、分区裁剪。
- 任务调度: DAG Scheduler将物理计划拆分为Stage,每个Stage包含一组可并行执行的Task。
- 数据读取: 从Hive分区读取ORC文件,利用Projection Pushdown只读取需要的列,减少IO。
- 计算执行: 在Executor上执行Filter、Map、Aggregate、Join等算子。关键优化点:
- Broadcast Join: 小表(<10MB)自动广播,避免Shuffle。
- Sort Merge Join: 大表JOIN时按分桶键排序,减少数据倾斜。
- AQE(Adaptive Query Execution): 动态调整分区数、Join策略、倾斜处理。
- 结果写入: 按
dt动态分区写入Hive表,控制每个分区文件大小(建议256MB~1GB),避免小文件问题。
2.3.3 回溯一致性校验
特征回溯完成后,必须进行一致性校验,确保离线特征与在线实时特征在相同时间点对齐。常用校验方法:
- 行数校验: 回溯结果的行数是否等于预期(如用户数 × 天数)。
- 分布校验: 对连续特征计算均值、方差、分位数,与在线特征的历史分布对比(KS检验)。
- 抽样比对: 随机抽取1000个样本,在离线表和在线服务中分别计算特征值,误差应小于1e-6。
- 时间戳边界: 确认回溯特征的时间戳严格小于等于回溯日期(无未来数据泄露)。
工程经验: 建议将回溯校验逻辑封装为自动化CI/CD流水线。每次特征逻辑变更后,自动触发全量回溯+校验,通过后才允许上线。这能将特征不一致问题扼杀在开发阶段。