想象你走进一家你常去的烘焙店。这家店最拿手的是“提前把面团准备好”:清晨五点,师傅们就已经把一天要用的面团、馅料、酱汁全部备好,分门别类放在冷藏柜里。你点单时,收银员只需要 30 秒就把成品交到你手上,而不是等你来了才从磨面粉开始做。 这家店的“玄机”有三个:第一,提前准备好——把费时的计算都挪到你不来的时候做;第二,用对的方式保存——做好的东西放在最方便取的地方,常卖的就放柜台,少人点的就放后厨;第三,流水线不断供——后厨有个专门的团队,每隔几分钟就检查一次冰柜,把快空的货补上,保证任何时候你进来都有现货。 一个“低延迟特征服务”,干的正是这家烘焙店的事。AI 系统要实时推荐商品、要实时拦截欺诈交易,靠的都是预先算好的“特征”——比如“你最近 5 分钟点击了什么”“这张卡过去一小时有没有异常”。把这些特征以极快的速度送到模型面前,就是低延迟特征服务的使命。本文就用这家烘焙店的比喻,带你拆解“毫秒级响应”背后的三个招数:预计算、缓存、流式更新。
一句话价值:掌握了低延迟特征服务的三大招(预计算、缓存、流式计算),你就能把 AI 系统的特征读取延迟压到毫秒级,这是支撑实时推荐、在线风控的硬功夫,也是进入大厂实时计算团队的敲门砖。
可视化演示
本节配有交互动效(进入视口后自动加载,约 3KB)。
一、低延迟挑战
回到烘焙店。你可能会问:既然提前备料这么好用,为什么每家烘焙店还要留一个“现场烤制”的烤箱?答案很简单——有些顾客就爱吃刚出炉的、热乎乎的面包,你没法把这些也提前备好。AI 系统也一样,有些特征(比如“用户 5 秒前刚点击了什么”)是实时的,离线根本算不出来。
低延迟特征服务要面对的第一个难题,就是时间。想象一个电商平台的大促之夜:用户点了“猜你喜欢”,系统必须在 100 毫秒内返回推荐结果。这 100 毫秒里,要完成请求接收、特征取出、模型打分、结果返回四个环节。如果特征读取就花掉 80 毫秒,模型纵有天大的本事也来不及。
传统做法为什么慢?因为特征通常存在关系型数据库里,每次请求都要发一条 SQL 去查。这就好比顾客点单后,收银员才跑回后厨翻原材料、现场切菜、现场炒,一条龙服务下来,顾客早等得不耐烦走了。数据库的磁盘寻址、网络往返、复杂 JOIN,每一项都是毫秒级的“隐形杀手”。
低延迟服务的目标,就是把“现场炒菜”变成“柜台取货”。拿到请求的那一刻,所有特征都已备好放在触手可及的地方,系统只需要做一次内存读取——这就是毫秒级响应的全部秘密。
回到烘焙店:顾客根本不关心面包是几点烤的,只关心到手快不快。同理,模型也不关心特征是怎么算出来的,只关心“我要的时候你给得够不够快”。低延迟服务的全部艺术,就是把这个“给得快”做到极致。
- 端到端延迟定义系统从收到请求到返回响应的总时间,是每个环节耗时的累加。
- 获取耗时 \(t_{\text{获取}_i}\)第 \(i\) 个特征从存储中取出的时间,数据库查询、网络 IO 都算在这里。
- 计算耗时 \(t_{\text{计算}_i}\)第 \(i\) 个特征需要临时加工的时间,比如算一次滑动窗口均值。
- 极限近似当所有特征都提前算好并放入缓存,求和式中前 \(n-1\) 项趋近于零。
二、特征预计算
烘焙店的第一步是“提前备料”。深夜收工后,师傅会把第二天要用的所有面团揉好、分成小份、贴上标签、冷藏保存。第二天早上开店,柜台里的每一样东西都已经是“成品状态”,只需要打包就能卖。
特征预计算的思路完全一致:把那些变化慢、可提前算的特征,用离线批处理任务(比如 Spark、Hive)事先算好,写进特征仓库。所谓“变化慢”,是指分钟级甚至小时级才变一次的特征,比如用户的历史购买偏好、商品的静态属性、最近 7 天的平均点击率。
这样做的好处是:在线服务读取时,不需要任何计算,直接查表就是 O(1) 的复杂度。代价是牺牲新鲜度——如果用户刚刚完成了购买,离线特征仓库里“近 7 天购买次数”要等到下一次批处理任务跑完才会 +1,可能滞后几十分钟。
那么问题来了:哪些特征适合预计算,哪些必须实时算?业界一条实用的分界线是“变化频率 vs 读取频率”。变化慢、读取频繁的特征,预计算性价比极高;变化快、对新鲜度敏感的特征,得交给后面的缓存和流式计算来处理。烘焙店也是这么分类的:招牌吐司天天有人买,一次做一大批;时令蛋糕一天只出几炉,宁可现场做也不囤货。
烘焙店深夜揉面时,前台早已打烊,没人排队等着,时间是充裕的。特征预计算也一样:离线批处理跑在流量低峰期,用“闲时的大把时间”换“忙时的一瞬间”,这笔账怎么算都划算。
- 查表场景特征已提前算好存在特征仓库中,在线读取只需一次哈希查找。
- 临时计算场景特征未预计算,必须实时从原始数据中加工,涉及多次 IO 和聚合。
- 预计算收益读频率越高、变化越慢的特征,把“临时计算”换为“查表”的收益越大。
三、缓存策略
烘焙店冰柜里的面团,并不是每个都放在同一个地方。最受欢迎的牛奶吐司放在柜台正前方的玻璃柜里,顾客一指就能拿到;不太热门的全麦面包放在后厨的冷藏架上,店员多走几步路也能拿到。这个“最方便的物理位置”,就是缓存的哲学。
在线特征服务最常见的缓存层是 Redis——一个跑在内存里的键值存储。所有高频访问的“热点特征”(比如用户最近 10 分钟的行为统计、商品当前的库存状态)都会写入 Redis,键是用户 ID 或商品 ID,值是算好的特征向量。模型服务读取特征时,先查 Redis,命中就直接返回,完全不碰数据库。
缓存的关键指标是命中率——所有特征请求中,有多少比例能从缓存直接拿到。命中率高,平均延迟自然低。但缓存有一个天然的敌人:过期。特征会随用户行为变化而失效,如果缓存里永远是旧值,模型就会“睁眼瞎”。所以每条缓存记录都要设置 TTL(生存时间),比如 30 秒。TTL 一到,缓存自动失效,系统回源到流式计算服务重新获取,再写回缓存。
这里有一个耐人寻味的权衡:TTL 太短,缓存频繁失效,回源压力大;TTL 太长,特征太旧,模型效果变差。烘焙店的解决方法是“按品类定保鲜期”:牛奶吐司 4 小时下架,蛋糕 1 小时下架。缓存策略同样要按特征区分 TTL——用户位置特征 5 秒,行为计数 30 秒,静态属性 1 小时。
把最热卖的面包放到最顺手的位置,把最热门的特征放进 Redis,本质上都是在做同一件事——用空间换时间,用“稍微旧一点”换“快很多倍”。顾客不会介意吐司烤好已经过了 10 分钟,模型也不会介意特征更新滞后了几十秒。
- 命中计数所有特征请求中,直接从缓存取到特征值的次数记为 \(N_{\text{命中}}\)。
- 未命中回源缓存中没有或已过期,需要去下层存储或实时计算重新获取特征。
- 命中率计算命中率是命中的请求数占总请求数的比例,目标是把分母中的未命中项压到极小。
四、流式计算
烘焙店光有“提前备料”和“柜台摆放”还不够。如果下午 3 点牛奶吐司突然卖爆,凌晨备的料中午就见了底,后厨就得有专人临时补货。AI 系统也一样:用户行为是持续不断产生的实时数据流,很多特征必须跟着这个流实时更新。
流式计算就是在数据产生的瞬间,立刻完成特征加工。最主流的组合是 Kafka + Flink:Kafka 像个传送带,把用户点击、交易、支付等事件源源不断地送进来;Flink 像流水线上的工人,按时间窗口把这些事件聚合成特征——“过去 5 分钟点击了 30 次”“过去 1 小时下单金额 500 元”。
窗口是流式计算的灵魂。滚动窗口把时间切成固定片段(每 5 分钟算一次),滑动窗口则每 30 秒就算一次“最近 5 分钟”的聚合。滑动窗口能保证特征始终“新鲜”,但计算量更大。Flink 的厉害之处在于它的状态管理:窗口里的中间结果保存在状态后端,事件来了增量更新,不需要每次都从头算。
补货线还有一个精妙的设计:输出即缓存。Flink 算出的新特征,直接写入 Redis 覆盖旧值。这样特征服务的读取侧完全无感知——它只负责从 Redis 取,至于值是谁更新的、什么时候更新的,都由“补货线”自动搞定。
烘焙店的补货师傅每隔 10 分钟看一眼冰柜,哪个快空就补哪个,不用等到客人催才手忙脚乱。流式计算的哲学就是这个——不让“旧”成为服务的短板,把更新变成一条自动运转的流水线。
- 窗口长度 \(W\)决定特征的时间范围,例如 \(W = 5\) 分钟,表示只关注最近 5 分钟内的数据。
- 窗口内累加将窗口内所有时间片的事件值 \(x_\tau\) 累加起来。
- 取均值累加和除以窗口长度,得到这一时刻的滑动平均特征。
五、特征一致性
烘焙店有一个隐形的原则:柜台卖的蛋糕,和后厨标签上写的内容必须一致。如果标签写着“草莓味”、实物却是“芒果味”,顾客投诉是小,整个店的信用都会打折。AI 特征服务也面临同样的“对账”问题,不过它发生在训练和在线之间。
机器学习有个基本假设:“模型训练时看到的数据分布,和线上推理时看到的数据分布要一致”。训练时,我们用离线批处理算好特征放在训练集里;上线后,特征从 Redis 和 Flink 里来。如果两条链路对同一特征的定义不同——比如说训练时“过去 7 天”是指自然日,在线时却按 168 小时滚动算——模型就会“水土不服”,线上效果断崖式下跌。
解决这个问题的常用手段有三件套。第一,统一特征定义,把每个特征名、计算口径、时间窗口写成一份“特征字典”,训练和在线共用;第二,双写对齐,离线批处理和实时流式计算写成“同一套代码逻辑”的两种实现,或者干脆用同一个框架(如 Flink 同时做批和流)跑,确保结果一致;第三,特征版本管理,每次改动特征计算逻辑都打上版本号,线上服务可以一键回滚到旧版本——就像面包配方改坏了,还能切回老配方。
一致性是低延迟服务最容易被忽视的地基。延迟做到 10 毫秒但特征口径乱了,模型照样大面积失效;反过来说,只要训练和在线对得上,偶尔慢个几十毫秒也能稳住效果。
烘焙店每天打烊前都要做一次盘点:标签、实物、收银系统三项对不上就查原因。特征服务也要做同样的“盘点”——定期用离线任务对线上采样特征做对比审计,发现偏差立即排查。这是低延迟服务的“质量巡检”。
- 训练特征值离线训练时,对样本 \(x\) 计算出的特征值 \(f_{\text{训练}}(x)\)。
- 在线特征值实时推理时,对同一请求 \(x\) 从缓存或流式计算中取到的特征值 \(f_{\text{在线}}(x)\)。
- 求最大偏差在所有特征上取两者差值的最大值,衡量“训练和在线有多对不上”。
六、实时应用
烘焙店的整套流程——提前备料、柜台摆放、流水线补货、标签对账——最终服务的是每一个进店的顾客。低延迟特征服务也最终要落到真实的业务场景里。最典型的有两个。
实时推荐:你在刷短视频 App 时,每次滑动,推荐系统都要在几百毫秒内返回一组新视频。推荐特征里,“用户刚看完的 5 个视频类别”是实时性最强的信号——它由流式计算实时更新,写入 Redis 后供推荐模型读取。配合预计算好的用户长期兴趣特征,系统既能跟上你当下的情绪,又不会忘了你长久的口味。
在线风控:一笔银行卡交易发生的那一瞬间,风控系统要在几十毫秒内决定“放行还是拦截”。欺诈特征极其敏感:交易的 IP 是否在 5 分钟内切换过、收款账户是否刚被大量新卡关联——这些都要靠流式计算从 Kafka 事件流中实时算出。风控场景对延迟和一致性的要求比推荐更苛刻,因为晚一秒可能就意味着一笔资金的损失。
从架构上看,一个成熟的低延迟特征平台通常分为三层:离线层(批处理预计算特征 → 特征仓库)、实时层(Kafka + Flink 流式计算 → Redis 缓存)、在线层(特征服务 API,统一对外提供特征读取)。三层各司其职,共同撑起毫秒级的响应承诺。
烘焙店的生意越做越大,老板会发现:光靠一个后厨和一排柜台不够,得把“备料—存储—补货—对账”做成一套标准化的流程。低延迟特征服务也是一样——从单点优化走向平台化,才能在业务不断增长的今天,守住那条毫秒级的生命线。
---
AI 应用场景
场景一:短视频推荐系统的“实时兴趣捕捉”
阶段:特征工程与特征平台 / 实时特征服务
解决的问题:用户兴趣变化极快,如果只靠离线特征,推荐内容会滞后十几分钟甚至更久。比如用户刚看完一个“美食教程”,系统要立刻在下一刷推荐更多美食视频,离线特征根本来不及更新。
具体案例:某短视频平台搭建了 Kafka + Flink 实时链路:用户每产生一次点击或播放完成事件,Flink 在 200 毫秒内更新“最近 10 条看过的视频类别”特征写入 Redis。推荐模型读取特征时,Redis 命中率超 95%,特征读取延迟稳定在 5 毫秒以内。用户连续刷了 8 个美食视频后,第 9 个推荐就精准切换到了同类型内容。
场景二:银行在线风控的“毫秒级拦截”
阶段:实时决策系统 / 在线风控
解决的问题:交易欺诈往往在几秒内发生,风控系统必须“边交易边判断”。传统做法查数据库做多表关联,单笔交易延迟 300 毫秒以上,严重影响支付体验。
具体案例:某银行将交易事件的流式计算前置:每笔交易到达时,系统通过预计算好的“用户历史交易画像” + 流式实时算出的“近 5 分钟交易频率”“设备 IP 变化次数”两组特征,在 50 毫秒内完成风险评分。某次大规模盗刷攻击中,系统因为“收款账户 1 分钟内被 20 个新卡关联”这个实时特征及时触发拦截,挽回损失数百万元。
Python 演示
"""低延迟特征服务简化演示:1. 离线预计算:用历史数据算出用户长期特征2. 实时流式更新:模拟 Kafka 事件流,更新滑动窗口特征3. 特征读取:先查 Redis 缓存,未命中则回源重新计算"""import timefrom collections import deque class FeatureStore: def __init__(self): # 模拟 Redis 缓存:key -> (value, expire_at) self.cache_dict = {} # 模拟特征仓库:key -> 预计算特征值 self.feature_repo = {} # 滑动窗口:key -> deque 最近 N 个事件值 self.window_map = {} self.ttl = 5 # 缓存 TTL:5 秒 def precompute(self, user_id, value): """离线预计算:把慢变的特征提前算好放入仓库""" self.feature_repo[user_id] = value def on_event(self, user_id, amount): """实时流式更新:收到事件就更新滑动窗口特征""" if user_id not in self.window_map: self.window_map[user_id] = deque(maxlen=5) self.window_map[user_id].append(amount) avg = sum(self.window_map[user_id]) / len(self.window_map[user_id]) # 新特征值写入缓存 self.cache_dict[user_id] = (avg, time.time() + self.ttl) def get_feature(self, user_id): """特征读取:先查缓存,命中直接返回""" item = self.cache_dict.get(user_id) if item and item[1] > time.time(): return item[0], "cache-hit" # 缓存未命中:回源特征仓库,重新计算 repo_value = self.feature_repo.get(user_id, 0.0) return repo_value, "recompute" def main(): store = FeatureStore() # 1. 预计算用户基础画像 store.precompute(1001, {"age_bucket": "18-25", "long_term_rpm": 3.2}) # 2. 模拟 3 笔实时交易事件 store.on_event(1001, 50) store.on_event(1001, 200) store.on_event(1001, 30) # 3. 在线读取特征 avg, src = store.get_feature(1001) print(f"近5笔平均金额: {avg:.2f} 元 (来源: {src})") if __name__ == "__main__": main()
self.cache_dict = {}模拟 Redis 缓存,键是用户 ID,值是特征值和过期时间的元组。self.window_map = {}用 deque 实现滑动窗口,maxlen=5 自动丢弃旧事件。def on_event(self, user_id, amount)流式更新的入口,每来一个用户行为事件就触发一次窗口聚合。deque(maxlen=5)关键设计:窗口长度固定,新事件进入、旧事件自动出队,永远只保留最近 5 个。avg = sum(...) / len(...)实时算出“近 5 笔平均金额”,这个特征会写入缓存供在线使用。item[1] > time.time()检查缓存记录的过期时间是否晚于当前时间,判断缓存是否仍有效。return repo_value, "recompute"缓存失效时的兜底回源,读取预计算仓库中的特征值。
常见误区
误区把所有特征都丢给离线批处理提前算好,线上就能全部走查表,延迟自然就低了。
正解预计算适合“变化慢”的特征,但用户行为是实时的——“你刚点击了什么”这类特征离线根本算不出来。这类特征必须靠流式计算兜底。打个比方,烘焙店可以把所有面包都提前烤好,但总不能把“顾客刚点的定制蛋糕”也提前烤了。预计算和流式计算是互补关系,不是替代关系。
为何常见:初学者容易把“预计算”和“低延迟”划等号,因为离线批处理是最直观、最容易上手的一招,忽略了实时数据流的不可预知性。
误区TTL 设长一点,缓存命中率就高,回源少,延迟自然更低,何乐而不为?
正解TTL 过长会让特征严重过期。比如用户 10 分钟前买了一件商品,“近 7 天购买次数”如果缓存 1 小时才更新,推荐系统就会持续给用户推荐刚买过的东西,体验灾难。TTL 的本质是“新鲜度与速度的权衡”,不同特征要设不同的保鲜期,而不是统一拉长。
为何常见:延迟是可见的、直觉的,新鲜度是隐形的、需要一定技术深度才能体会的,所以新手容易只盯着延迟指标优化。
误区训练时把特征算好放进训练集就行,线上特征怎么实时算都行,反正都是“同一个特征”。
正解训练和在线特征计算逻辑只要有一处不一致,比如时间窗口的边界定义不同,模型上线后就会因为特征分布偏移而效果大跌。这就像面包店后厨的配方和柜台卖的成品对不上号——标签写“低糖”,实物却正常含糖,顾客吃一次就不会再来了。必须通过特征字典、双写对齐、版本管理来保证两边完全一致。
为何常见:训练和在线是两条独立的链路,很多人把它们当成“天然一致”的,忽略了计算逻辑分叉的风险。
误区延迟高就上更好的服务器、更快的 SSD、更大的内存,硬件堆上去问题就解决了。
正解硬件只能缓解单点瓶颈,无法解决架构性问题。如果特征没有预计算、缓存命中率低、流式更新不及时,再快的 CPU 也要被数据库 JOIN 和重复计算拖垮。烘焙店生意好,光换个大烤箱没用——你得把备料流程、柜台摆放、补货节奏都理顺。真正的低延迟来自架构设计:该预计算的就别临时算,该缓存的就别查库,该流式更新的就别等批处理。
为何常见:硬件升级立竿见影且操作简单,架构优化则要动脑且见效慢,人的惰性天然倾向前者。
面试题
Q1. 实时特征服务中,特征预计算解决的核心问题是什么?
- A.解决所有特征的实时更新需求
- B.把慢变特征提前算好以换取 O(1) 查表← 正确答案
- C.提升模型的离线训练精度
- D.替代缓存层成为唯一存储介质
答案:B。预计算适合变化慢、读取多的特征,把它提前算好存入特征仓库,线上只需一次哈希查表。选项 A 错在“所有特征”——实时特征无法预计算;C、D 与预计算的目的无关。就像烘焙店提前揉好面团备料,而不是把顾客现点的定制蛋糕也提前烤了。
Q2. 缓存 TTL(生存时间)设置过长的直接后果是什么?
- A.缓存命中率必然下降
- B.特征值过期,模型效果变差← 正确答案
- C.数据库压力显著增大
- D.流式计算延迟下降
答案:B。TTL 过长意味着缓存中的特征值长时间不被更新,特征新鲜度丧失,模型读到旧数据,效果自然变差。TTL 过长反而会让命中率更高,所以 A 错;数据库压力增大是 TTL 过短的后果,C 错;D 与 TTL 无关。
Q3. 以下哪些手段可以保证训练特征与在线特征的一致性?
- A.统一维护特征口径字典← 正确答案
- B.离线训练和在线推理共用 Flink 逻辑← 正确答案
- C.加大缓存 TTL 延长保存时间
- D.特征计算逻辑打版本号支持回滚← 正确答案
答案:ABD。A 统一口径、B 共用计算逻辑、D 版本管理都能消除训练在线的定义偏差。C 加大 TTL 只会让在线特征更旧、偏差更大。一致性像后厨标签和柜台实物对账,标签得和实物完全一致才能避免“货不对板”。
Q4. Kafka 在流式计算架构中的角色类似于烘焙店里的什么?
- A.冷藏冰柜
- B.后厨到柜台的传送带← 正确答案
- C.收银台
- D.烤箱
答案:B。Kafka 负责把用户行为事件源源不断地传输给 Flink,相当于后厨与柜台之间的传送带——事件流不断送进来。冰柜对应存储系统、烤箱对应批处理计算,收银台对应特征服务 API,都不能准确描述事件传输的角色。
Q5. 滑动窗口和滚动窗口的本质区别是什么?
- A.滚动窗口计算更快,滑动窗口更慢
- B.滚动窗口按固定时间片切分,滑动窗口覆盖最近窗口长度← 正确答案
- C.滑动窗口只能用于离线,滚动窗口用于实时
- D.滚动窗口精度更高,滑动窗口精度更低
答案:B。滚动窗口把时间切成不相交的固定片段,滑动窗口则在每个时刻都覆盖“最近 N 分钟”的范围,更新更频繁、新鲜度更高。A 不是本质区别而是衍生影响;C 两者都能用于实时;D 说反了,滑动窗口的新鲜度更高而非精度更低。
Q6. 降低实时特征读取延迟的可行手段有哪些?
- A.使用 Redis 缓存热点特征← 正确答案
- B.离线预计算慢变特征← 正确答案
- C.每次请求都实时 JOIN 多张表
- D.用 Kafka + Flink 实时更新特征写回缓存← 正确答案
答案:ABD。缓存热点特征、预计算慢变特征、流式更新写回缓存,是低延迟特征服务的三大招。C 恰恰相反——实时 JOIN 多张表是传统数据库做法的最大延迟来源,应尽量用预计算和缓存消除它。
Q7. 某特征“每小时仅变化一次,但每秒被读取 1 万次”,最适合的处理方式是什么?
- A.用 Flink 每秒重算一次
- B.离线预计算后放入缓存长期有效← 正确答案
- C.每次请求实时查询原始数据库
- D.用 Kafka 存储原始日志供查询
答案:B。变化频率远低于读取频率,是预计算 + 缓存的最佳场景。每小时跑一次批处理更新特征仓库,缓存 TTL 设为 1 小时即可,读取全部命中缓存。A 是过度计算,C 是过度查询,D 只存日志不解决读取问题。烘焙店的招牌吐司一天卖一万个,自然要一次备好大量货,而不是现做现卖。
Q8. 特征服务中“双写对齐”的核心意图是什么?
- A.把特征同时写入两个缓存以提升并发
- B.让离线批处理和在线流式计算产出一致← 正确答案
- C.用两个不同的特征源互相备份防止宕机
- D.把特征同时写入 Redis 和 MySQL 以防丢失
答案:B。双写对齐指离线训练和在线实时计算用同一套逻辑产出特征,消除训练与在线的特征口径偏差。它是特征一致性三件套之一,目标不是提升并发或备份可靠性,而是“两边算出来必须一样”。
Q9. 一个典型的低延迟特征平台架构包含哪些层次?
- A.离线层:批处理预计算特征← 正确答案
- B.实时层:Kafka + Flink 流式更新← 正确答案
- C.在线层:特征服务统一 API 读取← 正确答案
- D.展示层:可视化报表系统
答案:ABC。标准三层架构是离线的“备料层”、实时的“补货层”、在线的“柜台层”。D 展示层属于 BI 报表范畴,不是特征平台的核心读取链路。三层各司其职,共同把特征读取延迟压到毫秒级。
Q10. 以下哪种特征最适合交给流式计算实时更新?
- A.用户注册时的性别
- B.用户最近 5 分钟点击的商品类别← 正确答案
- C.商品的上架日期
- D.用户所在城市
答案:B。“最近 5 分钟点击或行为”天然依赖实时事件流,用 Kafka + Flink 实时聚合最合适。A、C、D 都是慢变甚至不变的静态特征,离线预计算 + 长期缓存即可满足。这正是烘焙店对不同食品分设不同保鲜期——现烤品必须时时补货,常温品一次备齐。