想象一下学校食堂。学生们排着长队,后厨厨师火力全开地颠勺炒菜,可每一道新菜都要等上好几分钟。某天食堂阿姨灵机一动:把最受欢迎的几道菜提前做好,放进保温窗口,同学们一来就能端走,等待时间从五分钟缩短到五秒钟。这个“熟食窗口”,就是缓存。 AI系统里的故事一模一样。模型推理就像那位大厨,手艺精湛但节奏慢吞吞;用户请求却像下课铃响后涌入食堂的学生,一波接一波。分布式缓存,就是把那些高频使用的数据——用户特征、模型结果、向量Embedding——提前“做好”,放到多个节点上的保温窗口里,让请求直接命中,不必每次都惊动那个慢悠悠的大厨。 但熟食窗口有不少讲究:菜做多了窗口放不下怎么办?菜放久了不新鲜怎么办?后厨改菜谱了,窗口里的旧菜怎么处理?哪个窗口该多备几份?这些问题分别对应着缓存的淘汰策略、过期时间、一致性方案和热点处理。这篇文章,就把这间“AI食堂”的经营之道一次讲透。

岗位关联

对AI从业者而言,缓存是性能优化的第一杠杆。掌握分布式缓存设计,意味着你可以在不动模型的情况下让服务响应速度提升数倍——这是AI系统架构师与后端工程师的核心竞争力所在。

目录
  1. 可视化演示
  2. 缓存基础:熟食窗口的“快”从哪来
  3. 三类缓存:食堂的三个保温窗口
  4. 特征缓存:给每道菜一个正确的牌号
  5. 结果缓存:识别“同一个问题”的智慧
  6. Embedding缓存:预制菜仓库的生意经
  7. 一致性:菜谱改了,旧菜怎么办
  8. 热点调优:爆款菜让队伍排到门口
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示分布式缓存在AI系统中的架构设计、特征/Embedding/结果三类缓存对比,以及缓存命中率与延迟优化趋势。

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

分布式缓存架构 App→Proxy→Nodes Hit: 缓存命中 DB: 回源数据库 一致性哈希 数据分片到多节点并行处理提升吞吐

缓存基础:熟食窗口的“快”从哪来

走进食堂,你会发现最热闹的不是后厨,而是那排熟食保温窗口。阿姨提前把宫保鸡丁、糖醋排骨做好,学生来了端走就走,根本不用等着下锅。这个窗口省下的时间,来自一个朴素的观察:一百个学生里,九十个点的都是那几道热门菜。把热门菜提前做好,大多数人就不用等——这个观察,在计算机里叫数据局部性(Data Locality)。

AI系统同样如此。用户请求不是均匀散布的:推荐系统里头部内容贡献了绝大多数流量,大模型服务里翻来覆去是那几句常见问法。如果我们把高频数据——某个热门特征、某条重复的问题、某个常用Embedding——提前存到一块快速存储区,请求到达时先查这块区域,命中就直接返回,未命中才往后端跑。这块快速存储区,就是缓存。

缓存的效果不是玄学,而是可以量化的。把能从缓存直接拿到结果的请求叫做“命中”,把没拿到、必须去后端计算的请求叫做“未命中”。命中数占总请求数的比例就是缓存命中率(Hit Rate),它是评估缓存设计好坏的第一指标,也是所有后续策略优化的出发点。命中率越高,系统中“秒级取餐”的比例就越大。

理解了命中率,就理解了缓存的一切——它本质上是一个概率问题:我们的“备菜”能不能命中“大多数人的需求”。

HitRate=HitsHits+Misses\displaystyle \text{HitRate} = \frac{\text{Hits}}{\text{Hits} + \text{Misses}}
命中率是缓存世界的“点单命中统计”:窗口里的现成菜被端走的比例越高,整个食堂的吞吐就越大。
  1. 分子 Hits(命中次数)学生直接从熟食窗口端走现成菜的次数,对应请求在缓存中直接拿到结果,没有惊动后端。
    Hits\text{Hits}
  2. 分母 Hits + Misses(总请求数)命中加未命中,就是所有来到系统的请求总量,无论走的是缓存还是后端。
    Hits+Misses\text{Hits} + \text{Misses}
  3. 比值命中率现成菜被直接端走的比例。比例越高,说明阿姨的“备菜”越准,系统整体等待时间越短。
    HitsHits+Misses\frac{\text{Hits}}{\text{Hits} + \text{Misses}}

