第三章:特征工程平台——实时特征与离线特征的统一管理
特征工程平台,说白了就是推荐系统的“后勤部”。
我见过太多团队,算法模型搞得风生水起,一到上线就卡在特征上。离线特征算好了,线上拿不到;实时特征写好了,跟离线对不上数。嗯,这种坑我踩过不止一次。
今天我们就聊聊,怎么用 Feast 和 Tecton 这类工具,把离线特征和实时特征管起来。
3.1 为什么需要统一管理?
先问个问题:你的推荐模型,离线训练时 AUC 0.78,上线后掉到 0.72。为什么?
我告诉你,八成是特征不一致。
离线训练时,你用 Hive 表里的“用户过去7天点击量”。线上服务时,你用 Redis 里实时攒的“用户过去7天点击量”。两个逻辑一模一样,但数据源不同、计算时间不同、甚至精度都不同。模型能不懵吗?
核心痛点:
- 离线特征和实时特征,两套代码、两套存储、两套运维
- 特征口径不一致,模型训练和推理存在偏差
- 特征复用率低,每个团队自己造轮子
统一管理,就是要解决这三个问题。让特征像数据库里的表一样,谁都能查,谁都能用,线上线下一个样。
3.2 特征存储的架构设计
我个人习惯把特征存储分成三层:
| 层级 | 名称 | 职责 | 典型组件 |
|---|---|---|---|
| L1 | 特征源层 | 原始数据接入 | Kafka、Hive、Kinesis |
| L2 | 特征计算层 | 特征变换与聚合 | Spark Streaming、Flink、Python UDF |
| L3 | 特征服务层 | 在线低延迟读取 | Redis、DynamoDB、Cassandra |
你想想看,这三层如果各管各的,那肯定乱套。Feast 和 Tecton 做的事,就是在这三层之上加一个统一的“特征注册中心”。
3.2.1 Feast 架构解析
Feast 是开源方案里比较成熟的。它的核心概念就两个:Feature View 和 Feature Service。
Feature View:定义了一个特征的“是什么”和“怎么算”。比如:
# 离线特征定义
user_click_features = FeatureView(
name="user_7d_click_count",
entities=["user_id"],
features=[
Feature(name="click_count", dtype=Int64),
Feature(name="click_item_ids", dtype=Array(Int64))
],
batch_source=BigQuerySource(
table_ref="project.dataset.user_click_7d"
),
ttl=timedelta(days=1)
)
# 实时特征定义
user_realtime_features = FeatureView(
name="user_realtime_click",
entities=["user_id"],
features=[
Feature(name="realtime_click_count", dtype=Int64)
],
stream_source=KafkaSource(
kafka_bootstrap_servers="localhost:9092",
topic="user_click_events"
),
ttl=timedelta(hours=1)
)
这里有个细节:Feast 要求你同时定义 batch_source 和 stream_source。为什么?
因为实时特征需要“回填”。比如用户刚注册,没有实时数据,那就用离线数据兜底。我曾在项目中遇到过,新用户因为没有实时特征,推荐结果全是空的。后来加了离线回填逻辑,才解决。
避坑指南:我曾经犯过一个错——实时特征的 TTL 设得太短。用户晚上10点刷了一下,第二天早上8点再打开,特征过期了,模型直接拿默认值。建议 TTL 至少覆盖用户两次访问的最大间隔。
3.2.2 Tecton 的企业级特性
Tecton 是商业版,比 Feast 多了几个杀手锏。
自动特征血缘:Tecton 会自动记录每个特征从原始数据到最终服务的完整链路。出了问题,一键回溯。我记得有一次线上特征异常,靠 Tecton 的血缘图,5分钟就定位到是某个 Flink 作业的窗口参数配错了。
特征监控与漂移检测:Tecton 内置了特征分布的监控。如果某个特征的均值、方差突然变化,它会自动告警。嗯,这个功能在推荐系统里特别有用——用户行为变了,特征分布肯定先变。
特征版本管理:Tecton 支持特征的多版本。你可以同时运行 v1 和 v2,做 A/B 测试。等 v2 验证通过,再一键切换。这个我在做特征升级时用过,确实省心。
3.3 实时特征与离线特征的对齐策略
统一管理最难的不是存储,而是“对齐”。
离线特征算的是“过去7天”,实时特征算的也是“过去7天”。但离线是 T-1 的精确值,实时是近似的滑动窗口。两者天然有差异。
我的做法是:以离线为准,实时做近似补偿。
- 离线特征每天凌晨计算一次,存入 Feast 的离线存储
- 实时特征在在线服务时,基于离线特征做增量更新
- 如果实时特征不可用,直接降级到离线特征
举个例子:
# 伪代码:实时特征补偿逻辑
def get_user_click_count(user_id):
# 先拿离线特征
offline_count = feast.get_offline_feature(
"user_7d_click_count", user_id
)
# 再拿实时增量
realtime_delta = redis.get(f"realtime_delta:{user_id}")
if realtime_delta is not None:
return offline_count + realtime_delta
else:
# 实时数据不可用,降级
return offline_count
这样,离线特征保证“准”,实时特征保证“新”。两者结合,既准又新。
注意:实时增量不能无限累加。我建议每小时做一次“全量同步”,把实时增量合并到离线特征中,然后清空增量。否则误差会越来越大。
3.4 特征存储的选型建议
选 Feast 还是 Tecton?我个人的经验是:
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 中小团队,预算有限 | Feast + Redis | 开源免费,社区活跃,够用 |
| 大厂,需要 SLA 保障 | Tecton + DynamoDB | 商业支持,监控完善,运维省心 |
| 实时要求极高(毫秒级) | 自研 + 内存网格 | Feast/Tecton 的 SDK 有网络开销 |
你想想看,如果你的推荐系统每天只有几百万请求,Feast 完全够用。但如果像抖音那样每秒几十万 QPS,那还是得自研。
3.5 实战:搭建一个最小特征平台
最后,我分享一个最小可用的特征平台搭建步骤。嗯,这个我在内部培训时经常用。
- 安装 Feast:
pip install feast - 初始化项目:
feast init my_feature_repo - 定义特征:在 feature_store.yaml 里配置离线源和在线源
- 注册特征:
feast apply - 离线写入:用 Spark 作业每天计算特征,写入 Feast
- 在线读取:服务端用
feast.get_online_features()获取
就这么简单。但要注意,Feast 本身不存储数据,它只是个“元数据管理”+“数据路由”。真正的数据存在你配置的 Redis 或 DynamoDB 里。
小技巧:我建议把 Feast 的元数据存在 PostgreSQL 里,比默认的 SQLite 稳定得多。SQLite 在并发高时会锁表,我吃过这个亏。
好了,特征工程平台的核心思路就这些。说白了,就是“统一注册、分层存储、离线兜底、实时补偿”。下一章我们聊聊模型训练平台,怎么把特征喂给模型。