3、特征工程实时化:实时特征计算、特征存储选型(Redis、HBase)、特征一致性保证
好,咱们接着聊实时特征工程。这一章,我打算把压箱底的一些实战经验掏出来。
很多人觉得推荐系统最难的是模型,其实我做了这么多年,发现真正让模型「跑不动」或者「效果差」的,往往是特征工程。尤其是实时特征,搞不好就是灾难现场。
3.1 实时特征计算:别让模型「饿着」等数据
实时特征计算,说白了就是用户刚点了个按钮,你马上得算出他最新的兴趣标签。不能等离线跑完,黄花菜都凉了。
我个人习惯把实时特征分成两类:
- 统计类特征:比如用户最近5分钟的点击次数、最近1小时的加购数。这类特征依赖窗口聚合。
- 序列类特征:比如用户最近浏览的10个商品ID,按时间排序。这类特征依赖滑动窗口。
我在项目中遇到过最头疼的事——窗口边界怎么定?你想想看,如果窗口是5分钟,那第4分59秒发生的行为,和第5分01秒发生的行为,按理说应该算在一个窗口里。但实际流式计算中,这两个事件可能被分配到不同的微批次里。
嗯,这里要注意:不要用处理时间,要用事件时间。我早期犯过这个错,用Flink的处理时间做窗口,结果凌晨流量低谷时,数据延迟了10秒才到,窗口直接算错了。后来全部改成事件时间 + Watermark,才稳住。
核心原则:实时特征计算,延迟和准确性是跷跷板。你要么牺牲一点延迟保证准确,要么接受近似值换取速度。我个人建议:宁可慢100ms,不能错一个数。
3.2 特征存储选型:Redis vs HBase,别选错了
特征算出来了,存哪?这是个大问题。我见过不少团队,上来就拍脑袋用Redis,结果存储成本爆炸;或者用HBase,结果查询延迟高得离谱。
咱们直接上对比表,这是我多年踩坑总结的:
| 维度 | Redis | HBase |
|---|---|---|
| 读写延迟 | 毫秒级(1-5ms) | 毫秒级(5-20ms) |
| 存储容量 | 受内存限制,大容量成本高 | 基于HDFS,可PB级扩展 |
| 数据结构 | 支持String、Hash、List、Sorted Set | 宽表、稀疏矩阵、列族 |
| 典型场景 | 用户实时画像、短时窗口特征 | 用户长期行为序列、全量特征存储 |
| 一致性 | 主从异步复制,可能丢数据 | 强一致性(基于HLog) |
| 成本 | 高(内存贵) | 低(磁盘便宜) |
怎么选?我一般这么判断:
- 如果特征需要频繁读写,且数据量可控(比如用户画像标签,每个用户几十个字段):无脑选Redis。用Hash结构,一个key存一个用户的所有特征,查询一次搞定。
- 如果特征是长序列,比如用户过去30天的行为列表,每个用户可能有几千条:选HBase。用Rowkey设计成 userId + timestamp,可以按时间范围扫描。
我曾经在一个项目中,把用户实时行为序列存在Redis的List里,结果每个用户存了2000多个元素,内存直接爆了。后来迁移到HBase,成本降了80%,查询延迟只多了3ms,完全能接受。
我的小技巧:如果预算充足,可以搞「冷热分离」。热数据(最近1小时的特征)放Redis,冷数据(1小时前的特征)放HBase。模型推理时,先查Redis,没命中再查HBase。这样既保证了速度,又控制了成本。
3.3 特征一致性保证:最容易被忽视的坑
特征一致性,说白了就是「离线训练时用的特征,和线上推理时用的特征,是不是同一个东西?」
我敢说,80%的推荐系统效果差,不是因为模型不行,而是特征不一致。举个例子:
- 离线训练时,用户特征用的是T-1天的全量数据,特征值是「过去7天点击次数=150」。
- 线上推理时,实时特征计算用的是最近5分钟的窗口,特征值是「过去7天点击次数=3」。
你看,同一个特征名,含义完全不同。模型学到的权重,在线上根本用不对。
怎么保证一致性?我总结了三个层次:
- 特征定义一致:离线特征和实时特征,必须用同一套代码生成。我习惯把特征计算逻辑封装成一个公共函数库,离线和实时都调用同一个函数。比如计算「过去7天点击次数」,离线用Spark SQL,实时用Flink,但底层逻辑一模一样。
- 特征时间对齐:实时特征的时间窗口,要和离线保持一致。离线用「自然天」,实时也得用「自然天」。别离线用UTC时间,实时用本地时间,那肯定对不上。
- 特征值校验:线上推理时,我会把实时特征的值和离线特征的值做对比。如果偏差超过阈值(比如点击次数差10%以上),就触发告警。我曾经用这个办法,抓出过好几次实时计算代码的bug。
避坑指南:我曾经在双11大促时,因为实时特征和离线特征不一致,导致推荐效果暴跌30%。查了两天才发现,离线特征用的是「过去7天」的滑动窗口,而实时特征用的是「固定7天」的日历窗口。7天这个数字一样,但含义完全不同。从那以后,我要求所有特征定义必须写文档,并且代码里加注释。
3.4 实战:一个完整的实时特征链路
最后,我给大家画一个我常用的实时特征链路图(文字版):
用户行为日志(Kafka)
↓
Flink 实时计算(事件时间窗口)
↓
特征结果写入:
- 热特征(最近1小时)→ Redis(Hash结构)
- 冷特征(1小时前)→ HBase(Rowkey: userId_timestamp)
↓
模型推理服务:
1. 先查Redis,获取实时画像
2. 再查HBase,获取长期行为序列
3. 合并特征,输入模型
↓
返回推荐结果
这个链路我用了三年,线上稳定运行。核心就是:实时计算要准,存储选型要对,一致性要盯死。
嗯,这一章就到这里。下一章咱们聊聊模型更新策略,那又是另一个大坑了。