Java 程序 DNS 问题

现象

运行 Java 应用时,可能会遇到 java.net.UnknownHostException 异常,如:

java.net.UnknownHostException: xxx.xxx.xxx.xxx

但是 ping 和 nslookup 能通。

解析

ping 和 nslookup 能通,说明操作系统的 DNS 解析是正常的。问题根源在于 Java 虚拟机 (JVM) 维护了自己独立的 DNS 缓存,它不一定会实时跟随系统的 DNS 状态。

🔎 JVM 的缓存机制是如何导致问题的?

Java 出于性能考虑,在启动时会从操作系统读取一次 DNS 配置,并在此后的一段时间内一直使用该配置,直到缓存过期。这个缓存行为由以下两个关键参数控制:

  • networkaddress.cache.ttl (成功解析缓存)

    • 默认值:30秒。
    • 作用:控制成功解析的域名和 IP 的缓存时间。
  • networkaddress.cache.negative.ttl (失败解析缓存)

    • 默认值:10秒。
    • 作用:控制解析失败的记录(这就是 UnknownHostException)的缓存时间。

间接影响:这个逻辑能解释你看到的偶尔、间歇性UnknownHostException。如果 DNS 服务有一次短暂的抖动,JVM 就会将这个失败的结果缓存起来。在此后的10秒内,所有新的连接请求都会直接抛出缓存的异常,而不会去重新查询 DNS。这甚至可能导致应用程序自带的重试机制失效,因为重试也发生在这个缓存窗口期内。

🛠️ 三步解决方案:优化 JVM DNS 缓存

1. 调整 JVM DNS 缓存策略 (最推荐)

最佳实践是将成功缓存(ttl)设为合理的值(如5-10秒),并将失败缓存(negative.ttl)设为0。

  • 方法一:通过 JVM 启动参数 (推荐):无需修改代码,适用于任何 Java 应用。

    java -Dsun.net.inetaddr.ttl=5 -Dsun.net.inetaddr.negative.ttl=0 -jar your-app.jar

    或通过环境变量设置 JAVA_TOOL_OPTIONS="-Dsun.net.inetaddr.ttl=5 -Dsun.net.inetaddr.negative.ttl=0"

  • 方法二:修改 java.security 配置文件:影响所有使用该 JDK 的 Java 程序。在 JAVA_HOME/conf/security/java.security 文件中找到并修改(或添加)以下两行:

    networkaddress.cache.ttl=5
    networkaddress.cache.negative.ttl=0

2. 临时规避:改用 IP 地址 (临时方案)

最快速绕过 DNS 问题的方法。将 JDBC URL 中的主机名直接替换为数据库的 IP 地址。例如:jdbc:postgresql://192.168.1.100:5432/mydb。但请注意,如果数据库 IP 将来发生变化,应用将无法连接。

3. 备选方案:使用操作系统级的 nscd 服务

启用 nscd (Name Service Cache Daemon) 可以统一管理缓存,Java 或可通过它受益。但该方法的效果不如前两种直接,不作为首选。

💎 总结

根本原因在于 JVM 内部的 DNS 解析和缓存机制与操作系统的解析是不同步的。最直接的解决方式是调整 JVM 的 DNS 缓存参数,特别是将失败缓存(negative.ttl)设置为 0。

参考