想象你是一家大餐厅的主厨。餐厅每天要出几千道菜,而许多菜都需要同样的基础备料——比如蒜蓉酱、高汤、卤汁。刚开始,你每接到一单就现剁蒜、现熬汤,结果后厨乱成一团,出菜慢得像蜗牛。后来你学聪明了:把最常用的备料提前做好,放在手边的备菜台上。客人点菜时,伸手就能端走——出菜速度快了十倍,客人满意,你也轻松。这个“备菜台”,就是今天的主角:特征缓存。 在 AI 系统里,模型每次推理都要“吃”一堆特征——用户的历史点击、商品热度、实时行为。这些特征如果每次都现场从数据库算一遍,耗时可能高达几十毫秒甚至更久,线上服务根本扛不住。但很多特征在一段时间内是“不变”的(比如用户近 7 天点击率),完全可以提前算好、放在内存这个“备菜台”上。问题来了:备菜台空间有限,放什么?放多久?扔什么?多个备菜台怎么同步?这就是特征缓存策略与性能优化的全部学问。看懂这张“备菜台管理手册”,你就能让特征服务又快又稳,省下大把计算资源。

岗位关联

对 AI 从业者而言,掌握特征缓存是构建高并发、低延迟特征平台的必备技能——它能直接决定线上模型的响应速度和系统成本。

目录
  1. 可视化演示
  2. 缓存本质与价值
  3. 命中率与性能指标
  4. 缓存淘汰策略
  5. 缓存更新机制
  6. 分布式缓存架构
  7. 缓存一致性保障
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示实时特征服务的缓存架构与请求路由流程,对比有无缓存时的延迟与吞吐性能差异,交互调节缓存命中率感受性能变化。

本节配有交互动效(进入视口后自动加载,约 3KB)。

特征缓存架构流程 在线请求先查缓存层(Redis/Memory),命中则直接返回避免重复计算 缓存未命中时回源计算特征值并写入缓存,TTL控制过期策略 延迟 = 命中率×缓存延迟 + (1-命中率)×计算延迟 高命中率是特征服务性能的关键

缓存本质与价值

餐厅出菜慢,往往不是大厨手艺差,而是备料每次都现场做。AI 系统也一样:模型推理的延迟大头常常不在模型本身,而在“特征获取”这个环节。一个用户特征可能要查数据库、做聚合计算、甚至跨服务调用,整套流程下来几十毫秒。传统做法是每个请求都现算——就像每单都从剁蒜开始,后厨很快就瘫痪了。

等等,有个关键点:特征计算是幂等的。同样一个“用户近 7 天点击率”,一秒前算和现在算,结果几乎一样。既然结果一样,何必重复算?于是我们把算好的特征放进内存缓存,这就是在计算和存储之间加了一个“备菜台”。请求来了先看备菜台,有就直接端走;没有才回厨房现做。

缓存带来的加速效果可以用一个加权平均公式描述:整体延迟等于“命中缓存的概率乘上缓存读取延迟”加上“未命中概率乘上回源计算延迟”。

当然,备菜台不是免费午餐:内存是钱,维护缓存是复杂度。所以“放什么”要看性价比——热门且稳定的特征值得放,冷门或瞬息万变的特征就算了。优秀的特征平台,永远在“省计算”和“耗内存”之间找平衡点,这就像大厨只备最畅销的十道菜,而不是把整个菜单都提前做一遍。

