如何使 Java 遵守 DNS 缓存超时?

我们使用 GSLB 进行地理分布和负载平衡。每个服务都被分配一个固定的域名。通过一些 DNS 魔术,域名被解析为一个 IP,最接近服务器,负载最少。为了实现负载平衡,应用服务器需要尊重来自 DNS 响应的 TTL,并在缓存超时时再次解析域名。然而,我无法找到一种方法来在 Java 中实现这一点。

应用程序在 Java5中,运行在 Linux 上(Centos 5)。

126675 次浏览

Java has some seriously weird dns caching behavior. Your best bet is to turn off dns caching or set it to some low number like 5 seconds.

Ttl (默认值: -1)
Indicates the caching policy for successful name lookups from the name service. The value is specified as as integer to indicate the number of seconds to cache the successful lookup. A value of -1 indicates "cache forever".

Networkaddress.cache. negative.ttl (默认值: 10)
指示名称服务未成功进行名称查找的缓存策略。该值指定为整数,以指示对于未成功查找缓存失败的秒数。值为0表示“从不缓存”。值 -1表示“永远缓存”。

为了详细说明 Byron 的答案,我相信您需要编辑 %JRE_HOME%\lib\security目录中的文件 java.security来实现这个更改。

以下是相关章节:

#
# The Java-level namelookup cache policy for successful lookups:
#
# any negative value: caching forever
# any positive value: the number of seconds to cache an address for
# zero: do not cache
#
# default value is forever (FOREVER). For security reasons, this
# caching is made forever when a security manager is set. When a security
# manager is not set, the default behavior is to cache for 30 seconds.
#
# NOTE: setting this to anything other than the default value can have
#       serious security implications. Do not set it unless
#       you are sure you are not exposed to DNS spoofing attack.
#
#networkaddress.cache.ttl=-1

关于 java.security文件 给你的文档。

这显然已经在较新的版本(SE 6和7)中得到修复。当使用 tcpdump 观察端口53活动时,运行下面的代码片段时,我经历了30秒的最大缓存时间。

/**
* http://stackoverflow.com/questions/1256556/any-way-to-make-java-honor-the-dns-caching-timeout-ttl
*
* Result: Java 6 distributed with Ubuntu 12.04 and Java 7 u15 downloaded from Oracle have
* an expiry time for dns lookups of approx. 30 seconds.
*/


import java.util.*;
import java.text.*;
import java.security.*;


import java.net.InetAddress;
import java.net.UnknownHostException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;


public class Test {
final static String hostname = "www.google.com";
public static void main(String[] args) {
// only required for Java SE 5 and lower:
//Security.setProperty("networkaddress.cache.ttl", "30");


System.out.println(Security.getProperty("networkaddress.cache.ttl"));
System.out.println(System.getProperty("networkaddress.cache.ttl"));
System.out.println(Security.getProperty("networkaddress.cache.negative.ttl"));
System.out.println(System.getProperty("networkaddress.cache.negative.ttl"));


while(true) {
int i = 0;
try {
makeRequest();
InetAddress inetAddress = InetAddress.getLocalHost();
System.out.println(new Date());
inetAddress = InetAddress.getByName(hostname);
displayStuff(hostname, inetAddress);
} catch (UnknownHostException e) {
e.printStackTrace();
}
try {
Thread.sleep(5L*1000L);
} catch(Exception ex) {}
i++;
}
}


public static void displayStuff(String whichHost, InetAddress inetAddress) {
System.out.println("Which Host:" + whichHost);
System.out.println("Canonical Host Name:" + inetAddress.getCanonicalHostName());
System.out.println("Host Name:" + inetAddress.getHostName());
System.out.println("Host Address:" + inetAddress.getHostAddress());
}


public static void makeRequest() {
try {
URL url = new URL("http://"+hostname+"/");
URLConnection conn = url.openConnection();
conn.connect();
InputStream is = conn.getInputStream();
InputStreamReader ird = new InputStreamReader(is);
BufferedReader rd = new BufferedReader(ird);
String res;
while((res = rd.readLine()) != null) {
System.out.println(res);
break;
}
rd.close();
} catch(Exception ex) {
ex.printStackTrace();
}
}
}

