资讯动态

AI推理缓存淘汰:LRU与LFU六种组合策略实战解析

发布时间:2026/10/17 16:19:42 来源:尧图企业网站定制
做架构的人都清楚缓存淘汰算法是一个“看起来简单、落地上头”的经典难题。LRU和LFU两个名字谁都能说出来可一旦放到大规模AI推理场景里大部分人对策略的认知就只剩“最近没用过就淘汰”和“用得少就淘汰”这两句话了。我今年在几个推理服务项目上做过一轮集中优化把缓存命中率从一个不太好看的数字拉到能稳定省下大半算力的水平。这篇就详细拆一下架构师手头的6种LRULFU组合策略附上可跑的Python实现和我在实际部署中踩过的坑。这次优化的背景是典型的LLM推理服务存在大量共享前缀的Prompt请求和嵌入向量检索。有些请求到服务端之后一大部分计算是可以直接复用缓存结果的但前提是缓存那一层能精准留住“真正值得留”的数据。传统Web缓存那套按小时过期、随机驱逐的逻辑在这里完全不适用。缓存的代价不对称性决定了我们必须精细到“淘汰谁”这个粒度来做决策因为错杀一次热点请求背后可能多付出一次完整前向推理的代价。这篇文章不会讲那些教科书式的定义我直接用工程视角把策略选型、实现细节、部署避坑一次说清楚。1. 先想清楚AI推理场景的缓存和Web缓存哪里不一样很多做后端出身的朋友上手就会拿Redis和CDN的经验来套AI推理缓存结果实测总是不对劲。问题不在于缓存框架本身而在于场景的访问特征和重算代价完全不同。1.1 请求分布的三个特征大规模AI推理服务的请求分布我这里总结了三个关键特征它们是所有策略选择的底层依据。第一是高幂律倾斜。线上数据统计下来往往是少数几十个Prompt模板贡献了80%以上的重复访问量比如某个智能客服的system prompt、某个爆款应用的任务模板。这些Key数量极少但访问频率极高。第二是临时热点层出不穷。每隔一段时间必然出现一批新的瞬时热点比如某项新功能灰度上线、某个话题在社会上突然爆火大量相似请求在几分钟内涌进来。临时热点要求缓存能快速识别并吸收这一波流量而不是等它凉了才反应过来。第三是长尾访问无规律。总有大量几乎不会被重复访问的Prompt或嵌入向量它们每次出现都像“一次性访客”占用内存空间拉低缓存效率还会干扰淘汰判断。这三类请求混杂在一起单纯靠“时间最近”或“次数最多”单一维度去淘汰必然顾此失彼。1.2 重算代价远高于存储代价传统Web缓存里一次缓存未命中的代价可能只是查一次数据库慢则几十毫秒。但在AI推理场景一次未命中的代价可能是跑完整个前向网络。尤其是对上下文长度比较长的LLM推理如果能在前缀处命中KV Cache不仅省去重复的预填充计算还能明显降低首个token的延迟。如果缓存落空意味着大量的矩阵乘法要重复执行耗时从几百毫秒到几秒不等。这就是我反复强调的代价不对称存储一份缓存值的空间成本是相对固定的但不同Key的重算成本可以相差几十倍。例如一个长文档生成的中间结果重算可能要几秒钟一些简单系统的短问题重算也就几十毫秒。如果淘汰策略在选择驱逐对象时无视这种差异那架构师就还没有真正理解AI推理缓存的核心矛盾。1.3 架构师真正需要权衡的三个维度基于上面的特征我对一次缓存淘汰策略的评判只用三个维度命中率单位时间内缓存命中的请求数占全部请求的比例这项最直观。抖动率热点数据被错误驱逐并很快再次请求的频率抖动率高意味着长尾延迟变大。蒸发率刚写入缓存但在很短时间后就不再访问的数据占用的内存比例太高说明缓存污染严重。在任何优化开始之前我会先测量这三种指标。因为策略的调整本质上就是在三个指标之间做权衡不存在一个万能策略同时把三个优化到极致。2. 六种策略的核心思路与选型对比这里说的六种策略既有经典的LRU和LFU也有在实际推理服务上验证有效的混合形态。它们之间不是纯替代关系而是各自侧重点不同。在做选型之前我先把它们的定位和适用场景盘清楚免得进了实现环节才来回推翻。2.1 六种策略一览策略核心信号本质思路最适用的场景实现复杂度经典LRU访问时间最近被访问过的优先保留访问模式相对均匀、临时热点多的服务低时间窗口LFU频率时间窗统计窗口内的访问次数次数高者留下幂律分布明显、热点持续期的服务中LRU-K最近K次访问时间不足K次的不算热点防一次性访问干扰存在大量一次性访问的长尾场景中高分段LRU访问时间分区冷热区域隔离保护真正的热点临时热点频繁涌入的场景中ARC时间频率自适应动态调节两个缓存区容量占比访问模式经常变化的混合场景高加权综合策略时间×频率组合分重算成本、新鲜度、频次一起打分重算代价差异极大的场景高这张表的意义在于选型的时候首先要回答一个问题你的场景主要矛盾是缓存污染还是热点抖动如果是污染就去用带历史门槛的LRU-K如果是热点识别太慢考虑分段LRU或ARC。不敢说百分之百准确但按这个思路选至少方向不会跑偏。2.2 选型背后的两个关键判断我在做实际选型时会先做两个前置判断。判断一是“重算代价是否差异巨大”。如果所有Key的重算代价差不多比如都是固定长度的嵌入计算那经典LRU其实够用。如果重算代价差异很大必须引入加权综合策略给不同Key赋予不同的重算成本权重不能一视同仁地淘汰。比如长文档缓存的重算成本是短问题的10倍那它应该被更宽容地对待。判断二是“热点是否具备可预测的持续性”。例如某个Agent应用固定跑一套多轮对话逻辑那它的Prompt前缀热度可以持续很久用LFU或加权策略更合适。如果是一次性的A/B测试流量热度只能持续几分钟那用前沿注意力更强的LRU会更灵活。实际情况下我会建议架构师不要看到“LFU”就认为它一定比“LRU”高级二者的关系更像是对偶的要实现同一个目标往往需要取长补短。3. 六种策略的代码实现与演进代码部分是这次分享的重头戏。我在实际工程里用Python做原型验证再迁移到对应的生产语言。这里给出的是一套可以运行的简单版本但把核心机制都体现了。后面我会按策略逐个讲关键实现和设计意图。3.1 基础LRU双向链表和哈希表的老组合LRU的实现方案很多但性能最优的仍然是哈希表加双向链表。哈希表保证O(1)查找双向链表保证O(1)插入和删除。加个哑结点可以省去大量判空逻辑。class DLinkedNode: __slots__ (key, value, prev, next) def __init__(self, keyNone, valueNone): self.key key self.value value self.prev None self.next None class LRUCache: def __init__(self, capacity: int): self.capacity capacity self.cache {} self.head DLinkedNode() self.tail DLinkedNode() self.head.next self.tail self.tail.prev self.head def _add_node(self, node): node.prev self.head node.next self.head.next self.head.next.prev node self.head.next node def _remove_node(self, node): node.prev.next node.next node.next.prev node.prev def _move_to_head(self, node): self._remove_node(node) self._add_node(node) def get(self, key): if key not in self.cache: return -1 node self.cache[key] self._move_to_head(node) return node.value def put(self, key, value): if key in self.cache: node self.cache[key] node.value value self._move_to_head(node) else: node DLinkedNode(key, value) self.cache[key] node self._add_node(node) if len(self.cache) self.capacity: last self.tail.prev self._remove_node(last) del self.cache[last.key]设计意图上_move_to_head把命中节点放到最前面保证链表尾部一定是“最久未访问”的节点淘汰时直接取tail.prev即可。这套实现做了三处优化建议用__slots__限制属性能省不少内存尤其是缓存条目多到百万级时效果很明显。命中时不重建节点只做链表指针交换避免频繁分配对象导致GC压力。缓存满了以后淘汰操作严格做到O(1)全程没有循环查找。3.2 时间窗口LFU统计频率的正确姿势经典LFU单独用有个大问题以前的历史访问次数会一直堆积导致一个Key仅仅因为在早期访问过很多次就能永久霸占缓存位即使它已经很久没被请求了。这在大模型场景下尤其不可接受因为一个历史热点可能已经失效却还在占据宝贵的KV Cache空间。所以我做LFU几乎都用时间窗口版只统计最近一段时间内的访问次数实现上用最小堆维护访问次数最低的节点。窗口可以用时间滑动的思路也可以用周期重置的思路下面的实现采用每个周期内衰减的简化版本。import heapq import time class TimedLFUCache: def __init__(self, capacity: int, time_window: float 60.0): self.capacity capacity self.time_window time_window self.cache {} # key - (value, freq, last_time) self.heap [] # (freq, last_time, key) self._clock 0.0 def get(self, key): entry self.cache.get(key) if entry is None: return -1 value, freq, last entry self._clock 1 # 移除旧的堆条目再推入新的模拟更新频率 freq 1 new_entry (value, freq, self._clock) self.cache[key] new_entry heapq.heappush(self.heap, (freq, self._clock, key)) return value def put(self, key, value): if key in self.cache: _, freq, _ self.cache[key] self._clock 1 self.cache[key] (value, freq 1, self._clock) heapq.heappush(self.heap, (freq 1, self._clock, key)) else: if len(self.cache) self.capacity: self._evict() self._clock 1 self.cache[key] (value, 1, self._clock) heapq.heappush(self.heap, (1, self._clock, key)) def _evict(self): while self.heap: freq, ts, key heapq.heappop(self.heap) entry self.cache.get(key) if entry is None: continue # 如果堆里的freq和cache里不一样说明是陈旧条目 value, cur_freq, cur_ts entry if cur_freq ! freq or cur_ts ! ts: continue del self.cache[key] return实现里一个重要技巧是堆条目可能过期因为节点在put和get时会把新的频率和时间戳重新压入堆老条目依然留在堆里。所以淘汰时要做一致性校验避免把已经被更新过的Key误淘汰。这里也用到了惰性删除堆中的陈旧条目到真正弹出时才被识别并跳过省了一层维护成本。不过这个简化版没有处理时间窗口滑动实际生产里我会做成分段时钟比如60秒一个桶保留最近3个桶每个桶记录频次并做部分衰减。从策略原理上讲窗口化LFU的核心价值在于“过期热度不累积”这一点比具体实现方式更重要。3.3 LRU-K给热点判定设一个准入门槛LRU-K是我非常喜欢用来防污染的方案它的核心非常简单只有访问次数达到K次的Key才有资格进入真正的缓存区低于K次的Key只在“观察区”里呆着一旦空间紧张观察区的数据最先被清走。为什么需要这个门槛因为在大规模推理服务里一次性访问或者两次访问就消失的Key非常多如果它们一进来就进入正式缓存极容易把宝贵的空间挤占掉真正的热点反而被挤出去。LRU-K通过历史门槛把这类访问过滤在正式缓存之外。class LRUKCache: def __init__(self, capacity: int, k: int 3): self.capacity capacity self.k k self.main {} # 正式缓存区 key - value self.main_recency {} # key - 最近在主缓存中被访问的时间戳 self.history {} # 观察区 key - list[最近访问时间] self._clock 0 def get(self, key): if key in self.main: self._clock 1 self.main_recency[key] self._clock return self.main[key] return -1 def put(self, key, value): if key in self.main: self.main[key] value return # 历史观察区记录访问时间 hist self.history.setdefault(key, []) self._clock 1 hist.append(self._clock) if len(hist) self.k: # 晋升到正式缓存 self.main[key] value self.main_recency[key] self._clock del self.history[key] self._maybe_evict_main() else: if len(self.history) self.capacity: self._evict_history() def _evict_history(self): # 观察区淘汰时间最老的历史记录防止历史区无限膨胀 oldest_key min(self.history, keylambda k: self.history[k][-1]) del self.history[oldest_key] def _maybe_evict_main(self): if len(self.main) self.capacity: lru_key min(self.main_recency, keyself.main_recency.get) del self.main[lru_key] del self.main_recency[lru_key]这里观察区也是缓存也会膨胀只是膨胀的优先级低而且容量上限可以设得非常小。很多工程实现里LRU-K的K值取2或3就够了K太大会让新热点进缓存的速度变慢K太小则过滤效果不足。这个阈值一般是看线上数据里“访问几次后就不再访问”的分布规律通常拍一个初始值再用A/B测试修正。3.4 分段LRU把冷热数据隔离在两个屋子里分段LRU的思路来自对物理内存中冷热分离的经验。它把LRU链表分成两个逻辑段一段是受保护的热区一段是冷区。新数据总是先进入冷区当它第二次被访问时才被提升到热区。热区淘汰的数据并不会直接丢弃而是降到冷区去再被淘汰一次才算真正离开。class SegmentedLRUCache: def __init__(self, capacity: int, hot_ratio: float 0.7): self.capacity capacity self.hot_capacity int(capacity * hot_ratio) self.cold_capacity capacity - self.hot_capacity self.hot LRUCache(self.hot_capacity) self.cold LRUCache(self.cold_capacity) self.access_count {} # 记录访问次数用于冷区到热区的晋升判断 def get(self, key): if key in self.hot.cache: self.access_count[key] self.access_count.get(key, 0) 1 return self.hot.get(key) if key in self.cold.cache: value self.cold.get(key) self.access_count[key] self.access_count.get(key, 0) 1 if self.access_count[key] 2: # 满足条件的冷数据晋升到热区 self.cold._remove_node(self.cold.cache[key]) del self.cold.cache[key] self.hot.put(key, value) return value return -1 def put(self, key, value): if key in self.hot.cache: self.hot.put(key, value) return if key not in self.cold.cache: if len(self.cold.cache) self.cold_capacity: # 冷区满驱逐冷区尾巴 victim self.cold.tail.prev self.cold._remove_node(victim) del self.cold.cache[victim.key] self.cold.put(key, value) else: self.cold.put(key, value)分段LRU的优势在于它很直观地把“缓存污染”和“热点抖动”两个问题分开解决。冷区就是缓冲区一次性访问的数据在冷区被直接淘汰不会污染热区而热区里的数据因为空间小被保护的强度更高抖动率会显著下降。hot_ratio这个参数很关键一般先按73分配冷区容量略小于热区宁可让冷区多淘汰几次也要确保热区稳定。3.5 ARC自适应地平衡时间与频率两个信号ARC自适应替换缓存是IBM的经典论文里提出的方案。它的精髓在于动态维护一个“最近使用”的L1区和“高频使用”的L2区并且根据实际命中情况持续调整两个区的容量占比。如果最近访问命中多L1变大如果历史命中多L2变大。这个“自适应性”让我在流量模式经常变化的服务里受益匪浅。class ARCCache: def __init__(self, capacity: int): self.capacity capacity self.p 0 # L1目标容量 self.t1 {} # 最近区 self.t2 {} # 高频区 self.b1 {} # 最近但已被淘汰的幽灵区 self.b2 {} # 高频但已被淘汰的幽灵区 def get(self, key): if key in self.t1: self._move_to_t2(key) return self.t1.get(key, self.t2.get(key)) if key in self.t2: self.t2[key] self.t2[key] return self.t2[key] return -1 def _move_to_t2(self, key): value self.t1.pop(key) if len(self.t2) self.capacity: old next(iter(self.t2)) self.b2[old] None del self.t2[old] self.t2[key] value def put(self, key, value): if key in self.t1 or key in self.t2: # 更新值并移动到t2 if key in self.t1: val self.t1.pop(key) else: val self.t2.pop(key) self.t2[key] value or val return if key in self.b1: # L1的幽灵被命中说明最近访问区太小 self.p min(self.capacity, self.p max(1, len(self.b2) // len(self.b1))) self._add_to_l1(key, value) return if key in self.b2: # L2的幽灵被命中说明高频访问区太小 self.p max(0, self.p - max(1, len(self.b1) // len(self.b2))) self._add_to_l2(key, value) return if len(self.t1) len(self.t2) self.capacity: if len(self.t1) self.capacity: self._replace_from_t1() else: self._replace_global() if len(self.t1) self.p: self._add_to_l1(key, value) else: self._add_to_l2(key, value)ARC的代码完整跑起来其实要处理不少边界条件我这里给的是一个足够表达核心调整逻辑的版本。实际工程里我建议直接找封装好的ARC实现来做集成不要反复造轮子。但理解它“幽灵区命中会调整容量比例”的思想特别重要它相当于在LRU和LFU之间装了一个自适应的滑杆。3.6 加权综合策略把重算成本放进淘汰公式最后一种策略是我在真实推理服务里用得最多的方案也是我向很多朋友推荐的“工程终局版”。它的核心思路是淘汰一个Key时不再是单纯看它“上次访问时间”或“访问次数”而是用一条打分公式把重算成本、新鲜度、频率三个因素按可配置的权重综合起来score w1 * log(freq1) - w2 * age - w3 * recompute_penalty其中freq是窗口内访问频率age是离上次访问的时间间隔recompute_penalty是该Key重算代价的估算值。得分越低越优先被淘汰。log是为了压住高频率Key的差异幅度避免顶部一个超高频Key垄断优势。import math import time class WeightedCache: def __init__(self, capacity: int, w1: float 1.0, w2: float 0.02, w3: float 0.5): self.capacity capacity self.w1 w1 self.w2 w2 self.w3 w3 self.cache {} self.freq {} self.last_access {} self.recompute_cost {} def _score(self, key): freq self.freq.get(key, 0) 1 age time.time() - self.last_access.get(key, time.time()) cost self.recompute_cost.get(key, 1.0) return (self.w1 * math.log(freq) - self.w2 * age - self.w3 * math.log(cost 1)) def get(self, key): if key not in self.cache: return -1 self.freq[key] self.freq.get(key, 0) 1 self.last_access[key] time.time() return self.cache[key] def put(self, key, value, cost1.0): if key not in self.cache and len(self.cache) self.capacity: self._evict() self.cache[key] value self.freq[key] self.freq.get(key, 0) 1 self.last_access[key] time.time() self.recompute_cost[key] cost def _evict(self): # 每次淘汰时扫描打分当缓存规模较大时这里要改成堆结构 victim min(self.cache, keyself._score) del self.cache[victim] self.freq.pop(victim, None) self.last_access.pop(victim, None) self.recompute_cost.pop(victim, None)这套策略的“重算成本”怎么算在LLM场景里我会用预估的token长度乘以单位token计算时间作为估算嵌入向量场景用向量维度乘以单层计算成本来估算。权重w1、w2、w3的初始值可以用一个简单的网格搜索来调先把freq和cost拉到同一量纲再根据实际抖动率调w2。这是六种策略里最灵活、也最有机会拿高分的一个但代价是需要线下数据做一次参数校准不像前几种开箱即用。4. 把策略真正部署到推理服务里的关键细节策略代码写完只是第一步。我在和团队成员联调的时候发现很多坑不在算法本身而在接入层、内存管理、并发设计这些地方。这里列几个我认为特别值得注意的工程点。4.1 内存预算和容量换算缓存策略容量参数通常按“条数”来设置但实际服务内存是按字节计费的。一个KV Cache条的体积可能差异很大短Prompt可能只有几百KB长对话可能有几十MB。如果只按条数控制可能线上实际内存远超预期。建议用字节预算做容器每次插入一个Key先计算value的字节数再决定是否触发淘汰直到总内存低于预算为止。这个逻辑要放到put方法里而不只是判断长度。用加权综合策略时还要额外估算每个Key的recompute_cost并一同存入元信息。4.2 并发安全分桶而不是全锁AI推理服务通常是多线程甚至多进程并发访问缓存。如果直接给整个缓存加一把大锁命中率再高锁竞争也会把性能拖垮。我一个比较稳的做法是分片加锁把哈希空间切分成多个桶每个桶有自己的锁和独立的淘汰策略散列时按mod分桶。这样并发度提高的同时淘汰也只在桶内进行不会互相干扰。分桶后有个副作用单个热点Key只能命中一个桶远没有全局LRU的“命中后提升优先级”的那种精准。但对大规模推理服务而言局部LRU的精度损失其实可以接受因为热点本来就是分散在多个请求模式里的不存在一个Key独占全部热度的情况。分桶数量通常取CPU核数的4倍到8倍。4.3 淘汰时机和异步化另一个常见问题是每次put都同步执行淘汰逻辑遇到全量扫描打分的加权策略时耗时会非常难看。我推荐把淘汰动作异步化put的时候只把“超出预算”的状态记录下来由一个后台线程每隔几百毫秒统一执行淘汰任务淘汰结果再从哈希表里删除原有条目。异步淘汰的一个核心难点在于业务读取时可能碰到“缓存未命中但实际还在写回”的中间状态这需要配合一个短暂的一致性窗口例如删除失败后允许回源重新计算。这个机制在推理服务里尤其重要因为它天然有一种重算兜底的路径不必为了强一致牺牲效率。4.4 指标监控六种策略上线之后如果不做监控等于摸黑开车。我至少会收集下列指标每个分桶的命中率淘汰批次每次扫描的条目数“二次访问”占比也就是缓存中至少被访问了两次的Key占总体缓存条目的比例平均重算成本用于评估淘汰策略是否偏向保留了“贵”的条目抖动率通过记录淘汰后短期内再次被访问的比例来估算这些指标不光是事后分析用更可以在运行时动态调整参数。例如抖动率突然升高就把加权策略里的w2调小降低新鲜度的权重给历史热点多一些生存空间。5. 常见问题与调优实录这一部分我会写一些实际部署时经常遇到的具体问题每个问题都附上排查思路和解决办法方便你直接把方法套到自己的项目里。5.1 缓存抖动率居高不下现象是某个热点Key每隔一段时间就被淘汰然后下次访问时又重新载入反复如此。命中了它内存也一直在波动。排查时我先看该Key的访问频率和淘汰原因如果它是被LRU因为“最近没有被访问”淘汰的多半是它的访问间隔大于LRU窗口期但它总体频次很高属于典型的低频持续型热点。对于这种热点单纯LRU很难解决我建议换成LRU-K或者分段LRU。低频持续型热点在LRU-K里只要能保持访问次数达到K就会被留在主区分段LRU则因为热区保护的存在也很难被直接驱逐。这类调优的收益在线下压测里通常非常明显抖动率能降一半以上。5.2 一次性访问数据挤占大量内存另一种常见问题是短时间内大批量一次性请求进入缓存后内存占满后续真实热点进不来全在走重算路径。这种问题在应对突发热点时尤为明显。解决思路有两种一是用LRU-K过滤垃圾访问二是把一次性访问数据的缓存时间强制设为极低值。实际操作里我通常把冷区上限卡死在很小值并把冷区条目的过期时间设为几秒。这样做可以保证突发的海量一次性请求只在冷区热个身就离开不会污染主缓存区。接入排队数据时需要评估好“重算成本”和“缓存成本”的关系如果缓存成本本身就非常低那么冷区大一点也不是坏事。5.3 加权策略参数到底怎么调问这个问题的朋友特别多我给出一个最朴素好用的操作顺序先把w1和w3固定为1.0w2设为0.01然后只调w2这一个旋钮逐步增大w2观察命中率和抖动率的变化。一般情况下抖动率上升时w2就是要回调拐点。先把w2调对再去调整w3对重算成本的感知强度。一个比较有效的经验是重算成本权重在高并发长上下文场景下应该明显加大因为短上下文重算不太在乎长上下文重算才是最大的成本黑洞。我在一次中长文档推理服务上做调优最明显的一次提升就是把w3从0.3调到0.8针对长文本请求的缓存命中率涨了约12个百分点。5.4 分桶哈希导致单个热点被分散到多个桶分桶实现之后某个热点本来应该高频命中但因为哈希把不同前缀的相近请求散列到不同桶导致每个桶内的指标都不高。这种情况下个别桶里这个热点属于低优先级容易被淘汰。解决办法是让同前缀同Key的请求尽量路由到同一个桶也就是在分桶划分时考虑业务维度的自然聚类而不是纯粹按key的哈希值机械打散。如果做不到完全聚类可以把同类型模板的Key映射到固定桶编号例如按业务线标识做一层封装。6. 我的实操体会和一些后续建议做大规模AI推理缓存优化真正难的不是哪个算法复杂而是如何在真实访问模式下选出最合适的那个算法并把参数调到与业务匹配的位置。我个人的体会是把“重算代价不对称”这几个字刻在脑子里这比任何算法都重要。策略只是工具工具是为业务目标服务的你的业务目标是低成本、低延迟地服务好大多数请求那就要把昂贵请求的缓存命中率作为第一优化目标。最后分享一个小技巧上线新策略时不要直接替换线上全量缓存。先用灰度策略或者AB实验对一小部分流量应用新算法观察几组核心指标后再逐步放开。我见过不少团队因为贪快直接全量替换结果发现某类长尾请求的性能反而劣化又不得不紧急回滚。缓存策略属于那种“看起来人畜无害、线上翻车很难马上定位”的系统灰度验证的成本远低于回滚修复的成本。如果你的业务场景里重算成本差异不那么大比如都是固定的短向量计算那本文里的前四种策略基本够用。如果你也做类似LLM这样高重算代价的推理服务我强烈建议把加权综合策略认真研究一下它值得你花一天时间做参数校准。后续如果再遇到命中率上不去的情况先别急着提升缓存容量回头看淘汰策略选型往往收益更大。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