1. 流式更新概述:为什么需要流式更新、离线与在线特征对比、核心挑战

各位同学,咱们今天聊聊召回特征流式更新。说实话,这个主题我琢磨了好几年。刚做推荐那会儿,我总觉得特征更新嘛,每天跑个离线任务就完事了。直到有一次线上事故,让我彻底改变了看法。

那是个双十一大促的晚上。用户刚点了一个商品,下一秒刷新页面,推荐结果还是老样子。用户明明已经买了某类商品,系统还在拼命推同类。CTR 直接掉了 15%。排查下来,问题就出在特征更新延迟上——离线任务要等 4 小时才能把用户行为刷进特征。4 小时,在推荐系统里,黄花菜都凉了。

所以,流式更新不是锦上添花,是刚需。

1.1 为什么需要流式更新

推荐系统的核心,是捕捉用户兴趣的变化。用户今天喜欢什么,和昨天可能完全不同。你想想看,一个用户刚搜了「婴儿奶粉」,说明家里有新生儿。接下来他可能关注纸尿裤、婴儿车。如果系统还在推游戏机、运动鞋,那体验就太差了。

流式更新的价值,说白了就是两个字:实时。

  • 用户行为实时反馈:点击、购买、收藏、加购,这些信号必须在秒级内反映到特征中。
  • 物品属性动态变化:库存、价格、标签,这些信息随时在变。我记得有个电商项目,商品价格一天能调 20 次。离线更新根本跟不上。
  • 上下文实时感知:时间、地点、天气、网络状态。比如中午 12 点,用户大概率想吃饭。流式更新能让特征带上时间戳,做更精准的推荐。

核心观点:流式更新不是可选项,而是推荐系统从「能用」到「好用」的分水岭。离线更新只能告诉你「用户过去喜欢什么」,流式更新才能告诉你「用户现在想要什么」。

1.2 离线与在线特征对比

咱们来做个对比。离线特征和在线特征,就像「历史课本」和「实时新闻」。各有各的用处,但场景完全不同。

对比维度 离线特征 在线特征(流式)
更新频率 天级 / 小时级 秒级 / 毫秒级
数据源 Hive / HDFS 批处理 Kafka / Pulsar 消息队列
计算引擎 Spark / MapReduce Flink / Spark Streaming
存储介质 HBase / MySQL / Hive Redis / 本地缓存 / 内存表
特征时效性 过去 1 天 ~ 30 天 过去 1 秒 ~ 1 小时
计算复杂度 高(全量计算) 低(增量计算)
容错能力 强(重跑即可) 中等(需要 checkpoint)
典型场景 用户长期兴趣画像、物品基础属性 实时点击序列、价格变动、库存状态

我个人的习惯是:离线特征做「骨架」,在线特征做「血肉」。用户的基础画像、物品的静态属性,这些变化慢的东西,离线跑没问题。但用户的实时行为序列、物品的动态状态,必须走流式。

避坑指南:我曾经在一个项目里,把所有特征都做成流式。结果呢?Flink 任务挂了,整个推荐系统直接瘫痪。后来我学乖了——核心的、变化慢的特征,离线兜底;变化快的特征,流式优先。这叫「冷热分离」。

1.3 核心挑战

流式更新听起来很美好,做起来全是坑。我踩过的坑,比走过的路还多。咱们一个一个说。

挑战一:数据一致性

离线任务可以保证「要么全做,要么不做」。流式呢?消息可能丢失、重复、乱序。你想想看,用户点击了商品 A,又点击了商品 B。如果消息乱序,系统可能先处理 B 再处理 A,特征就乱了。

解决方案?幂等性设计 + 事件时间语义。每条消息带上唯一 ID 和时间戳,处理时做去重和排序。Flink 的 watermark 机制就是干这个的。

挑战二:延迟与吞吐的平衡

流式更新,延迟越低越好。但低延迟意味着高成本。每秒钟处理 10 万条消息,还要在 100 毫秒内更新特征,这对系统压力很大。

我记得有个项目,为了追求极致延迟,把特征更新直接写在用户请求链路里。结果呢?接口响应时间从 20ms 涨到 200ms,用户直接骂娘。后来我改成异步更新 + 本地缓存,延迟控制在 50ms 以内,吞吐量还翻了一倍。

挑战三:特征存储的实时读写

离线特征可以存在 HBase 里,批量读写没问题。在线特征呢?要求毫秒级读取,还要支持高频写入。Redis 是首选,但 Redis 也有坑——内存有限,数据过期策略要设计好。

我见过一个团队,把所有特征都塞进 Redis,结果内存爆了,OOM 导致整个缓存集群雪崩。后来他们做了特征分级:热数据放 Redis,温数据放 SSD 本地缓存,冷数据回源 HBase。

挑战四:特征回溯与修复

流式系统一旦出问题,数据就丢了。离线任务可以重跑,流式呢?消息已经消费了,怎么回溯?

我的做法是:流式 + 离线双链路。流式负责实时更新,离线负责天级全量修复。每天凌晨,离线任务把当天的特征重新算一遍,覆盖流式可能产生的错误。这叫「流批一体」的降级方案。

注意:流式更新不是银弹。如果你的业务场景对实时性要求不高(比如用户画像每天更新一次就够了),别盲目上流式。流式系统的运维成本,比离线高一个数量级。

1.4 流式更新整体架构

说了这么多,咱们来看看流式更新的整体架构长什么样。下面这张图,是我在多个项目中沉淀下来的通用方案。

召回特征流式更新架构图 用户行为日志 物品属性变更 上下文信号 消息队列(Kafka / Pulsar) 流式计算引擎(Flink / Spark Streaming) 特征提取 → 特征变换 → 特征聚合 → 特征编码 特征存储(Redis / 本地缓存 / 内存表) 召回服务(实时读取特征进行召回) 说明:数据从多个源进入消息队列,流式计算引擎实时消费并处理,结果写入特征存储。召回服务在请求时直接读取最新特征。 离线任务每天凌晨全量修复,保证数据最终一致性。

这个架构,我在三个项目里验证过。核心思路就是:消息队列解耦 + 流式计算处理 + 高速缓存存储。每一层都有明确的职责,出了问题也好排查。

1.5 本章小结

咱们这一章,主要讲了三个东西:

  • 为什么需要流式更新:用户行为实时反馈、物品属性动态变化、上下文实时感知。说白了,就是让推荐系统跟上用户的变化节奏。
  • 离线与在线特征对比:离线特征适合变化慢、计算复杂的场景;在线特征适合变化快、要求低延迟的场景。两者互补,不是替代关系。
  • 核心挑战:数据一致性、延迟与吞吐平衡、存储实时读写、特征回溯修复。每个挑战都有对应的解决方案,后面几章会详细展开。

一句话总结:流式更新是推荐系统从「离线时代」迈向「实时时代」的关键一步。但这一步,需要扎实的架构设计和性能优化功底。接下来的 29 章,我会带着大家把每个环节都拆开揉碎,讲清楚、说明白。