成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

一次排查多線程引發(fā)Java DNS緩存的Bug

cocopeak / 3039人閱讀

摘要:問題描述最近通知應(yīng)用在近三個月內(nèi)出現(xiàn)過次緩存的問題,第一次在重啟之后一直沒有出現(xiàn)過問題,所以也沒有去重視,但是最近又出現(xiàn)過一次,看來很有必要徹底排查一次具體的錯誤日志如下具體表現(xiàn)就是出現(xiàn)此異常之后連續(xù)的出現(xiàn)大量此異常

問題描述
最近通知應(yīng)用在近三個月內(nèi)出現(xiàn)過2次DNS緩存的問題,第一次在重啟之后一直沒有出現(xiàn)過問題,所以也沒有去重視,但是最近又出現(xiàn)過一次,看來很有必要徹底排查一次;具體的錯誤日志如下:

2018-03-16 18:53:59,501 ERROR [DefaultMessageListenerContainer-1] (com.bill99.asap.service.CryptoClient.seal(CryptoClient.java:34))- null
java.lang.NullPointerException
    at java.net.InetAddress$Cache.put(InetAddress.java:779) ~[?:1.7.0_79]
    at java.net.InetAddress.cacheAddresses(InetAddress.java:858) ~[?:1.7.0_79]
    at java.net.InetAddress.getAddressesFromNameService(InetAddress.java:1334) ~[?:1.7.0_79]
    at java.net.InetAddress.getAllByName0(InetAddress.java:1248) ~[?:1.7.0_79]
    at java.net.InetAddress.getAllByName(InetAddress.java:1164) ~[?:1.7.0_79]
    at java.net.InetAddress.getAllByName(InetAddress.java:1098) ~[?:1.7.0_79]
    at java.net.InetAddress.getByName(InetAddress.java:1048) ~[?:1.7.0_79]
    at java.net.InetSocketAddress.(InetSocketAddress.java:220) ~[?:1.7.0_79]
    at sun.net.NetworkClient.doConnect(NetworkClient.java:180) ~[?:1.7.0_79]
    at sun.net.www.http.HttpClient.openServer(HttpClient.java:432) ~[?:1.7.0_79]
    at sun.net.www.http.HttpClient.openServer(HttpClient.java:527) ~[?:1.7.0_79]
    at sun.net.www.http.HttpClient.(HttpClient.java:211) ~[?:1.7.0_79]
    at sun.net.www.http.HttpClient.New(HttpClient.java:308) ~[?:1.7.0_79]
    at sun.net.www.http.HttpClient.New(HttpClient.java:326) ~[?:1.7.0_79]
    at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(HttpURLConnection.java:997) ~[?:1.7.0_79]
    at sun.net.www.protocol.http.HttpURLConnection.plainConnect(HttpURLConnection.java:933) ~[?:1.7.0_79]
    at sun.net.www.protocol.http.HttpURLConnection.connect(HttpURLConnection.java:851) ~[?:1.7.0_79]
    at sun.net.www.protocol.http.HttpURLConnection.getOutputStream(HttpURLConnection.java:1092) ~[?:1.7.0_79]
    at org.springframework.ws.transport.http.HttpUrlConnection.getRequestOutputStream(HttpUrlConnection.java:81) ~[spring-ws-core.jar:1.5.6]
    at org.springframework.ws.transport.AbstractSenderConnection$RequestTransportOutputStream.createOutputStream(AbstractSenderConnection.java:101) ~[spring-ws-core.jar:1.5.6]
    at org.springframework.ws.transport.TransportOutputStream.getOutputStream(TransportOutputStream.java:41) ~[spring-ws-core.jar:1.5.6]
    at org.springframework.ws.transport.TransportOutputStream.write(TransportOutputStream.java:60) ~[spring-ws-core.jar:1.5.6]

具體表現(xiàn)就是出現(xiàn)此異常之后連續(xù)的出現(xiàn)大量此異常,同時系統(tǒng)節(jié)點不可用;

問題分析
1.既然InetAddress$Cache.put報空指針,那就具體看一下源代碼:

if (policy != InetAddressCachePolicy.FOREVER) {
     // As we iterate in insertion order we can
     // terminate when a non-expired entry is found.
     LinkedList expired = new LinkedList<>();
     long now = System.currentTimeMillis();
     for (String key :  ) {
            CacheEntry entry = cache.get(key);
 
            if (entry.expiration >= 0 && entry.expiration < now) {
                    expired.add(key);
            } else {
                    break;
            }
     }
 
     for (String key : expired) {
            cache.remove(key);
     }
}

報空指針的的地方就是entry.expiration,也就是說從cache取出來的entry為null,可以查看cache寫入的地方:

CacheEntry entry = new CacheEntry(addresses, expiration);
cache.put(host, entry);

