使用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 {

	@Bean
	public HttpClientCustomizer httpClientCustomizer() {

		return new HttpClientCustomizer() {

			@Override
			public 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查询一定能成功)。

更多推荐