如何让Java尊重DNS缓存超时?

时间:2009-08-10 18:49:48

标签: java dns gslb

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

该应用程序使用Java 5,在Linux(Centos 5)上运行。

7 个答案:

答案 0 :(得分:59)

Java有一些非常奇怪的dns缓存行为。最好的办法是关闭dns缓存或将其设置为5秒的低数字。

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

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

答案 1 :(得分:58)

根据Per Byron的回答,您无法使用networkaddress.cache.ttl标记或调用networkaddress.cache.negative.ttl-DSystem.setProperty设置为系统属性,因为这些不是系统属性 - 它们是安全性属性。

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

-Dsun.net.inetaddr.ttl=0

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

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

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

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

这一点非常重要,因为,例如,如果您在.war文件中调用Security.setProperty并将.war部署到Tomcat,这将无效:Tomcat使用Java网络堆栈进行初始化比你的.war代码被执行了。由于这种“竞争条件”,在启动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影响时才能这样做。

答案 2 :(得分:21)

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

答案 3 :(得分:17)

要扩展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文件here上的文档。

答案 4 :(得分:6)

总结其他答案,在<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。与生存时间属性不同,可以将其设置为JVM参数或通过System.setProperty)。

IBM有一篇关于WebSphere如何处理DNS缓存here的非常详细的文章。以上相关内容是:

  

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

答案 5 :(得分:1)

根据official oracle java propertiesselect cd.dept_no, d.dept_name, count(*) as num_current, sum(case when e.gender = 'M' then 1 else 0 end) as num_males, sum(case when e.gender = 'F' then 1 else 0 end) as num_females from current_dept_emp cd inner join departments d on cd.dept_no = d.dept_no inner join employees e on e.emp_no = cd.emp_no where cd.to_date = '9999-01-01' group by cd.dept_no order by cd.dept_no; 是特定于Sun实现的属性,“在将来的版本中可能不支持”。 “首选方法是使用安全属性”sun.net.inetaddr.ttl

答案 6 :(得分:0)

所以我决定查看java源代码,因为我发现官方文档有点混乱。我发现(针对 OpenJDK 11)的内容与其他人编写的内容基本一致。重要的是属性评估的顺序。

InetAddressCachePolicy.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 (FOREVER),如果他们持有以下值-1.如果未设置任何内容,则默认为 30 秒。事实证明,对于 OpenJDK 来说,情况几乎总是如此,因为默认情况下 java.security 不设置该值,只有一个负值。

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

顺便说一句,如果 networkaddress.cache.negative.ttl 未设置(从文件中删除),java 类中的默认值为 0。这方面的文档是错误的。这就是让我绊倒的原因。