每次都是new一個CacheEntry然后再put到cache中,不會寫入null進去;此時猜測是多線程引發(fā)的問題,cache.keySet()在遍歷的時候同時也進行了remove操作,導致cache.get(key)到一個空值,查看源代碼可以發(fā)現(xiàn)一共有兩次對cache進行remove的地方,分別是put方法和get方法,put方法代碼如上,每次在遍歷的時候檢測是否過期,然后統(tǒng)一進行remove操作;還有一處就是get方法,代碼如下:

public CacheEntry get(String host) {
       int policy = getPolicy();
       if (policy == InetAddressCachePolicy.NEVER) {
             return null;
       }
       CacheEntry entry = cache.get(host);
 
       // check if entry has expired
       if (entry != null && policy != InetAddressCachePolicy.FOREVER) {
             if (entry.expiration >= 0 && entry.expiration < System.currentTimeMillis()) {
                    cache.remove(host);
                    entry = null;
              }
       }
       return entry;
 }

類似put方法也是每次在get的時候進行有效期檢測,然后進行remove操作;
所以如果出現(xiàn)多線程問題大概就是:1.同時調(diào)用put,get方法,2.多個線程都調(diào)用put方法;繼續(xù)查看源碼調(diào)用put和get的地方,一共有三處分別是:

private static void cacheInitIfNeeded() {
        assert Thread.holdsLock(addressCache);
        if (addressCacheInit) {
            return;
        }
        unknown_array = new InetAddress[1];
        unknown_array[0] = impl.anyLocalAddress();
 
        addressCache.put(impl.anyLocalAddress().getHostName(),
                         unknown_array);
 
        addressCacheInit = true;
    }
 
    /*
     * Cache the given hostname and addresses.
     */
    private static void cacheAddresses(String hostname,
                                       InetAddress[] addresses,
                                       boolean success) {
        hostname = hostname.toLowerCase();
        synchronized (addressCache) {
            cacheInitIfNeeded();
            if (success) {
                addressCache.put(hostname, addresses);
            } else {
                negativeCache.put(hostname, addresses);
            }
        }
    }
 
    /*
     * Lookup hostname in cache (positive & negative cache). If
     * found return addresses, null if not found.
     */
    private static InetAddress[] getCachedAddresses(String hostname) {
        hostname = hostname.toLowerCase();
 
        // search both positive & negative caches
 
        synchronized (addressCache) {
            cacheInitIfNeeded();
 
            CacheEntry entry = addressCache.get(hostname);
            if (entry == null) {
                entry = negativeCache.get(hostname);
            }
 
            if (entry != null) {
                return entry.addresses;
            }
        }
 
        // not found
        return null;
    }

cacheInitIfNeeded只在cacheAddresses和getCachedAddresses方法中被調(diào)用,用來檢測cache是否已經(jīng)被初始化了;而另外兩個方法都加了對象鎖addressCache,所以不會多線程問題;

2.猜測外部直接調(diào)用了addressCache,沒有使用內(nèi)部提供的方法
查看源碼可以發(fā)現(xiàn)addressCache本身是私有屬性,也不存在對外的訪問方法

private static Cache addressCache = new Cache(Cache.Type.Positive);

那業(yè)務(wù)代碼中應(yīng)該也不能直接使用,除非使用反射的方式,隨手搜了一下全局代碼查看關(guān)鍵字”addressCache”,搜到了類似如下代碼:

static{
      Class clazz = java.net.InetAddress.class;  
      final Field cacheField = clazz.getDeclaredField("addressCache");  
      cacheField.setAccessible(true);  
      final Object o = cacheField.get(clazz);  
      Class clazz2 = o.getClass();  
      final Field cacheMapField = clazz2.getDeclaredField("cache");  
      cacheMapField.setAccessible(true);  
      final Map cacheMap = (Map)cacheMapField.get(o); 
}

通過反射的方式獲取了addressCache對象,然后又獲取了cache對象(cache是一個LinkedHashMap),同時提供了一個類似如下的方法:

    public class TEst {
 
    public static void main(String[] args) throws IOException, InterruptedException {
        final LinkedHashMap map = new LinkedHashMap<>();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 2000; i++) {
                    map.put(new Random().nextInt(1000), new HH(new Random(100).nextInt()));
                }
            }
        }).start();
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
 
                @Override
                public void run() {
                    for (int i = 0; i < 500; i++) {
                        map.remove(new Random().nextInt(1000));
                    }
                }
            }).start();
        }
        Thread.sleep(2000);
        System.out.println("size=" + map.keySet().size() + "," + map.keySet());
        for (Integer s : map.keySet()) {
            System.out.println(map.get(s));
        }
    }
}
 
class HH {
    private int k;
 
    public HH(int k) {
        this.k = k;
    }
 