三类缓存:食堂的三个保温窗口

食堂不会只开一个窗口。有的窗口卖做好的热菜,学生来了直接端;有的窗口卖洗净切好的净菜,厨师拿回去炒一下就行;还有的窗口放标准化酱料包,哪个窗口缺了随时补。三种窗口备货逻辑不同,但目标一致:让学生少等。

AI服务里也有三个对应的“窗口”:

  • 特征缓存:存用户画像、物品属性等做模型输入的特征值。同一用户反复请求时,不用每次重新算特征。
  • 结果缓存:直接存模型对某个输入的最终输出。相同或相似请求再次出现时,整个推理过程都可以跳过。
  • Embedding缓存:存模型把ID映射成的稠密向量。这些向量在推荐、搜索、RAG中被反复使用,重算极其浪费。

选择哪类缓存,取决于“什么东西可以被复用”。特征缓存的复用单位是“实体”——同一个用户或物品的多次请求共用特征;结果缓存的复用单位是“请求”——相同或语义相同的输入,输出可以直接复用;Embedding缓存的复用单位是“实体参与的计算”——向量在多个环节被重复读取。用平均延迟公式可以统一评估三类缓存的价值:命中率提升带来的延迟下降,是否值得那部分内存成本。

三个窗口各有各的菜,但都遵循同一笔账:多备一份菜,少等一个学生,值不值?平均延迟公式就是那本账本。

Tavg=h×Tcache+(1−h)×Tbackend\displaystyle T_{\text{avg}} = h \times T_{\text{cache}} + (1 - h) \times T_{\text{backend}}
平均延迟 = 命中概率 × 缓存速度 + 未命中概率 × 后端速度。这个式子揭示了缓存的价值:命中率越高,整体响应越快。
  1. T_cache(缓存延迟)从窗口端菜的时间,通常只有几毫秒。
    TcacheT_{\text{cache}}
  2. T_backend(后端延迟)等厨师现炒的时间,模型推理通常要几十到几百毫秒。
    TbackendT_{\text{backend}}
  3. h(命中率)请求命中缓存的比例,就是上一节算出的命中率。
    hh
  4. 加权求和平均等待时间等于两种情况的等待时间按命中率加权。命中的请求越多,整体等待越接近“端菜”而非“炒菜”。
    h×Tcache+(1−h)×Tbackendh \times T_{\text{cache}} + (1 - h) \times T_{\text{backend}}

特征缓存:给每道菜一个正确的牌号

熟食窗口最怕什么?怕菜做出来却没挂牌子,或者牌子挂错。学生看到“鱼香肉丝”的牌子,端走的却是宫保鸡丁,下次就不敢来了。特征缓存也一样,键(Key)就是牌子:牌子挂得稳、挂得唯一,请求才能准确找到要的数据。

特征缓存以 KV 形式存储,键通常由“实体ID + 特征名 + 版本号”组成。比如 user:12345:age_group:v3 表示用户 12345 的年龄分组特征,版本是 v3。设计键的最大原则是“稳定重现”:同一次查询,无论什么时候发起,拼出来的键必须完全一致。用户ID拼错一个空格、特征名大小写不一致,都会导致缓存永远命中不了。

工程上有几个常见的键设计套路:

  • 统一格式:实体类型:实体ID:特征名:版本号,冒号分隔,便于排查与统计。
  • 对长键做 Hash 压缩,减少内存占用。
  • 特征版本号写进键里,模型升级后自动改用新键,天然做到新老缓存隔离,不用手动清空。

牌子挂对了,学生才能准确地把菜端走;键设计对了,特征缓存才能真正发挥“按需取用”的威力。

