使用spring-cloud-gateway时,有时会报DnsNameResolverTimeoutException异常。堆栈信息类似:
Caused by: java.net.UnknownHostException: Failed to resolve 'cloudconnector.linkup-sage.com'at io.netty.resolver.dns.DnsResolveContext.finishResolve(DnsResolveContext.java:1047)at io.netty.resolver.dns.DnsResolveContext.tryToFinishResolve(DnsResolveContext.java:1000)at io.netty.resolver.dns.DnsResolveContext.query(DnsResolveContext.java:418)at io.netty.resolver.dns.DnsResolveContext.access$600(DnsResolveContext.java:66)at io.netty.resolver.dns.DnsResolveContext$2.operationComplete(DnsResolveContext.java:467)at io.netty.util.concurrent.DefaultPromise.notifyListener0(DefaultPromise.java:578)at io.netty.util.concurrent.DefaultPromise.notifyListeners0(DefaultPromise.java:571)at io.netty.util.concurrent.DefaultPromise.notifyListenersNow(DefaultPromise.java:550)at io.netty.util.concurrent.DefaultPromise.notifyListeners(DefaultPromise.java:491)at io.netty.util.concurrent.DefaultPromise.setValue0(DefaultPromise.java:616)at io.netty.util.concurrent.DefaultPromise.setFailure0(DefaultPromise.java:609)at io.netty.util.concurrent.DefaultPromise.tryFailure(DefaultPromise.java:117)at io.netty.resolver.dns.DnsQueryContext.tryFailure(DnsQueryContext.java:256)at io.netty.resolver.dns.DnsQueryContext$4.run(DnsQueryContext.java:208)at io.netty.util.concurrent.PromiseTask.runTask(PromiseTask.java:98)at io.netty.util.concurrent.ScheduledFutureTask.run(ScheduledFutureTask.java:153)at io.netty.util.concurrent.AbstractEventExecutor.runTask(AbstractEventExecutor.java:174)at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:167)at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:470)at io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:406)at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)at java.lang.Thread.run(Thread.java:833) Caused by: io.netty.resolver.dns.DnsNameResolverTimeoutException: [/10.43.0.10:53] query '42865' via UDP timed out after 5000 milliseconds (no stack trace available)
比如当前使用spring-cloud-starter-gateway
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId><version>3.1.3</version></dependency>
若使用的spring-boot-starter-webflux的版本
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webflux</artifactId><version>2.7.0</version></dependency>
则其中包含netty-resolver-dns的版本如下,报错信息就是从这个组件产生的。
<dependency><groupId>io.netty</groupId><artifactId>io.netty.resolver.dns</artifactId><version>4.1.77.Final</version></dependency>
下面分析一下使用spring-cloud-gateway的应用,在启动过程中执行的主要代码
首先,在应用启动时,会先查找DNS服务器的地址:
io.netty.resolver.dns.DefaultDnsServerAddressStreamProvider
创建http连接时,会先从/etc/resolv.conf中获取到nameserver、search domain、ndots、timeout、attempts等参数的值,用于创建解析器
io.netty.resolver.dns.DnsNameResolver
然后,创建连接池并获取连接
reactor.netty.resources.PooledConnectionProvider
获取连接,若连接不存在,则要解析域名并建立连接
reactor.netty.transport.TransportConnector
解析域名时会先查hosts文件,如果没查到,则从nameServer中查找
io.netty.resolver.dns.DnsNameResolver
从nameServer中查找时,查看searchDomain是否存在,或ndts是否为0,或域名后是否带点。如果是,则直接查域名;如果不是,则在域名后拼上domain再查
io.netty.resolver.dns.DnsResolveContext
实际的DNS查询,以及对结果的处理也是在这里做的
通过上面的代码,我们可以推测是出DnsNameResolverTimeoutException异常的几种原因,以及相对应的解决方法:
1、DNS查询一般是UDP的,UDP不可靠,容易timeout,可以把超时时间设置长一点。
2、如果查找到的domain较多,域名会拼上这些domain查询,可能多数情况下这种查询是没有意义的,不但浪费带宽,还会增加DNS服务器的压力,从而提高了DNS查询失败的概率。
3、如果域名对应的IP地址不变,可以直接配置到hosts文件中,这样就免去了DNS查询的过程。
4、DNS查询不但可以使用UDP,也可以使用TCP,可以在UDP查询失败后,采用TCP进行补查,这样可大大提高DNS查询成功概率,但前提是DNS服务器要开启TCP查询,同时io.netty.resolver.dns还要升级到4.1.105.Final或以上版本。
5、也可以将dns缓存的TTL设置大一点,这样可以减少DNS查询次数,缺点就是一旦域名对应的IP发生变化,客户端可能由于更新不及时造成调用失败。
以上解决方法都可以通过配置实现,下面提供一个例子:
import java.time.Duration;import org.springframework.cloud.gateway.config.HttpClientCustomizer;
import org.springframework.context.annotation.Bean;import reactor.netty.http.client.HttpClient;public class AppCfg {@Beanpublic HttpClientCustomizer httpClientCustomizer() {return new HttpClientCustomizer() {@Overridepublic HttpClient customize(HttpClient httpClient) {HttpClient newHttpClient = httpClient.resolver(spec -> {spec.queryTimeout(Duration.ofSeconds(10));spec.ndots(0);spec.retryTcpOnTimeout(true); // 需要io.netty.resolver.dns-4.1.105.Final或以上版本spec.cacheMaxTimeToLive(Duration.ofSeconds(100));});return newHttpClient;}};}
}
其它可以配置的参数可以在如下类中查到:
reactor.netty.transport.NameResolverProvider.NameResolverSpec
应用可以根据自身实际情况,配置上述配置或实现相应的接口,就能缓解甚至就解决DnsNameResolverTimeoutException异常的发生(之所以大多数据情况下只能缓解,是因为DNS查询一般是UDP的,存在丢包的可能性,不能保证UDP查询一定能成功)。