在这里插入图片描述

在这里插入图片描述

一、前置思考

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开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