Key=Hash(user_id  ∥  feature_name  ∥  version)\displaystyle \text{Key} = \text{Hash}(\text{user\_id} \;\|\; \text{feature\_name} \;\|\; \text{version})
缓存键 = 学号 + 菜名 + 菜谱版本号,三者拼成唯一字符串,再哈希成定长索引。
  1. user_id(实体ID)实体的唯一标识,类似于学生的学号或者商品的条码。
    user_id\text{user\_id}
  2. feature_name(特征名)要取的特征名称,比如“年龄分段”“商品类目”。
    feature_name\text{feature\_name}
  3. 拼接符 $\|$把各段字符串连成一个整体,如 user:123:age_group:v3,保证键的唯一性。
    user_id  ∥  feature_name  ∥  version\text{user\_id} \;\|\; \text{feature\_name} \;\|\; \text{version}
  4. Hash 压缩把长字符串映射成固定长度的数字,便于在缓存集群中定位到具体节点,同时控制键的内存开销。
    Hash(⋯ )\text{Hash}(\cdots)

结果缓存:识别“同一个问题”的智慧

食堂阿姨是半个语言学家。学生说“宫保鸡丁盖饭”和“宫保鸡丁饭,不要辣”,她能判断出这是同一道菜,配方早就准备好,直接盛饭就行。结果缓存的难点也在这里:如何识别“字面不同、语义相同”的请求。

朴素的缓存拿原始文本做等值匹配,只有完全相同的字符串才能命中。但真实用户的问题千变万化:“报销流程是什么”“怎么报销”“报销步骤有哪些”——同一个意图,三种说法。于是需要用语义哈希(Semantic Hashing):把文本编码成向量,再比较向量间的相似度。

典型流程:

1. 用轻量编码器把请求文本转成向量。

2. 用余弦相似度与缓存池中的已有向量比对。

3. 相似度超过阈值(如 0.95)判定为重复请求,直接返回缓存中的历史回答。

4. 同时缓存“无答案”之类的空结果,防止无效请求反复穿透到模型,浪费算力。

阿姨听的是“话里的需求”,不是“字面上的句子”。结果缓存的价值,就在于理解深层语义,把同一道菜的千万种说法归到同一个牌号下。

sim(q⃗1,q⃗2)=q⃗1⋅q⃗2∣q⃗1∣×∣q⃗2∣\displaystyle \text{sim}(\vec{q}_1, \vec{q}_2) = \frac{\vec{q}_1 \cdot \vec{q}_2}{|\vec{q}_1| \times |\vec{q}_2|}
余弦相似度衡量两个请求“口味配方”的重合程度,数值越接近 1,说明它们越是同一道菜。
  1. 向量 q1、q2两个请求文本各自的语义向量,由编码器生成的高维数字列表。
    q⃗1,q⃗2\vec{q}_1, \vec{q}_2
  2. 点积 q1·q2两向量逐维相乘再求和,数值越大表示两个向量的方向越接近。
    q⃗1⋅q⃗2\vec{q}_1 \cdot \vec{q}_2
  3. 模长乘积分别求两个向量的长度再相乘,用来归一化,消除文本长短的影响。
    ∣q⃗1∣×∣q⃗2∣|\vec{q}_1| \times |\vec{q}_2|
  4. 余弦值范围在 -1 到 1 之间,越接近 1 表示语义越相近。0.95 以上通常可视为同一个问题。
    q⃗1⋅q⃗2∣q⃗1∣×∣q⃗2∣\frac{\vec{q}_1 \cdot \vec{q}_2}{|\vec{q}_1| \times |\vec{q}_2|}

Embedding缓存:预制菜仓库的生意经

食堂有个后仓,整箱整箱地堆着标准化净菜包:每包 500 克,按固定配方封装,厨师拆开就能下锅。虽然占地方,但随取随用,省下的备菜时间非常可观。Embedding 缓存就是 AI 系统里的那个后仓。

Embedding 是模型把离散的 ID(用户ID、商品ID、文本块ID)映射成的稠密向量,常听到的 128 维、768 维就是指它。推荐系统里每个用户和商品都有向量,RAG 里每个知识块也有向量。这些向量在每次请求中都要被读取参与计算,如果每次都重新生成,成本极高;缓存下来,等于把“现做”变成“拆包”。

Embedding 缓存的特殊之处在于数据量大、访问模式固定,设计上有几个关键点:

  • 内存采用连续行存储,保证批量读取时的空间局部性。
  • 常用 int8 量化压缩,省内存带宽,精度损失可接受。
  • 配合 ANN(近似最近邻)只检索需要的向量子集,避免全量扫描。
  • 服务启动时把头部实体的向量批量预热,避免冷启动穿透。

