缓存失效策略
概览
“计算机科学中只有两件难事:缓存失效和命名。” - Phil Karlton 缓存失效是当底层数据改变时移除或更新缓存数据的过程。如果处理不当,会导致用户收到过时的数据。如果处理得当,它确保了数据一致性的同时保持了性能优势。
前置知识
- 理解缓存概念和好处
- 了解Redis或类似的缓存技术
- 熟悉数据库操作和数据一致性
- 基本了解分布式系统
关键概念
为什么缓存失效这么难
缓存失效之所以具有挑战性,因为:
- 多层缓存:数据可能在多个级别上被缓存(浏览器、CDN、应用缓存、数据库缓存)
- 复杂依赖:缓存的数据可能依赖多个数据源
- 时间问题:在缓存更新时可能会发生变更
- 分布式系统:多个服务器可能有不一致的缓存状态
- 性能权衡:激进的失效影响性能,保守的失效可能导致过时数据
缓存失效模式
基于时间的过期(TTL)
最简单的方法 - 缓存条目在固定时间后过期。 优点:
- 简单实现
- 不需要失效逻辑
- 适用于变化缓慢的数据 缺点:
- 可能在TTL过期前提供过时数据
- 在未改变的数据上浪费缓存空间
- 难以选择最佳TTL
事件驱动失效
数据改变时立即使缓存失效。 优点:
- 立即一致性
- 不提供过时数据
- 精确控制 缺点:
- 实现更复杂
- 需要事件跟踪
- 可能影响性能
写入时缓存
同时向缓存和数据库写入数据。 优点:
- 缓存始终是最新的
- 简单的读取逻辑 缺点:
- 写入变慢(缓存+数据库)
- 可能会写入从未读取的数据
写后(写回)缓存
首先写入缓存,然后异步持久化到数据库。 优点:
- 快速写入(仅缓存)
- 可以批量数据库写入 缺点:
- 如果缓存失败则有数据丢失风险
- 实现复杂
- 最终一致性
缓存旁路(懒加载)
首先检查缓存,如果未命中则从数据库加载。 优点:
- 简单实现
- 仅缓存访问过的数据
- 适用于读多写少的工作负载 缺点:
- 缓存踩踏风险
- 第一个请求较慢
- 直到TTL过期都是过时数据
读穿缓存
缓存抽象处理从数据库加载。 优点:
- 清晰的关注点分离
- 集中式缓存逻辑
- 易于添加功能 缺点:
- 稍微复杂一些
- 需要自定义缓存实现
实施指南
基于时间的过期(TTL)
// 设置缓存和TTL
await cache.set('user:123', userData, { ttl: 3600 }); // 1小时
// 从缓存中获取
const cached = await cache.get('user:123');
if (cached) {
return cached;
}
// 缓存未命中 - 从数据库获取
const data = await db.users.findById(123);
await cache.set('user:123', data, { ttl: 3600 });
return data;
选择TTL:
// 基于数据变更频率的自适应TTL
function calculateTTL(dataType, lastModified) {
const age = Date.now() - lastModified;
switch (dataType) {
case 'user_profile':
return 3600; // 1小时 - 很少变化
case 'stock_price':
return 5; // 5秒 - 频繁变化
case 'news_feed':
return 300; // 5分钟 - 适度变化
default:
return 600; // 10分钟默认
}
}
// 使用
const userData = await db.users.findById(123);
const ttl = calculateTTL('user_profile', userData.updatedAt);
await cache.set(`user:${userData.id}`, userData, { ttl });
事件驱动失效
// 更新用户并使缓存失效
async function updateUser(userId, updates) {
// 更新数据库
const user = await db.users.update(userId, updates);
// 使缓存失效
await cache.del(`user:${userId}`);
return user;
}
// 删除用户并使缓存失效
async function deleteUser(userId) {
await db.users.delete(userId);
await cache.del(`user:${userId}`);
}
事件总线模式:
const EventEmitter = require('events');
class CacheInvalidator extends EventEmitter {
constructor() {
super();
this.setupListeners();
}
setupListeners() {
// 监听数据变更事件
this.on('user:updated', async ({ userId }) => {
await cache.del(`user:${userId}`);
await cache.del(`user:${userId}:profile`);
await cache.del(`user:${userId}:settings`);
});
this.on('post:created', async ({ userId }) => {
// 使用户帖子缓存失效
await cache.del(`user:${userId}:posts`);
// 使Feed缓存失效
await cache.del('feed:recent');
});
this.on('post:deleted', async ({ userId, postId }) => {
await cache.del(`user:${userId}:posts`);
await cache.del(`post:${postId}`);
});
}
}
// 使用
const invalidator = new CacheInvalidator();
async function createPost(userId, content) {
const post = await db.posts.create({ userId, content });
// 发出事件
invalidator.emit('post:created', { userId, postId: post.id });
return post;
}
写入时缓存
async function updateUser(userId, updates) {
const user = await db.users.update(userId, updates);
// 写入缓存
await cache.set(`user:${userId}`, user, { ttl: 3600 });
return user;
}
// 读取总是命中缓存
async function getUser(userId) {
const cached = await cache.get(`user:${userId}`);
if (cached) {
return cached;
}
const user = await db.users.findById(userId);
await cache.set(`user:${userId}`, user, { ttl: 3600 });
return user;
}
写后(写回)缓存
class WriteBehindCache {
constructor() {
this.writeQueue = [];
this.processing = false;
this.startProcessing();
}
async set(key, value, options) {
// 立即写入缓存
await cache.set(key, value, options);
// 排队数据库写入
this.writeQueue.push({ key, value, timestamp: Date.now() });
}
startProcessing() {
setInterval(async () => {
if (this.writeQueue.length === 0 || this.processing) return;
this.processing = true;
try {
const batch = this.writeQueue.splice(0, 100); // 批量处理
await this.writeBatchToDatabase(batch);
} catch (error) {
console.error('写回错误:', error);
// 重新排队失败的写入
this.writeQueue.unshift(...batch);
} finally {
this.processing = false;
}
}, 100); // 每100ms处理一次
}
async writeBatchToDatabase(batch) {
// 批量写入数据库
const operations = batch.map(({ key, value }) => {
const [type, id] = key.split(':');
return db[type].update(id, value);
});
await Promise.all(operations);
}
}
缓存旁路(懒加载)
async function getUser(userId) {
// 检查缓存
const cached = await cache.get(`user:${userId}`);
if (cached) {
return cached;
}
// 缓存未命中 - 从数据库加载
const user = await db.users.findById(userId);
// 填充缓存
await cache.set(`user:${userId}`, user, { ttl: 3600 });
return user;
}
缓存踩踏防止:
async function getUserWithStampedeProtection(userId) {
const cacheKey = `user:${userId}`;
const lockKey = `lock:${cacheKey}`;
// 检查缓存
const cached = await cache.get(cacheKey);
if (cached) {
return cached;
}
// 尝试获取锁
const lock = await cache.set(lockKey, '1', {
ttl: 10, // 锁在10s后过期
nx: true // 仅当不存在时设置
});
if (lock) {
// 我们持有锁 - 从数据库加载
try {
const user = await db.users.findById(userId);
await cache.set(cacheKey, user, { ttl: 3600 });
await cache.del(lockKey);
return user;
} catch (error) {
await cache.del(lockKey);
throw error;
}
} else {
// 另一个请求正在加载 - 等待并重试
await sleep(100);
return getUserWithStampedeProtection(userId);
}
}
读穿缓存
class ReadThroughCache {
constructor(loader) {
this.loader = loader;
}
async get(key) {
// 检查缓存
const cached = await cache.get(key);
if (cached) {
return cached;
}
// 缓存未命中 - 使用loader
const value = await this.loader(key);
// 填充缓存
await cache.set(key, value, { ttl: 3600 });
return value;
}
}
// 使用
const userCache = new ReadThroughCache(async (key) => {
const userId = key.split(':')[1];
return await db.users.findById(userId);
});
const user = await userCache.get('user:123');
失效策略
清除(删除特定键)
移除特定的缓存条目。
// 简单清除
await cache.del('user:123');
// 多个键
await cache.del('user:123', 'user:123:profile', 'user:123:settings');
// 基于模式的清除(Redis)
await cache.del('user:123:*');
何时使用:
- 单个数据源变更
- 简单的键结构
- 需要精确失效
禁令(基于模式的失效)
移除所有匹配模式的缓存条目。
// Redis基于模式的删除
async function banPattern(pattern) {
const keys = await cache.keys(pattern);
if (keys.length > 0) {
await cache.del(...keys);
}
}
// 使用
await banPattern('user:123:*'); // 删除用户123的所有缓存
await banPattern('feed:*'); // 删除所有Feed缓存
基于标签的失效:
class TaggedCache {
constructor() {
this.keyTags = new Map(); // key -> 标签集合
this.tagKeys = new Map(); // 标签 -> 键集合
}
async set(key, value, options = {}) {
const tags = options.tags || [];
// 存储值
await cache.set(key, value, options);
// 更新标签映射
this.keyTags.set(key, new Set(tags));
for (const tag of tags) {
if (!this.tagKeys.has(tag)) {
this.tagKeys.set(tag, new Set());
}
this.tagKeys.get(tag).add(key);
}
}
async invalidateByTag(tag) {
const keys = this.tagKeys.get(tag);
if (!keys) return;
// 删除所有具有此标签的键
const keyArray = Array.from(keys);
await cache.del(...keyArray);
// 清理映射
for (const key of keyArray) {
const tags = this.keyTags.get(key);
tags.delete(tag);
if (tags.size === 0) {
this.keyTags.delete(key);
}
}
this.tagKeys.delete(tag);
}
}
// 使用
const taggedCache = new TaggedCache();
// 带标签的缓存
await taggedCache.set('user:123', userData, {
tags: ['user', 'profile', 'premium']
});
await taggedCache.set('user:456', userData, {
tags: ['user', 'profile']
});
// 使所有用户缓存失效
await taggedCache.invalidateByTag('user');
// 仅使高级用户缓存失效
await taggedCache.invalidateByTag('premium');
刷新(原地更新)
用新数据更新缓存而不移除它。
async function refreshUser(userId) {
// 获取新数据
const user = await db.users.findById(userId);
// 原地更新缓存
await cache.set(`user:${userId}`, user, { ttl: 3600 });
return user;
}
// 后台刷新
async function backgroundRefresh(key, loader) {
try {
const freshData = await loader(key);
await cache.set(key, freshData, { ttl: 3600 });
} catch (error) {
console.error('后台刷新失败:', error);
}
}
// 到期前主动刷新
async function getWithProactiveRefresh(key, loader) {
const cached = await cache.get(key);
if (cached) {
const ttl = await cache.ttl(key);
// 如果TTL低于阈值(例如,剩余10%)则刷新
if (ttl < 360) { // 3600 * 0.1
backgroundRefresh(key, loader);
}
return cached;
}
// 缓存未命中
const data = await loader(key);
await cache.set(key, data, { ttl: 3600 });
return data;
}
软清除(提供过时服务同时重新验证)
标记缓存为过时但继续提供服务,同时刷新。
class SoftPurgeCache {
async get(key) {
const cached = await cache.get(key);
if (!cached) {
return null;
}
// 检查是否标记为软清除
if (cached._stale) {
// 触发后台刷新
this.backgroundRefresh(key);
// 返回过时数据
return cached._data;
}
return cached;
}
async softPurge(key) {
const cached = await cache.get(key);
if (cached) {
// 标记为过时但保留数据
await cache.set(key, {
_stale: true,
_data: cached,
_purgedAt: Date.now(),
});
}
}
async backgroundRefresh(key) {
const cached = await cache.get(key);
// 检查是否已经在刷新
if (cached && cached._refreshing) {
return;
}
// 标记为刷新中
await cache.set(key, {
...cached,
_refreshing: true,
});
try {
const freshData = await this.loader(key);
await cache.set(key, freshData, { ttl: 3600 });
} catch (error) {
console.error('刷新失败:', error);
// 移除刷新标志
await cache.set(key, { ...cached, _refreshing: false });
}
}
}
缓存踩踏防止
基于锁的防止
async function getWithLock(key, loader, ttl = 3600) {
const cached = await cache.get(key);
if (cached) {
return cached;
}
const lockKey = `lock:${key}`;
const lockValue = Date.now().toString();
// 尝试获取锁
const acquired = await cache.set(lockKey, lockValue, {
ttl: 10, // 锁在10s后过期
nx: true,
});
if (acquired) {
// 我们持有锁
try {
const value = await loader(key);
await cache.set(key, value, { ttl });
await cache.del(lockKey);
return value;
} catch (error) {
await cache.del(lockKey);
throw error;
}
} else {
// 等待锁持有者
await sleep(50);
// 再次检查缓存
const cached = await cache.get(key);
if (cached) {
return cached;
}
// 仍然没有缓存,再试一次
return getWithLock(key, loader, ttl);
}
}
请求合并
class RequestCoalescer {
constructor() {
this.pendingRequests = new Map();
}
async get(key, loader) {
// 检查是否有待处理的请求
if (this.pendingRequests.has(key)) {
return await this.pendingRequests.get(key);
}
// 创建新的promise
const promise = this.loadAndCache(key, loader);
this.pendingRequests.set(key, promise);
try {
return await promise;
} finally {
this.pendingRequests.delete(key);
}
}
async loadAndCache(key, loader) {
const cached = await cache.get(key);
if (cached) {
return cached;
}
const value = await loader(key);
await cache.set(key, value, { ttl: 3600 });
return value;
}
}
// 使用
const coalescer = new RequestCoalescer();
async function getUser(userId) {
return await coalescer.get(`user:${userId}`, async (key) => {
const id = key.split(':')[1];
return await db.users.findById(id);
});
}
分布式缓存失效
Pub/Sub模式
const Redis = require('ioredis');
class DistributedCacheInvalidator {
constructor() {
this.publisher = new Redis();
this.subscriber = new Redis();
this.setupSubscriber();
}
setupSubscriber() {
this.subscriber.psubscribe('cache:*');
this.subscriber.on('pmessage', (pattern, channel, message) => {
const [action, key] = channel.split(':').slice(1);
switch (action) {
case 'invalidate':
cache.del(key);
break;
case 'invalidate_pattern':
this.invalidatePattern(message);
break;
}
});
}
async invalidate(key) {
// 使本地缓存失效
await cache.del(key);
// 通知其他实例
await this.publisher.publish(`cache:invalidate:${key}`, '');
}
async invalidatePattern(pattern) {
const keys = await cache.keys(pattern);
if (keys.length > 0) {
await cache.del(...keys);
}
}
async invalidatePatternDistributed(pattern) {
// 使本地缓存失效
await this.invalidatePattern(pattern);
// 通知其他实例
await this.publisher.publish(`cache:invalidate_pattern:${pattern}`, '');
}
}
数据库变更数据捕获(CDC)
const { DebeziumConnector } = require('debezium-connector');
class CDCInvalidator {
constructor() {
this.connector = new DebeziumConnector({
bootstrapServers: 'localhost:9092',
topic: 'dbserver1.inventory.users',
});
this.setupListener();
}
setupListener() {
this.connector.on('change', async (event) => {
const { op, before, after } = event;
switch (op) {
case 'u': // 更新
case 'd': // 删除
const userId = before.id;
await cache.del(`user:${userId}`);
await cache.del(`user:${userId}:profile`);
break;
case 'c': // 创建
const newUserId = after.id;
// 可选预热缓存
await cache.set(`user:${newUserId}`, after, { ttl: 3600 });
break;
}
});
}
}
缓存标签和分组
层次化标签
class HierarchicalCache {
constructor() {
this.tagHierarchy = new Map(); // 标签 -> 父标签
}
defineTag(tag, parentTags = []) {
this.tagHierarchy.set(tag, parentTags);
}
async set(key, value, options = {}) {
const tags = options.tags || [];
// 解析所有父标签
const allTags = new Set(tags);
for (const tag of tags) {
const parents = this.tagHierarchy.get(tag) || [];
parents.forEach(parent => allTags.add(parent));
}
// 用所有标签存储
await cache.set(key, value, { ...options, tags: Array.from(allTags) });
}
async invalidateTag(tag) {
// 获取所有具有此标签或其子标签的键
const keys = await this.getKeysByTag(tag);
await cache.del(...keys);
}
async getKeysByTag(tag) {
const keys = new Set();
// 直接标签匹配
const directKeys = await this.tagKeys.get(tag) || [];
directKeys.forEach(key => keys.add(key));
// 子标签匹配
for (const [childTag, parentTags] of this.tagHierarchy) {
if (parentTags.includes(tag)) {
const childKeys = await this.tagKeys.get(childTag) || [];
childKeys.forEach(key => keys.add(key));
}
}
return Array.from(keys);
}
}
// 使用
const cache = new HierarchicalCache();
// 定义标签层次结构
cache.defineTag('user', ['data']);
cache.defineTag('post', ['data']);
cache.defineTag('feed', ['data', 'aggregated']);
// 带标签的缓存
await cache.set('user:123', userData, { tags: ['user'] });
await cache.set('post:456', postData, { tags: ['post'] });
await cache.set('feed:recent', feedData, { tags: ['feed'] });
// 使所有数据失效
await cache.invalidateTag('data');
版本化缓存键
在缓存键中包含版本以简化失效。
class VersionedCache {
constructor() {
this.versions = new Map(); // 前缀 -> 版本号
}
async get(prefix, key) {
const version = this.getVersion(prefix);
const versionedKey = `${prefix}:v${version}:${key}`;
return await cache.get(versionedKey);
}
async set(prefix, key, value, options) {
const version = this.getVersion(prefix);
const versionedKey = `${prefix}:v${version}:${key}`;
return await cache.set(versionedKey, value, options);
}
getVersion(prefix) {
if (!this.versions.has(prefix)) {
this.versions.set(prefix, 1);
}
return this.versions.get(prefix);
}
async invalidate(prefix) {
// 增加版本
const currentVersion = this.getVersion(prefix);
this.versions.set(prefix, currentVersion + 1);
// 旧键将自然过期
// 可选,立即删除旧键
await this.deleteOldVersionKeys(prefix, currentVersion);
}
async deleteOldVersionKeys(prefix, oldVersion) {
const pattern = `${prefix}:v${oldVersion}:*`;
const keys = await cache.keys(pattern);
if (keys.length > 0) {
await cache.del(...keys);
}
}
}
// 使用
const cache = new VersionedCache();
// 设置缓存
await cache.set('user', '123', userData, { ttl: 3600 });
await cache.get('user', '123'); // user:v1:123
// 使所有用户缓存失效
await cache.invalidate('user');
// 新版本
await cache.set('user', '123', userData, { ttl: 3600 });
await cache.get('user', '123'); // user:v2:123
缓存预热策略
按需预热
首次访问时预热缓存。
async function getWithWarmup(key, loader) {
const cached = await cache.get(key);
if (cached) {
return cached;
}
// 缓存未命中 - 加载并预热
const value = await loader(key);
await cache.set(key, value, { ttl: 3600 });
// 预热相关缓存
await warmRelatedCaches(value);
return value;
}
async function warmRelatedCaches(user) {
// 预热用户的帖子
const posts = await db.posts.findByUserId(user.id);
await cache.set(`user:${user.id}:posts`, posts, { ttl: 3600 });
// 预热用户的朋友
const friends = await db.friends.findByUserId(user.id);
await cache.set(`user:${user.id}:friends`, friends, { ttl: 3600 });
}
计划预热
按计划预热缓存。
class CacheWarmer {
constructor() {
this.jobs = new Map();
}
schedule(key, loader, interval) {
const job = setInterval(async () => {
try {
const value = await loader(key);
await cache.set(key, value, { ttl: interval * 2 });
} catch (error) {
console.error('缓存预热失败:', error);
}
}, interval);
this.jobs.set(key, job);
// 初始预热
loader(key).then(value => {
cache.set(key, value, { ttl: interval * 2 });
});
}
unschedule(key) {
const job = this.jobs.get(key);
if (job) {
clearInterval(job);
this.jobs.delete(key);
}
}
}
// 使用
const warmer = new CacheWarmer();
// 每小时预热用户缓存
warmer.schedule('user:123', async () => {
return await db.users.findById(123);
}, 3600000);
预测性预热
基于访问模式预热缓存。
class PredictiveCacheWarmer {
constructor() {
this.accessPatterns = new Map(); // key -> 访问时间戳
this.predictions = new Map(); // key -> 下次访问预测
}
recordAccess(key) {
const now = Date.now();
const timestamps = this.accessPatterns.get(key) || [];
timestamps.push(now);
// 只保留最后100次访问
if (timestamps.length > 100) {
timestamps.shift();
}
this.accessPatterns.set(key, timestamps);
this.updatePrediction(key);
}
updatePrediction(key) {
const timestamps = this.accessPatterns.get(key);
if (timestamps.length < 2) return;
// 计算平均间隔
let totalInterval = 0;
for (let i = 1; i < timestamps.length; i++) {
totalInterval += timestamps[i] - timestamps[i - 1];
}
const avgInterval = totalInterval / (timestamps.length - 1);
// 预测下次访问
const lastAccess = timestamps[timestamps.length - 1];
const predictedAccess = lastAccess + avgInterval;
this.predictions.set(key, predictedAccess);
// 在预测访问前预热
const warmupTime = predictedAccess - avgInterval * 0.1; // 提前10%
const delay = warmupTime - Date.now();
if (delay > 0 && delay < 3600000) { // 在下一个小时内
setTimeout(async () => {
await this.warmKey(key);
}, delay);
}
}
async warmKey(key) {
const value = await this.loader(key);
await cache.set(key, value, { ttl: 3600 });
}
}
多级缓存(L1/L2)
两级缓存
class TwoLevelCache {
constructor(l1, l2) {
this.l1 = l1; // 快速,小缓存(例如,内存中)
this.l2 = l2; // 慢速,大缓存(例如,Redis)
}
async get(key) {
// 首先检查L1
const l1Value = await this.l1.get(key);
if (l1Value) {
return l1Value;
}
// 检查L2
const l2Value = await this.l2.get(key);
if (l2Value) {
// 提升到L1
await this.l1.set(key, l2Value, { ttl: 300 }); // 5分钟
return l2Value;
}
return null;
}
async set(key, value, options = {}) {
// 在两个级别都设置
await this.l1.set(key, value, { ttl: 300, ...options });
await this.l2.set(key, value, options);
}
async invalidate(key) {
await this.l1.del(key);
await this.l2.del(key);
}
}
// 使用
const l1 = new InMemoryCache({ maxSize: 1000 });
const l2 = new RedisCache();
const cache = new TwoLevelCache(l1, l2);
写入时多级
class WriteThroughMultiTierCache {
constructor(tiers) {
this.tiers = tiers; // [L1, L2, L3, ...]
}
async get(key) {
for (const tier of this.tiers) {
const value = await tier.get(key);
if (value) {
// 提升到更高级别
this.promote(key, value);
return value;
}
}
return null;
}
async set(key, value, options) {
// 写入所有级别
const promises = this.tiers.map(tier =>
tier.set(key, value, options)
);
await Promise.all(promises);
}
async invalidate(key) {
const promises = this.tiers.map(tier => tier.del(key));
await Promise.all(promises);
}
async promote(key, value) {
// 只写入到更高级别
for (let i = 0; i < this.tiers.length - 1; i++) {
await this.tiers[i].set(key, value, { ttl: 300 });
}
}
}
CDN缓存失效
CDN清除
const CloudFront = require('aws-sdk/clients/cloudfront');
const cloudfront = new CloudFront({
region: 'us-east-1',
});
async function invalidateCDN(paths) {
const params = {
DistributionId: process.env.CLOUDFRONT_DISTRIBUTION_ID,
InvalidationBatch: {
CallerReference: Date.now().toString(),
Paths: {
Quantity: paths.length,
Items: paths,
},
},
};
const result = await cloudfront.createInvalidation(params).promise();
return result.Invalidation;
}
// 使用
await invalidateCDN(['/user/123', '/user/123/profile']);
CDN缓存标签
// 设置缓存头
app.get('/user/:id', async (req, res) => {
const user = await db.users.findById(req.params.id);
res.set('Cache-Control', 'public, max-age=3600');
res.set('Cache-Tag', `user-${user.id},premium-${user.isPremium}`);
res.json(user);
});
// 通过标签失效
async function invalidateByTag(tag) {
await cloudfront.createInvalidation({
DistributionId: process.env.CLOUDFRONT_DISTRIBUTION_ID,
InvalidationBatch: {
CallerReference: Date.now().toString(),
Paths: {
Quantity: 1,
Items: [`*`], // 失效所有,按标签过滤
},
},
}).promise();
}
数据库查询缓存失效
基于查询的失效
class QueryCache {
constructor() {
this.queryDependencies = new Map(); // 查询 -> 影响的表
}
async query(sql, params, loader) {
const cacheKey = this.getQueryKey(sql, params);
const cached = await cache.get(cacheKey);
if (cached) {
return cached;
}
const result = await loader(sql, params);
await cache.set(cacheKey, result, { ttl: 3600 });
// 跟踪依赖
this.trackDependencies(cacheKey, sql);
return result;
}
trackDependencies(cacheKey, sql) {
const tables = this.extractTables(sql);
this.queryDependencies.set(cacheKey, tables);
}
extractTables(sql) {
// 简单的表提取
const matches = sql.match(/FROM\s+(\w+)/gi) || [];
return matches.map(m => m.replace(/FROM\s+/i, '').toLowerCase());
}
async invalidateTable(table) {
for (const [cacheKey, tables] of this.queryDependencies) {
if (tables.includes(table)) {
await cache.del(cacheKey);
}
}
}
}
// 使用
const queryCache = new QueryCache();
async function getUsers() {
return await queryCache.query(
'SELECT * FROM users WHERE active = true',
[],
(sql, params) => db.query(sql, params)
);
}
// 当用户表变更时失效
await queryCache.invalidateTable('users');
最终一致性处理
版本向量
class VersionVectorCache {
constructor() {
this.versions = new Map(); // key -> { replicaId: version }
}
async get(key) {
const cached = await cache.get(key);
if (!cached) {
return null;
}
// 检查这个副本的版本是否是最新的
const myVersion = this.getVersion(key, this.replicaId);
const cachedVersion = cached._version[this.replicaId];
if (myVersion > cachedVersion) {
// 我们的版本更新 - 缓存是过时的
return null;
}
return cached._data;
}
async set(key, value) {
// 增加我们的版本
this.incrementVersion(key, this.replicaId);
const version = this.getVersion(key, this.replicaId);
await cache.set(key, {
_data: value,
_version: this.getFullVersion(key),
});
}
getVersion(key, replicaId) {
const versions = this.versions.get(key) || {};
return versions[replicaId] || 0;
}
incrementVersion(key, replicaId) {
const versions = this.versions.get(key) || {};
versions[replicaId] = (versions[replicaId] || 0) + 1;
this.versions.set(key, versions);
}
}
监控缓存命中率/未命中率
指标收集
class CacheMetrics {
constructor() {
this.hits = 0;
this.misses = 0;
this.errors = 0;
}
recordHit() {
this.hits++;
}
recordMiss() {
this.misses++;
}
recordError() {
this.errors++;
}
getHitRate() {
const total = this.hits + this.misses;
return total > 0 ? this.hits / total : 0;
}
getStats() {
return {
hits: this.hits,
misses: this.misses,
errors: this.errors,
hitRate: this.getHitRate(),
total: this.hits + this.misses,
};
}
reset() {
this.hits = 0;
this.misses = 0;
this.errors = 0;
}
}
// 使用
const metrics = new CacheMetrics();
async function get(key) {
const cached = await cache.get(key);
if (cached) {
metrics.recordHit();
return cached;
}
metrics.recordMiss();
const value = await loader(key);
await cache.set(key, value);
return value;
}
// 定期报告指标
setInterval(() => {
console.log('缓存统计:', metrics.getStats());
metrics.reset();
}, 60000);
常见反模式
1. 无锁的缓存旁路
// 坏:多个请求可以加载相同的数据
async function getUser(userId) {
const cached = await cache.get(`user:${userId}`);
if (cached) return cached;
const user = await db.users.findById(userId); // 多个请求命中数据库
await cache.set(`user:${userId}`, user);
return user;
}
// 好:使用锁定或合并请求
async function getUser(userId) {
return await coalescer.get(`user:${userId}`, async (key) => {
const id = key.split(':')[1];
return await db.users.findById(id);
});
}
2. 不一致的失效
// 坏:更新缓存但未更新数据库
async function updateUser(userId, updates) {
await cache.set(`user:${userId}`, updates); // 缓存已更新
// 忘记更新数据库!
}
// 好:先更新数据库,然后使缓存失效
async function updateUser(userId, updates) {
const user = await db.users.update(userId, updates);
await cache.del(`user:${userId}`);
return user;
}
3. 无TTL
// 坏:无过期,缓存永远不清理
await cache.set('user:123', userData);
// 好:总是设置TTL
await cache.set('user:123', userData, { ttl: 3600 });
4. 缓存一切
// 坏:缓存一切,包括很少访问的数据
async function getData(key) {
const cached = await cache.get(key);
if (cached) return cached;
const value = await db.get(key);
await cache.set(key, value);
return value;
}
// 好:根据访问模式有选择地缓存
async function getData(key) {
if (!shouldCache(key)) {
return await db.get(key);
}
const cached = await cache.get(key);
if (cached) return cached;
const value = await db.get(key);
await cache.set(key, value, { ttl: 3600 });
return value;
}
最佳实践
- 选择正确的策略
- TTL用于变化缓慢的数据
- 事件驱动用于关键数据
- 写入时用于读多写少的工作负载
- 缓存旁路用于一般用途
- 防止缓存踩踏
- 使用锁定或请求合并
- 实施后台刷新
- 考虑对高流量键使用软清除
- 优雅地处理失败
- 始终有数据库作为回退
- 记录缓存错误
- 实施断路器
- 监控和调整
- 跟踪命中率/未命中率
- 监控缓存大小
- 根据模式调整TTL
- 考虑一致性
- 了解你的一致性需求
- 使用适当的失效策略
- 小心地处理分布式场景