使用Node进行高并发请求时需要注意DNS解析问题
发布于 6 年前 作者 blackmatch 6067 次浏览 来自 分享

前言

我们可以使用Node.js的 http 模块进行网络请求,比如使用 http.get 方法进行__GET__请求。当时在高并发请求的情况下,很容易出现如下的错误:

Error: getaddrinfo ENOTFOUND www.baidu.com www.baidu.com:80
    at GetAddrInfoReqWrap.onlookup [as oncomplete] (dns.js:57:26)

或者是这样的错误:

Error: queryA ETIMEOUT www.baidu.com
    at QueryReqWrap.onresolve [as oncomplete] (dns.js:197:19)

这两个错误都和 DNS 解析有关。

一种解决方案

在Google搜索一番就会找到如下的解决方案:

const opts = {
  host: 'www.baidu.com',
  family: 4
};

http.get(opts, (res) => {
  // handle the response
})

关键点是在请求的时候添加了 family 这个参数,我们先来看一下这个参数的官方解释:

family <number> IP address family to use when resolving host and hostname. Valid values are 4 or 6. When unspecified, both IP v4 and v6 will be used.

大概的意思是:这个参数可以指定解析 hosthostname 的时候所使用的IP地址族。可接受的参数为 46 。如果不指定,会同时使用IP-v4和IP-v6。

所以,这种解决方案就是让Node在调用 dns 模块解析域名的时候,指定使用IP-v4。这样能够在一定程度上解决问题。为什么说们说是一定程度上呢?因为经过我的验证,当请求并发量继续增大的时,还是会存在问题。

一些猜想

Node使用 dns 模块来提供域名解析服务,当我们调用 httpnet 等相关模块时,也会使用到 dns 模块来进行相关的操作。当然,dns 模块也可以单独使用,例如:

const dns = require('dns');

const len = 10000;

const run = () => {
  for (let i = 0; i < len; i++) {
    dns.resolve4('www.baidu.com', (err, addresses) => {
      if (err) {
        console.log(`i: ${i}`);
        throw err;
        process.exit(0);
      }
      
      console.log(addresses[0]);
    });
  }
};

run();

那为什么高并发请求的时候会出现问题呢?我的猜想是:dns 模块对域名解析有一定的性能限制,当并发量达到一定程度时,就会出现超时,从而导致各种问题。那为什么使用IP-v4就能得到一定程度的改善呢?我的猜想是:默认情况下,dns 模块使用的是IP-v4和IP-v6进行域名解析,在切换解析规则时或者使用不同的规则对性能有一定的依赖,当指定使用IP-v4的时候,能够使得 dns 模块发挥最佳的性能,从而使问题得到一定的改善。

一些建议

在一些高并发请求的场景(比如爬虫)下,很有可能会导致DNS无法正常解析的问题,比如使用 request 模块进行高并发请求的时候也会出现问题。除了上述提到的解决方案外,还应该合理控制好并发量。此外,也可以尝试使用高性能的DNS服务器来提供DNS解析的效率。

参考资料

http://www.ruanyifeng.com/blog/2016/06/dns.html

https://github.com/nodejs/node/issues/1644

8 回复

@XadillaX 死月大佬曾经修复过 dns 模块相关的bug,希望能从更深层次帮忙解释一下这个问题。谢谢~~

我印象中,linux服务器默认是不缓存dns解析结果的,每次http请求发出去都会请求dns服务器,所以其实也有不少dns cache方面的库。

https://www.npmjs.com/package/cache-dns

你看看能不能指定http请求使用自定义的dns实例,如果可以的话,就换成这种dns cache的库。

@alsotang 感谢大佬提供思路。我晚上回去试试。

还有65535端口的问题,服务端没有这个限制,但客户端有,一个客户端请求就会占用一个端口,准确的说是一个IP一个连接占用一个端口

linux 系统装个 dnsmasq 解决。

@waitingsong dnsmasq的方案太重了吧

今天又遇到这个问题,然后 Google 搜到了这个帖子,没想到有朝一日挖自己的坟贴。。。

这方法还挺管用,8k多次的请求用此方法不再报错。

回到顶部