Lavg=h×Lcache+(1−h)×Lsource\displaystyle L_{\text{avg}} = h \times L_{\text{cache}} + (1 - h) \times L_{\text{source}}
平均延迟是“命中”与“回源”两条路径的加权平均,命中率越高,整体越快。
  1. h 命中率请求在缓存中找到结果的概率,取值范围 0 到 1。h 越高,加权平均延迟越被缓存速度主导。
    h∈[0,1]\displaystyle h \in [0, 1]
  2. L_cache 缓存延迟从内存或 Redis 中读取特征的时间,通常只有微秒到毫秒级,相当于“伸手端菜”。
    Lcache≈10−6∼10−3 s\displaystyle L_{\text{cache}} \approx 10^{-6} \sim 10^{-3}\,\text{s}
  3. L_source 回源延迟从底层存储重新计算特征的耗时,可能是几十毫秒甚至上百毫秒,相当于“现场下锅炒菜”。
    Lsource≈10−2∼10−1 s\displaystyle L_{\text{source}} \approx 10^{-2} \sim 10^{-1}\,\text{s}
  4. 加权求和把两种路径按命中率加权,得到一个请求的平均期望延迟。h 从 0 变到 1 时,延迟从满额回源逐步逼近缓存延迟。
    h→1  ⟹  Lavg→Lcache\displaystyle h \to 1 \;\Longrightarrow\; L_{\text{avg}} \to L_{\text{cache}}

命中率与性能指标

既然命中率是缓存效果的核心,那它到底该怎么衡量?回想备菜台:一晚上接了 1000 单,其中 830 单直接从备菜台端走,命中率就是 83%。在特征缓存里,我们需要精确计数:一段时间内,特征请求总数为 T,其中从缓存命中的次数为 H,未命中回源的次数为 M,则 T = H + M。

这里有个反直觉的事实:特征访问往往是高度倾斜的。少数热门特征(比如头部用户的近期行为)占据了绝大多数请求,而大部分冷门特征几乎没人访问。备菜台只需要覆盖前 20% 的热门特征,就能获得 80% 甚至 95% 以上的命中率——这就是特征工程里的“二八定律”。

那么什么因素会影响 HR?第一是缓存容量:备菜台越大,能放的菜越多,但边际收益递减——加大内存只能捞回越来越冷门的特征。第二是访问局部性:业务越集中,命中率越高。第三是TTL 策略:保质期越长,菜越不容易过期,但数据越旧。实践中我们会监控两个黄金指标:命中率(影响性能)和平均延迟(影响体验),两者联动看,不能只看一个。备菜台管理得好不好,看它一晚上直出了多少菜,而不是看它堆了多少菜。

HR=HH+M×100%\displaystyle \text{HR} = \frac{H}{H + M} \times 100\%
命中率的本质是“缓存对请求流的覆盖程度”,它直接决定平均延迟和下游存储的负载。
  1. H 命中次数在缓存 TTL 有效期内直接读到的请求数量,等价于备菜台“直出”的菜。
    H=∑i=1T1[第 i 次请求命中]\displaystyle H = \sum_{i=1}^{T} \mathbb{1}[\text{第 }i\text{ 次请求命中}]
  2. M 未命中次数缓存中没有、必须回源计算的请求数量,等价于“现场开火”的次数。
    M=T−H\displaystyle M = T - H
  3. 分母 T总请求数 = 命中 + 未命中,保证命中率是一个 0% 到 100% 的归一化比例。
    T=H+M\displaystyle T = H + M
  4. 乘以 100%把比例转换成百分比,便于跨团队对齐目标,比如“线上命中率须 ≥ 95%”。
    HR×100%\displaystyle \text{HR} \times 100\%

缓存淘汰策略

备菜台就这么大,菜放满了怎么办?必须把一些菜扔掉,给新菜腾地方。问题来了:扔谁?这就要看淘汰策略。三种经典思路,对应三种不同的“扔菜哲学”。

LRU(最近最久未使用):每次要腾位置,就扔掉“最久没有被客人点过”的那份菜。它的底层逻辑是:既然很久没人点了,说明现在也不受欢迎。实现上用链表维护访问顺序,每次命中就把菜挪到最前面,淘汰时从末尾扔。这是最通用的策略,对突然爆发的热点适应很快。

LFU(最不经常使用):扔掉“被点次数最少”的那份菜。它记录每道菜的历史访问频次,适合访问模式非常稳定、热点长期不变的特征——比如“节日红包特征”这类每天被大量请求但从不突然消失的场景。缺点是:一个昨天还火爆、今天过气的菜,因为历史计数高,会赖在台上很久,占着地方不干活。

FIFO(先进先出):谁先放上来,谁先被扔掉,完全不看访问情况。它实现最简单,但很容易误伤“刚放上来就不停被点”的好菜。

