性能优化与缓存策略
性能优化与缓存策略
Express作为Node.js的流行框架,性能优化与缓存策略直接影响应用响应速度与用户体验。合理的缓存机制能显著减少数据库查询、降低服务器负载,而性能优化则确保应用在高并发下稳定运行。
理解Express中间件性能
Express中间件的执行顺序直接影响性能。不当的中间件顺序可能导致不必要的计算。例如,静态文件中间件应放在路由处理之前:
const express = require('express');
const app = express();
// 正确顺序:先处理静态资源
app.use(express.static('public'));
// 然后处理动态路由
app.get('/api/data', (req, res) => {
// 数据处理逻辑
});
避免在中间件中进行同步操作,这会阻塞事件循环。改用异步方式:
// 避免这样写
app.use((req, res, next) => {
const data = fs.readFileSync('large-file.json');
next();
});
// 应该这样写
app.use(async (req, res, next) => {
const data = await fs.promises.readFile('large-file.json');
next();
});
路由优化技巧
复杂路由结构可能导致性能下降。使用路由参数而非多个独立路由:
// 不推荐
app.get('/users/1', getUser1);
app.get('/users/2', getUser2);
// 推荐
app.get('/users/:id', getUser);
对于频繁访问的路由,考虑提前编译正则表达式:
const pattern = new RegExp('^/products/([0-9]+)$');
app.get(pattern, (req, res) => {
const productId = req.params[0];
// 处理逻辑
});
缓存策略实现
内存缓存
Node.js进程内存适合短期缓存。使用对象或Map实现简单缓存:
const cache = new Map();
app.get('/expensive-route', (req, res) => {
const cacheKey = req.originalUrl;
if (cache.has(cacheKey)) {
return res.json(cache.get(cacheKey));
}
// 模拟耗时计算
const result = computeExpensiveResult();
cache.set(cacheKey, result);
// 设置缓存过期时间
setTimeout(() => cache.delete(cacheKey), 60000);
res.json(result);
});
Redis缓存
对于分布式系统,Redis是更优选择:
const redis = require('redis');
const client = redis.createClient();
app.get('/api/products/:id', async (req, res) => {
const { id } = req.params;
const cacheKey = `product:${id}`;
try {
const cachedData = await client.get(cacheKey);
if (cachedData) {
return res.json(JSON.parse(cachedData));
}
const product = await db.products.findOne({ where: { id } });
await client.setEx(cacheKey, 3600, JSON.stringify(product));
res.json(product);
} catch (error) {
res.status(500).send('Server Error');
}
});
响应缓存头设置
正确设置HTTP缓存头能减少重复请求:
app.get('/static-data', (req, res) => {
const data = getStaticData();
// 设置强缓存
res.set('Cache-Control', 'public, max-age=3600');
// 或者协商缓存
res.set('ETag', generateETag(data));
res.json(data);
});
处理条件请求:
app.get('/conditional', (req, res) => {
const data = getData();
const etag = generateETag(data);
if (req.headers['if-none-match'] === etag) {
return res.status(304).end();
}
res.set('ETag', etag);
res.json(data);
});
数据库查询优化
减少数据库查询次数是性能关键。使用批量查询代替循环单条查询:
// 不推荐
app.get('/users', async (req, res) => {
const userIds = [1, 2, 3];
const users = [];
for (const id of userIds) {
users.push(await User.findById(id));
}
res.json(users);
});
// 推荐
app.get('/users', async (req, res) => {
const userIds = [1, 2, 3];
const users = await User.findAll({ where: { id: userIds } });
res.json(users);
});
使用投影只查询必要字段:
app.get('/users/light', async (req, res) => {
// 只查询name和email字段
const users = await User.find({}, 'name email');
res.json(users);
});
集群模式与进程管理
利用多核CPU提升性能:
const cluster = require('cluster');
const numCPUs = require('os').cpus().length;
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
const app = express();
// 应用配置
app.listen(3000);
}
使用PM2等进程管理器:
pm2 start app.js -i max
压缩响应数据
启用Gzip压缩减少传输量:
const compression = require('compression');
app.use(compression());
对特定路由禁用压缩:
app.use(compression({
filter: (req, res) => {
if (req.headers['x-no-compression']) {
return false;
}
return compression.filter(req, res);
}
}));
避免内存泄漏
常见内存泄漏场景包括全局变量存储请求相关数据:
// 错误示例
const userSessions = {};
app.get('/leak', (req, res) => {
userSessions[req.ip] = Date.now();
res.send('Data stored');
});
正确做法是使用WeakMap或设置过期:
const sessionCache = new Map();
setInterval(() => {
const now = Date.now();
for (const [key, value] of sessionCache) {
if (now - value.timestamp > 3600000) {
sessionCache.delete(key);
}
}
}, 60000);
实时监控与性能分析
使用性能监控工具:
const promBundle = require('express-prom-bundle');
const metricsMiddleware = promBundle({
includeMethod: true,
includePath: true
});
app.use(metricsMiddleware);
分析慢请求:
app.use((req, res, next) => {
const start = process.hrtime();
res.on('finish', () => {
const duration = process.hrtime(start);
const milliseconds = duration[0] * 1000 + duration[1] / 1e6;
if (milliseconds > 500) {
console.warn(`Slow request: ${req.method} ${req.url} took ${milliseconds}ms`);
}
});
next();
});
前端资源优化
虽然主要是后端话题,但Express也影响前端资源交付:
app.use('/static', express.static('public', {
maxAge: '1y',
immutable: true,
setHeaders: (res, path) => {
if (path.endsWith('.js')) {
res.set('Content-Encoding', 'br');
}
}
}));
启用HTTP/2推送:
const spdy = require('spdy');
const express = require('express');
const app = express();
app.get('/', (req, res) => {
const stream = res.push('/main.css', {
request: { accept: 'text/css' },
response: { 'content-type': 'text/css' }
});
stream.end('body { color: red; }');
res.send('<link rel="stylesheet" href="/main.css">');
});
spdy.createServer(options, app).listen(443);
负载测试与基准测试
使用autocannon进行压力测试:
const autocannon = require('autocannon');
autocannon({
url: 'http://localhost:3000',
connections: 100,
duration: 10,
requests: [
{ method: 'GET', path: '/api/data' }
]
}, console.log);
对比优化前后性能:
# 优化前
Requests: 2000, mean latency: 450ms
# 优化后
Requests: 2000, mean latency: 120ms
微优化技巧
小技巧也能带来提升:
// 使用res.send()替代res.json()当数据已经是字符串时
app.get('/fast', (req, res) => {
const data = JSON.stringify({ fast: true });
res.send(data); // 比res.json()快
});
// 预先设置常用header
app.use((req, res, next) => {
res.set('X-Powered-By', 'Express');
next();
});
避免在热路径中使用console.log:
// 生产环境中禁用
if (process.env.NODE_ENV === 'production') {
console.log = () => {};
}
缓存失效策略
常见的缓存失效模式:
// 时间基础失效
function getWithTTL(key, ttl, fetchFn) {
const cached = cache.get(key);
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.value;
}
const freshData = fetchFn();
cache.set(key, { value: freshData, timestamp: Date.now() });
return freshData;
}
// 事件驱动失效
eventBus.on('data-updated', (key) => {
cache.delete(key);
});
高级缓存模式
实现读写分离缓存:
async function readThroughCache(key, fetchFn) {
let value = await cache.get(key);
if (!value) {
value = await fetchFn();
await cache.set(key, value);
}
return value;
}
async function writeBehindCache(key, value, persistFn) {
await cache.set(key, value);
// 异步持久化
setImmediate(() => persistFn(key, value));
}
实战中的缓存策略
电商网站商品页示例:
const productCache = new Map();
app.get('/products/:id', async (req, res) => {
const { id } = req.params;
// 检查内存缓存
if (productCache.has(id)) {
return res.json(productCache.get(id));
}
// 检查Redis缓存
const redisData = await client.get(`product:${id}`);
if (redisData) {
const product = JSON.parse(redisData);
productCache.set(id, product); // 填充内存缓存
return res.json(product);
}
// 查询数据库
const product = await db.products.findOne({ where: { id } });
if (!product) {
return res.status(404).send('Not found');
}
// 设置缓存
productCache.set(id, product);
await client.setEx(`product:${id}`, 3600, JSON.stringify(product));
res.json(product);
});
性能优化检查清单
- 启用Gzip压缩
- 设置合适的缓存头
- 实现服务器端缓存
- 优化数据库查询
- 避免阻塞操作
- 使用集群模式
- 监控性能指标
- 定期负载测试
- 实现渐进式缓存失效
- 保持中间件精简高效
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:测试驱动开发支持
下一篇:中间件的基本概念与工作原理