缓存技术
概述
缓存是一种提高系统性能的技术,通过将频繁访问的数据存储在快速访问的存储介质中,减少对慢速存储的访问次数。
核心概念
缓存的作用
- 提高响应速度: 减少数据获取时间
- 降低系统负载: 减少数据库和外部服务压力
- 提升用户体验: 更快的页面加载和操作响应
- 节省成本: 减少服务器资源消耗
缓存层次
用户请求 → CDN缓存 → 应用缓存 → 数据库缓存 → 磁盘存储
↑ ↑ ↑ ↑
最快 较快 中等 较慢
缓存策略
1. Cache-Aside (旁路缓存)
javascript
// 读取数据
const getUser = async (userId) => {
// 1. 先查缓存
let user = await cache.get(`user:${userId}`);
if (user) {
console.log('缓存命中');
return JSON.parse(user);
}
// 2. 缓存未命中,查数据库
console.log('缓存未命中');
user = await User.findById(userId);
if (user) {
// 3. 写入缓存
await cache.setex(`user:${userId}`, 3600, JSON.stringify(user));
}
return user;
};
// 更新数据
const updateUser = async (userId, userData) => {
// 1. 更新数据库
const user = await User.findByIdAndUpdate(userId, userData, { new: true });
// 2. 删除缓存
await cache.del(`user:${userId}`);
return user;
};
2. Write-Through (直写缓存)
javascript
// 直写缓存
const writeThroughCache = {
async set(key, value, ttl = 3600) {
// 同时写入缓存和数据库
await Promise.all([
cache.setex(key, ttl, JSON.stringify(value)),
this.writeToDatabase(key, value)
]);
},
async writeToDatabase(key, value) {
// 根据key确定数据库操作
if (key.startsWith('user:')) {
const userId = key.split(':')[1];
await User.findByIdAndUpdate(userId, value, { upsert: true });
}
}
};
// 使用直写缓存
const createUser = async (userData) => {
const user = new User(userData);
await user.save();
// 直写缓存
await writeThroughCache.set(`user:${user._id}`, user);
return user;
};
3. Write-Behind (异步写入)
javascript
// 异步写入缓存
class WriteBehindCache {
constructor() {
this.writeQueue = [];
this.isProcessing = false;
}
async set(key, value, ttl = 3600) {
// 立即写入缓存
await cache.setex(key, ttl, JSON.stringify(value));
// 加入写入队列
this.writeQueue.push({ key, value, timestamp: Date.now() });
// 异步处理队列
this.processQueue();
}
async processQueue() {
if (this.isProcessing || this.writeQueue.length === 0) {
return;
}
this.isProcessing = true;
try {
const batch = this.writeQueue.splice(0, 100); // 批量处理
for (const item of batch) {
try {
await this.writeToDatabase(item.key, item.value);
} catch (error) {
console.error('异步写入失败:', error);
// 重新加入队列
this.writeQueue.unshift(item);
}
}
} finally {
this.isProcessing = false;
// 如果队列还有数据,继续处理
if (this.writeQueue.length > 0) {
setTimeout(() => this.processQueue(), 1000);
}
}
}
async writeToDatabase(key, value) {
if (key.startsWith('user:')) {
const userId = key.split(':')[1];
await User.findByIdAndUpdate(userId, value, { upsert: true });
}
}
}
4. Refresh-Ahead (提前刷新)
javascript
// 提前刷新缓存
class RefreshAheadCache {
constructor(refreshThreshold = 0.8) {
this.refreshThreshold = refreshThreshold; // 80%过期时刷新
}
async get(key, fetchFunction, ttl = 3600) {
const cached = await cache.get(key);
if (!cached) {
// 缓存未命中,获取数据并缓存
const data = await fetchFunction();
await cache.setex(key, ttl, JSON.stringify(data));
return data;
}
const parsed = JSON.parse(cached);
const ttlInfo = await cache.ttl(key);
// 检查是否需要提前刷新
if (ttlInfo < ttl * (1 - this.refreshThreshold)) {
// 异步刷新缓存
this.refreshCache(key, fetchFunction, ttl);
}
return parsed;
}
async refreshCache(key, fetchFunction, ttl) {
try {
const data = await fetchFunction();
await cache.setex(key, ttl, JSON.stringify(data));
console.log(`缓存已刷新: ${key}`);
} catch (error) {
console.error(`缓存刷新失败: ${key}`, error);
}
}
}
// 使用提前刷新缓存
const userCache = new RefreshAheadCache();
const getUser = async (userId) => {
return await userCache.get(
`user:${userId}`,
() => User.findById(userId),
3600
);
};
Redis缓存
Redis基础操作
javascript
const redis = require('redis');
const client = redis.createClient({
host: 'localhost',
port: 6379,
password: 'your-password'
});
// 连接Redis
client.on('connect', () => {
console.log('Redis连接成功');
});
client.on('error', (err) => {
console.error('Redis连接错误:', err);
});
// 基础操作
class RedisCache {
async set(key, value, ttl = 3600) {
return await client.setex(key, ttl, JSON.stringify(value));
}
async get(key) {
const value = await client.get(key);
return value ? JSON.parse(value) : null;
}
async del(key) {
return await client.del(key);
}
async exists(key) {
return await client.exists(key);
}
async expire(key, ttl) {
return await client.expire(key, ttl);
}
async ttl(key) {
return await client.ttl(key);
}
}
Redis数据结构
字符串 (String)
javascript
// 字符串操作
await client.set('user:123:name', '张三');
await client.setex('user:123:name', 3600, '张三'); // 设置过期时间
const name = await client.get('user:123:name');
// 计数器
await client.incr('page:views');
await client.incrby('page:views', 10);
const views = await client.get('page:views');
哈希 (Hash)
javascript
// 哈希操作
await client.hset('user:123', {
name: '张三',
email: '[email protected]',
age: '25'
});
const user = await client.hgetall('user:123');
const name = await client.hget('user:123', 'name');
await client.hdel('user:123', 'age');
列表 (List)
javascript
// 列表操作
await client.lpush('recent:users', 'user:123');
await client.rpush('recent:users', 'user:456');
const users = await client.lrange('recent:users', 0, 9); // 获取最近10个用户
await client.ltrim('recent:users', 0, 99); // 只保留最近100个
集合 (Set)
javascript
// 集合操作
await client.sadd('user:123:followers', 'user:456', 'user:789');
const followers = await client.smembers('user:123:followers');
const count = await client.scard('user:123:followers');
const isFollower = await client.sismember('user:123:followers', 'user:456');
有序集合 (Sorted Set)
javascript
// 有序集合操作
await client.zadd('leaderboard', {
'user:123': 1000,
'user:456': 1500,
'user:789': 800
});
const topUsers = await client.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
const rank = await client.zrevrank('leaderboard', 'user:123');
const score = await client.zscore('leaderboard', 'user:123');
Redis集群和哨兵
javascript
// Redis集群配置
const Redis = require('ioredis');
const cluster = new Redis.Cluster([
{
host: 'redis-node-1',
port: 6379
},
{
host: 'redis-node-2',
port: 6379
},
{
host: 'redis-node-3',
port: 6379
}
]);
// Redis哨兵配置
const sentinel = new Redis({
sentinels: [
{ host: 'sentinel-1', port: 26379 },
{ host: 'sentinel-2', port: 26379 },
{ host: 'sentinel-3', port: 26379 }
],
name: 'mymaster'
});
内存缓存
Node.js内存缓存
javascript
// 简单的内存缓存
class MemoryCache {
constructor() {
this.cache = new Map();
this.timers = new Map();
}
set(key, value, ttl = 3600000) { // 默认1小时
// 清除现有定时器
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
}
// 设置缓存
this.cache.set(key, {
value,
timestamp: Date.now(),
ttl
});
// 设置过期定时器
const timer = setTimeout(() => {
this.delete(key);
}, ttl);
this.timers.set(key, timer);
}
get(key) {
const item = this.cache.get(key);
if (!item) {
return null;
}
// 检查是否过期
if (Date.now() - item.timestamp > item.ttl) {
this.delete(key);
return null;
}
return item.value;
}
delete(key) {
this.cache.delete(key);
if (this.timers.has(key)) {
clearTimeout(this.timers.get(key));
this.timers.delete(key);
}
}
clear() {
this.cache.clear();
for (const timer of this.timers.values()) {
clearTimeout(timer);
}
this.timers.clear();
}
size() {
return this.cache.size;
}
keys() {
return Array.from(this.cache.keys());
}
}
// 使用内存缓存
const cache = new MemoryCache();
cache.set('user:123', { name: '张三', email: '[email protected]' }, 300000); // 5分钟
const user = cache.get('user:123');
LRU缓存
javascript
// LRU (Least Recently Used) 缓存
class LRUCache {
constructor(capacity = 100) {
this.capacity = capacity;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) {
return null;
}
// 移动到末尾(最近使用)
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key, value) {
if (this.cache.has(key)) {
// 更新现有项
this.cache.delete(key);
} else if (this.cache.size >= this.capacity) {
// 删除最久未使用的项
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
delete(key) {
return this.cache.delete(key);
}
clear() {
this.cache.clear();
}
size() {
return this.cache.size;
}
}
缓存模式
1. 缓存穿透
javascript
// 缓存穿透防护
class CachePenetrationProtection {
constructor() {
this.cache = new Map();
this.bloomFilter = new Set(); // 简化的布隆过滤器
}
async get(key, fetchFunction) {
// 检查布隆过滤器
if (!this.bloomFilter.has(key)) {
return null; // 数据不存在
}
// 检查缓存
let value = this.cache.get(key);
if (value === undefined) {
// 缓存未命中,获取数据
value = await fetchFunction();
if (value !== null) {
// 数据存在,加入缓存和布隆过滤器
this.cache.set(key, value);
this.bloomFilter.add(key);
} else {
// 数据不存在,加入布隆过滤器防止重复查询
this.bloomFilter.add(key);
// 设置空值缓存,避免重复查询数据库
this.cache.set(key, null);
}
}
return value;
}
// 预热布隆过滤器
async warmupBloomFilter(keys) {
for (const key of keys) {
this.bloomFilter.add(key);
}
}
}
2. 缓存雪崩
javascript
// 缓存雪崩防护
class CacheAvalancheProtection {
constructor() {
this.cache = new Map();
}
async get(key, fetchFunction, baseTtl = 3600000) {
let item = this.cache.get(key);
if (!item || Date.now() > item.expireTime) {
// 缓存未命中或已过期
const value = await fetchFunction();
// 添加随机过期时间,避免同时过期
const randomTtl = baseTtl + Math.random() * 300000; // 增加0-5分钟随机时间
item = {
value,
expireTime: Date.now() + randomTtl
};
this.cache.set(key, item);
}
return item.value;
}
}
3. 缓存击穿
javascript
// 缓存击穿防护
class CacheBreakdownProtection {
constructor() {
this.cache = new Map();
this.locks = new Map();
}
async get(key, fetchFunction, ttl = 3600000) {
let item = this.cache.get(key);
if (!item || Date.now() > item.expireTime) {
// 获取或创建锁
let lock = this.locks.get(key);
if (!lock) {
lock = new Promise((resolve) => {
this.locks.set(key, resolve);
});
}
// 等待锁释放
await lock;
// 再次检查缓存(双重检查锁定)
item = this.cache.get(key);
if (!item || Date.now() > item.expireTime) {
// 获取数据
const value = await fetchFunction();
item = {
value,
expireTime: Date.now() + ttl
};
this.cache.set(key, item);
// 释放锁
this.locks.delete(key);
}
}
return item.value;
}
}
缓存监控
缓存命中率监控
javascript
// 缓存监控
class CacheMonitor {
constructor() {
this.stats = {
hits: 0,
misses: 0,
sets: 0,
deletes: 0
};
}
get hitRate() {
const total = this.stats.hits + this.stats.misses;
return total > 0 ? this.stats.hits / total : 0;
}
recordHit() {
this.stats.hits++;
}
recordMiss() {
this.stats.misses++;
}
recordSet() {
this.stats.sets++;
}
recordDelete() {
this.stats.deletes++;
}
getStats() {
return {
...this.stats,
hitRate: this.hitRate,
totalRequests: this.stats.hits + this.stats.misses
};
}
reset() {
this.stats = {
hits: 0,
misses: 0,
sets: 0,
deletes: 0
};
}
}
// 使用监控的缓存
const monitor = new CacheMonitor();
class MonitoredCache {
async get(key, fetchFunction) {
const cached = await cache.get(key);
if (cached) {
monitor.recordHit();
return cached;
}
monitor.recordMiss();
const value = await fetchFunction();
if (value !== null) {
await cache.set(key, value);
monitor.recordSet();
}
return value;
}
}
缓存预热
javascript
// 缓存预热
class CacheWarmup {
constructor(cache) {
this.cache = cache;
}
async warmupUserCache(userIds) {
console.log('开始预热用户缓存...');
const batchSize = 100;
const batches = [];
for (let i = 0; i < userIds.length; i += batchSize) {
batches.push(userIds.slice(i, i + batchSize));
}
for (const batch of batches) {
await Promise.all(
batch.map(async (userId) => {
try {
const user = await User.findById(userId);
if (user) {
await this.cache.set(`user:${userId}`, user);
}
} catch (error) {
console.error(`预热用户缓存失败: ${userId}`, error);
}
})
);
// 避免过载
await new Promise(resolve => setTimeout(resolve, 100));
}
console.log('用户缓存预热完成');
}
async warmupProductCache(productIds) {
console.log('开始预热商品缓存...');
const products = await Product.find({
_id: { $in: productIds }
});
await Promise.all(
products.map(async (product) => {
await this.cache.set(`product:${product._id}`, product);
})
);
console.log('商品缓存预热完成');
}
}
最佳实践
1. 缓存键设计
javascript
// 好的缓存键设计
const cacheKeys = {
// 用户相关
user: (userId) => `user:${userId}`,
userProfile: (userId) => `user:${userId}:profile`,
userPermissions: (userId) => `user:${userId}:permissions`,
// 商品相关
product: (productId) => `product:${productId}`,
productList: (category, page) => `products:${category}:page:${page}`,
productSearch: (query) => `search:${query}`,
// 订单相关
order: (orderId) => `order:${orderId}`,
userOrders: (userId, status) => `user:${userId}:orders:${status}`,
// 统计数据
stats: (type, date) => `stats:${type}:${date}`,
leaderboard: (type) => `leaderboard:${type}`
};
2. 缓存失效策略
javascript
// 缓存失效策略
class CacheInvalidation {
constructor(cache) {
this.cache = cache;
}
// 精确失效
async invalidateUser(userId) {
const keys = [
`user:${userId}`,
`user:${userId}:profile`,
`user:${userId}:permissions`
];
await Promise.all(
keys.map(key => this.cache.delete(key))
);
}
// 模式失效
async invalidateUserPattern(userId) {
const pattern = `user:${userId}:*`;
const keys = await this.cache.keys(pattern);
await Promise.all(
keys.map(key => this.cache.delete(key))
);
}
// 级联失效
async invalidateCascade(userId) {
// 失效用户相关缓存
await this.invalidateUser(userId);
// 失效用户订单缓存
await this.invalidateUserPattern(`user:${userId}:orders`);
// 失效统计数据缓存
await this.cache.delete('stats:users');
await this.cache.delete('stats:orders');
}
}
3. 缓存配置
javascript
// 缓存配置
const cacheConfig = {
// 用户缓存
user: {
ttl: 3600, // 1小时
maxSize: 10000,
strategy: 'lru'
},
// 商品缓存
product: {
ttl: 1800, // 30分钟
maxSize: 50000,
strategy: 'lru'
},
// 统计数据缓存
stats: {
ttl: 300, // 5分钟
maxSize: 1000,
strategy: 'fifo'
},
// 搜索结果缓存
search: {
ttl: 600, // 10分钟
maxSize: 5000,
strategy: 'lru'
}
};
// 根据配置创建缓存
class ConfigurableCache {
constructor(config) {
this.configs = config;
this.caches = new Map();
}
getCache(type) {
if (!this.caches.has(type)) {
const config = this.configs[type];
this.caches.set(type, new LRUCache(config.maxSize));
}
return this.caches.get(type);
}
async get(type, key, fetchFunction) {
const cache = this.getCache(type);
const config = this.configs[type];
let item = cache.get(key);
if (!item || Date.now() > item.expireTime) {
const value = await fetchFunction();
item = {
value,
expireTime: Date.now() + config.ttl * 1000
};
cache.set(key, item);
}
return item.value;
}
}
总结
缓存技术是提高系统性能的重要手段,需要综合考虑:
- 缓存策略: 选择合适的缓存策略(Cache-Aside、Write-Through等)
- 缓存存储: 选择合适的存储介质(内存、Redis等)
- 缓存模式: 防止缓存穿透、雪崩、击穿等问题
- 缓存监控: 监控缓存命中率和性能指标
- 缓存优化: 合理设计缓存键和失效策略
良好的缓存设计能够显著提升系统性能和用户体验。