java dns解析缓存之源码解析

java默认情况会缓存dns解析的结果,导致的结果是当域名对应的ip已经变化后,正在运行的java程序不会立刻知道ip的变化,而是仍然访问的变化前的ip。问题来了,默认情况java虚拟机会缓存dns解析结果多少秒?如何修改缓存时间呢?能不能把dns解析缓存关掉呢?就这些问题,我研究了下源码,以下是源码解析。

dns缓存机制

以获取一个域名www.1688.com对应的ip为例,其代码踪迹是这样的:

  1. InetAddress.getByName(“www.1688.com”)
  2. InetAddress.getAllByName(“www.1688.com”)[0]
  3. InetAddress.getAllByName(“www.1688.com”, null)
  4. InetAddress.getAllByName0(“www.1688.com”, null, true)
  5. InetAddress.getCachedAddress(“www.1688.com”)

InetAddress.getCachedAddress的代码如下:

可以看到cache有两个,即有效DNS解析的cache(addressCache)和无效DNS解析的cache(negativeCache),为了让dns解析效率更高java很聪明的把dns解析正确的域名保存下来了,方便后续再查时能最快的返回结果;同时也缓存了无效的域名,因为根据DNS解析的原理,往往无效的域名解析耗时比正常的域名解析时间要长,所以缓存无效的域名可以有效的避免浪费时间在查找无效域名上,提升代码性能。下面是java.net.InetAddress$Cache的源码:

从上面的Cache类的get方法可以看到:

  • 如果policy为0,那么cache的get方法就永远返回null,相当于cache不起作用了;
  • 如果policy为-1,那么cache缓存过的entry永远都不会过期,也就是说就算域名对应的ip变了,cache再也不更新ip了
  • 如果policy为n,根据“System.currentTimeMillis() + (policy * 1000)”,那么这个entry就可以在cache中存活n秒钟,即n秒后,该entry会被移除出cache

问题是policy怎么来的,看方法Cache.getPolicy()方法里面,主要是InetAddressCachePolicy类的get()和getNegative()方法。看sun.net.InetAddressCachePolicy类的源码:http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b27/sun/net/InetAddressCachePolicy.java#InetAddressCachePolicy

 

看到这个代码就恍然大悟了,policy的值可以来自两个地方(值根据需要自己设置,此处为示例):

  • 一个是在java.security文件中配置networkaddress.cache.ttl=1111networkaddress.cache.negative.ttl=2222
  • 另一个是在启动单数中添加这两个参数-Dsun.net.inetaddr.ttl=1111-Dsun.net.inetaddr.negative.ttl=2222

而且这两中配置方法有一个优先级,即第一种方法优先级高,但是第一种方法需要在打开Java中的SecurityManager,若没有打开则${java.home}/jre/lib/security/java.security文件的配置将不会生效,可以参考http://www.314p.com/gywm.aspx?lm=30&wzid=4077打开Java中的SecurityManager。

下面附上我本机的jdk1.6.0_35的${java.home}/jre/lib/security/java.security文件的部分源码:

以上就是默认的配置,可以看到

  • #networkaddress.cache.ttl=-1这一行是被注释掉的,也就是说默认的DEFAULT_POSITIVE,即30秒更新一次可访问的DNS解析
  • networkaddress.cache.negative.ttl=10没有被注释掉,说明不可访问的DNS解析缓存10秒

DNS解析缓存实现的一点思考

看了源码之后才发现里面有多处synchronized锁,主要是两块:

但是若仔细看会发现,对cache的synchronized是在getCachedAddress(host)方法上,也就是说每个获取域名的方法都需要调这个方法,而这个方法里面有对cache的synchronized,这会不会是一个性能考虑的点呢?这里是读cache,并不是写cache,为了保证写的时候(可能cache会扩容),度不会出问题,加了synchronized,感觉不值啊,这也是不建议有事没事调用InetAddress.getByName(host)的原因。

个人觉得,是否可以考虑把这个地方设计成Copy-On-Write的Map,也就是说在写cache的时候,copy一份写,读不加锁,写完后,把cache替换一下,可以保证数据的最终一致性。这样写性能还行,读没有任何锁,性能很多,对于获取域名这种场景,肯定是读远多于写的。

这里一篇关于Copy-On-Write的文章http://www.kankanews.com/ICkengine/archives/118889.shtml可以参考

看看大家的意见,欢迎抛砖。

 

 

 

发表评论

电子邮件地址不会被公开。

forty five ÷ = 9