预制菜仓库虽贵,但拆包即炒的快感让食堂阿姨们趋之若鹜。Embedding 缓存的内存成本,换回的是计算和延迟的双重节省。

Memory=N×D×410243 GB\displaystyle \text{Memory} = \frac{N \times D \times 4}{1024^3} \ \text{GB}
内存估算公式:缓存条数 × 维度 × 每维字节数,再除以 1024³ 换算成 GB。
  1. N(缓存条数)需要缓存的实体数量,比如 1 亿用户。
    NN
  2. D(维度)每条 Embedding 的维度数,常见 128、768 等。
    DD
  3. 乘以 4每个 float32 数值占 4 字节,这是内存计算的基本单位。
    N×D×4N \times D \times 4
  4. 除以 1024³把字节换算成 GB。例如 1 亿条 128 维向量约需 47.7 GiB,所以容量规划必须先算清这笔账。
    N×D×410243\frac{N \times D \times 4}{1024^3}

一致性:菜谱改了,旧菜怎么办

后厨突然推出新配方:宫保鸡丁改版了,减油减辣。但熟食窗口还摆着旧配方做的菜。阿姨心里清楚:卖,学生吃到的就是过期口味;不卖,这几个窗口就浪费了。缓存一致性,就是讨论“旧菜该怎么处理”的学问。

问题出在缓存里的数据和数据源(数据库/模型)不同步了。两类经典策略:

  • 写穿透(Write Through):数据变更时同步更新数据库和缓存,保证强一致;代价是每次写操作都要两处落盘,写放大明显。
  • 失效广播(Cache Aside):数据变更时通知各缓存节点删除对应键,下次请求再重新计算回填;实现简单,读多写少场景效率高。

工业界主流的做法是“过期时间兜底 + 版本号校验”。缓存数据附带一个版本号,读取时拿它和数据源的当前版本比较:如果缓存版本不落后,直接用;落后了,判定失效并重新计算。分布式场景下,通过消息队列把失效事件广播给所有缓存节点,达到最终一致——几毫秒内旧版本被清出,用户几乎无感知。

广播一响,各窗口同步撤旧菜、再上新菜。版本号让每一盘菜都清楚自己是哪个时代的产物,这就是最终一致的优雅之处。

Δv=vlatest−vcache(Δv≤0⇒valid)\displaystyle \Delta v = v_{\text{latest}} - v_{\text{cache}} \quad (\Delta v \le 0 \Rightarrow \text{valid})
版本号就是后厨的“出品批次号”。缓存批次不早于最新批次,菜就可以继续卖;早于,就得撤掉重做。
  1. v_cache(缓存版本号)缓存这份数据的版本标识,比如"V3"。
    vcachev_{\text{cache}}
  2. v_latest(数据源版本号)数据源当前最新的版本标识,比如"V5"。
    vlatestv_{\text{latest}}
  3. 做差 Δv用最新版本减去缓存版本,判断缓存落后了多少。
    Δv=vlatest−vcache\Delta v = v_{\text{latest}} - v_{\text{cache}}
  4. Δv ≤ 0 有效缓存版本不落后于最新版本,可以直接使用;反之则过期,需要重新计算并回填。
    Δv≤0\Delta v \le 0

热点调优:爆款菜让队伍排到门口

期末考试前,糖醋排骨突然成为全食堂的爆款。问题是:所有的糖醋排骨都放在同一个保温台,几百个学生全挤那一个窗口,其他人只能干着急。这个“一个窗口承载全校食欲”的场景,就是分布式缓存里的热点问题。

分布式缓存把数据分散在很多节点上,一个 key 正常情况下只落在某一个节点。当某个 key 成为热点——爆款商品的 Embedding、热门新闻的特征——大量请求会同时打向同一个节点,造成节点过载、延迟飙升,而其他节点却闲着。这就是“热点倾斜”。

应对热点有三板斧:

1. 热点识别:统计 key 的访问频率和增速,提前发现可能爆发的热点。

2. 本地缓存:在应用进程内加一层小型缓存,把热点请求直接挡在进程内,不再经过分布式缓存节点。

3. 副本扩容:把热点 key 复制出多个副本分布到不同节点,把流量摊开。

