摘要:?jiǎn)栴}描述最近通知應(yīng)用在近三個(gè)月內(nèi)出現(xiàn)過(guò)次緩存的問(wèn)題,第一次在重啟之后一直沒(méi)有出現(xiàn)過(guò)問(wèn)題,所以也沒(méi)有去重視,但是最近又出現(xiàn)過(guò)一次,看來(lái)很有必要徹底排查一次具體的錯(cuò)誤日志如下具體表現(xiàn)就是出現(xiàn)此異常之后連續(xù)的出現(xiàn)大量此異常
問(wèn)題描述
最近通知應(yīng)用在近三個(gè)月內(nèi)出現(xiàn)過(guò)2次DNS緩存的問(wèn)題,第一次在重啟之后一直沒(méi)有出現(xiàn)過(guò)問(wèn)題,所以也沒(méi)有去重視,但是最近又出現(xiàn)過(guò)一次,看來(lái)很有必要徹底排查一次;具體的錯(cuò)誤日志如下:
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)大量此異常,同時(shí)系統(tǒng)節(jié)點(diǎn)不可用;
問(wèn)題分析
1.既然InetAddress$Cache.put報(bào)空指針,那就具體看一下源代碼:
if (policy != InetAddressCachePolicy.FOREVER) { // As we iterate in insertion order we can // terminate when a non-expired entry is found. LinkedListexpired = 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); } }
報(bào)空指針的的地方就是entry.expiration,也就是說(shuō)從cache取出來(lái)的entry為null,可以查看cache寫(xiě)入的地方:
CacheEntry entry = new CacheEntry(addresses, expiration); cache.put(host, entry);
每次都是new一個(gè)CacheEntry然后再put到cache中,不會(huì)寫(xiě)入null進(jìn)去;此時(shí)猜測(cè)是多線(xiàn)程引發(fā)的問(wèn)題,cache.keySet()在遍歷的時(shí)候同時(shí)也進(jìn)行了remove操作,導(dǎo)致cache.get(key)到一個(gè)空值,查看源代碼可以發(fā)現(xiàn)一共有兩次對(duì)cache進(jìn)行remove的地方,分別是put方法和get方法,put方法代碼如上,每次在遍歷的時(shí)候檢測(cè)是否過(guò)期,然后統(tǒng)一進(jìn)行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; }
類(lèi)似put方法也是每次在get的時(shí)候進(jìn)行有效期檢測(cè),然后進(jìn)行remove操作;
所以如果出現(xiàn)多線(xiàn)程問(wèn)題大概就是:1.同時(shí)調(diào)用put,get方法,2.多個(gè)線(xiàn)程都調(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)用,用來(lái)檢測(cè)cache是否已經(jīng)被初始化了;而另外兩個(gè)方法都加了對(duì)象鎖addressCache,所以不會(huì)多線(xiàn)程問(wèn)題;
2.猜測(cè)外部直接調(diào)用了addressCache,沒(méi)有使用內(nèi)部提供的方法
查看源碼可以發(fā)現(xiàn)addressCache本身是私有屬性,也不存在對(duì)外的訪問(wèn)方法
private static Cache addressCache = new Cache(Cache.Type.Positive);
那業(yè)務(wù)代碼中應(yīng)該也不能直接使用,除非使用反射的方式,隨手搜了一下全局代碼查看關(guān)鍵字”addressCache”,搜到了類(lèi)似如下代碼:
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); }
通過(guò)反射的方式獲取了addressCache對(duì)象,然后又獲取了cache對(duì)象(cache是一個(gè)LinkedHashMap),同時(shí)提供了一個(gè)類(lèi)似如下的方法:
public class TEst { public static void main(String[] args) throws IOException, InterruptedException { final LinkedHashMapmap = 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; } }
模擬單線(xiàn)程put操作,業(yè)務(wù)端會(huì)有多條線(xiàn)程同時(shí)remove操作,執(zhí)行看輸出結(jié)果(可以執(zhí)行多次看結(jié)果):
size=0,[121, 517, 208] null null null
可以發(fā)現(xiàn)會(huì)出現(xiàn)猜測(cè)的情況,HashMap中的size屬性本身不是線(xiàn)程安全的,所以多線(xiàn)程的情況下有可能出現(xiàn)0,這樣導(dǎo)致get方法獲取都為null,當(dāng)然HashMap還有很多其他的多線(xiàn)程問(wèn)題,因?yàn)镠ashMap也不是為多線(xiàn)程準(zhǔn)備的,至此大概了解了原因。
問(wèn)題解決
給反射獲取的cache對(duì)象加上和cacheAddresses方法同樣的鎖,或者直接不在業(yè)務(wù)代碼中處理cache對(duì)象;可以借鑒一下阿里在github開(kāi)源的操作dns緩存的項(xiàng)目:https://github.com/alibaba/ja...
總結(jié)
本次排查問(wèn)題花了一些時(shí)間在排查是不是jdk提供的類(lèi)是不是有bug,這其實(shí)是有些浪費(fèi)時(shí)間的;還有就是在排查問(wèn)題中不要放過(guò)任何一種可能往往問(wèn)題就發(fā)生在那些理所當(dāng)然的地方。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/71053.html
摘要:直到有一天你會(huì)碰到線(xiàn)上奇奇怪怪的問(wèn)題,如線(xiàn)程執(zhí)行一個(gè)任務(wù)遲遲沒(méi)有返回,應(yīng)用假死。正好這次借助之前的一次生產(chǎn)問(wèn)題來(lái)聊聊如何排查和解決問(wèn)題。本地模擬上文介紹的是線(xiàn)程相關(guān)問(wèn)題,現(xiàn)在來(lái)分析下內(nèi)存的問(wèn)題。盡可能的減少多線(xiàn)程競(jìng)爭(zhēng)鎖。 showImg(https://segmentfault.com/img/remote/1460000015568421?w=2048&h=1150); 前言 之前或...
摘要:很顯然對(duì)于不同規(guī)模,不同功能的系統(tǒng),這個(gè)問(wèn)題無(wú)法一概而論。生產(chǎn)事件上報(bào)客服上報(bào)此類(lèi)問(wèn)題往往來(lái)自用戶(hù)投訴,最重要的就是問(wèn)題現(xiàn)象的復(fù)現(xiàn)。線(xiàn)上問(wèn)題處理的核心是快速修復(fù)。以上說(shuō)的都是問(wèn)題發(fā)生后的消極應(yīng)對(duì)措施。 前言一線(xiàn)程序員在工作中經(jīng)常需要處理線(xiàn)上的問(wèn)題或者故障,但工作幾年下來(lái)發(fā)現(xiàn),有些同事其實(shí)并不知道該如何去分析和解決這些問(wèn)題,毫無(wú)章法的猜測(cè)和嘗試,雖然在很多時(shí)候可以最終解決問(wèn)題,但往往也會(huì)浪費(fèi)大...
摘要:結(jié)構(gòu)型模式適配器模式橋接模式裝飾模式組合模式外觀模式享元模式代理模式。行為型模式模版方法模式命令模式迭代器模式觀察者模式中介者模式備忘錄模式解釋器模式模式狀態(tài)模式策略模式職責(zé)鏈模式責(zé)任鏈模式訪問(wèn)者模式。 主要版本 更新時(shí)間 備注 v1.0 2015-08-01 首次發(fā)布 v1.1 2018-03-12 增加新技術(shù)知識(shí)、完善知識(shí)體系 v2.0 2019-02-19 結(jié)構(gòu)...
摘要:現(xiàn)象項(xiàng)目組一妹子程序員求助,說(shuō)有,有一個(gè)值明明設(shè)置的是,但是存到數(shù)據(jù)庫(kù)里面卻會(huì)自動(dòng)變成,嘗試了各種調(diào)整也找不原因,都快急瘋了我以前確實(shí)沒(méi)有研究過(guò)源碼,本著專(zhuān)研問(wèn)題的精神,決定通過(guò)對(duì)一探究竟。 現(xiàn)象 ??項(xiàng)目組一妹子程序員求助,說(shuō)mybatis有bug,有一個(gè)值明明設(shè)置的是A.prop1=XXX,但是存到數(shù)據(jù)庫(kù)里面卻會(huì)自動(dòng)變成A.prop1=true,嘗試了各種調(diào)整也找不原因,都快急瘋了...
摘要:如問(wèn)到是否使用某框架,實(shí)際是是問(wèn)該框架的使用場(chǎng)景,有什么特點(diǎn),和同類(lèi)可框架對(duì)比一系列的問(wèn)題。這兩個(gè)方向的區(qū)分點(diǎn)在于工作方向的側(cè)重點(diǎn)不同。 [TOC] 這是一份來(lái)自嗶哩嗶哩的Java面試Java面試 32個(gè)核心必考點(diǎn)完全解析(完) 課程預(yù)習(xí) 1.1 課程內(nèi)容分為三個(gè)模塊 基礎(chǔ)模塊: 技術(shù)崗位與面試 計(jì)算機(jī)基礎(chǔ) JVM原理 多線(xiàn)程 設(shè)計(jì)模式 數(shù)據(jù)結(jié)構(gòu)與算法 應(yīng)用模塊: 常用工具集 ...
閱讀 2913·2021-11-22 15:11
閱讀 3636·2021-09-28 09:43
閱讀 2961·2019-08-30 13:05
閱讀 3495·2019-08-30 11:18
閱讀 1510·2019-08-29 16:34
閱讀 1424·2019-08-29 13:53
閱讀 2993·2019-08-29 11:03
閱讀 1730·2019-08-29 10:57