
线上Java服务突然开始间歇性报错java.net.UnknownHostException: api.example.com at java.base/java.net.InetAddress$CachedAddresses.get(InetAddress.java:...) at java.base/java.net.InetAddress.getAllByName0(InetAddress.java:...)第一反应通常是DNS挂了于是马上登录服务器nslookup api.example.com结果正常。再执行ping api.example.com也能解析出IP。甚至手工curl https://api.example.com一样正常。可Java应用日志里UnknownHostException还是隔一段时间出现一次。更奇怪的是有时候重启Java服务以后问题会暂时消失。这种现场最容易产生一个判断nslookup都正常应该不是DNS问题吧其实不能这么下结论。因为nslookup此刻能解析成功不等于Java进程在报错那个时刻也成功解析过这个域名。真正需要排查的是Java进程 → Pod/主机Resolver → DNS Server/CoreDNS这一整条解析链。一、先给核心判断nslookup成功只能证明“现在这一次查询成功”假设Java在14:02:13报UnknownHostException你在14:05:30登录机器执行nslookup api.example.com成功。这两件事情并不冲突。因为14:02可能发生过DNS查询超时。CoreDNS瞬时不可用。网络抖动。DNS Server响应失败。Java缓存了失败结果。三分钟以后DNS已经恢复。你再执行nslookup当然正常。所以遇到这类问题第一个动作不是反复执行nslookup而是先把异常发生时间精确下来。然后确认那一刻DNS链路到底发生了什么。二、Java有自己的DNS缓存这是最容易漏掉的一层很多人以为Java每次访问api.example.com都会重新问DNS服务器。实际上并不一定。Java的InetAddress本身存在DNS解析缓存机制。包括Positive Cache解析成功以后缓存api.example.com → 10.10.2.18也包括Negative Cache解析失败以后短时间内记住这个域名解析失败了。这就会出现一个非常典型的现场。第一次请求时CoreDNS刚好抖了一下。Java解析失败UnknownHostException随后DNS已经恢复。这时候你执行nslookup api.example.com完全正常。但Java应用仍可能继续报错一段时间。因为它可能还在使用之前缓存下来的失败结果。三、所以“重启Java以后恢复”是一个非常重要的Evidence如果一个服务表现为DNS明明已经正常。Java一直UnknownHost。重启应用后马上恢复。这时候我会优先检查JVM DNS Cache。因为进程重启以后原来的解析缓存也会重新建立。这不能100%证明就是缓存问题。但这是一个非常明显的排查方向。Java里常见的相关配置包括networkaddress.cache.ttl networkaddress.cache.negative.ttl具体行为还要结合JDK版本。安全配置。运行环境来看。不要一看到UnknownHost就直接把TTL改成0。真正应该先确认Java到底缓存了什么以及缓存多久。四、为什么nslookup和Java看到的结果可能不同因为两者并不是完全相同的运行现场。nslookup是你在某个Shell里某一个时刻主动执行一次DNS查询。Java则是一个已经运行几小时、几天甚至几个月的长期进程。它有自己的DNS缓存。自己的连接生命周期。自己的线程状态。而且如果运行在容器里它看到的网络环境甚至可能和宿主机不同。所以Host上nslookup正常 ≠ Pod里DNS正常。这在Kubernetes环境里尤其常见。五、Kubernetes里第一件事一定要进出问题的Pod里查假设Java运行在Podpayment-service-7dd8f6c9d8-x8abc你登录Node执行nslookup api.example.com成功。这个结果价值其实有限。真正应该进入出问题的那个Pod。在同一个网络命名空间里执行解析测试。然后检查cat /etc/resolv.conf你真正想知道的是这个Java进程使用的Resolver到底是什么。有没有指向集群DNS。search domain是什么。options是什么。因为宿主机和Pod的/etc/resolv.conf很可能根本不是同一套配置。六、一个高频问题CoreDNS不是完全挂而是“偶尔处理不过来”如果CoreDNS彻底挂掉问题反而好排。所有服务都会大面积失败。真正麻烦的是偶发超时。例如平时99.9%的请求正常。只有高峰期一小部分DNS查询失败。Java应用就会表现为绝大多数请求成功。偶尔UnknownHostException这种时候你手工nslookup十次很可能一次都遇不到。所以需要看异常发生期间CoreDNS有没有请求量暴涨。CPU升高。Pod重启。延迟上升。上游DNS超时。日志异常。不要只问CoreDNS现在是不是RunningRunning并不代表当时每次DNS查询都及时成功。七、Search Domain和ndots也很容易把解析链变复杂Kubernetes里的/etc/resolv.conf经常会带一组search domain。比如类似default.svc.cluster.local svc.cluster.local cluster.local同时还有options ndots:...这意味着你请求一个名字时Resolver可能不会立刻把它当成最终FQDN。而是根据search规则尝试多个名字。假设应用访问api.example.com在某些配置下解析过程可能比你想象得复杂。如果Search Domain很多。ndots策略不合适。DNS本身又有延迟。就可能制造更多查询和更多失败机会。所以遇到Kubernetes DNS问题时不要只看域名本身。还要看Resolver到底发出了哪些查询。八、能用FQDN时尽量先排除搜索域干扰例如内部服务调用user-serviceResolver可能根据当前Namespace和search domain进行补全。这很方便。但排障时如果怀疑搜索路径。Namespace。短域名有问题可以尝试对比完整域名。例如user-service.default.svc.cluster.local如果完整FQDN稳定而短名称偶发异常方向就开始变得清晰。重点应该查search domain。Namespace。ndots。DNS查询次数。而不是继续怀疑Java HTTP Client。九、另一个容易忽略的问题应用里真正解析的域名可能不是你测试的那个这听起来很低级但线上特别常见。例如配置中心里写的是https://api.example.com你手工测试的也是api.example.com看起来一致。但某些环境实际运行时经过环境变量覆盖。配置中心动态配置。Service Discovery。代理配置。最终Java真正请求的是api-prod.internal或者api.example.com.甚至带了隐藏空格、错误后缀。所以看到UnknownHost以后不要凭记忆测试域名。应该直接从异常日志、配置和运行时参数里确认Java到底在解析哪个Host。十、配置里的一个小空格也可能制造非常诡异的UnknownHost例如配置API_HOSTapi.example.com最后多了一个空格。如果业务代码直接拼接、传入解析逻辑最终Host可能变成api.example.com 你手工执行nslookup api.example.com当然正常。因为你测试的根本不是Java实际收到的字符串。这种问题特别适合通过打印原始配置值。打印字符串长度。检查不可见字符快速确认。有时候根因并不是DNS基础设施。只是应用传给Resolver的Host本身就错了。十一、多个DNS Server也可能造成“偶发正常、偶发失败”/etc/resolv.conf里可能配置了多个nameserver。例如nameserver A nameserver B如果A稳定。B异常。或者其中一个到上游DNS的链路有问题那么解析表现就可能出现有时成功。有时超时。人工测试一次很难抓到。所以不能只验证“DNS能不能成功一次”。更应该在故障窗口连续观察查询延迟。失败比例。不同DNS Server结果。这比单次nslookup更接近真实应用行为。十二、最快排查顺序我建议直接按这8步走以后遇到nslookup正常但Java频繁UnknownHostException可以直接照这个顺序。第一步确认准确异常时间先拿到秒级时间点。不要隔十分钟以后再靠一次nslookup判断。第二步确认Java真正解析的Host直接从异常堆栈、运行配置和日志确认。排除错误域名。空格。配置覆盖。错误Namespace。第三步进入同一个Pod / 容器测试不要只在宿主机执行nslookup。保证测试环境和Java尽可能一致。第四步检查/etc/resolv.conf确认nameserver。search domain。options。有没有异常。第五步检查CoreDNS / DNS Server把DNS日志、延迟、重启和异常时间对齐。第六步检查JVM DNS Cache尤其是DNS恢复后Java仍持续失败。重启Java立即恢复这种场景。第七步检查短域名、FQDN和Search行为确认是不是Resolver补全路径造成额外失败。第八步持续复现而不是只查一次需要观察失败率。解析延迟。Java错误率。CoreDNS指标是否同步变化。十三、为什么“直接重启服务”是个危险的解决方式因为它经常真的有效。所以特别容易让问题就此结束。Java重启以后DNS缓存清掉。连接重新建立。配置重新加载。很多临时状态都消失。UnknownHost不再出现。然后大家得出结论Java偶发抽风。这反而会掩盖真正的问题。如果根因是CoreDNS高峰期抖动。Negative Cache。DNS Server偶发失败。Search配置不合理。那下一次高峰仍然会回来。所以重启只能算恢复手段不能算根因修复。十四、为什么不要把DNS TTL直接全部设成0有些人看到缓存导致问题以后第一反应是那就完全不要缓存。这同样可能带来新的问题。如果每次请求都需要重新解析DNS查询量会明显增加。CoreDNS压力更高。上游DNS依赖更强。高并发场景下甚至可能把偶发问题放大。所以正确思路不是不缓存。而是设置合理的缓存策略让它和服务发现、IP变化、DNS稳定性匹配。特别是服务IP会动态变化。云环境。Kubernetes。需要综合考虑。十五、Java长期缓存旧IP和UnknownHost其实是两种问题这里也容易混淆。如果Java缓存了一个已经失效的旧IP通常更容易看到的是连接超时。Connection refused。Connection reset。而不是UnknownHostException因为域名实际上已经成功解析过了。UnknownHost更直接指向名字解析阶段失败。所以排查时不要把DNS Cache所有问题全都归到UnknownHost。先确认错误发生在Resolve还是Connect这一层。这会省很多时间。十六、怎么做修复后的验证这类问题修完以后不要只执行一次nslookup然后说正常了。真正应该重新验证原来报错的Java进程。原来的Pod。原来的DNS路径。原来的高峰并发。至少持续观察UnknownHostException数量。DNS请求失败率。DNS查询延迟。CoreDNS资源。Java重启前后表现。如果修改的是DNS Cache配置还要观察缓存失效以后解析是否稳定。如果修改的是CoreDNS容量要重新制造接近原来的查询压力。这样才能证明问题不是暂时消失。十七、ChatGPT、Codex在这类问题里怎么用更有效不要只告诉它nslookup正常但是Java UnknownHost怎么办最好一起给异常时间。完整UnknownHost堆栈。实际Host。Pod里的/etc/resolv.conf。Java版本。DNS缓存相关配置。CoreDNS日志。Pod重启记录。问题是否在重启Java后恢复。这样ChatGPT、Codex才能把问题分成JVM缓存。Pod Resolver。CoreDNS。Search Domain。应用配置几个方向。而不是给你一堆泛化DNS建议。这种问题最适合AI做的其实是把跨Java、Linux、Kubernetes的Evidence串起来。十八、Plus和Pro怎么判断如果你只是偶尔让ChatGPT、Codex分析一次UnknownHost。看几份DNS配置。检查一段Java调用。整理排查步骤。这种集中任务Plus通常已经够用。如果你的日常已经变成持续排查Java微服务。Kubernetes。CoreDNS。Service Discovery。Ingress。应用日志。网络Trace。还需要Codex修改配置、补监控、跑测试和持续验证这种多系统、多轮、长时间排障已经成为日常那Pro会更适合。判断标准不是DNS问题难不难。而是ChatGPT、Codex是不是已经长期参与整条线上网络排障链。最后nslookup明明正常为什么Java还是频繁报UnknownHostException因为“我现在手工查到DNS正常”和“Java报错那一刻解析成功”完全不是一回事。真正需要看的是异常时间。Java实际Host。Pod内Resolver。CoreDNS状态。Search Domain。JVM DNS Cache。所以以后再遇到这类问题不要停在nslookup能不能成功更完整的排查链应该是异常时间 → 实际Host → Pod内解析 → resolv.conf → CoreDNS → JVM Cache → TTL / Search → 持续验证一旦把Shell里的DNS测试和Java进程真正经历的解析路径分开很多看起来“DNS明明正常Java为什么还报错”的问题就不再玄学了。持续分享 Codex、大模型开发与 AI 编程实战内容也整理了稳定的 Plus/Pro 订阅渠道有需要下方可自取。