最后是监控与调优指标:命中率、平均延迟、p99 延迟、节点 CPU、淘汰量、热点 Top-K。容量规划遵循“内存换时间”原则,一般给总数据量的 20%-40% 分配缓存内存,优先覆盖头部热数据。

爆款菜多开几个窗口、每个窗口备一份,队伍自然就消化了。热点处理的核心不是把菜做多,而是让“窗口”变多。

QPSmax≈1Tavg\displaystyle \text{QPS}_{\text{max}} \approx \frac{1}{T_{\text{avg}}}
吞吐量上限是平均延迟的倒数。平均延迟降下来,单位时间能处理的请求自然就上去了。
  1. QPS(每秒查询数)系统每秒能处理的请求个数,相当于食堂每分钟能接待的学生数量。
    QPSmax\text{QPS}_{\text{max}}
  2. T_avg(平均延迟)每个请求的平均耗时,就是第 2 节算出的加权延迟。
    TavgT_{\text{avg}}
  3. 取倒数单个请求越省时,单位时间能服务的请求就越多。缓存把 T_avg 从 300ms 压到 50ms,QPS 立刻提升 6 倍。
    1Tavg\frac{1}{T_{\text{avg}}}

AI 应用场景

RAG知识库问答的结果缓存

阶段:模型推理与服务部署

解决的问题:RAG 服务中用户 query 的重复度高,每个新问题都要先做文档检索再做文本生成,耗时 2-5 秒。传统关键词缓存无法识别语义近似的问题,稍有改写就失配,导致大量重复计算。用语义哈希将 query 编码成向量,相似度超过阈值直接复用历史回答,命中时延迟从 3 秒降到 50 毫秒。

具体案例:某企业知识库机器人,早高峰 5000 个 query 中约 30% 是同一问题的不同问法(“报销流程是什么”“怎么报销”“报销步骤有哪些”)。接入语义结果缓存后,模型实际调用减少 35%,平均响应时间下降 60%,后端 GPU 成本也同步缩减。

推荐系统的特征与 Embedding 缓存

阶段:特征工程与在线推理

解决的问题:推荐服务每个请求要读取数百个特征和向量,全量查数据库或重新计算耗时严重。传统缓存按原始 ID 等值存储,热点 ID 容易拥塞单节点。采用两级缓存:特征缓存存用户画像 KV,Embedding 缓存存向量并做 int8 量化压缩,配合 LRU 淘汰和本地缓存兜底热点。

具体案例:某电商推荐场景,将 Top 10% 热门商品的 Embedding 在服务启动时批量预热到本地内存,命中率从 78% 提升到 94%,p99 延迟从 180ms 降至 90ms,大促期间缓存节点 CPU 峰值也明显回落。

Python 演示

python76 行
import timefrom collections import OrderedDict  class TTLCache:    """带过期时间和LRU淘汰的缓存,模拟AI推理结果缓存。"""     def __init__(self, capacity=1024, ttl=60):        self.capacity = capacity        self.ttl = ttl        self._cache = OrderedDict()  # key -> (value, expire_at)        self._hit_count = 0        self._miss_count = 0     def get(self, key):        # 读取缓存:命中则刷新LRU顺序,未命中返回None        now = time.time()        if key not in self._cache:            self._miss_count += 1            return None        value, expire_at = self._cache[key]        if now > expire_at:            # 过期:删除并视为未命中            del self._cache[key]            self._miss_count += 1            return None        # LRU: 先删除再插入,让它在有序字典中排到末尾        del self._cache[key]        self._cache[key] = (value, expire_at)        self._hit_count += 1        return value     def set(self, key, value):        # 写入缓存:容量满时淘汰最久未用的项        now = time.time()        if key in self._cache:            del self._cache[key]        self._cache[key] = (value, now + self.ttl)        if len(self._cache) > self.capacity:            self._cache.popitem(last=False)     @property    def hit_rate(self):        total = self._hit_count + self._miss_count        return self._hit_count / total if total else 0.0  def slow_predict(user_id, query):    # 模拟耗时的AI推理,比如大模型生成回答    time.sleep(0.5)    return f"回答[{user_id}]: {query} 的推理结果"  def main():    cache = TTLCache(capacity=3, ttl=5)    requests = [        ("u1", "什么是缓存"),        ("u2", "机器学习"),        ("u1", "什么是缓存"),        ("u3", "深度学习"),        ("u1", "什么是缓存"),    ]    for uid, q in requests:        key = f"{uid}:{q}"        result = cache.get(key)        if result is None:            result = slow_predict(uid, q)            cache.set(key, result)            print(f"[未命中] {key} -> {result}")        else:            print(f"[命中] {key} -> {result[:12]}...")    print(f"命中率: {cache.hit_rate:.1%}")  if __name__ == "__main__":    main()
目的:实现了一个带 TTL 过期和 LRU 淘汰的结果缓存,模拟 AI 推理服务中重复请求直接命中缓存、新请求才触发推理的过程。
  1. from collections import OrderedDictLRU 需要有序字典记录键的访问先后顺序。
  2. self._cache = OrderedDict()缓存主体,既能做 LRU 淘汰,又能配合时间戳做 TTL 过期检查。
  3. if now > expire_at:判断持久层里这个灵魂拷问:这盘“预制菜”还新不新鲜?
  4. self._cache.popitem(last=False)容量满时弹出最久未用的键,对应食堂撤掉最没人吃的菜。
  5. slow_predict()模拟耗时 0.5 秒的 AI 推理,对应“后厨现炒”。
  6. cache.set(key, result)计算完写回缓存,下一次同样的请求就能直接命中。

