JVM DNS Cache

背景

JVM DNS Cache机制,在应用程序使用域名来访问外部服务时可能会存在问题。

举个例子:

在做应用容灾秒级切换场景中,应用层通过域名访问中间件服务,若应用层DNS Cache没有及时更新,则无法做到秒级切换,因为DNS Cache中缓存的是旧的IP地址。

特别是,多数中间件服务会对外提供域名的方式来提供服务,对外屏蔽底层的高可用切换。应用程序往往是先解析域名,获得IP地址,再通过IP跟中间件进行数据传输。若IP地址迟迟没更新,即使中间件服务做了高可用切换,应用层还是感知不到。

本文对JVM DNS Cache机制进行探究分析。

JVM DNS解析域名是使用java.net.InetAddress这个类来实现域名解析服务。

常见的用法如下

       InetAddress inetAddress = InetAddress.getByName("csdn.net");
       System.out.println(inetAddress);

InetAddress源码剖析

查看InetAddress源码。

主要是这个内部类来缓存过期时间、地址。

验证

原理

通过定期解析域名地址,并设置休眠时间,查看缓存的过期时间

方法

通过反射机制查看Cache过期时间。

环境

使用JVM 11来作为验证环境

代码

import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.Security;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;


public class DnsCache {
    public static void main(String[] args) throws Exception {
        //   判断是否有Security Manager
        System.out.println(System.getSecurityManager());
        // negative取得到值
        System.out.println(Security.getProperty("networkaddress.cache.negative.ttl"));
        // 这个取不到值,返回结果是null
        System.out.println(Security.getProperty("networkaddress.cache.ttl"));
        printDns("www.baidu.com");

        System.out.println("Sleep 10 Second");
        TimeUnit.SECONDS.sleep(10);
        printDns("www.baidu.com");

        System.out.println("Sleep 19 Second");
        TimeUnit.SECONDS.sleep(19);
        printDns("www.baidu.com");

        System.out.println("Sleep 2 Second");
        TimeUnit.SECONDS.sleep(2);
        printDns("www.baidu.com");

        System.out.println("Sleep 10 Second");
        TimeUnit.SECONDS.sleep(10);
        printDns("www.baidu.com");
    }

    private static void printDns(String domain) throws UnknownHostException, NoSuchFieldException, IllegalAccessException {
        // do dns resolve
        InetAddress inetAddress = InetAddress.getByName(domain);
        System.out.println(inetAddress);
        Class inetAddressClass = java.net.InetAddress.class;
        final Field cacheField = inetAddressClass.getDeclaredField("cache");
        cacheField.setAccessible(true);
        final Map cacheMap = (Map) cacheField.get(inetAddressClass);
        cacheMap.forEach((k, v) -> {
            Class cacheEntryClass = v.getClass();
            try {
                Field expf = cacheEntryClass.getDeclaredField("expiryTime");
                expf.setAccessible(true);
                long expires = (Long) expf.get(v);
                Field af = cacheEntryClass.getDeclaredField("inetAddresses");
                af.setAccessible(true);
                InetAddress[] addresses = (InetAddress[]) af.get(v);
                List<String> ads = new ArrayList<String>(addresses.length);
                for (InetAddress add : addresses) {
                    ads.add(add.getHostAddress());
                }
                System.out.println(new Date() + ",Host:" + k + ", Cache Expires At:" + new Date(expires) + ",Address:" + ads);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        });
    }
}

结果

可以看到,在29秒前,Cache还是不变。

在31秒后,Cache发生了更改。

猜测缓存默认是30秒。

官方解释

JDK 8 & 11

Oracle JDK 8

https://docs.oracle.com/javase/8/docs/technotes/guides/net/properties.html

JDK 11 

Networking Properties (Java SE 11 & JDK 11 )

若设置-1,则永不过期(在JVM运行期间),需要重启JVM或者手动设置此值才会变更。

查看Security文件(java安装环境下,%JAVA_HOME%/conf/security/java.security)

大概翻译下:默认是永不过期。但是有个例外,如果没有设置Security Manager,则默认过期时间是30秒。

那么怎么查看有没有设置Security Manager呢

输出为Null,则表示没有设置,所以本文的JVM环境,DNS Cache TTL默认就是30秒

 System.out.println(System.getSecurityManager());