特征缓存的策略选型,关键在于理解你的请求分布:如果特征热度随时间剧烈变化,LRU 更灵活;如果热度长期稳定、有明显冷热分层,LFU 更精准。工业界还有一种聪明的做法:用 LFU 的思路近似 LRU 的“时间衰减”——Redis 的近似 LFU 就是给每份菜加一个随时间衰减的计数器,兼顾两者优点。选策略就像选菜单:连锁快餐店(稳定热点)和网红餐厅(流量不定)的备菜逻辑,怎么可能一样呢?

缓存更新机制

备菜台上的菜不是永远不会坏。蒜蓉酱放久了会馊,特征数据放久了会过时——用户昨天点的赞,今天就成了旧数据。所以缓存必须有更新机制,让“菜”保持新鲜。核心是 TTL(Time To Live,存活时间),即每份缓存数据设一个保质期,到期自动失效,下次请求触发重新计算。

TTL 怎么定?定太短,菜刚摆上就过期,命中率暴跌;定太长,菜虽然一直能直出,但客人吃到的全是馊的。假设数据在 TTL 内均匀被访问,那么用户读到的平均数据年龄大约是 TTL 的一半——这就是新鲜度与命中率的“跷跷板”。

光靠 TTL 被动过期还不够。工程上还有两种主动策略:主动刷新——定时任务在 TTL 到期前把热门菜重新做一遍,保证客人任何时候来都有新鲜菜(特征平台凌晨对次日热点特征做预热);事件驱动更新——数据源发生变更时(比如用户改了头像、风控名单更新),立刻通过消息队列通知缓存删掉旧数据。这就像一个灵敏的后厨广播系统:哪道菜的原食材换了,备菜台马上收到通知重做,而不是等客人吃出馊味再补救。成熟的实时特征平台会组合使用三者:事件驱动保实时、主动刷新保热度、TTL 兜底防脏数据。

E[age]=TTL2\displaystyle E[\text{age}] = \frac{\text{TTL}}{2}
每份缓存数据在被读到的那一刻,平均已经“老”了 TTL/2;想要更新鲜,就只能牺牲命中率。
  1. E[age] 平均数据年龄从数据写入缓存到被读取,平均经过了多长时间。年龄越大,数据越陈旧。
    E[age]=∫0TTLtTTL dt\displaystyle E[\text{age}] = \int_0^{\text{TTL}} \frac{t}{\text{TTL}}\,dt
  2. TTL 存活时间缓存的保质期,单位是秒或分钟,比如“用户近 7 天点击率”可以设 TTL = 300 秒。
    TTL=300 s\displaystyle \text{TTL} = 300\,\text{s}
  3. 均匀假设假设请求在 TTL 内等概率到达,则数据的“年龄”在 0 到 TTL 之间均匀分布。
    age∼Uniform(0,TTL)\displaystyle \text{age} \sim \text{Uniform}(0, \text{TTL})
  4. 除以 2均匀分布的平均值等于区间中点,所以平均年龄是 TTL 的一半。
    E[age]=0+TTL2\displaystyle E[\text{age}] = \frac{0 + \text{TTL}}{2}

分布式缓存架构

一家餐厅的备菜台再大也有限。连锁餐厅的做法是:每位厨师兜里揣一张速记本(本地缓存),后厨中央还有一个共享备菜房(集中式缓存,比如 Redis)。客人点菜时,厨师先翻速记本——这个零延迟,但只有自己知道的内容;翻不到,再去中央备菜房问——虽然要跑一小段路,但全店共享;中央也没有,才现做。

这就是多级缓存:本地缓存最快但命中率有限,集中式缓存稍慢但容量大、可共享。特征服务通常先查本地(毫秒内),未命中再查 Redis(几毫秒),最后才回源数据库(几十毫秒)。多级查表的平均延迟,就是每一级命中概率和延迟的层层加权嵌套:

多级缓存还要考虑故障降级:中央备菜房停电了怎么办?不能所有厨师都冲向后厨,那会造成“缓存雪崩”——大量请求同时回源,直接把数据库打爆。标准做法:本地缓存加长 TTL 兜底,同时对回源请求限流、熔断;宁可让一部分请求拿旧数据,也不能让系统整体崩溃。就像连锁餐厅的自救手册:中央厨房断了,各家分店先就着手头存货出菜,同时限购,等中央恢复再补货。这就是特征服务的降级哲学。

Ltotal=h1×Llocal+(1−h1)×(h2×Lredis+(1−h2)×Lsource)\displaystyle L_{\text{total}} = h_1 \times L_{\text{local}} + (1 - h_1) \times \bigl( h_2 \times L_{\text{redis}} + (1 - h_2) \times L_{\text{source}} \bigr)
多级缓存的核心思想是:让最快的那一级拦下最多的请求,只有漏网的才逐级下探。
  1. h1 × L_local 第一层命中本地缓存命中时直接返回,延迟最小,概率 h1。
    h1×Llocal\displaystyle h_1 \times L_{\text{local}}
  2. 1 - h1 未命中本地只有本地没找到时,才需要继续向下一级查询。
    1−h1\displaystyle 1 - h_1
  3. h2 × L_redis 第二层命中在集中式缓存 Redis 中命中的部分,概率 h2,延迟中等。
    h2×Lredis\displaystyle h_2 \times L_{\text{redis}}
  4. 1 - h2 回源兜底Redis 也没有,才回源计算,承担最大的延迟 L_source 和最重的代价。
    (1−h2)×Lsource\displaystyle (1 - h_2) \times L_{\text{source}}
  5. 加权嵌套外层括号把“第二层 + 回源”合并成一个整体期望,再与第一层结果按 h1 加权。
    Ltotal=Llocal⋅h1+(1−h1)⋅Lnext\displaystyle L_{\text{total}} = L_{\text{local}} \cdot h_1 + (1-h_1) \cdot L_{\text{next}}

缓存一致性保障

备菜台上的菜和真实情况不一致,客人点的“今日推荐”其实已经下架,这就是一致性问题。特征缓存里,脏数据会直接污染模型预测:比如风控系统发现某个用户有风险,黑名单特征更新了,但缓存里的旧特征还在服务,就可能放走一个坏订单。

一致性按严格程度分档:强一致要求每次读到的都是最新值,这在分布式缓存里代价极高;最终一致允许短暂读到旧值,但保证在一段时间后收敛到新值——这是特征平台的现实选择。实现最终一致的关键是版本号机制:每份特征数据带一个版本号(或更新时间戳),更新时比较新旧版本,版本大的覆盖版本小的。

版本号机制要落地,还离不开失效通知:当特征源数据更新,不仅写库,还要发一条“该特征已过期”的消息到缓存层,让各节点的本地缓存立刻失效。实践中常用“双删”策略:更新数据库后,先删缓存,延迟几百毫秒再删一次,防止并发读把旧值又塞回去。另外,还需要雪崩防护:给缓存设置不同过期时间(TTL 加随机扰动),避免大量特征在同一秒集体过期导致回源风暴——就像备菜台不要把所有菜安排在同一分钟报废,错峰换菜,后厨才不会瞬间忙乱。归根结底,特征缓存的一致性不是追求“绝对新鲜”,而是在“性能”和“可信”之间守住底线:版本号保证方向对,TTL 保证会收敛,通知机制保证收敛得快。

vlocal<vsource⟹vlocal←vsource\displaystyle v_{\text{local}} < v_{\text{source}} \quad \Longrightarrow \quad v_{\text{local}} \leftarrow v_{\text{source}}
版本号机制的本质是“新数据说了算”——用时间戳或版本号排序,保证收敛方向永远朝最新版本走。
  1. v_local 缓存中的版本备菜台上这份菜的“生产批次号”,写入时为当时的源数据版本。
    vlocal\displaystyle v_{\text{local}}
  2. v_source 数据源的最新版本底料仓库里这份食材的最新批次,源数据每次更新都递增。
    vsource\displaystyle v_{\text{source}}
  3. 比较条件只有当缓存版本落后于源版本时,才触发更新;反过来说明缓存已经是最新,不动作。
    vlocal<vsource\displaystyle v_{\text{local}} < v_{\text{source}}
  4. 覆盖赋值用新版本覆盖旧版本,完成一次“换菜”操作,系统向最终一致迈进一步。
    vlocal:=vsource\displaystyle v_{\text{local}} := v_{\text{source}}

