ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Java DNS 负缓存问题分析与解决方案

2026/10/1 14:34:39 拓冰建站 浏览量
Java DNS 负缓存问题分析与解决方案 适用读者:Java 后端 / 中间件研发,遇到过UnknownHostException偶发、频繁复现又难以定位的同学。一句话结论:JVM 会把一次瞬时 DNS 解析失败缓存 10 秒,这 10 秒内所有请求直接抛异常、根本不会去问 DNS;要治本,得改java.security里的负缓存 TTL。一、问题背景1.1 线上异常现象payment-service(10.0.0.15)服务调用下游接口:http://biz-api.example.com/api/v1/company/decide偶现未知主机名异常java.net.UnknownHostException: biz-api.example.com。完整异常堆栈:2026-09-29.07:00:09.360 catalina-exec-41 ERROR BusinessFrontFacadeImpl [queryBusinessType:79] a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6 - [渠道系统][前置模块]未知主机名 java.net.UnknownHostException: biz-api.example.com at java.net.InetAddress$CachedAddresses.get(InetAddress.java:764) at java.net.InetAddress.getAllByName0(InetAddress.java:1291) at java.net.InetAddress.getAllByName(InetAddress.java:1144) at java.net.InetAddress.getAllByName(InetAddress.java:1065) at org.apache.http.impl.conn.SystemDefaultDnsResolver.resolve(SystemDefaultDnsResolver.java:45) at org.apache.http.impl.conn.DefaultHttpClientConnectionOperator.connect(DefaultHttpClientConnectionOperator.java:112) at org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:376) at org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:393) at org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:236) at org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:186) at org.apache.http.impl.execchain.RetryExec.execute(RetryExec.java:89) at org.apache.http.impl.execchain.RedirectExec.execute(RedirectExec.java:110) at org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:185) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:83) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:108) at org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:56) at com.example.payment.common.util.HttpRequester.send(HttpRequester.java:235) at com.example.payment.common.util.HttpRequester.sendPostForm(HttpRequester.java:185) at com.example.payment.front.facade.impl.BusinessFrontFacadeImpl.queryBusinessType(BusinessFrontFacadeImpl.java:62) at com.example.rentcar.biz.impl.BusinessBizImpl.decideBusinessUserByPhone(BusinessBizImpl.java:1300)1.2 问题根因初步定位图示:请求 → 操作系统 DNS → 首次瞬时失败 → 写入 JVM 负缓存(10s)→ 窗口内所有请求直接抛异常 → 10s 后自动恢复运维核查 DNS 链路后发现:第一次 DNS 解析出现瞬时失败;JVM 将解析失败结果做内存缓存(DNS 负缓存),默认缓存时长 10 秒;在这 10 秒缓存窗口内,同一 JVM 实例上所有请求该域名的调用,不再走操作系统 DNS 查询,直接抛出UnknownHostException;等待缓存过期后,才会重新发起真实 DNS 查询,业务自动恢复。⚠️ 注意:JVM 的 DNS 缓存是 JVM 进程内存级缓存,独立于操作系统 DNS 缓存与/etc/resolv.conf配置。操作系统 DNS 正常,不代表 JVM 内部解析正常。二、Java DNS 缓存核心原理(JDK8 源码解析)2.1 核心组件说明java.net.InetAddress是 JDK 域名解析的核心类,维护两套内存缓存:组件作用InetAddressCachePolicy缓存策略控制器,控制正向 TTL、负向 TTL 时长,读取java.security配置addressCache正向缓存:保存解析成功的域名-IP 数组,ConcurrentHashMap存储negativeCache负向缓存:保存解析失败标记unknown_array,即 DNS 负缓存getAllByName0()核心执行方法:优先读缓存,缓存未命中才调用操作系统域名服务解析关键配置参数(JDK8)配置文件路径:JDK8:$JAVA_HOME/jre/lib/security/java.securityJDK11+:$JAVA_HOME/conf/security/java.security2.2 HttpClient4.x 调用链路Apache HttpClient4 默认使用SystemDefaultDnsResolver,底层直接调用 JDKInetAddress.getAllByName(host),因此会完整受 JVM DNS 缓存控制。// org.apache.http.impl.conn.SystemDefaultDnsResolver@OverridepublicInetAddress[]resolve(finalStringhost)throwsUnknownHostException{returnInetAddress.getAllByName(host);}完整调用链路:HttpClient.execute()→SystemDefaultDnsResolver.resolve()→InetAddress.getAllByName()→getAllByName0()2.3 源码流程拆解getAllByName0()首先调用getCachedAddresses(host)读取缓存;优先查正向缓存,未命中再查负向缓存 negativeCache。InetAddress[]addresses=getCachedAddresses(host);if(addresses==null){// 缓存为空,才真正调用操作系统DNS解析addresses=getAddressesFromNameService(host,reqAddr);}// 如果从缓存拿到失败标记unknown_array,直接抛异常,不会访问DNSif(addresses==unknown_array)thrownewUnknownHostException(host);真实 DNS 解析逻辑getAddressesFromNameService:当 DNS 解析失败,赋值addresses = unknown_array,success = false。catch(UnknownHostExceptionuhe){if(host.equalsIgnoreCase("localhost")){// localhost特殊处理}else{addresses=unknown_array;success=false;ex=uhe;}}cacheAddresses()将结果写入对应缓存:成功写入addressCache;解析失败写入**negativeCache**负缓存。privatestaticvoidcacheAddresses(Stringhostname,InetAddress[]addresses,booleansuccess){hostname=hostname.toLowerCase();synchronized(addressCache){cacheInitIfNeeded();if(success){addressCache.put(hostname,addresses);}else{// DNS负缓存写入位置negativeCache.put(hostname,addresses);}}}负缓存 TTL 读取逻辑:读取networkaddress.cache.negative.ttl,默认值10,单位秒,计算过期时间存入CacheEntry。重要:负缓存只作用于当前 JVM 进程,不同实例之间缓存完全隔离。某一台机器出现瞬时 DNS 抖动,只会影响该实例,其他实例不受影响。2.4 完整时序操作系统 / DNS 服务器JVM InetAddress 缓存业务线程操作系