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


101

我们使用GSLB进行地理分布和负载平衡。每个服务都分配有一个固定的域名。通过一些DNS魔术,可以将域名解析为最接近服务器且负载最小的IP。为了使负载平衡起作用,应用程序服务器需要遵循DNS响应中的TTL,并在缓存超时时再次解析域名。但是,我找不到用Java做到这一点的方法。

该应用程序是在Linux(Centos 5)上运行的Java 5中。

Answers:


76

根据Byron的回答,您不能使用标志或调用来设置networkaddress.cache.ttl或将networkaddress.cache.negative.ttl其设置为系统属性,因为它们不是系统属性-它们是安全性属性。-DSystem.setProperty

如果要使用System属性触发此行为(因此可以使用-Dflag或call System.setProperty),则需要设置以下System属性:

-Dsun.net.inetaddr.ttl=0

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

但请注意:如果-D在启动JVM进程时不使用该标志,而是选择从代码中调用它:

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

必须先执行此代码,然后JVM中的任何其他代码才尝试执行网络操作。

这很重要,因为例如,如果您调用Security.setProperty.war文件并将该.war部署到Tomcat,则将无法正常工作:Tomcat在执行.war的代码之前就使用Java网络堆栈对其进行了初始化。由于这种“竞争条件”,通常-D在启动JVM进程时使用该标志更为方便。

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

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

但是请注意围绕这些属性的注释中的安全警告。仅当您有足够的信心确定自己不会受到DNS欺骗攻击的影响时,才执行此操作。


2
FQN为java.security.Security(至少在jdk7中)
Pablo Fernandez

1
仅作评论,这些安全警告主要与安全管理器和远程加载有关。对于任何在一定程度上信任DNS的普通服务器应用程序来说,减小TTL都是可以的。(但是,我认为0不是一个很好的最小值,对于大多数安全情况,非安全经理的默认值30s就可以了)。
eckes

3
系统属性是否也可以与OpenJDK一起使用,或者它是否特定于Oracle?
mhlz

67

Java具有一些非常奇怪的dns缓存行为。最好的选择是关闭dns缓存或将其设置为较低的数字(例如5秒)。

networkaddress.cache.ttl(默认值:-1)
指示从名称服务成功查找名称的缓存策略。该值被指定为整数,以指示缓存成功查找的秒数。值-1表示“永远缓存”。

networkaddress.cache.negative.ttl(默认值:10)
指示从名称服务查找名称失败的缓存策略。该值被指定为整数,以指示为不成功的查询而缓存故障的秒数。值为0表示“从不缓存”。值-1表示“永远缓存”。


7
注意:这不会禁用操作系统中的所有DNS缓存。只是在库中禁用Java自己的中断内存缓存。您可以在调用JVM时在命令行上简单地设置这些属性。
尼尔森2009年

2
我不知道“中断”是有效的。Java(出于安全原因)将永久缓存DNS条目,或者直到JVM重新启动为止(以先到者为准)。(据我所知)这是设计使然。可以在java.security策略文件中或在命令行中进行设置。每个设置都不相同。参考:rgagnon.com/javadetails/java-0445.html
Milner,

4
请注意,您不能将它们设置为系统属性(即,使用-D标志或System.setProperty),因为它们不是系统属性,而是安全性属性。
Les Hazlewood

6
该文档在1.7中稍有不同。具体来说,现在仅当存在安全管理器时才永久缓存:“默认行为是在安装安全管理器时永远缓存,而在未安装安全管理器时缓存实现特定的时间。” docs.oracle.com/javase/7/docs/technotes/guides/net/…–
Brett Okken

1
@迈克尔见System.getSecurityManager()。Java 8文档:docs.oracle.com/javase/8/docs/api/java/lang/…–
gesellix

22

这显然已在较新的版本(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();
        }
    }
}

16
是的,Java 1.5的默认值是无限缓存。Java 1.6和1.7的默认值为30秒。
迈克尔

7
1.7的文档指出,只有在不存在安全管理器的情况下,情况才可能成立:“默认行为是在安装安全管理器时永久缓存,并在实现安全性后的特定时间段内缓存管理器未安装。” docs.oracle.com/javase/7/docs/technotes/guides/net/…–
Brett Okken

1
@Michael愿意分享该信息的来源吗?
rustyx

4
@rustyx Oracle的1.6和1.7 JDK在jre / lib / security / java.security中的networkaddress.cache.ttl中具有此名称:未设置安全性#管理器时,默认行为是缓存30秒。” 因此,通过Java Web Start部署的小应用程序和应用程序仍将永远缓存,否则将持续30秒。
迈克尔

1
这是指向OpenJDK 8的java.security的代码指针,该代码指示没有安全管理器的TTL是30s:hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/f940e7a48b72/src/share/…。我在Mac OS X和Ubuntu 14.04上进行了测试。

18

为了扩展Byron的答案,我相信您需要编辑目录java.security中的%JRE_HOME%\lib\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文件在这里


5
除此之外,在使用tomcat6时,我不得不修改我的lib / security文件,因为无法通过编程方式或通过JAVA_OPTS变量设置networkaddress.cache.ttl或sun.net.inetaddr.ttl。
轻率

1
@bramp谢谢您,兄弟,我也面临着同样的问题,并通过使用您的评论和答案+1进行评论和回答来解决。
Bhavik Ambani

7

总结其他答案,<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将是不够的。您需要将系统属性设置disableWSAddressCachingtrue。与生存时间属性不同,可以将其设置为JVM参数或通过来设置System.setProperty

IBM已经在WebSphere处理DNS缓存怎样一个相当详细的具体位置。上面相关的部分是:

要禁用Web服务的地址缓存,您需要将其他JVM定制属性disableWSAddressCaching设置为true。使用此属性可以禁用Web服务的地址缓存。如果您的系统通常在许多客户端线程上运行,并且在wsAddrCache缓存上遇到锁争用,则可以将此自定义属性设置为true,以防止对Web服务数据进行缓存。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.