← 中文资源

简体中文版本

直写、回写与绕写

缓存写策略决定写入何时到达源站:直写同步双写、回写异步排队承担丢失风险、绕写只写源站绕开缓存。

阅读英文原文

概念定义

缓存必须给写入一个说法。三种经典策略:同步双写(直写)、异步延迟写(回写)、只写源站(绕写)——各自在写延迟、持久性与缓存新鲜度之间选不同的点。

工作机制

直写在两个存储都接受更新后才完成,失败可见可回滚。回写把条目标脏、排队冲刷、提前完成——崩溃恢复要重放队列,除非有日志否则会丢。绕写直接写源站,缓存条目过期或失效,只有重新读取才会回填。

使用场景

写率低且一致性要紧用直写;突发写负载能批量吸收用回写,但必须配持久队列与重放工具;批量导入与归档写不该驱逐活数据,用绕写。

常见误区

回写让缓存在冲刷前成为事实上的记录系统——这份责任要按此评估。直写的部分失败需要两存储间的回滚顺序。绕写加紧跟的读取会产生 miss 风暴:要配合预热或首次访问的 read-through。“直写保证一致性”只保证完成时的缓存-源站一致,并发读者仍可能看到交错。

代码示例

import { createClient } from 'redis';

const redis = createClient({ url: process.env.REDIS_URL ?? 'redis://127.0.0.1:6379' });
redis.on('error', (err) => console.error('redis:', err.message));
await redis.connect();

const TTL_SECONDS = 300;

async function cached(key, loader) {
  const hit = await redis.get(key);
  if (hit !== null) {
    await redis.incr('cache:stats:hits');
    return JSON.parse(hit);
  }
  const fresh = await loader();
  await redis.set(key, JSON.stringify(fresh), { EX: TTL_SECONDS, NX: true });
  await redis.incr('cache:stats:misses');
  return fresh;
}

async function invalidate(prefix) {
  const pattern = prefix + ':*';
  for await (const key of redis.scanIterator({ MATCH: pattern, COUNT: 200 })) {
    await redis.del(key);
  }
}

const product = await cached('product:42', () => fetchProduct(42));
await invalidate('product');
await redis.quit();