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 回填”的逐级降级链路。
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,形成“降级淘汰链”。
3.4 一致性闭环与缓存失效
缓存架构的最终目标是保证“读到的数据与索引文件一致”。三级缓存配合以下失效机制形成闭环:
- 写穿透(Write-Through):索引更新时,同步更新 L1 缓存,保证 L1 强一致。
- 失效广播:索引更新后,向所有缓存节点广播失效消息,L1 立即清除对应条目,L2 标记为“过期”。
- 版本号校验:L3 缓存条目携带索引版本号,查询时对比当前索引版本,不一致则重新加载。
- 异步回填: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 强一致) |