阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 性能优化与缓存策略

性能优化与缓存策略

作者:陈川 阅读数:23700人阅读 分类: Node.js

性能优化与缓存策略

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);
});

性能优化检查清单

  1. 启用Gzip压缩
  2. 设置合适的缓存头
  3. 实现服务器端缓存
  4. 优化数据库查询
  5. 避免阻塞操作
  6. 使用集群模式
  7. 监控性能指标
  8. 定期负载测试
  9. 实现渐进式缓存失效
  10. 保持中间件精简高效

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