【问题标题】:How to make Java honor the DNS Caching Timeout?如何让 Java 遵守 DNS 缓存超时?
【发布时间】:2010-11-18 09:34:03
【问题描述】:

我们使用 GSLB 进行地理分布和负载平衡。每个服务都分配了一个固定的域名。通过一些 DNS 魔术,域名被解析为最接近负载最少的服务器的 IP。为了使负载均衡发挥作用,应用服务器需要遵守来自 DNS 响应的 TTL,并在缓存超时时重新解析域名。但是,我想不出在 Java 中执行此操作的方法。

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

【问题讨论】:

    标签: java dns gslb


    【解决方案1】:

    根据 Byron 的回答,您不能使用 -D 标志或调用 System.setPropertynetworkaddress.cache.ttlnetworkaddress.cache.negative.ttl 设置为系统属性,因为这些不是系统属性 - 它们是安全性 属性。

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

    -Dsun.net.inetaddr.ttl=0
    

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

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

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

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

    这很重要,因为例如,如果您在 .war 文件中调用 Security.setProperty 并将该 .war 部署到 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
    

    但请注意这些属性周围的 cmets 中的安全警告。仅当您有理由确信自己不会受到 DNS spoofing attacks 的影响时才这样做。

    【讨论】:

    • FQN 是 java.security.Security(至少在 jdk7 中)
    • 只是评论,这些安全警告主要与安全管理器和远程加载有关。对于在某种程度上信任 DNS 的任何普通服务器应用程序,减少 TTL 是可以的。 (但我不认为 0 是一个好的最小值,在大多数情况下,非安全经理的默认值 30 秒就可以了)。
    • 系统属性是否也适用于 OpenJDK 还是 Oracle 特定的?
    • 在第一次查找后不再检查 DNS 并不能保护您免受欺骗攻击,它会使欺骗攻击成为永久性而不是暂时性的。
    【解决方案2】:

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

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

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

    【讨论】:

    • 注意:这不会禁用您操作系统中的所有 DNS 缓存。只需在库中禁用 Java 自己损坏的内存缓存即可。您可以在调用 JVM 时简单地在命令行上设置这些属性。
    • 我不知道“破碎”是否有效。 Java(出于安全原因)永远缓存 DNS 条目,或者直到 JVM 重新启动,以先到者为准。这(据我所知)是设计使然。可以在 java.security 策略文件中或在命令行中进行设置。每个人的设置都不同。参考:rgagnon.com/javadetails/java-0445.html
    • 请注意,您不能将这些设置为系统属性(即使用 -D 标志或 System.setProperty),因为它们不是系统属性 - 它们是安全属性。
    • 此文档在 1.7 中略有不同。具体来说,现在永久缓存仅在存在安全管理器时发生:“默认行为是在安装安全管理器时永久缓存,并在未安装安全管理器时缓存特定实现的时间段。” docs.oracle.com/javase/7/docs/technotes/guides/net/…
    • @Michael 见System.getSecurityManager()。 Java 8 文档:docs.oracle.com/javase/8/docs/api/java/lang/…
    【解决方案3】:

    这显然已在较新的版本(SE 6 和 7)中得到修复。在使用 tcpdump 观察端口 53 活动时,我在运行以下代码 sn-p 时遇到最长 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();
            }
        }
    }
    

    【讨论】:

    • 是的,Java 1.5 的默认值是无限缓存。 Java 1.6 和 1.7 有 30 秒的默认值。
    • 1.7 的文档表明这可能仅在安全管理器不存在的情况下才是正确的:“默认行为是在安装安全管理器时永久缓存,并为实现进行缓存未安装安全管理器的特定时间段。” docs.oracle.com/javase/7/docs/technotes/guides/net/…
    • @Michael 愿意分享该信息的来源吗?
    • @rustyx Oracle 的 1.6 和 1.7 JDK 在 jre/lib/security/java.security for networkaddress.cache.ttl 中有这个:“# 默认值是永远 (FOREVER)。出于安全原因,这个 #设置安全管理器时,将永久缓存。当未设置安全管理器时,默认行为是缓存 30 秒。所以通过 Java Web Start 部署的小程序和应用程序仍然永远缓存,否则为 30 秒。
    • 这是一个指向 OpenJDK 8 的 java.security 的代码指针,它表示如果没有安全管理器,TTL 为 30 秒:hg.openjdk.java.net/jdk8u/jdk8u/jdk/file/f940e7a48b72/src/share/…。我在 Mac OS X 和 Ubuntu 14.04 上对此进行了测试。
    【解决方案4】:

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

    【讨论】:

    • 要补充一点,当使用 tomcat6 时,我必须修改我的 lib/security 文件,因为以编程方式或通过 JAVA_OPTS 变量设置 networkaddress.cache.ttl 或 sun.net.inetaddr.ttl 没有工作。
    • @bramp 谢谢兄弟,我也面临同样的问题,并通过使用您的评论和答案 +1 进行评论和回答来解决。
    【解决方案5】:

    总结其他答案,在<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 缓存 here 的非常详细的文章。与上述相关的部分是:

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

    【讨论】:

      【解决方案6】:

      根据official oracle java propertiessun.net.inetaddr.ttl 是特定于 Sun 实现的属性,“可能在未来的版本中不受支持”。 “首选方法是使用安全属性”networkaddress.cache.ttl

      【讨论】:

        【解决方案7】:

        所以我决定查看 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。在这方面文档是错误的。这就是让我绊倒的原因。

        【讨论】:

        • 我会说文档是正确的,因为默认值为 10 秒。从 java.security 文件中删除配置后,它不再是原始默认行为。
        猜你喜欢
        • 2016-05-19
        • 2015-02-04
        • 2020-04-04
        • 1970-01-01
        • 2013-03-24
        • 2015-09-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多