服务端与数据 / 进阶

Redis 缓存一致性:先接受短暂旧数据,再设计失效

缓存不是第二个真相来源。以数据库为准,使用 Cache-Aside、版本化 key、随机 TTL 和请求合并控制一致性与稳定性。

数据库是事实来源

典型 Cache-Aside 流程是读缓存,未命中则读数据库并回填;写入时先提交数据库,再删除缓存。不要让应用分别写数据库和缓存并假设两次操作永远同时成功。

EXAMPLE / 01Redis
async function getProduct(id) {
  const key = `product:v3:${id}`;
  const cached = await redis.get(key);
  if (cached) return JSON.parse(cached);

  const product = await db.product.findUnique({ where: { id } });
  if (product) {
    const ttl = 300 + Math.floor(Math.random() * 60);
    await redis.set(key, JSON.stringify(product), { EX: ttl });
  }
  return product;
}

写数据库后删除缓存

删除通常比更新缓存安全,因为更新时容易遗漏衍生字段和列表缓存。数据库提交成功后删除失败,可以通过 outbox、CDC 或重试任务再次失效。对一致性要求极高的读取,应直接读主库或使用版本号校验。

商品描述允许旧 30 秒和账户余额必须读到最新值,是两种完全不同的需求。缓存方案必须先写清可接受的陈旧时间。

防止热点 key 同时失效

TTL 加随机抖动,避免大量 key 在同一秒过期;热点数据使用进程内请求合并,让同一时刻只有一个回源;允许旧数据时采用 stale-while-revalidate;不存在的数据短期缓存空值,防止缓存穿透;key 中加入 schema 版本,发布结构变更时自然切换。