4. 离线特征工程基础:数据源接入、特征计算与存储
离线特征工程是批流一体特征平台的基石。本章聚焦离线链路的核心三环节:数据源接入(Hive/HDFS)、特征计算(Spark SQL)和特征存储(HBase/MySQL)。掌握这三步,即可构建稳定、可回溯的离线特征生产管线。
4.1 数据源接入:Hive 与 HDFS
离线特征的数据源通常来自数据仓库的 Hive 表或 HDFS 上的原始日志文件。接入阶段需要解决分区发现、数据格式和读取效率三个问题。
| 数据源类型 | 典型场景 | 读取方式 | 注意事项 |
|---|---|---|---|
| Hive 表 | 用户画像、订单宽表 | Spark SQL 直接读取 | 关注分区裁剪、小文件合并 |
| HDFS 文件 | 埋点日志、实时落盘 | Spark DataFrame 读取 | 指定 schema、处理脏数据 |
| Kafka(离线回溯) | 历史消息回放 | Spark Streaming 批模式 | 设置合理的 offset 策略 |
最佳实践:离线特征接入时,优先使用 Hive 表的增量分区(如 dt=2025-04-01),避免全表扫描。对于 HDFS 原始日志,建议先做一次 ETL 清洗,再写入 Hive 中间表,形成统一的特征数据源。
4.2 特征计算:Spark SQL 核心操作
Spark SQL 是离线特征计算的主力引擎。通过 SQL 或 DataFrame API,可以完成聚合统计、窗口计算、多表 Join 等特征加工逻辑。
4.2.1 典型特征计算模式
- 统计特征:COUNT、SUM、AVG、MAX、MIN 等聚合函数,按用户/物品分组。
- 时序特征:使用窗口函数(如 ROW_NUMBER、LAG、LEAD)计算近 N 天行为统计。
- 交叉特征:多表 Join 后计算比率、差值或组合标签。
- 分桶/离散化:使用 CASE WHEN 或 UDF 将连续值映射为类别。
-- 示例:计算用户近7天点击率特征
SELECT
user_id,
COUNT(*) AS total_click,
SUM(CASE WHEN action = 'buy' THEN 1 ELSE 0 END) AS buy_cnt,
ROUND(SUM(CASE WHEN action = 'buy' THEN 1 ELSE 0 END) / COUNT(*), 4) AS buy_rate_7d
FROM user_behavior
WHERE dt >= date_sub('2025-04-01', 7) AND dt <= '2025-04-01'
GROUP BY user_id;
性能要点:Spark SQL 特征计算时,优先使用列式存储(Parquet/ORC)和动态分区插入。对于大表 Join,开启 Broadcast Join 或 Sort Merge Join 优化,避免 Shuffle 数据倾斜。
4.3 特征存储:HBase 与 MySQL
离线特征计算完成后,需要将结果存储到在线可用的存储系统中。HBase 和 MySQL 是两种主流选择,分别适用于高并发随机读取和结构化查询场景。
| 对比维度 | HBase | MySQL |
|---|---|---|
| 数据模型 | 宽表、稀疏列族 | 关系型、固定 schema |
| 读写模式 | 随机读写、批量写入 | 随机读写、事务支持 |
| 扩展性 | 水平扩展、自动分片 | 主从复制、分库分表 |
| 典型特征场景 | 用户实时特征、物品画像 | 特征元数据、小规模配置 |
| 写入方式 | BulkLoad / Put 批量 | JDBC Batch Insert |
4.3.1 HBase 存储设计要点
- RowKey 设计:通常使用 user_id 或 item_id 作为前缀,避免热点。
- 列族规划:按特征类型分列族(如 cf_stats、cf_seq),控制列族数量(建议 ≤3)。
- TTL 设置:离线特征通常设置 7~30 天 TTL,自动过期旧数据。
-- HBase 建表示例(通过 HBase Shell)
create 'user_features',
{NAME => 'stats', VERSIONS => 1, TTL => 604800},
{NAME => 'seq', VERSIONS => 1, TTL => 2592000}
4.3.2 MySQL 存储设计要点
- 表结构:特征名作为列,每行对应一个实体(用户/物品)。
- 索引:对 entity_id 建立唯一索引,支持快速点查。
- 批量写入:使用 REPLACE INTO 或 INSERT … ON DUPLICATE KEY UPDATE 避免重复。
-- MySQL 特征表建表示例
CREATE TABLE user_features_daily (
user_id VARCHAR(64) NOT NULL,
feature_name VARCHAR(128) NOT NULL,
feature_value DOUBLE,
dt DATE NOT NULL,
PRIMARY KEY (user_id, feature_name, dt)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
避坑提醒:HBase 的 RowKey 设计不当会导致 Region 热点,写入性能急剧下降。建议对 user_id 做散列(如 MD5 取前 4 位)作为前缀。MySQL 存储特征时,避免单表行数超过 5000 万,需提前规划分表策略。
4.4 离线特征管线整体流程
下图展示了从数据源接入、特征计算到特征存储的完整离线链路,以及各环节的关键组件。
4.5 本章小结
- 数据源接入:Hive 表优先使用分区裁剪,HDFS 文件需指定 schema 并做预处理。
- 特征计算:Spark SQL 支持聚合、窗口、Join 等丰富语义,注意数据倾斜和 Shuffle 优化。
- 特征存储:HBase 适合高并发随机读取,RowKey 设计是关键;MySQL 适合结构化查询,注意索引和分表。
- 管线调度:使用 Airflow 或 DolphinScheduler 编排离线任务,保障数据产出的时效性和稳定性。
课后练习:请设计一个用户近 30 天购买金额特征的离线计算任务,数据源为 Hive 表
orders(分区字段 dt),将计算结果写入 HBase 表 user_purchase_features,RowKey 为 user_id 的 MD5 前缀 + user_id。尝试写出完整的 Spark SQL 和 HBase 写入代码框架。