缓存策略与实现方案
缓存是提升应用性能的重要手段,合理的缓存策略能显著减少服务器负载和响应时间。Express作为Node.js的流行框架,提供了灵活的缓存实现方式,包括内存缓存、分布式缓存和HTTP缓存等。
内存缓存实现
内存缓存是最简单的缓存形式,适合小型应用或开发环境。Node.js的memory-cache
模块可以快速实现内存缓存:
const express = require('express');
const cache = require('memory-cache');
const app = express();
// 缓存中间件
const memCache = (duration) => {
return (req, res, next) => {
const key = '__express__' + req.originalUrl;
const cachedContent = cache.get(key);
if (cachedContent) {
res.send(cachedContent);
return;
} else {
res.sendResponse = res.send;
res.send = (body) => {
cache.put(key, body, duration * 1000);
res.sendResponse(body);
};
next();
}
};
};
// 使用示例
app.get('/api/data', memCache(30), (req, res) => {
// 模拟耗时操作
setTimeout(() => {
res.json({ data: Date.now() });
}, 1000);
});
这种方案需要注意内存泄漏问题,当缓存数据量较大时需要设置合理的过期时间和内存限制。
Redis分布式缓存
对于生产环境,Redis是更可靠的选择。以下是Express集成Redis的完整示例:
const redis = require('redis');
const { promisify } = require('util');
const client = redis.createClient({
host: 'redis-server',
port: 6379
});
const getAsync = promisify(client.get).bind(client);
const setexAsync = promisify(client.setex).bind(client);
app.get('/api/users/:id', async (req, res) => {
const cacheKey = `user_${req.params.id}`;
try {
// 尝试从缓存获取
const cachedData = await getAsync(cacheKey);
if (cachedData) {
return res.json(JSON.parse(cachedData));
}
// 缓存未命中则查询数据库
const user = await User.findById(req.params.id);
// 设置缓存(30秒过期)
await setexAsync(cacheKey, 30, JSON.stringify(user));
res.json(user);
} catch (err) {
res.status(500).send(err.message);
}
});
Redis方案需要考虑缓存雪崩问题,可以通过随机过期时间或互斥锁解决:
// 解决缓存雪崩的互斥锁实现
const acquireLock = async (lockKey, expireTime = 10) => {
const result = await setnxAsync(lockKey, 'LOCK');
if (result === 1) {
await expireAsync(lockKey, expireTime);
return true;
}
return false;
};
HTTP缓存策略
Express可以通过设置响应头实现HTTP缓存:
app.get('/static/image.jpg', (req, res) => {
res.set({
'Cache-Control': 'public, max-age=86400', // 1天
'ETag': '123456789',
'Last-Modified': new Date('2023-01-01').toUTCString()
});
res.sendFile('/path/to/image.jpg');
});
对于动态内容,可以使用条件请求:
app.get('/api/news', async (req, res) => {
const news = await getLatestNews();
const etag = crypto.createHash('md5').update(JSON.stringify(news)).digest('hex');
if (req.headers['if-none-match'] === etag) {
return res.status(304).end();
}
res.set({
'ETag': etag,
'Cache-Control': 'no-cache'
}).json(news);
});
缓存更新策略
常见的缓存更新策略需要根据业务场景选择:
- Cache Aside模式:
// 读取
async function getProduct(id) {
let product = await cache.get(id);
if (!product) {
product = await db.query(id);
await cache.set(id, product);
}
return product;
}
// 更新
async function updateProduct(id, data) {
await db.update(id, data);
await cache.del(id); // 使缓存失效
}
- Write Through模式:
async function writeThroughUpdate(id, data) {
await cache.set(id, data); // 先更新缓存
await db.update(id, data); // 再更新数据库
}
- Write Behind模式:
const writeQueue = new Queue();
async function writeBehindUpdate(id, data) {
await cache.set(id, data);
writeQueue.add({ id, data }); // 异步批量写入数据库
}
缓存监控与调优
实现缓存监控中间件:
app.use((req, res, next) => {
const start = Date.now();
const originalSend = res.send;
res.send = function(body) {
const duration = Date.now() - start;
const cacheStatus = res.get('X-Cache') || 'MISS';
// 记录缓存命中率
statsd.increment(`cache.${cacheStatus.toLowerCase()}`);
statsd.timing('response_time', duration);
originalSend.call(this, body);
};
next();
});
使用Redis监控命令:
# 查看缓存命中率
redis-cli info stats | grep keyspace_hits
redis-cli info stats | grep keyspace_misses
# 内存使用情况
redis-cli info memory
缓存问题解决方案
缓存穿透解决方案:
// 布隆过滤器实现
const { BloomFilter } = require('bloom-filters');
const filter = new BloomFilter(1000, 0.01);
// 添加合法ID
validIds.forEach(id => filter.add(id));
app.get('/api/items/:id', async (req, res) => {
if (!filter.has(req.params.id)) {
return res.status(404).send('Not Found');
}
// ...正常处理逻辑
});
缓存击穿解决方案:
// 使用互斥锁
const mutex = new Mutex();
app.get('/api/hot-item', async (req, res) => {
const cacheKey = 'hot_item';
let data = await cache.get(cacheKey);
if (!data) {
const release = await mutex.acquire();
try {
// 双重检查
data = await cache.get(cacheKey);
if (!data) {
data = await fetchHotItem();
await cache.set(cacheKey, data, 60);
}
} finally {
release();
}
}
res.json(data);
});
多级缓存架构
大型应用可以采用多级缓存架构:
// 三级缓存示例:内存 -> Redis -> CDN
app.get('/api/popular', async (req, res) => {
// 1. 检查内存缓存
const memoryKey = `popular_${req.query.page}`;
const memoryCache = localCache.get(memoryKey);
if (memoryCache) return res.json(memoryCache);
// 2. 检查Redis缓存
const redisKey = `popular:${req.query.page}`;
const redisData = await redisClient.get(redisKey);
if (redisData) {
localCache.set(memoryKey, redisData, 10); // 回写到内存缓存
return res.json(JSON.parse(redisData));
}
// 3. 回源查询
const data = await fetchPopularData(req.query.page);
// 设置各级缓存
localCache.set(memoryKey, data, 10); // 内存缓存10秒
await redisClient.setex(redisKey, 3600, JSON.stringify(data)); // Redis缓存1小时
cdnCache.set(`/api/popular?page=${req.query.page}`, data); // CDN缓存
res.json(data);
});
缓存版本控制
对于长期缓存需要版本控制:
// 基于内容hash的版本控制
app.get('/static/:file', (req, res) => {
const filePath = path.join(__dirname, 'static', req.params.file);
const fileContent = fs.readFileSync(filePath);
const hash = crypto.createHash('sha1').update(fileContent).digest('hex').slice(0, 8);
res.set({
'Cache-Control': 'public, max-age=31536000', // 1年
'ETag': hash
});
res.sendFile(filePath);
});
前端引用时使用带hash的文件名:
<script src="/static/app.a1b2c3d4.js"></script>
<link href="/static/style.e5f6g7h8.css" rel="stylesheet">
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn