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 离线特征的全链路对比。你可以把它当作本章的「知识地图」:

实时特征 vs 离线特征:全链路对比 离线特征链路 数据源:HDFS / 数据仓库 / S3 计算引擎:Spark / Hive(批处理) 存储:Parquet / ORC / 离线 KV 服务:批量推理 / 离线评估 实时特征链路 数据源:Kafka / Pulsar / Kinesis 计算引擎:Flink / Kafka Streams(流处理) 存储:Redis / HBase / 在线 KV 服务:在线推理 / 实时决策 核心差异总结 离线:T+1 时效 · 批处理 · 低成本列存 · 适合训练 实时:秒级时效 · 流处理 · 高成本内存 · 适合推理

2.5 选型决策:到底该用哪个?

你可能会问:那我做特征存储,到底选实时还是离线?

我的回答是:别二选一,要混搭。

实际生产环境里,90% 的系统都是「离线为主、实时为辅」:

  • 离线特征:负责基础画像、历史统计、模型训练
  • 实时特征:负责当前行为、session 信息、实时修正
  • 近线特征:小时级更新的标签,走 HBase 或 Cassandra

一个实用的选型原则:

  • 如果特征更新频率 > 1 小时 → 用离线
  • 如果特征更新频率 < 1 分钟 → 用实时
  • 如果特征更新频率在 1 分钟 ~ 1 小时之间 → 用近线(HBase/Cassandra)