策略
TTL 过期通过无需协调的方式限制陈旧时间:简单,但正确性取决于 TTL 是否可接受。版本化键把版本或内容哈希编码进键,新版本自然未命中,旧条目变得不可达。显式清除在变更后删除键,但需要顺序和重试逻辑。写穿在写入路径同时更新缓存,以写延迟换取更快的读。
const cache = new Map();
async function getUser(id, version) {
const key = `user:${id}:v${version}`;
if (cache.has(key)) return cache.get(key);
const user = await db.users.findById(id);
cache.set(key, user);
return user;
}
// 变更时递增版本,旧键不再被使用。
async function renameUser(id, name, currentVersion) {
await db.users.update(id, { name });
return currentVersion + 1;
}
写/删竞态
常见序列是:写者更新数据库、删除缓存键;某个读者在更新前读到旧值,却在删除后把旧值写回。版本化键可以避免,因为读者写入旧版本,新读者不再使用它。如果使用显式删除,应在提交后删除,考虑延迟二次删除或使用锁。
何时使用哪种
对于可接受有界陈旧的数据(如公开内容)使用 TTL。对于权限、账户状态和库存,使用版本化键或显式失效。与无缓存相比,失效增加正确性工作;与每次请求读数据库相比,它用新鲜度换取延迟和负载。
陷阱
失效事件可能丢失;应配合重试并以 TTL 兜底。跨服务共享的缓存需要每个键有明确所有者。监控命中率、陈旧读错误和清除失败;一个很快但错误的缓存比没有缓存更糟。