【轉載】關於java dns cache (域名緩存時間)

今天剛剛碰到一個JVM緩存DNS的引起的問題,分享一下一個同事的博文:

 

------------------------------------------------------醜陋的分割線---------------------------------------------

最近手上的某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

 

來源:飄零的代碼 piao2010 ’s blog

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章