AI 应用场景

推荐系统实时特征服务

阶段:实时特征服务 / 在线推理

解决的问题:推荐系统每秒要处理数万请求,每个请求需要用户的实时兴趣特征、物品热度特征。传统方式每次都从数仓回算聚合特征,延迟高达 50ms 以上,根本无法支撑在线推荐。引入特征缓存后,热门特征直接从备菜台端走,P99 延迟降至 10ms 以内。

具体案例:某短视频推荐平台将“用户近 1 小时点赞视频的类别分布”设为 TTL=60s 的缓存特征,事件驱动在用户新点赞时立即刷新;头部用户的高热度特征由本地缓存 + Redis 多级承接,命中率稳定在 96% 以上。

实时风控反欺诈

阶段:实时推理 / 规则与模型融合

解决的问题:风控决策必须在几十毫秒内完成,黑名单、设备指纹、行为序列等特征更新极快。缓存需要“快”与“新鲜”兼得。通过事件驱动更新 + 版本号机制,黑名单特征秒级生效,设备风险分缓存 5 秒内收敛,同时避免每次请求都重算特征。

具体案例:某支付平台在交易请求链路中设置两级特征缓存:本地缓存承接重复交易特征,Redis 承接跨服务共享的账户风险分。当风控名单更新时,通过消息队列广播失效通知,各节点版本号比对后更新,既保证低延迟又不放过风险订单。

广告点击率预估的冷启动优化

阶段:特征工程 / 在线服务

解决的问题:广告 CTR 模型依赖大量稀疏特征,新广告上线时缓存为空,直接回源计算会拖垮服务。通过主动刷新机制对预估流量高的广告提前预热特征,并在缓存中应用 LFU 策略淘汰长期无人问津的广告特征,让备菜台始终为“当红菜品”服务。

具体案例:某广告平台每天早上根据投放计划,对预计曝光前 10% 的广告提前计算并写入特征缓存;运行中 Redis 采用近似 LFU 策略,周末促销广告热度上升时自动获得更多缓存空间。

Python 演示

python51 行
# -*- coding: utf-8 -*-"""LRU + TTL 特征缓存模拟:备菜台的自管理演示"""import timefrom collections import OrderedDict  class FeatureCache:    def __init__(self, capacity=128, ttl=60):        self.capacity = capacity        self.ttl = ttl        self._cache_map = OrderedDict()  # key -> (value, expire_at)     def _compute_feature(self, key):        """模拟回源计算特征,代价高昂"""        time.sleep(0.005)  # 模拟5ms的DB/聚合开销        return sum(ord(c) for c in key) % 1000     def get(self, key):        """读特征:命中则直出,未命中则现算并放入备菜台"""        now = time.time()        if key in self._cache_map:            value, expire_at = self._cache_map[key]            if now < expire_at:                self._cache_map.move_to_end(key)                return value, True            del self._cache_map[key]        value = self._compute_feature(key)        self._cache_map[key] = (value, now + self.ttl)        self._cache_map.move_to_end(key)        if len(self._cache_map) > self.capacity:            self._cache_map.popitem(last=False)        return value, False  def main():    cache = FeatureCache(capacity=50, ttl=30)    keys = [f"user_{i % 20}" for i in range(2000)]  # 20个热点用户反复请求    hit_count = 0    start = time.time()    for i, key in enumerate(keys):        _, hit = cache.get(key)        hit_count += int(hit)    elapsed = time.time() - start    print(f"请求数: {len(keys)}")    print(f"命中次数: {hit_count}")    print(f"命中率: {hit_count / len(keys) * 100:.1f}%")    print(f"总耗时: {elapsed:.2f}s (仅回源累计耗时 = {(len(keys) - hit_count) * 0.005:.2f}s)")  if __name__ == "__main__":    main()
目的:用 OrderedDict 实现一个带 LRU 淘汰和 TTL 过期的特征缓存,演示“备菜台”如何自动管理空间与新鲜度,并统计命中率。
  1. self._cache_map = OrderedDict()有序字典即“备菜台”,键为特征名,值为数据和过期时间。
  2. if key in self._cache_map:客人点菜先查备菜台,有记录再看是否过期。
  3. self._cache_map.move_to_end(key)每次命中把菜挪到“最新”位置,实现 LRU 的访问序维护。
  4. value = self._compute_feature(key)未命中时回源现算,simulate 5ms 的计算代价。
  5. self._cache_map[key] = (value, now + self.ttl)新菜放上备菜台,并贴上保质期(now + TTL)。
  6. if len(self._cache_map) > self.capacity:备菜台满了要淘汰,否则内存无上限。
  7. self._cache_map.popitem(last=False)从头部弹出最久未使用的菜,这就是 LRU 的“扔菜”动作。
  8. keys = [f"user_{i % 20}" for i in range(2000)]模拟 20 个热点用户的高频访问分布。

