[eggjs/egg]httpclient问题

2025-11-04 981 views
1

最近做的一个功能中,需要用httpclient请求另一个服务器中的一个http接口。但是发现正常运行几天或者十几天后,会报ENOBUFS错误。并且重启应用也不行,必须重启系统。抓包发现httpclient请求目标服务器时使用的端口号越来越高,怀疑是端口号用尽导致的错误。请问请求目标服务器时使用的端口号越来越高是正常现象吗?是否有办法控制它只是用几十个端口?

回答

1

不正常,你的代码有 BUG。

3
Node Version: 10.15.0 Egg Version: 2.23.0 Platform: windows 10 旗舰版/windows server 2016 Mini Showcase Repository: https://github.com/sonzawau/egg-curl-port-incr

我生产中遇见的问题是有十几个定时任务,从另一个服务器中定时读取数据,拼装后存入sqlite数据库。在运行几天到几星期不等后,再次访问服务器报错(ENOBUFS),重启应用无法恢复,必须重启电脑。现在抓包发现通过curl每访问一次对方服务,tcp源端口号都会增加,最小复现代码中通过定时任务每10秒访问一次百度,也出现了此种现象。

1
async subscribe() {

        let resultDest = await this.ctx.curl('http://www.baidu.com', {
            gzip: true,
            timeout: 100000,
            timing: true
        });
        if (resultDest.status === 200) {
            let dataArray = resultDest.res.data;
            let dataobj = {
                CachedKey: "{\"url\":\"86267f60-81e3-11e9-bbc3-231f46191c24\",\"param\":{\"fields\":{}}}",
                CachedValue: dataArray,
                ExpireTime: new Date(Date.now() + 6000000000)
            };

        }
    }

代码仅此而已,实际代码中,前面有一个取目标URL字段的逻辑,后面有一个存sqllite的逻辑,存sqlite用sequelize 4.0完成。

7

这次大概一星期后,仍然出现错误,{"errno":"ENOBUFS","code":"ENOBUFS","syscall":"connect","address":"10.0.20.251","port":19998,"name":"ResponseError","path":"/WS.asmx/QueryFacePass","status":-1,"headers":{},"res":{"status":-1,"statusCode":-1,"statusMessage":null,"headers":{},"size":0,"aborted":false,"rt":0,"keepAliveSocket":true,"requestUrls":["http://10.0.20.251:19998/WS.asmx/QueryFacePass"],"timing":{"queuing":0,"dnslookup":0,"connected":0,"requestSent":0,"waiting":0,"contentDownload":0},"socketHandledRequests":1,"socketHandledResponses":0}}

4

抱歉,最近工作事情比较多,我现在看下本地能不能复现你说的问题。

8

你都没有配置 agent,没法管理 keep alive 的。。。

schedule/update_interface_25.js 加个全局的 agent 的配置吧(其实放到 extend/application.js 里面更恰当):

const http = require('http');
const agent = new http.Agent({
    keepAlive: true
});

然后调用的地方加个 agent 参数:

let resultDest = await this.ctx.curl('http://www.baidu.com', {
  gzip: true,
  timeout: 100000,
  timing: true,
  agent
});

这样才可以 reuse free socket,其实 egg 的 curl 方法调用的是 urllib,里面默认的 agent 不是 keepalive 的:

https://github.com/node-modules/urllib/blob/master/lib/urllib.js#L34

这里可以考虑改进下,毕竟现在默认应该都设置 keep alive 才对

4

我上面贴的ENOBUFS的保存信息中,有keepAliveSocket":true字样,应该默认是有keepalive的

3

@sonzawau http.agent 里面的 keepAlive 参数和 http 协议的 connection: keep-alive 不一样的。具体的可以看下 _http_agent.js 的逻辑,agent 是管理复用 socket 的,你可以按照我上面的修改方式改下你的样例代码再抓包看下。

实际上 http.agent 不是 keepalive 的话,本次请求结束 socket 就会被 destroy 掉,这是导致你用 wireshark 抓包看到每次端口都不一样的原因

8

这是修改后的代码:

const Subscription = require('egg').Subscription;

const http = require('http');
const agent = new http.Agent({
    keepAlive: true
});

class UpdateCache extends Subscription {
    // 通过 schedule 属性来设置定时任务的执行间隔等配置
    static get schedule() {
        return {
            interval: '10s', // 1 分钟间隔
            type: 'worker', // 指定所有的 worker 都需要执行
            immediate: true,
            //disable:true
        };
    }

    // subscribe 是真正定时任务执行时被运行的函数
    async subscribe() {
        //let destData = await this.ctx.service.dataQueryService.GetDataqueryById("0952dda0-7d1d-11e9-9f28-4d473cd910b0");

        let resultDest = await this.ctx.curl('http://www.baidu.com', {
            gzip: true,
            timeout: 100000,
            timing: true,
            agent
        });
        if (resultDest.status === 200) {
            let dataArray = resultDest.res.data;
            let dataobj = {
                CachedKey: "{\"url\":\"86267f60-81e3-11e9-bbc3-231f46191c24\",\"param\":{\"fields\":{}}}",
                CachedValue: dataArray,
                ExpireTime: new Date(Date.now() + 6000000000)
            };
            console.log(dataobj)
        }
    }
}

module.exports = UpdateCache;
0

好的,感谢您的回复

1

@sonzawau https://eggjs.org/zh-cn/core/httpclient.html#agent-httpagent ,这里咨询了下,egg 内置的 curl 确实开启了 keep-alive agent,只不过默认的配置针对 keepalive socket 的超时是 4s,你这里 10s 调用一次,就每次都创建新的 socket 可,解决办法还是得自己设置一个超时更久一些的全局 agent 覆盖掉的默认的配置,或者自己配置里面的 httpclient.httpAgent.freeSocketTimeout 设置为 15s

5

好的,感谢您百忙之中的指导