    public int getK() {
        return k;
    }
 
    public void setK(int k) {
        this.k = k;
    }
}

模擬單線程put操作,業(yè)務(wù)端會有多條線程同時remove操作,執(zhí)行看輸出結(jié)果(可以執(zhí)行多次看結(jié)果):

size=0,[121, 517, 208]
null
null
null

可以發(fā)現(xiàn)會出現(xiàn)猜測的情況,HashMap中的size屬性本身不是線程安全的,所以多線程的情況下有可能出現(xiàn)0,這樣導致get方法獲取都為null,當然HashMap還有很多其他的多線程問題,因為HashMap也不是為多線程準備的,至此大概了解了原因。

問題解決
給反射獲取的cache對象加上和cacheAddresses方法同樣的鎖,或者直接不在業(yè)務(wù)代碼中處理cache對象;可以借鑒一下阿里在github開源的操作dns緩存的項目:https://github.com/alibaba/ja...

總結(jié)
本次排查問題花了一些時間在排查是不是jdk提供的類是不是有bug,這其實是有些浪費時間的;還有就是在排查問題中不要放過任何一種可能往往問題就發(fā)生在那些理所當然的地方。

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/71053.html

相關(guān)文章

  • 一次線上問題排查引發(fā)思考

    摘要:直到有一天你會碰到線上奇奇怪怪的問題,如線程執(zhí)行一個任務(wù)遲遲沒有返回,應(yīng)用假死。正好這次借助之前的一次生產(chǎn)問題來聊聊如何排查和解決問題。本地模擬上文介紹的是線程相關(guān)問題,現(xiàn)在來分析下內(nèi)存的問題。盡可能的減少多線程競爭鎖。 showImg(https://segmentfault.com/img/remote/1460000015568421?w=2048&h=1150); 前言 之前或...

    levy9527 評論0 收藏0
  • 線上系統(tǒng)性問題定位與方法論

    摘要:很顯然對于不同規(guī)模,不同功能的系統(tǒng),這個問題無法一概而論。生產(chǎn)事件上報客服上報此類問題往往來自用戶投訴,最重要的就是問題現(xiàn)象的復現(xiàn)。線上問題處理的核心是快速修復。以上說的都是問題發(fā)生后的消極應(yīng)對措施。 前言一線程序員在工作中經(jīng)常需要處理線上的問題或者故障,但工作幾年下來發(fā)現(xiàn),有些同事其實并不知道該如何去分析和解決這些問題,毫無章法的猜測和嘗試,雖然在很多時候可以最終解決問題,但往往也會浪費大...

    leonardofed 評論0 收藏0
  • Javag工程師成神之路(2019正式版)

    摘要:結(jié)構(gòu)型模式適配器模式橋接模式裝飾模式組合模式外觀模式享元模式代理模式。行為型模式模版方法模式命令模式迭代器模式觀察者模式中介者模式備忘錄模式解釋器模式模式狀態(tài)模式策略模式職責鏈模式責任鏈模式訪問者模式。 主要版本 更新時間 備注 v1.0 2015-08-01 首次發(fā)布 v1.1 2018-03-12 增加新技術(shù)知識、完善知識體系 v2.0 2019-02-19 結(jié)構(gòu)...

    Olivia 評論0 收藏0
  • 一次mybatis中ognl引發(fā)bug排查

    摘要:現(xiàn)象項目組一妹子程序員求助,說有,有一個值明明設(shè)置的是,但是存到數(shù)據(jù)庫里面卻會自動變成,嘗試了各種調(diào)整也找不原因,都快急瘋了我以前確實沒有研究過源碼,本著專研問題的精神,決定通過對一探究竟。 現(xiàn)象 ??項目組一妹子程序員求助,說mybatis有bug,有一個值明明設(shè)置的是A.prop1=XXX,但是存到數(shù)據(jù)庫里面卻會自動變成A.prop1=true,嘗試了各種調(diào)整也找不原因,都快急瘋了...

    RichardXG 評論0 收藏0
  • Java面試 32個核心必考點完全解析

    摘要:如問到是否使用某框架,實際是是問該框架的使用場景,有什么特點,和同類可框架對比一系列的問題。這兩個方向的區(qū)分點在于工作方向的側(cè)重點不同。 [TOC] 這是一份來自嗶哩嗶哩的Java面試Java面試 32個核心必考點完全解析(完) 課程預(yù)習 1.1 課程內(nèi)容分為三個模塊 基礎(chǔ)模塊: 技術(shù)崗位與面試 計算機基礎(chǔ) JVM原理 多線程 設(shè)計模式 數(shù)據(jù)結(jié)構(gòu)與算法 應(yīng)用模塊: 常用工具集 ...

    JiaXinYi 評論0 收藏0

發(fā)表評論

0條評論

cocopeak

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<