鸿蒙数据持久化高级架构:内存缓存L1 + 磁盘缓存L2 + 数据库L3三层缓存架构与缓存一致性保障
·


一、前置思考
1.1 单层存储永远不够
数据读多了发现 IO 慢、读少了发现数据旧、重启了发现全没了——任何单一存储都满足不了"快、新、持久"三个要求:
内存: 快但易失 → 适合 L1
磁盘文件: 中等速度, 重启不丢 → 适合 L2
数据库: 可靠但最慢 → 适合 L3
三层缓存架构把三者组合:L1 管速度、L2 管重启、L3 管持久。
1.2 缓存不一致的连锁灾难
灾难1: 用户改了昵称, 列表还显示旧昵称
→ L1 缓存未失效, 用户以为没保存成功
灾难2: 下单后库存没更新
→ 缓存与数据库不一致, 超卖风险
灾难3: 换了账号, 还看到上一个账号的缓存
→ 缓存未按用户维度隔离
1.3 本文路线
设计 L1(LruCache) + L2(磁盘文件/KV) + L3(数据库) 三层缓存架构,重点解决缓存一致性、失效策略、穿透/击穿/雪崩三大难题。
二、核心原理
2.1 三层缓存架构
┌──────────────────────────────────────────────┐
│ L1: 内存缓存 (LruCache) │
│ 读 0.01ms · 容量小 · 进程死即失 │
├──────────────────────────────────────────────┤
│ L2: 磁盘缓存 (KV/文件) │
│ 读 0.1-1ms · 容量中 · 重启不丢 │
├──────────────────────────────────────────────┤
│ L3: 数据库 (RelationalStore/KV) │
│ 读 1-10ms · 容量大 · 持久可靠 │
└──────────────────────────────────────────────┘
读取路径: L1 → L2 → L3 → (回填 L1/L2)
写入路径: L3 落库 → 更新 L1 → L2 异步写
2.2 缓存读路径与回填
get(key):
L1 命中? → 返回 (最快)
L2 命中? → 回填 L1 → 返回
L3 命中? → 回填 L2/L1 → 返回
全未命中 → 查数据源(网络/计算) → 回填 L3/L2/L1
回填策略要点:
- L1 容量小(几百条),LRU 淘汰;
- L2 容量中(几十 MB),按 TTL 过期;
- L3 是权威数据源,L1/L2 只是加速层。
2.3 缓存写路径与一致性
写操作两条路线:
路线A (写穿透 Write-Through):
update → L3 落库(事务) → 更新 L1 → L2 异步写
→ 强一致, 写延迟略高
路线B (写回 Write-Back):
update → 更新 L1 → 异步批量写 L3
→ 低延迟, 存在短暂不一致窗口
路线C (失效 Cache-Aside):
update → L3 落库 → 删除 L1/L2 缓存
→ 下次读取重建, 最简单不易错
推荐:业务数据用 Cache-Aside(删缓存而非更新缓存)——避免并发写时缓存与库的竞态。
2.4 缓存三大难题:穿透/击穿/雪崩
| 难题 | 定义 | 危害 |
|---|---|---|
| 穿透 | 查不存在的 key,每层都 miss,打到数据源 | 无效查询打爆 DB |
| 击穿 | 热点 key 过期瞬间大量并发涌入 | 瞬间打爆 DB |
| 雪崩 | 大量 key 同时过期 | DB 整体被打爆 |
三、源码/API 深度解析
3.1 内存 L1:LruCache 封装
import { util } from '@kit.ArkTS';
class L1Cache {
private cache: util.LruCache<string, string>;
private ttl: Map<string, number> = new Map(); // key -> 过期时间戳
private readonly defaultTtlMs: number;
constructor(capacity: number, ttlMs: number) {
this.defaultTtlMs = ttlMs;
this.cache = new util.LruCache<string, string>(capacity);
}
get(key: string): string | undefined {
const expire = this.ttl.get(key);
if (expire !== undefined && expire < Date.now()) {
this.cache.remove(key); // 过期即淘汰
this.ttl.delete(key);
return undefined;
}
return this.cache.get(key);
}
put(key: string, value: string, ttlMs?: number): void {
this.cache.put(key, value);
this.ttl.set(key, Date.now() + (ttlMs ?? this.defaultTtlMs));
}
remove(key: string): void {
this.cache.remove(key);
this.ttl.delete(key);
}
clear(): void {
this.cache.clear();
this.ttl.clear();
}
}
3.2 磁盘 L2:KV 缓存封装
import { distributedKVStore } from '@kit.ArkData';
class L2Cache {
private kv: distributedKVStore.SingleKVStore | null = null;
async init(manager: distributedKVStore.KVManager): Promise<void> {
this.kv = await manager.getKVStore('l2_cache', {
createIfMissing: true,
securityLevel: distributedKVStore.SecurityLevel.S1
});
}
async get(key: string): Promise<string | undefined> {
if (!this.kv) { return undefined; }
const v = await this.kv.get(key);
return v === undefined ? undefined : String(v);
}
async put(key: string, value: string): Promise<void> {
if (this.kv) { await this.kv.put(key, value); }
}
async remove(key: string): Promise<void> {
if (this.kv) { await this.kv.delete(key); }
}
}
3.3 三层缓存管理器(Cache-Aside 核心)
class ThreeLayerCache {
private l1: L1Cache;
private l2: L2Cache;
// l3: 数据库访问函数 (由业务注入)
constructor(l1: L1Cache, l2: L2Cache) {
this.l1 = l1;
this.l2 = l2;
}
// 读: L1 → L2 → 数据源 → 回填
async get(key: string,
loadFromSource: (key: string) => Promise<string | undefined>): Promise<string | undefined> {
const v1 = this.l1.get(key);
if (v1 !== undefined) { return v1; }
const v2 = await this.l2.get(key);
if (v2 !== undefined) {
this.l1.put(key, v2); // L2 → 回填 L1
return v2;
}
const v3 = await loadFromSource(key); // 数据源 (DB/网络)
if (v3 !== undefined) {
await this.l2.put(key, v3); // 回填 L2
this.l1.put(key, v3); // 回填 L1
}
return v3;
}
// 写: Cache-Aside → 数据源落库 → 删缓存
async set(key: string, value: string,
saveToSource: (key: string, value: string) => Promise<void>): Promise<void> {
await saveToSource(key, value); // 先写权威源
this.l1.remove(key); // 再删缓存
await this.l2.remove(key);
}
}
四、企业级实战落地
4.1 防穿透:空值缓存
// 查询不存在的 key 也缓存空值, 防止穿透打到数据源
async function getWithAntiPenetration(cache: ThreeLayerCache, key: string): Promise<string | null> {
const v = await cache.get(key, async () => {
const data = await queryDb(key);
return data ?? 'NULL'; // 空值占位
});
return v === 'NULL' ? null : v;
}
4.2 防击穿:互斥锁重建
class MutexGuard {
private locks: Map<string, Promise<any>> = new Map();
async run<T>(key: string, task: () => Promise<T>): Promise<T> {
const existing = this.locks.get(key);
if (existing) { return existing as Promise<T>; } // 并发请求复用同一重建任务
const p = task().finally(() => { this.locks.delete(key); });
this.locks.set(key, p);
return p;
}
}
// 使用: 热点 key 过期后, 只有第一个请求真正重建, 其余等待复用
async function getHotKey(key: string): Promise<string> {
const mutex = new MutexGuard();
return mutex.run(key, async () => {
const data = await loadFromDb(key); // 只执行一次
return data;
});
}
4.3 防雪崩:过期时间打散
// 避免大量 key 同时过期 → TTL 加随机抖动
function jitteredTtl(baseMs: number): number {
return baseMs + Math.floor(Math.random() * baseMs * 0.2); // ±10% 抖动
}
4.4 用户维度缓存隔离
// 多账号场景: key 带 userId 前缀, 换号即换缓存空间
function userKey(userId: string, bizKey: string): string {
return `u:${userId}:${bizKey}`;
}
// 登出时按前缀清理
class UserCacheCleaner {
private l1: L1Cache;
constructor(l1: L1Cache) { this.l1 = l1; }
clearUser(userId: string): void {
// 遍历 L1 删除该用户前缀的 key (实际按缓存实现能力做)
}
}
五、问题排查与性能优化
| 坑 | 现象 | 原因 | 解决 |
|---|---|---|---|
| 数据旧 | 改了不生效 | 缓存未失效 | Cache-Aside 删缓存 |
| 慢查询 | 缓存全 miss | 无 L1/L2 | 三层回填 |
| 内存爆炸 | L1 无上限 | 容量未设 | LruCache 容量 |
| 无效查询 | 穿透打 DB | 空值未缓存 | 空值缓存 |
| 热点击穿 | 过期瞬间打爆 | 无互斥 | 互斥重建 |
| 雪崩 | 大量 key 同过期 | TTL 一致 | 随机抖动 |
| 换号串数据 | 看到上个用户缓存 | 未隔离 | userId 前缀 |
| 写库后读旧 | 缓存 vs 库不一致 | 更新缓存竞态 | 删缓存而非更新 |
5.1 一致性保障优先级
强一致要求 (订单/支付): 写穿透 (先落库, 再删缓存)
最终一致允许 (Feed流): 写回 + 定期刷新
低延迟优先 (热点详情): Cache-Aside + 互斥重建
5.2 缓存命中率监控
指标:
命中率 = L1命中 / 总读次数
分层命中: L1 / L2 / L3 各自占比
回填率: 数据源加载次数 / 总读次数
优化方向:
命中率低 → 容量不够 / 过期太短 / key 设计碎片化
回填率高 → 冷数据多, 考虑预热
5.3 缓存预热
// 应用启动后预加载热点数据到 L1
async function warmUp(cache: ThreeLayerCache, hotKeys: string[]): Promise<void> {
const tasks = hotKeys.map(k => cache.get(k, async () => loadFromDb(k)));
await Promise.all(tasks);
}
六、高阶总结与最佳实践
- 三层各司其职:L1 管速度(LruCache)、L2 管重启(KV/文件)、L3 管持久(数据库)。
- Cache-Aside 是默认写策略:先写权威源再删缓存,避免并发竞态。
- 三防必做:防穿透(空值缓存)、防击穿(互斥重建)、防雪崩(TTL 抖动)。
- 缓存按用户/租户隔离:key 带维度前缀,换号即清。
- 监控命中率:用数据驱动容量与 TTL 调优,而不是拍脑袋。
一句话记住:读走三级加速回填,写走"落库+删缓存",穿透缓存空值、击穿互斥重建、雪崩 TTL 抖动——一致性靠失效策略,性能靠命中率监控。
更多推荐



所有评论(0)