过期时间与 Cache-Aside 缓存

TTL 是缓存设计的核心。没有过期时间的缓存可能永久占用内存,也可能长期返回旧数据。

设置过期时间

await client.set("nloop:verification:13800138000", "482913", {
  EX: 300,
});

EX: 300 表示 300 秒后过期。也可以使用毫秒级 PX

只在键不存在时写入:

const result: string | null = await client.set(
  "nloop:idempotency:request-123",
  "processing",
  {
    EX: 60,
    NX: true,
  },
);

const acquired: boolean = result === "OK";

查看 TTL

const ttlSeconds: number = await client.ttl("nloop:verification:13800138000");

常见返回值:

Cache-Aside 模式

Cache-Aside 也叫旁路缓存,是最常见的缓存模式:

  1. 先查 Redis。
  2. 命中则返回。
  3. 未命中则查数据库。
  4. 将结果写入 Redis,并设置 TTL。
  5. 返回结果。
interface UserDto {
  id: number;
  name: string;
  email: string;
}

async function findUserFromDatabase(id: number): Promise<UserDto | null> {
  // 示例占位:真实项目在这里查询 MySQL。
  return { id, name: "Alice", email: "alice@example.com" };
}

async function getUser(id: number): Promise<UserDto | null> {
  const key: string = `nloop:user-cache:${id}`;
  const cached: string | null = await client.get(key);

  if (cached !== null) {
    return JSON.parse(cached) as UserDto;
  }

  const user: UserDto | null = await findUserFromDatabase(id);

  if (user === null) {
    return null;
  }

  await client.set(key, JSON.stringify(user), {
    EX: 300,
  });

  return user;
}

as UserDto 只能帮助 TypeScript 编译,不能验证 Redis 中的真实数据。重要业务应使用运行时校验函数或 schema 库。

数据更新时怎么办

常见策略是“先更新数据库,再删除缓存”:

async function updateUserName(id: number, name: string): Promise<void> {
  await updateUserNameInDatabase(id, name);
  await client.del(`nloop:user-cache:${id}`);
}

下一个读取请求会从数据库重新构建缓存。

为什么通常删除而不是直接更新缓存?因为同一个数据库记录可能对应多个缓存视图,直接更新容易漏字段或漏键。

缓存穿透

大量请求查询不存在的数据,每次都落到数据库。可以短暂缓存“空结果”:

const notFoundValue: string = "__NOT_FOUND__";

await client.set(key, notFoundValue, { EX: 30 });

空值 TTL 应比正常数据短,避免真实数据创建后仍被误判不存在。

缓存击穿

一个热点键失效时,大量请求同时查询数据库。常见缓解方式:

TTL 抖动示例:

const baseTtl: number = 300;
const jitter: number = Math.floor(Math.random() * 60);
await client.set(key, value, { EX: baseTtl + jitter });

缓存雪崩

大量键在同一时刻过期,导致请求集中落到数据库。避免给同一批键设置完全相同的过期时间,并为数据库准备限流和降级措施。

最佳实践