 其它版本JDK 

其它版本的JDK,可以在帮助中心,全局搜一下此参数。

Oracle Help Center - Search

JVM如何读取DNS TTL配置

源码

InetAddress中使用了sun.net.InetAddressCachePolicy来获取缓存策略,可以细看下这个类

读取过程

优先从Security Policy配置文件读取

networkaddress.cache.ttl

读取不到就读取系统变量

sun.net.inetaddr.ttl

若此系统变量还是读取不到,则判断有没有设置SecurityManager,没有的化,就设置默认值30.

如何查看JVM DNS TTL

有了上述的加载过程,就比较容易理解了。

import java.security.Security;

public class JVMDnsCacheManager {
    public static void main(String[] args) throws Exception {
        //   判断是否有Security Manager
        System.out.println(System.getSecurityManager());
        
        System.out.println(Security.getProperty("networkaddress.cache.ttl"));
        System.out.println(System.getProperty("sun.net.inetaddr.ttl"));
        
        // 设置后,可以取到值
        Security.setProperty("networkaddress.cache.ttl" , "20");
        System.out.println(Security.getProperty("networkaddress.cache.ttl"));
    }
}

如何修改JVM DNS TTL

方式一 使用代码更改TTL

注意,必须在使用InetAddress之前更改,若程序已经使用了InetAddress进行域名解析,则再使用程序的方式进行修改就不生效。(所以无法动态更改此参数,一旦设置,需要重启应用)

请看如下试验

import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.Security;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.text.SimpleDateFormat;
import java.util.concurrent.TimeUnit;


public class DnsCache {
    public static void main(String[] args) throws Exception {
        //   判断是否有Security Manager
        System.out.println(System.getSecurityManager());
        // negative取得到值
        System.out.println(Security.getProperty("networkaddress.cache.negative.ttl"));
        // 这个取不到值,返回结果是null
        System.out.println(Security.getProperty("networkaddress.cache.ttl"));

        //  change the Cache TTL to 10 Second
        System.out.println("We set the ttl to 10 Second");
        Security.setProperty("networkaddress.cache.ttl" , "10");
        printDns("www.baidu.com");

        System.out.println("Sleep 2 Second");
        TimeUnit.SECONDS.sleep(2);
        printDns("www.baidu.com");
        System.out.println("Sleep 9 Second,Total Sleep 11 Second");
        TimeUnit.SECONDS.sleep(9);
        printDns("www.baidu.com");
        TimeUnit.SECONDS.sleep(5);
        System.out.println("Sleep 5 Second,Total Sleep 16 Second");
        printDns("www.baidu.com");

        TimeUnit.SECONDS.sleep(15);

        // 验证在使用InetAddress进行域名解析后,再设置Cache TTL是否生效
        System.out.println("We set the ttl to 3 Second");
        Security.setProperty("networkaddress.cache.ttl" , "3");
        printDns("www.baidu.com");
        TimeUnit.SECONDS.sleep(5);
        System.out.println("Sleep 5 Second,Total Sleep 5 Second");
        printDns("www.baidu.com");
        TimeUnit.SECONDS.sleep(6);
        System.out.println("Sleep 6 Second,Total Sleep 11 Second");
        printDns("www.baidu.com");
    }

    private static void printDns(String domain) throws UnknownHostException, NoSuchFieldException, IllegalAccessException {
        // do dns resolve
        InetAddress inetAddress = InetAddress.getByName(domain);
        System.out.println(inetAddress);
        Class inetAddressClass = java.net.InetAddress.class;
        final Field cacheField = inetAddressClass.getDeclaredField("cache");
        cacheField.setAccessible(true);
        final Map cacheMap = (Map) cacheField.get(inetAddressClass);
        cacheMap.forEach((k, v) -> {
            Class cacheEntryClass = v.getClass();
            try {
                Field expf = cacheEntryClass.getDeclaredField("expiryTime");
                expf.setAccessible(true);
                long expires = (Long) expf.get(v);
                Field af = cacheEntryClass.getDeclaredField("inetAddresses");
                af.setAccessible(true);
                InetAddress[] addresses = (InetAddress[]) af.get(v);
                List<String> ads = new ArrayList<String>(addresses.length);
                for (InetAddress add : addresses) {
                    ads.add(add.getHostAddress());
                }
                System.out.println(new Date() + ",Host:" + k + ", Cache Expires At:" + new Date(expires) + ",Address:" + ads);
            } catch (NoSuchFieldException e) {
                throw new RuntimeException(e);
            } catch (IllegalAccessException e) {
                throw new RuntimeException(e);
            }
        });
    }
}

试验结果

所以最好在整个应用Init入口进行初始化设置,防止不生效。

方式二 修改Security配置文件

方式 三 设置JVM启动变量

如下所示,设置启动参数。

-Dsun.net.inetaddr.ttl=10

按照JVM读取DNS Cache TTL逻辑,正确读取到了sun.net.inetaddr.ttl设置的10秒

总结

影响JVM Dns Cache主要有两个值

  1. 一个是解析成功的的缓存时间(networkaddress.cache.ttl),默认不过期(-1);若没有设置Security Manager,则默认是30秒。
  2. 一个是解析不成功时的缓存时间(networkaddress.cache.negative.ttl),默认是10秒。设置成0,表示不缓存,设置成-1,表示一直缓存,不过期
  3. 推荐使用方式三,直接设置JVM启动参数的方式更改DNS Cache TTL值

  • 27
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值