大量并发GET请求耗时过长的原因及优化方案咨询
并发10000个HTTP请求耗时过长的原因与优化方案
根本原因
- 操作系统资源限制:每个HTTP请求对应一个TCP连接,而每个连接会占用一个文件描述符。Linux默认单进程文件描述符上限仅1024,Windows也有类似套接字连接限制。10000个请求远超这个阈值,系统会排队等待可用描述符,直接拉长总耗时。
- 本地端口耗尽:客户端发起请求需要占用本地端口(默认范围32768-65535,共约3万个),短时间内发起10000个请求会快速耗尽可用端口。处于TIME_WAIT状态的端口默认要等待约1分钟才能释放,后续请求只能排队等待,导致总耗时飙升。
- 客户端连接池限制:Got库默认有并发连接数限制(默认最大并发数通常较低),即便创建了10000个Promise,底层仍会通过有限连接排队发送请求,无法实现真正的全并发。
- 服务器/代理限流:目标服务器或中间CDN可能对同一IP的并发请求做了限流,超出阈值的请求会被放入队列处理,这也是总耗时增加的常见原因。
优化方案
1. 调整操作系统资源限制
- Linux临时调整文件描述符上限:
ulimit -n 65535;永久修改需编辑/etc/security/limits.conf,添加:* soft nofile 65535 * hard nofile 65535 - Windows可通过注册表扩大套接字连接数,或执行命令调整端口范围:
netsh int ipv4 set dynamicport tcp start=1024 num=64511
2. 启用HTTP/2多路复用
HTTP/2支持单个TCP连接处理多个请求,大幅减少连接开销和端口占用。修改Got配置启用HTTP/2:
const got = require("got"); console.time('total'); const urlList = Array(10000).fill("https://example.com"); // 创建复用连接的HTTP/2客户端实例 const http2Client = got.extend({ http2: true, timeout: {request: 60000}, retry: {limit: 0}, pool: {maxSockets: 1000} // 根据系统能力调整 }); const pList = urlList.map(url => http2Client(url).catch(e => { console.log(e); return null; }) ); Promise.all(pList).then(result => { console.timeEnd('total'); }).catch(e => { console.log('error: ' + e); });
3. 控制并发请求批次
避免一次性发起所有请求,用并发控制库分批次发送,减少系统资源瞬间占用:
const got = require("got"); const pLimit = require("p-limit"); console.time('total'); const urlList = Array(10000).fill("https://example.com"); // 限制并发数为100(根据服务器和系统情况调整) const limit = pLimit(100); const pList = urlList.map(url => limit(() => got(url, { timeout: {request: 60000}, retry: {limit: 0} }).catch(e => { console.log(e); return null; })) ); Promise.all(pList).then(result => { console.timeEnd('total'); }).catch(e => { console.log('error: ' + e); });
4. 优化TCP参数
Linux下调整参数减少端口占用时间:
# 允许复用TIME_WAIT状态的端口 echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse # 缩短FIN_WAIT2超时时间 echo 30 > /proc/sys/net/ipv4/tcp_fin_timeout
5. 规避服务器限流
如果目标服务器有IP限流,可使用代理IP池分散请求来源,避免被服务器排队处理。
内容的提问来源于stack exchange,提问作者gacchan
相关产品推荐
相关产品推荐

