3. 倒排索引缓存架构:缓存层级设计(L1/L2/L3)、缓存分片策略、缓存淘汰策略(LRU/LFU)

在倒排索引系统中,缓存是降低磁盘 I/O、加速查询响应的核心手段。然而,索引更新(增量/全量)会导致缓存数据与底层索引文件不一致。本章围绕缓存层级、分片与淘汰三大设计维度,构建一套高吞吐、低延迟、易维护的倒排索引缓存架构。

3.1 缓存层级设计(L1 / L2 / L3)

为平衡访问速度与缓存容量,采用三级缓存架构。每一级在延迟、容量、成本和一致性策略上各有侧重。

层级 存储介质 典型延迟 容量 一致性策略 主要用途
L1 堆内内存(On-Heap) < 1 μs GB 级 写穿透 + 失效广播 热词倒排链、高频 Term 词典
L2 堆外内存(Off-Heap) 1–10 μs 数十 GB TTL + 异步刷新 中频 Term 的 Posting List
L3 本地 SSD / 共享缓存 100–500 μs TB 级 版本号 + 懒加载 冷数据、全量索引快照
设计要点:L1 采用写穿透(Write-Through)保证强一致性;L2 允许秒级延迟的异步刷新;L3 通过版本号校验实现最终一致性。三级之间形成“L1 命中即返回 → L2 回填 → L3 回填”的逐级降级链路。
倒排索引三级缓存架构(L1 / L2 / L3) L1 堆内缓存 On-Heap 热词倒排链 / 词典 L2 堆外缓存 Off-Heap 中频 Posting List L3 本地缓存 SSD / 共享 冷数据 / 快照 查询请求 L1 命中 → 直接返回 未命中 → 查 L2 L2 命中 → 回填 L1 未命中 → 查 L3 L3 命中 → 回填 L2 未命中 → 查磁盘索引 逐级回填(L3 → L2 → L1) 索引更新 → 失效广播 → L1 立即失效 → L2 标记过期 → L3 版本号校验

3.2 缓存分片策略

单机缓存容量有限,且热点 Term 可能集中在少数分片。合理的分片策略能提升并发度、降低锁竞争,并支持水平扩展。

3.2.1 分片维度

  • Term 哈希分片:对 Term 字符串计算 MurmurHash3,按分片数取模。实现简单,分布均匀,适合无状态路由。
  • Term 前缀分片:按 Term 首字母或前 N 个字符分组。利于前缀查询的局部性,但可能产生热点分片(如“a”开头 Term 过多)。
  • 动态权重分片:根据 Term 的查询频率(QPS)动态调整分片边界,将高频 Term 分散到更多分片。实现复杂,但抗热点能力强。
分片策略 均匀性 热点抗性 实现复杂度 适用场景
Term 哈希分片 高 中 低 通用场景,默认首选
Term 前缀分片 低 低 低 前缀查询密集、Term 分布均匀
动态权重分片 中 高 高 长尾热点严重、QPS 波动大

3.2.2 分片数量与内存布局

分片数量通常设置为 CPU 核数的 2–4 倍,每个分片拥有独立的锁和淘汰策略。L1 堆内缓存每个分片建议 64–256 MB,L2 堆外缓存每个分片建议 512 MB–2 GB。

// 分片路由示例(Java 伪代码)
int shardCount = Runtime.getRuntime().availableProcessors() * 2;
int shardIndex = (term.hashCode() & 0x7FFFFFFF) % shardCount;
CacheShard shard = shards[shardIndex];
PostingList result = shard.get(term);

3.3 缓存淘汰策略(LRU / LFU)

倒排索引缓存的访问模式兼具时间局部性(近期查询的 Term 可能再次被查)和频率局部性(高频 Term 持续被查)。单一淘汰策略难以同时适配两种模式。

3.3.1 LRU(Least Recently Used)

  • 原理:淘汰最长时间未被访问的条目。基于访问时间排序。
  • 实现:双向链表 + HashMap(LinkedHashMap),访问时移动节点到链表头部。
  • 优点:实现简单,对突发热点响应快。
  • 缺点:扫描式查询(Scan)会污染缓存,将冷数据推入缓存。

3.3.2 LFU(Least Frequently Used)

  • 原理:淘汰访问频率最低的条目。基于访问计数排序。
  • 实现:最小堆或多级频率桶(Count-Min Sketch + 双向链表)。
  • 优点:能长期保留高频 Term,抗扫描污染能力强。
  • 缺点:历史频率权重过高,新晋热点 Term 难以及时进入缓存;实现复杂度高。

3.3.3 混合策略:L1 用 LRU,L2 用 LFU

在生产实践中,推荐 L1 使用 LRU、L2 使用 LFU 的混合方案:

  • L1(LRU):快速响应突发热点,短时间窗口内的重复查询直接命中 L1。
  • L2(LFU):过滤扫描流量,只有持续高频的 Term 才能长期驻留 L2。
  • L3(TTL + 大小限制):冷数据按 TTL 过期,不参与复杂淘汰。
工程经验:L1 的 LRU 容量设为可容纳 5–10 分钟热点 Term 的倒排链;L2 的 LFU 频率衰减窗口设为 30 分钟,避免历史频率永久累积。当 L1 淘汰一个条目时,不直接丢弃,而是降级写入 L2,形成“降级淘汰链”。
缓存淘汰策略混合架构(L1 LRU → L2 LFU) L1 堆内缓存(LRU) 双向链表 + HashMap 容量:5–10 分钟热点 淘汰 → 降级写入 L2 L2 堆外缓存(LFU) 频率桶 + Count-Min Sketch 频率衰减窗口:30 分钟 淘汰 → 直接丢弃 降级淘汰 查询路径:L1(LRU) 命中 → 返回;未命中 → 查 L2(LFU) → 命中则回填 L1 L2 未命中 → 查 L3/磁盘 → 结果写入 L2(LFU) 并回填 L1(LRU) 效果:突发热点快速进入 L1,持续高频 Term 稳定驻留 L2,扫描流量被 L2 的 LFU 过滤

3.4 一致性闭环与缓存失效

缓存架构的最终目标是保证“读到的数据与索引文件一致”。三级缓存配合以下失效机制形成闭环:

  1. 写穿透(Write-Through):索引更新时,同步更新 L1 缓存,保证 L1 强一致。
  2. 失效广播:索引更新后,向所有缓存节点广播失效消息,L1 立即清除对应条目,L2 标记为“过期”。
  3. 版本号校验:L3 缓存条目携带索引版本号,查询时对比当前索引版本,不一致则重新加载。
  4. 异步回填:L2/L3 命中后,异步回填到 L1/L2,回填过程中加分布式锁防止缓存击穿。
避坑提醒:不要对 L1 使用 TTL 过期,因为 L1 要求强一致,TTL 会导致短暂的不一致窗口。L1 的失效必须由写操作或广播消息主动触发。L2 和 L3 可以接受秒级 TTL 作为兜底。

3.5 架构总结

维度 设计选择 关键指标
缓存层级 L1 堆内 / L2 堆外 / L3 SSD L1 命中率 > 60%,L2 命中率 > 85%
分片策略 Term 哈希分片,分片数 = CPU 核数 × 2 分片内锁竞争 < 5%
淘汰策略 L1 LRU / L2 LFU / L3 TTL 缓存污染率 < 3%,热点 Term 驻留时间 > 30 分钟
一致性闭环 写穿透 + 失效广播 + 版本号校验 不一致窗口 < 100 ms(L1 强一致)