常见误区

误区只要服务器内存够大,把数据库里所有数据都缓存起来,命中率不就 100% 了吗?

正解缓存成本不只是内存空间,还有预热时间、一致性维护复杂度。全量缓存意味着每次数据变更都要同步海量 key,淘汰策略也失去意义。工业界通常只缓存高频的头部数据——20% 的热数据覆盖 80% 的请求——并配合精心设计的 TTL 和淘汰策略。食堂若把未来一个月的菜都提前做好,只会全部馊掉。

为何常见:直觉上“全部放进缓存 = 全部命中”,却忽略了缓存与数据源同步的代价和时效性问题。

误区设置一个合理的全局过期时间(比如 300 秒),所有 key 都按这个时间过期,省心省事。

正解不同数据的变更频率差异巨大:模型版本升级一次全量结果作废,用户年龄特征一个月才变一次,商品销量却每秒在变。合理做法是按数据维度设置差异化 TTL,甚至用版本号显式控制失效。食堂的热菜和冷冻肉品的保质期不可能一样。

为何常见:图省事的惰性思维,没有区分数据的时效性特征。

误区只要把命中率做到 99% 以上,系统性能问题就彻底解决了。

正解命中率高不代表整体延迟低——如果缓存本身成为瓶颈(缓存读要序列化、锁竞争、网络广播),或热点集中在单个节点导致倾斜,p99 可能依然很高。缓存是手段而非目的,最终要盯的是平均延迟、p99 和吞吐量。高命中率应以收益覆盖成本为前提。

为何常见:混淆了中间指标和目标指标,命中率只是过程指标而非最终体验。

误区缓存里的数据和数据库必须分毫不差,否则就是系统 bug。

正解分布式系统中强一致缓存的成本极高——每次写入都要同步所有节点——绝大多数场景接受最终一致:允许短暂的旧数据,几毫秒内通过失效广播或版本号拉齐。像推荐特征、模型结果这类允许新鲜度延迟的数据,最终一致利远大于弊。只有支付风控等极少数场景,才需要写穿透式的强一致。

为何常见:拿事务型数据库的一致性预期套用到缓存上,没有意识到这是两种不同量级的代价。

面试题

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

单选题 · easy

  • A.命中率越高缓存越没价值
  • B.命中率等于命中数占比← 正确答案
  • C.命中率只与容量有关
  • D.命中率越低系统越快

答案:B。命中率 = 命中次数 / 总请求数,B 正确。A 说反了,命中率高正说明缓存在发挥价值;C 忽略了访问模式和 TTL 等影响因素;D 与性能优化目标完全相反。

Q2. LRU 淘汰策略的核心思想是什么?

单选题 · easy

  • A.淘汰过期最早的键
  • B.淘汰最久未访问的键← 正确答案
  • C.淘汰容量最大的键
  • D.随机淘汰任意键

