Categories
语言基础

JDK DNS解析策略

应开发内网命名服务的需求,需要调研Java是如何使用DNS的。以下文字是调研的一些结果,主要关注本地缓存、过期时间、多条A记录的选择策略以及如何自定义解析规则等方面。调研对象为JDK8。

解析逻辑

在现有的网络体系中,域名必须被解析为IP地址,才能正确的传递数据。JDK中java.net.InetSocketAddress表示一个Socket地址,当我们需要开启端口或者创建连接的时候需要创建InetSocketAddress对象。而InetSocketAddress内部使用java.net.InetAddress类将域名解析为IP地址。

@InetSocketAddress
    public InetSocketAddress(String hostname, int port) {
        checkHost(hostname);
        InetAddress addr = null;
        String host = null;
        try {
            addr = InetAddress.getByName(hostname);
        } catch(UnknownHostException e) {
            host = hostname;
        }
        holder = new InetSocketAddressHolder(host, addr, checkPort(port));
    }

InetAddress.getByName(host)方法是域名解析的入口类,接下来深入InetAddress内部来追溯域名解析的逻辑。

@InetAddress
    public static InetAddress getByName(String host)
        throws UnknownHostException {
        return InetAddress.getAllByName(host)[0];
    }

首先getByName方法能得出两个结论,第一如果域名有多个A记录JDK会一次获得所有的A记录,第二多个A记录的选择策略是简单的总是返回第一个A记录。继续跟踪代码到getAllByName0方法,大概逻辑是首先检查本地缓存,本地不存在就检查DNS服务器:

@InetAddress
    private static InetAddress[] getAllByName0 (String host, InetAddress reqAddr, boolean check)
        throws UnknownHostException  {

        InetAddress[] addresses = getCachedAddresses(host);

        /* If no entry in cache, then do the host lookup */
        if (addresses == null) {
            addresses = getAddressesFromNameService(host, reqAddr);
        }

        if (addresses == unknown_array)
            throw new UnknownHostException(host);

        return addresses.clone();
    }

检查缓存的部分比较简单,直接跳过,继续跟进检查DNS服务器部分,并最终跟进到Inet4AddressImpl类的lookupAllHostAddr方法,这是一个native方法。大概的逻辑就是首先检查本地hosts文件,然后向远端DNS服务器发起请求,并返回解析结果。

至此DNS的请求流程就完成了,接下来分析一下缓存策略。

缓存策略

InetAddress内部有两个缓存:”addressCache”缓存解析成功的结果;”negativeCache”缓存解析失败的结果。两个缓存都是InetAddress的内部类Cache的对象。关于缓存就必须提过期策略。Cache的过期策略有三种FOREVER\NEVER\TIMED,顾名思义是永远缓存、永不缓存和定时过期的意思。

接下来看JDK默认的过期策略与过期时间是怎样的,过期策略与过期时间的逻辑主要在InetAddressCachePolicy类中,该类中定义了一些静态代码块来给”addressCache”和”negativeCache”两个缓存设置过期策略。以下是”addressCache”缓存的过期策略设置规则:

//addressCache缓存的过期策略设置规则
@InetAddressCachePolicy

    cachePolicy = -1;
    if(var0 != null) {
        cachePolicy = var0.intValue();
        if(cachePolicy < 0) {
            cachePolicy = -1;
        }

        propertySet = true;
    } else if(System.getSecurityManager() == null) {
        cachePolicy = 30;
    }

var0是可以自定义的一些参数值,默认为空,下文会详细介绍。可以看到因为var0默认为空,所以过期策略取决于System.getSecurityManager(),关于Security Manager请参考。System.getSecurityManager()默认是空的,所以默认情况下”addressCache”的过期策略是30秒过期。”addressCache”存方的是DNS服务器的正确解析结果,所以JDK默认的DNS解析记录的过期策略是30秒过期。

接下来看如何自定义过期策略,主要的逻辑依然在InetAddressCachePolicy的静态代码快中。

