最近手上的某java应用频繁因网络问题而出现故障,同时也抛出一个问题:JAVA本身对DNS的缓存时间是多久?
对于非公司内部产品的疑问,第一反应Google之,大致有两种说法:
第1种:默认情况下networkaddress.cache.ttl=-1,代表永久缓存(配置文件路径: JAVA_HOME/jre/lib/security/java.security),就是在应用启动之后第一次DNS 解析成功的结果会一直cache到应用停止。显然在域名对应的IP有变更的时候,如果不重启应用就会造成故障。有部分同事以前也做过相关测试,认同这种说法。
第2种:jdk1.5和1.5之前的版本默认DNS 缓存时间是永久缓存,jdk 1.6以后与security manager策略有关(jboss tomcat 等app server默认不启用,详见此文),如果没有启用security manager ,默认DNS 缓存时间30秒。策略配置文件:JAVA_HOME/jre/lib/security/java.policy
根据上述说法,先看一下配置文件JAVA_HOME/jre/lib/security/java.security
?View Code BASH
# # 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 in this implementation # 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 # The Java-level namelookup cache policy for failed lookups: # # any negative value: cache forever # any positive value: the number of seconds to cache negative lookup results # zero: do not cache # # In some Microsoft Windows networking environments that employ # the WINS name service in addition to DNS, name service lookups # that fail may take a noticeably long time to return (approx. 5 seconds). # For this reason the default caching policy is to maintain these # results for 10 seconds. # # networkaddress.cache.negative.ttl=10 |
查看了jboss的run.sh脚本并未设置 java.security 相关参数,那我们的默认缓存时间应该是30 seconds
理论依据往往没有实验结果让人信服,于是又继续搜索相关的内容,终于在stackoverflow上找到了可以输出缓存内容的脚本。
我稍微修改了一下:
?View Code JAVA
import java.lang.reflect.Field; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.text.SimpleDateFormat; public class DNSCache { public static void main(String[] args) throws Exception { Date d = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); InetAddress.getByName("www.google.com"); try { InetAddress.getByName("nowhere.example.com"); } catch (UnknownHostException e) { } System.out.println("current time:" + sdf.format(d)); String addressCache = "addressCache"; System.out.println(addressCache); printDNSCache(addressCache); String negativeCache = "negativeCache"; System.out.println(negativeCache); printDNSCache(negativeCache); } private static void printDNSCache(String cacheName) throws Exception { Class<InetAddress> klass = InetAddress.class; Field acf = klass.getDeclaredField(cacheName); acf.setAccessible(true); Object addressCache = acf.get(null); Class cacheKlass = addressCache.getClass(); Field cf = cacheKlass.getDeclaredField("cache"); cf.setAccessible(true); Map<String, Object> cache = (Map<String, Object>) cf.get(addressCache); for (Map.Entry<String, Object> hi : cache.entrySet()) { Object cacheEntry = hi.getValue(); Class cacheEntryKlass = cacheEntry.getClass(); Field expf = cacheEntryKlass.getDeclaredField("expiration"); expf.setAccessible(true); long expires = (Long) expf.get(cacheEntry); Field af = cacheEntryKlass.getDeclaredField("address"); af.setAccessible(true); InetAddress[] addresses = (InetAddress[]) af.get(cacheEntry); List<String> ads = new ArrayList<String>(addresses.length); for (InetAddress address : addresses) { ads.add(address.getHostAddress()); } System.out.println(hi.getKey() + " "+new Date(expires) +" " +ads); } } } |
编译 javac -Xlint:unchecked DNSCache.java
执行 java DNSCache 得到结果:
current time:2012-07-27 11:35:31
addressCache
0.0.0.0 Fri Jul 27 11:36:01 CST 2012 [0.0.0.0]
www.google.com Fri Jul 27 11:36:01 CST 2012 [74.125.71.105, 74.125.71.106, 74.125.71.147, 74.125.71.99, 74.125.71.103, 74.125.71.104]
negativeCache
nowhere.example.com Fri Jul 27 11:35:41 CST 2012 [0.0.0.0]
解析成功的域名www.google.com 缓存时间正好30 seconds
解析失败的域名nowhere.example.com 缓存时间正好10 seconds
与前面的理论完全对上,而且我们还看到对于多条A记录的域名它会全部缓存起来,并不是只缓存其中的一条A记录。
这里又引出了一个疑问:对于多条A记录是采用轮循还是什么策略使用呢?
我们可以修改脚本测试一下:
?View Code JAVA
import java.lang.reflect.Field; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.text.SimpleDateFormat; public class DNSCache { public static void main(String[] args) throws Exception { System.out.println("start loop\n\n"); for(int i = 0; i < 30; ++i) { Date d = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println("current time:" + sdf.format(d)); InetAddress addr1 = InetAddress.getByName("www.google.com"); String addressCache = "addressCache"; System.out.println(addressCache); printDNSCache(addressCache); System.out.println("getHostAddress:" + addr1.getHostAddress()); System.out.println("*******************************************"); System.out.println("\n"); java.lang.Thread.sleep(10000); } System.out.println("end loop"); } private static void printDNSCache(String cacheName) throws Exception { Class<InetAddress> klass = InetAddress.class; Field acf = klass.getDeclaredField(cacheName); acf.setAccessible(true); Object addressCache = acf.get(null); Class cacheKlass = addressCache.getClass(); Field cf = cacheKlass.getDeclaredField("cache"); cf.setAccessible(true); Map<String, Object> cache = (Map<String, Object>) cf.get(addressCache); for (Map.Entry<String, Object> hi : cache.entrySet()) { Object cacheEntry = hi.getValue(); Class cacheEntryKlass = cacheEntry.getClass(); Field expf = cacheEntryKlass.getDeclaredField("expiration"); expf.setAccessible(true); long expires = (Long) expf.get(cacheEntry); Field af = cacheEntryKlass.getDeclaredField("address"); af.setAccessible(true); InetAddress[] addresses = (InetAddress[]) af.get(cacheEntry); List<String> ads = new ArrayList<String>(addresses.length); for (InetAddress address : addresses) { ads.add(address.getHostAddress()); } System.out.println(hi.getKey() + " "+new Date(expires) +" " +ads); } } } |
编译执行
?View Code BASH
start loop current time:2012-07-28 15:30:58 addressCache 0.0.0.0 Sat Jul 28 15:31:28 CST 2012 [0.0.0.0] www.google.com Sat Jul 28 15:31:28 CST 2012 [74.125.71.103, 74.125.71.104, 74.125.71.105, 74.125.71.106, 74.125.71.147, 74.125.71.99] getHostAddress:74.125.71.103 ******************************************* current time:2012-07-28 15:31:08 addressCache 0.0.0.0 Sat Jul 28 15:31:28 CST 2012 [0.0.0.0] www.google.com Sat Jul 28 15:31:28 CST 2012 [74.125.71.103, 74.125.71.104, 74.125.71.105, 74.125.71.106, 74.125.71.147, 74.125.71.99] getHostAddress:74.125.71.103 ******************************************* current time:2012-07-28 15:31:18 addressCache 0.0.0.0 Sat Jul 28 15:31:28 CST 2012 [0.0.0.0] www.google.com Sat Jul 28 15:31:28 CST 2012 [74.125.71.103, 74.125.71.104, 74.125.71.105, 74.125.71.106, 74.125.71.147, 74.125.71.99] getHostAddress:74.125.71.103 ******************************************* current time:2012-07-28 15:31:28 addressCache www.google.com Sat Jul 28 15:31:58 CST 2012 [74.125.71.104, 74.125.71.105, 74.125.71.106, 74.125.71.147, 74.125.71.99, 74.125.71.103] getHostAddress:74.125.71.104 ******************************************* #后面省略 |
结论:在缓存有效期内,取到的IP永远是缓存中全部A记录的第一条,并没有轮循之类的策略。
缓存失效之后重新进行DNS解析,因为每次域名解析返回的A记录顺序会发生变化(dig www.google.com测试可见),所以缓存中的数据顺序也变了,取到的IP也变化。
当然最可靠的还是看下源码实现,有研究的同学请告诉我一下:)
最后附上几种修改缓存时间的方法:
1. jvm启动参数里面配置-Dsun.net.inetaddr.ttl=value
2. 修改 配置文件JAVA_HOME/jre/lib/security/java.security相应的参数networkaddress.cache.ttl=value
3. 代码里直接设置:java.security.Security.setProperty(”networkaddress.cache.ttl” , “value”);
参考资料:
http://docs.oracle.com/javase/6/docs/api/java/net/InetAddress.html
http://kenwublog.com/java-dns-cache-setting
http://stackoverflow.com/questions/1835421/java-dns-cache-viewer
http://docs.jboss.org/jbossas/docs/Server_Configuration_Guide/4/html/Security_on_JBoss-Running_JBoss_with_a_Java_2_security_manager.html