常见误区

误区备菜台越大,能放的菜越多,命中率越高,所以内存不设上限、特征全部缓存就对了。

正解缓存容量有边际递减效应。热门特征就那么多,加到一定程度后,新增容量只能捞回极冷门的特征,命中率几乎不再上升,却白白占用宝贵内存。更致命的是,缓存越大,TTL 过期时“集体失效”的面越大,雪崩风险越高。正确做法是根据访问分布统计,定位“容量-命中率”曲线的拐点,用最小的备菜台覆盖最多的热门菜。

为何常见:直觉上“更多缓存 = 更快”,忽略了内存成本和容量与命中率的非线性关系。

误区TTL 越大,菜在备菜台待得越久,命中率越高,所以把 TTL 设成 1 小时甚至 1 天。

正解TTL 是新鲜度与命中率的跷跷板。TTL 越大,数据平均年龄 TTL/2 越老,模型吃到的特征越陈旧。对实时性敏感的特征(如用户当前所在城市、风险名单),陈旧数据会直接导致模型误判。实战中应该按特征的“变化速度”分别设置 TTL:稳定特征用长 TTL,变化快的特征靠事件驱动刷新 + 短 TTL 兜底。

为何常见:只盯着命中率这个单一指标,没把“数据新鲜度”纳入性能评估体系。

误区线上命中率 98%,缓存系统一定很健康,不用再优化了。

正解高命中率可能掩盖两个隐患:一是无效命中——缓存命中了,但数据已过期或与源数据版本不一致,模型吃了一肚子馊菜;二是热key极端倾斜——命中率高只说明少数key被反复直出,一旦这些key集体失效,回源风暴照样击垮系统。务必同时监控“命中率、平均延迟、P99 延迟、陈旧数据比例”四个指标,才能真实评估缓存健康度。

为何常见:把缓存效果简化为单一百分比,忽略了命中质量与极端情况的风险。

面试题

Q1. 特征缓存最核心的收益是什么?

单选题 · easy

  • A.减少模型参数数量
  • B.用内存空间换计算时间← 正确答案
  • C.提升特征数据精度
  • D.简化特征工程流程

答案:B。特征缓存把幂等重计算的结果暂存内存,命中时直接读取,本质是用空间换时间,减少回源计算开销。它不改变参数、精度或流程,只加速“取特征”这一步。

Q2. 平均延迟公式 L = h×L_cache + (1-h)×L_source 中,h 的含义是?

单选题 · easy

  • A.缓存容量大小
  • B.特征维度数量
  • C.缓存命中率← 正确答案
  • D.数据过期时间

答案:C。h 是命中率,表示请求从缓存直接拿到结果的比例。h 越接近 1,整体延迟越接近缓存延迟;h 越接近 0,越接近回源延迟,整个系统被高频重计算拖慢。

Q3. LRU 淘汰策略的“扔菜”依据是什么?