//自定义addressCache缓存的过期策略逻辑
@InetAddressCachePolicy
    Integer var0 = (Integer)AccessController.doPrivileged(new PrivilegedAction<Integer>() {
        public Integer run() {
            String var1;
            try {
                var1 = Security.getProperty("networkaddress.cache.ttl");
                if(var1 != null) {
                    return Integer.valueOf(var1);
                }
            } catch (NumberFormatException var3) {
                ;
            }

            try {
                var1 = System.getProperty("sun.net.inetaddr.ttl");
                if(var1 != null) {
                    return Integer.decode(var1);
                }
            } catch (NumberFormatException var2) {
                ;
            }

            return null;
        }
    });

可以看到主要通过读取Security参数”networkaddress.cache.ttl”或者System参数”sun.net.inetaddr.ttl”的形式进行改变过期策略,其中”networkaddress.cache.ttl”的优先级要高。Security参数可以通过修改${JAVA_HOME}/jre/lib/security/java.security的方式生效,而System参数可以通过在java启动脚本上添加”-D”参数的形式生效。

//通过System参数设置过期策略
-Dsun.net.inetaddr.ttl=-1 //永不过期
-Dsun.net.inetaddr.ttl=0  //立即过期
-Dsun.net.inetaddr.ttl=10 //10秒过期

自定义解析规则

如果想改变首先解析本地hosts文件然后发送DNS请求的解析方式的话,可以通过自定义NameService的方式自定义解析规则。首先看看JDK创建NameService的流程:

  • 第一步:创建Inet4AddressImpl类,Inet4AddressImpl类会调用native方法进行默认解析,默认的NameService就是使用Inet4AddressImpl类进行域名解析。
  • 第二步:查看是否指定了NameService的实现类,通过查看System参数”sun.net.spi.nameservice.provider.1 2 3…”
  • 第三步:如果没有指定,则创建默认NameService。
//创建NameService的流程
@InetAddress

    static {
        // create the impl
        impl = InetAddressImplFactory.create();

        // get name service if provided and requested
        String provider = null;;
        String propPrefix = "sun.net.spi.nameservice.provider.";
        int n = 1;
        nameServices = new ArrayList<NameService>();
        provider = AccessController.doPrivileged(
                new GetPropertyAction(propPrefix + n));
        while (provider != null) {
            NameService ns = createNSProvider(provider);
            if (ns != null)
                nameServices.add(ns);

            n++;
            provider = AccessController.doPrivileged(
                    new GetPropertyAction(propPrefix + n));
        }

        // if not designate any name services provider,
        // create a default one
        if (nameServices.size() == 0) {
            NameService ns = createNSProvider("default");
            nameServices.add(ns);
        }
    }

通过观察createNSProvider方法的代码,发现如果provider=“default”,则创建默认的NameService使用Inet4AddressImpl进行域名解析,否则使用JDK ServiceLoader机制,从classpath下加载sun.net.spi.nameservice.NameServiceDescriptor的实现类,并跟provider进行匹配,并最终创建NameService。

综上所述自定以解析规则的步骤大概是,首先通过System参数”sun.net.spi.nameservice.provider.1 2 3…”定义一系列NameServiceDescriptor的实现类;其次自定义sun.net.spi.nameservice.NameServiceDescriptor与sun.net.spi.nameservice.NameService实现解析逻辑;最后按照ServiceLoader的规范进行配置。

版权声明:文章为作者辛勤劳动的成果,转载请注明作者与出处。

6 replies on “JDK DNS解析策略”

I precisely desired to say thanks again. I’m not certain the things I could possibly have achieved in the absence of the basics contributed by you about my area. Completely was a challenging circumstance for me, but being able to view a expert approach you dealt with it forced me to cry for contentment. Extremely grateful for the advice and believe you are aware of a powerful job that you’re accomplishing educating many others all through a site. I’m certain you’ve never come across all of us.

I am commenting to let you know of the fine encounter my cousin’s daughter experienced reading your site. She even learned many issues, not to mention what it’s like to have a wonderful giving mindset to have many people quite simply grasp chosen hard to do subject areas. You really exceeded our expectations. Many thanks for giving the insightful, healthy, educational and as well as easy thoughts on the topic to Jane.

I’m commenting to let you be aware of of the amazing discovery my child developed browsing the blog. She came to find a good number of details, including how it is like to have a great coaching character to get a number of people clearly completely grasp a number of advanced issues. You actually did more than her desires. I appreciate you for giving the insightful, healthy, educational and as well as easy thoughts on the topic to Jane.

Leave a Reply

Your email address will not be published. Required fields are marked *