2、实时特征 vs 离线特征:时效性差异、计算模式对比、存储介质选择
聊特征存储,第一个绕不开的问题就是:实时特征和离线特征到底差在哪?
很多人觉得,不就是延迟不同嘛——离线跑几个小时,实时几毫秒。嗯,这么说也没错,但太表面了。我做了几年特征工程,踩过不少坑,今天把核心差异掰开揉碎了讲清楚。
2.1 时效性差异:从「昨天」到「刚才」
先看一个最直观的对比:
| 维度 | 离线特征 | 实时特征 |
|---|---|---|
| 数据新鲜度 | T+1 或小时级 | 秒级/毫秒级 |
| 典型延迟 | 几分钟 ~ 几小时 | < 100ms |
| 数据窗口 | 全量历史 | 滑动窗口(最近 N 分钟) |
| 典型场景 | 用户画像、批量报表 | 实时推荐、反欺诈、广告竞价 |
离线特征,说白了就是「回头看」的数据。比如昨天用户买了什么、上周点击了多少次。这些数据稳定、完整,适合做模型训练。
实时特征呢?是「当下看」的数据。用户刚点了一个商品,3 秒后又点了一个——这两次点击之间的关联,离线特征根本抓不住。
核心结论:离线特征回答「用户是谁」,实时特征回答「用户现在想要什么」。
我记得有一次做推荐系统,离线特征 AUC 做到 0.85,上线后效果却很差。排查了半天,发现是实时行为特征没跟上——用户已经搜了「iPhone 15」,离线特征还在推「iPhone 14 保护壳」。这就是时效性带来的致命差异。
2.2 计算模式对比:批处理 vs 流处理
计算模式上,两者几乎是两个物种。
离线特征:批处理(Batch Processing)
离线特征的计算模式很「老实」——定时调度,全量扫描,一次算完。
- 调度周期:每天凌晨跑一次,或者每小时跑一次
- 计算引擎:Spark、Hive、MapReduce
- 数据源:数据仓库、HDFS、S3
- 输出:Parquet/ORC 文件,或者离线 KV 存储
举个例子,计算「用户过去 7 天购买金额」这个特征:
-- 离线 SQL 示例
SELECT
user_id,
SUM(amount) AS 7d_purchase_amount
FROM orders
WHERE dt >= DATE_SUB('2024-01-15', 7)
AND dt <= '2024-01-15'
GROUP BY user_id;
这种写法简单直接,但问题是——它只能算到昨天。今天用户刚下的单,要等到明天才能反映出来。
实时特征:流处理(Stream Processing)
实时特征的计算模式就「激进」多了——事件驱动,增量计算,毫秒级响应。
- 触发方式:每条数据到来时立即计算
- 计算引擎:Flink、Spark Streaming、Kafka Streams
- 数据源:Kafka、Pulsar、Kinesis
- 输出:Redis、HBase、在线 KV 存储
同样的「用户过去 7 天购买金额」,实时计算是这样写的:
// Flink SQL 示例
CREATE TABLE orders_stream (
user_id BIGINT,
amount DOUBLE,
order_time TIMESTAMP(3),
WATERMARK FOR order_time AS order_time - INTERVAL '5' SECOND
) WITH (
'connector' = 'kafka',
'topic' = 'orders',
'format' = 'json'
);
SELECT
user_id,
SUM(amount) AS 7d_purchase_amount
FROM orders_stream
GROUP BY
user_id,
HOP(order_time, INTERVAL '1' MINUTE, INTERVAL '7' DAY);
看到区别了吗?离线是「算完存起来」,实时是「边来边算」。这里有个坑——实时计算的状态管理。7 天的滑动窗口,意味着 Flink 要维护每个用户 7 天的状态。用户量一大,状态后端就扛不住了。
我曾经踩过的坑:做实时用户画像时,窗口设了 30 天,结果 Flink 状态涨到几百 GB,Checkpoint 频繁超时。后来改成「离线预计算 + 实时增量修正」才解决。别迷信纯实时,该混搭就混搭。
2.3 存储介质选择:快、稳、省,三者不可兼得
存储介质的选择,直接决定了特征服务的延迟和成本。我把它分成三类:
| 存储类型 | 典型产品 | 读写延迟 | 适用特征 | 成本 |
|---|---|---|---|---|
| 内存 KV | Redis、Memcached | < 1ms | 实时特征、高频访问 | 高 |
| LSM-Tree KV | HBase、Cassandra | 1-10ms | 近线特征、中等频次 | 中 |
| 列式存储 | Parquet、ORC | 秒级 | 离线特征、批量读取 | 低 |
内存 KV:快,但贵
Redis 是实时特征存储的「标配」。为什么?因为特征服务要求 p99 延迟 < 10ms,只有内存能扛住。
但 Redis 有个问题——容量有限。1GB 内存几百块一个月,存几亿用户的特征?钱包受不了。
我的建议:只把「高频访问 + 实时更新」的特征放 Redis。比如用户最近 10 次点击、当前 session 信息。低频特征放 HBase,历史特征放离线存储。
LSM-Tree KV:折中方案
HBase 和 Cassandra 用的是 LSM-Tree,写入快、容量大、成本适中。适合存「近线特征」——比如小时级更新的用户标签。
但读取延迟比 Redis 高一个数量级。如果特征服务要求 5ms 以内,HBase 可能扛不住。
列式存储:便宜,但慢
Parquet/ORC 是离线特征的标配。压缩率高、扫描快,适合批量读取。但单条查询延迟是秒级的,没法用在在线推理链路里。
2.4 一张图看懂整体架构
下面这张图,是我自己总结的实时 vs 离线特征的全链路对比。你可以把它当作本章的「知识地图」:
2.5 选型决策:到底该用哪个?
你可能会问:那我做特征存储,到底选实时还是离线?
我的回答是:别二选一,要混搭。
实际生产环境里,90% 的系统都是「离线为主、实时为辅」:
- 离线特征:负责基础画像、历史统计、模型训练
- 实时特征:负责当前行为、session 信息、实时修正
- 近线特征:小时级更新的标签,走 HBase 或 Cassandra
一个实用的选型原则:
- 如果特征更新频率 > 1 小时 → 用离线
- 如果特征更新频率 < 1 分钟 → 用实时
- 如果特征更新频率在 1 分钟 ~ 1 小时之间 → 用近线(HBase/Cassandra)