NodeJS中如何在主fetch函数外预建立并维持HTTP长连接?
如何预建立undici的TCP/HTTP连接以避免首次请求的连接开销
我使用undici fetch库编写了如下fetch函数:
async function call() { return await fetch('http://127.0.0.1:3000/', { dispatcher: new Agent({ keepAliveTimeout: 100000000, keepAliveMaxTimeout: 100000000 }), method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ 'a': 2, 'b': '0x', 'data': `${Data}`, 'limit': 100, 'loops': 1, 'value': '0' }) }).then((response) => response.json()) .then((responseData) => { //console.log(responseData); return responseData; }) .catch(error => console.warn(error)); };我运行的是高时效性应用,需要实现延迟低于1ms的POST请求,API服务器部署在本地。
经排查发现每次调用call函数都会新建TCP/HTTP连接,这是性能瓶颈,因此我添加了dispatcher参数配置keepalive,在undici fetch中似乎能生效。
由于首次请求会建立TCP连接,我编写了一个与call函数几乎一致的独立函数,用于在调用call()前预建立连接。
但我无法通过这种方式维持连接,只有多次调用call函数后,才能消除新建连接的开销。
请问如何在调用call()函数前预建立TCP/HTTP连接,避免首次调用时新建连接?
解决方案
核心问题分析
你当前的代码每次调用call函数都会新建一个Agent实例,每个Agent维护独立的连接池。预热函数用的是另一个Agent的连接池,和call函数的Agent完全无关,所以无法复用预热的连接。
具体解决步骤
1. 全局复用同一个Agent实例
将Agent实例从call函数中提取出来,全局创建一次,所有请求共用这个实例的连接池。
2. 编写正确的预热函数
使用同一个全局Agent发起预热请求,确保连接被存入连接池,后续call请求可以直接复用。
3. 优化Agent配置(针对本地场景)
调整连接池相关参数,适配本地高时效需求。
修改后的代码示例
const { Agent, fetch } = require('undici'); // 全局复用的Agent实例,配置长连接与连接池参数 const keepAliveAgent = new Agent({ keepAliveTimeout: 100000000, keepAliveMaxTimeout: 100000000, maxConnections: 10, // 本地服务可按需增大,确保有足够的空闲连接 connectTimeout: 100, // 本地连接超时设置为短时间 tls: false // 本地服务如果不用HTTPS,可显式关闭减少开销 }); // 预热连接函数:用同一个Agent发起轻量请求,预建立连接 async function prewarmConnection() { try { // 优先使用OPTIONS请求(如果服务器支持),比POST更轻量 const response = await fetch('http://127.0.0.1:3000/', { dispatcher: keepAliveAgent, method: 'OPTIONS' }); // 读取响应内容,确保连接被正确加入连接池(避免undici主动关闭未完成的连接) await response.text(); console.log('连接预热完成'); } catch (error) { // 如果服务器不支持OPTIONS,改用最小合法的POST请求 try { const response = await fetch('http://127.0.0.1:3000/', { dispatcher: keepAliveAgent, method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ a: 0, b: '', data: '', limit: 0, loops: 0, value: '0' }) }); await response.text(); console.log('连接预热完成(使用POST fallback)'); } catch (fallbackError) { console.warn('预热连接失败:', fallbackError); } } } // 修改后的call函数:使用全局复用的Agent async function call(Data) { try { const response = await fetch('http://127.0.0.1:3000/', { dispatcher: keepAliveAgent, method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' }, body: JSON.stringify({ 'a': 2, 'b': '0x', 'data': `${Data}`, 'limit': 100, 'loops': 1, 'value': '0' }) }); return await response.json(); } catch (error) { console.warn(error); throw error; // 建议抛出错误,让调用方处理,避免吞掉异常影响业务逻辑 } }
使用方式
- 在应用启动后、首次调用
call函数之前,先执行prewarmConnection(),等待预热完成。 - 后续所有
call请求都会复用预热好的TCP连接,彻底消除首次请求的连接建立开销。
补充说明
- 本地场景下,TCP连接建立本身耗时极短,但复用连接可以完全避免三次握手+TCP慢启动的开销,确保首次请求延迟稳定低于1ms。
- 若长期没有请求,
keepAliveTimeout配置会让连接保持存活,避免再次建立连接的开销。
内容的提问来源于stack exchange,提问作者Optimus Servers
相关产品推荐
相关产品推荐