单选题 · mid

  • A.被访问次数最少
  • B.最早放入缓存
  • C.最长时间未被访问← 正确答案
  • D.数据体积最大

答案:C。LRU(最近最久未使用)淘汰的是“最久没被访问”的条目,基于时间局部性:很久没用的菜现在也不太可能被点。LFU 才看访问次数,FIFO 才看放入顺序。

Q4. 某特征每小时更新一次用户画像,业务要求画像变化后 5 分钟内生效,最佳 TTL 策略是?

单选题 · mid

  • A.TTL 设为 24 小时
  • B.TTL 设为 1 秒
  • C.事件驱动刷新 + TTL 300 秒兜底← 正确答案
  • D.完全不设 TTL

答案:C。用户画像变化频率不高但需及时生效,适合“源变更事件触发刷新”主推,TTL=300s 兜底防止漏通知。TTL 太长会馊,太短会白白丢命中率,不设 TTL 会堆积脏数据。

Q5. 特征访问分布高度倾斜(20% 特征占 95% 请求),且热点长期稳定,优先选择哪种淘汰策略?

单选题 · mid

  • A.LRU
  • B.LFU← 正确答案
  • C.FIFO
  • D.随机淘汰

答案:B。LFU 按历史频次淘汰“被点最少”的菜,适合热点长期稳定、访问分布倾斜的场景。LRU 更擅长应对热点突变,FIFO 完全忽略访问模式,随机淘汰不可控。

Q6. 多级缓存(本地 → Redis → 回源)中,第一级本地缓存的主要价值是?

单选题 · mid

  • A.提升数据一致性
  • B.拦截绝大多数请求,降低延迟← 正确答案
  • C.扩大总缓存容量
  • D.替代数据库存储

答案:B。本地缓存离应用最近,延迟最低,价值是“快的那级拦下最多的请求”。它容量小、不共享,无法替代 Redis 的统一缓存或数据库的持久存储,也解决不了一致性问题。

Q7. 以下哪些方法可以帮助保障缓存数据的一致性?

多选题 · mid

  • A.版本号比较,大者覆盖← 正确答案
  • B.数据更新后广播失效通知← 正确答案
  • C.尽量延长 TTL 减少更新
  • D.双删策略防止旧值回填← 正确答案

答案:ABD。版本号机制保证收敛方向正确,失效通知加速旧数据淘汰,双删防止并发读把旧值写回缓存。延长 TTL 恰恰会加剧数据陈旧,与一致性目标背道而驰。

Q8. 关于特征缓存命中率,下列说法正确的是?

多选题 · mid

  • A.命中率 = 命中次数 / 总请求次数← 正确答案
  • B.命中率越高,平均延迟一定越低
  • C.命中率受缓存容量和访问分布影响← 正确答案
  • D.命中率高就代表缓存数据质量高

答案:AC。命中率定义是命中数除以总数;容量和访问分布决定能覆盖多少请求。B 不一定——回源延迟很低时命中率提升收益有限;D 是常见误区,命中率高也可能命中过期脏数据。

Q9. 缓存雪崩的主要成因和常用防御手段是?

单选题 · hard

  • A.单个 key 被高频访问,解决方法是扩容
  • B.大量缓存同时过期集体回源,解决方法是 TTL 加随机扰动← 正确答案
  • C.请求查询不存在的 key,解决方法是布隆过滤器
  • D.缓存数据被篡改,解决方法是加签名

答案:B。雪崩是“大量缓存同时失效导致回源流量打垮后端”,TTL 加随机值让过期时间错峰是标准解法。A 是热 key 问题,C 是缓存穿透问题,D 是安全防护问题,三者场景不同。

Q10. 事件驱动更新特征缓存的完整链路包括哪些环节?

多选题 · hard

  • A.特征源数据变更触发消息← 正确答案
  • B.消息队列广播失效通知← 正确答案
  • C.各节点校验版本号后更新← 正确答案
  • D.模型权重同步刷新

答案:ABC。事件驱动链路是“源变更 → 发消息 → 广播通知 → 各节点版本校验 → 更新或删除缓存”。模型权重属于训练范畴,与特征缓存的事件驱动更新无关。