答案:B。LRU(Least Recently Used)假设“最近没用的以后大概率还用不上”,所以优先淘汰最久未被访问的键。A 是 TTL 过期的逻辑,C 和 D 都不是 LRU 的规则。

Q3. 下列哪类缓存直接保存模型对输入的最终输出?

单选题 · easy

  • A.特征特征向量缓存
  • B.模型生成结果缓存← 正确答案
  • C.词向量Embedding缓存
  • D.进程内本地缓存

答案:B。结果缓存直接复用模型的最终输出,相同或相似请求来了直接返回。特征缓存存的是模型输入特征,Embedding 缓存存的是向量,本地缓存是一个部署形态而非按内容分类。

Q4. 语义哈希在结果缓存中的主要作用是什么?

单选题 · mid

  • A.压缩缓存存储体积
  • B.识别语义重复请求← 正确答案
  • C.加密保护用户数据
  • D.加速网络传输速度

答案:B。语义哈希把文本编码成向量并计算相似度,目的是识别“报销流程是什么”和“怎么报销”这类字面不同、语义相同的请求。A、C、D 都与语义匹配无关。

Q5. 缓存写穿透(Write Through)策略的含义是?

单选题 · mid

  • A.只写缓存不写数据库
  • B.同步更新数据库和缓存← 正确答案
  • C.每次请求绕过缓存
  • D.只写数据库清除缓存

答案:B。写穿透在数据变更时同步更新数据库和缓存,保证两者一致,但写放大明显。A 是只写缓存,数据会丢;C 是绕缓存;D 是 Cache Aside 中的失效思路,都不属于写穿透。

Q6. 缓存一致性中,版本号机制的核心思想是?

单选题 · mid

  • A.用随机数标记缓存数据
  • B.比较版本号判断是否过期← 正确答案
  • C.每次请求都重算模型
  • D.用时间戳代替所有缓存

答案:B。版本号机制给缓存数据和数据源各标一个版本,读取时比较:缓存版本落后就判定过期,重新计算。A 的随机数无法比较新旧;C 取消了缓存的意义;D 夸大了时间戳的用途。

Q7. 缓存 1 亿条 128 维 float32 向量,内存占用约为多少?

单选题 · hard

  • A.约 0.5 GB
  • B.约 4.8 GB
  • C.约 51.2 GB← 正确答案
  • D.约 512 MB

答案:C。按公式 N×D×4 字节计算,1 亿 × 128 × 4 = 512 亿字节 ≈ 47.7 GiB,约等于 51.2 GB(十进制)。A 和 D 差了 100 倍,B 是按 1 千万条估算的结果。

Q8. 下列哪些做法能有效提升缓存命中率?

多选题 · mid

  • A.合理设置TTL过期策略← 正确答案
  • B.热点key配置本地缓存← 正确答案
  • C.缓存所有特征不加限制
  • D.按访问频率设计淘汰← 正确答案

答案:ABD。合理 TTL 让合适的数据存活更久;本地缓存兜底热点 key 减少穿透;按访问频率淘汰(如 LRU)保证留下来的都是高频数据。C 不加限制地全量缓存会带来一致性和内存失控,反而伤害系统。

Q9. 关于分布式缓存的热点问题,正确的说法有?

多选题 · hard

  • A.热点key集中在单节点← 正确答案
  • B.增加副本可缓解热点← 正确答案
  • C.热点后可部署多级缓存← 正确答案
  • D.热点与缓存容量无关

答案:ABC。热点 key 只落在某个节点的确会导致单点倾斜;增加副本能把流量分摊到多个节点;本地缓存 + 分布式缓存的多级结构也能缓解热点。D 错误,热点与容量有关——容量越紧,淘汰越频繁,越容易引发对同一批 key 的重复穿透。

Q10. 语义哈希做缓存判重时,为降低误判可以采取哪些手段?

多选题 · extreme

  • A.余弦相似度超过阈值判定重复← 正确答案
  • B.多哈希桶投票降低碰撞率← 正确答案
  • C.存储原始文本二次确认← 正确答案
  • D.直接用哈希结果不确认

答案:ABC。用余弦相似度阈值、多哈希桶投票、原始文本二次确认都能显著降低“把不同问题当成同一问题”的误判。D 直接使用哈希结果不做任何校验,误判率最高,尤其在长尾问题上容易张冠李戴。