在这里插入图片描述

在这里插入图片描述

一、前置思考

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);
}

六、高阶总结与最佳实践

  1. 三层各司其职:L1 管速度(LruCache)、L2 管重启(KV/文件)、L3 管持久(数据库)。
  2. Cache-Aside 是默认写策略:先写权威源再删缓存,避免并发竞态。
  3. 三防必做:防穿透(空值缓存)、防击穿(互斥重建)、防雪崩(TTL 抖动)。
  4. 缓存按用户/租户隔离:key 带维度前缀,换号即清。
  5. 监控命中率:用数据驱动容量与 TTL 调优,而不是拍脑袋。

一句话记住:读走三级加速回填,写走"落库+删缓存",穿透缓存空值、击穿互斥重建、雪崩 TTL 抖动——一致性靠失效策略,性能靠命中率监控。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