根据 Byron 的回答,不能通过使用 -D标志或调用 System.setProperty来将 networkaddress.cache.ttlnetworkaddress.cache.negative.ttl设置为系统属性,因为它们不是 System 属性——它们是 保安属性。

如果要使用 System 属性触发此行为(因此可以使用 -D标志或调用 System.setProperty) ,则需要设置以下 系统属性:

-Dsun.net.inetaddr.ttl=0

此系统属性将启用所需的效果。

但是要注意: 如果在启动 JVM 进程时没有使用 -D标志,而是选择从代码调用这个标志:

java.security.Security.setProperty("networkaddress.cache.ttl" , "0")

在 JVM 中的任何其他代码尝试执行网络操作之前执行此代码 必须的

这一点很重要,因为例如,如果在。战争文件,并部署。对于 Tomcat 来说,这是不可行的: Tomcat 使用 Java 网络栈来比您的。战争的准则已经执行。由于这种“竞争条件”,在启动 JVM 进程时使用 -D标志通常更方便。

如果不使用 -Dsun.net.inetaddr.ttl=0或调用 Security.setProperty,则需要编辑 $JRE_HOME/lib/security/java.security并在该文件中设置这些安全属性,例如。

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

但请注意围绕这些属性的评论中的安全警告。只有当你有理由相信你不易受到 DNS spoofing attacks的影响时才这样做。

要总结其他答案,可以在 <jre-path>/lib/security/java.security中设置属性 networkaddress.cache.ttl的值,以调整 DNS 查找的缓存方式。请注意,这是 没有一个系统属性,但一个安全属性。我可以设置如下:

java.security.Security.setProperty("networkaddress.cache.ttl", "<value>");

这也可以由系统属性 -Dsun.net.inetaddr.ttl设置,但是如果在其他地方设置安全属性,则不会覆盖该属性。

我还想补充一点,如果您在 WebSphere 中看到 Web 服务的这个问题,像我一样,设置 networkaddress.cache.ttl将是不够的。您需要将系统属性 disableWSAddressCaching设置为 true。与 time-to-live 属性不同,可以将其设置为 JVM 参数或通过 System.setProperty)。

IBM 有一篇关于 WebSphere 如何处理 DNS 缓存 给你的相当详细的文章。与上述内容相关的是:

要禁用 Web 服务的地址缓存,需要将另外一个 JVM 自定义属性 disableWSAddressCache 设置为 true。使用此属性可禁用 Web 服务的地址缓存。如果系统通常使用大量客户端线程运行,并且在 wsAddrCache 缓存上遇到锁争用,则可以将此自定义属性设置为 true,以防止缓存 Web 服务数据。

根据 甲骨文爪哇官方属性sun.net.inetaddr.ttl是 Sun 特定于实现的属性,“在未来的版本中可能不支持”。“首选的方法是使用安全属性”networkaddress.cache.ttl

因此,我决定查看 Java 源代码,因为我发现官方文档有点令人困惑。我所发现的(针对 OpenJDK 11)大多与其他人所编写的内容一致。重要的是性质评估的顺序。

Java (为了便于阅读,我省略了一些样板文件) :


String tmpString = Security.getProperty("networkaddress.cache.ttl");
if (tmpString != null) {
tmp = Integer.valueOf(tmpString);
return;
}
...
String tmpString = System.getProperty("sun.net.inetaddr.ttl");
if (tmpString != null) {
tmp = Integer.valueOf(tmpString);
return;
}
...
if (tmp != null) {
cachePolicy = tmp < 0 ? FOREVER : tmp;
propertySet = true;
} else {
/* No properties defined for positive caching. If there is no
* security manager then use the default positive cache value.
*/
if (System.getSecurityManager() == null) {
cachePolicy = 30;
}
}


您可以清楚地看到,安全属性首先被计算,系统属性次之,如果其中任何一个被设置为 cachePolicy,则将其值设置为该数字; 如果它们的值小于 -1,则设置为 -1 (FOREVER)。如果什么都没有设置,则默认为30秒。事实证明,OpenJDK 几乎总是这样,因为默认情况下,java.security不设置该值,只设置负值。

#networkaddress.cache.ttl=-1 <- this line is commented out
networkaddress.cache.negative.ttl=10

BTW if the networkaddress.cache.negative.ttl is not set (removed from the file) the default inside the java class is 0. Documentation is wrong in this regard. This is what tripped me over.