使用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查詢一定能成功)。