You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node/ExpressJs无法同时处理1000请求的问题排查求助

Node.js Express 请求批量延迟问题分析与解决方案

场景复现

部署了两个Express服务器,代码如下:

Server A 代码

const express = require('express')
const axios = require('axios');
const app = express()
const port = 3000

function deepSleep (duration) {
  const start = new Date().getTime();
  while (true) {
    const current = new Date().getTime();
    if (current - start > duration) break;
  }
}

app.get('/:id', async (req, res) => {
  deepSleep(30); // 测试时便于观察
  await axios.get('http://localhost:3001/' + req.params.id);  // 调用Server B
  res.send('Hello World!');
})

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

Server B 代码

const express = require('express')
const app = express()
const port = 3001

app.get('/:id', (req, res) => {
  console.log(new Date().getTime());
  console.log(req.params);
  res.sendStatus(200);
})

const server = app.listen(port, () => {
  console.log(`Example app listening on port ${port}`)
})

调用端代码

const axios = require('axios');

for (let i = 0; i < 1000; i+= 1) {
  axios.get(`http://localhost:3000/${i}`);
}

问题现象

向Server A发送1000个请求时,预期每个请求会立即触发对Server B的调用,但实际是所有请求在Server A处理完成后,才批量打向Server B,导致Server B过载、请求耗时剧增。即使移除deepSleep的阻塞影响,axios的异步请求依然没有按预期立即触发Server B的连接。

原因分析

1. 同步阻塞直接卡死事件循环

deepSleep是同步阻塞代码,会完全占用Node.js的单线程事件循环。每个请求进入路由处理后,先执行30ms的while循环,这段时间事件循环无法处理任何其他任务——包括其他请求的处理、异步IO的回调。1000个请求就需要30*1000=30秒才能完成所有同步阻塞逻辑,之后才会批量执行所有axios请求,直接导致Server B瞬间被打满。

2. 异步场景下的隐性限制

即使移除deepSleep,问题依然存在,核心原因有两个:

  • TCP连接队列与请求处理顺序:Node.js的HTTP服务器会将到来的请求缓存到TCP队列中,Express会依次取出请求处理。调用端一次性发起1000个请求,会把Server A的TCP队列占满,Express只能逐个处理请求。
  • HTTP客户端并发限制:axios底层依赖Node.js的http模块,旧版本中maxSockets(最大并发连接数)默认是5,新版本虽改为Infinity,但操作系统的端口限制、TCP连接建立的开销,会导致大量axios请求被客户端队列缓存,最终集中到达Server B。

优化方案

1. 彻底移除同步阻塞代码

用异步延迟替代同步deepSleep,避免卡死事件循环:

// 替换为异步sleep函数
async function sleep(duration) {
  return new Promise(resolve => setTimeout(resolve, duration));
}

// 路由中使用
app.get('/:id', async (req, res) => {
  await sleep(30);
  await axios.get(`http://localhost:3001/${req.params.id}`);
  res.send('Hello World!');
})

2. 给Server A添加请求限流

控制Server A同时处理的请求数量,避免被打垮:

const rateLimit = require('express-rate-limit');
// 1秒内最多处理50个请求
const limiter = rateLimit({
  windowMs: 1000,
  max: 50,
  standardHeaders: true,
  legacyHeaders: false,
});
app.use(limiter);

3. 限制向Server B的并发请求数

用p-limit控制axios的并发量,避免瞬间压垮Server B:

const pLimit = require('p-limit');
// 最多同时发起20个请求到Server B
const limit = pLimit(20);

app.get('/:id', async (req, res) => {
  await sleep(30);
  // 用limit包裹axios请求,控制并发
  await limit(() => axios.get(`http://localhost:3001/${req.params.id}`));
  res.send('Hello World!');
});

4. 调用端控制并发

不要一次性发起1000个请求,分批限制并发:

const pLimit = require('p-limit');
const limit = pLimit(50);

const requestTasks = [];
for (let i = 0; i < 1000; i++) {
  requestTasks.push(limit(() => axios.get(`http://localhost:3000/${i}`)));
}
// 等待所有请求完成
await Promise.all(requestTasks);

5. Server B增强抗过载能力

给Server B添加限流和超时处理,提升稳定性:

const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
  windowMs: 1000,
  max: 100, // 1秒内最多处理100个请求
});
app.use(limiter);

// 添加请求超时处理
app.use((req, res, next) => {
  res.setTimeout(5000, () => {
    res.status(504).send('Request Timeout');
  });
  next();
});

内容的提问来源于stack exchange,提问作者Brian Phan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 07:32:17