摘要:此時我想到了福爾摩斯說過的一句話當(dāng)你排除掉各種不可能出現(xiàn)的情況之后,剩下的情況無論多么難以置信,都是真相。福爾摩斯冷靜下來想一想,這個線程,有可能靜悄悄地退出了嗎,沒留下半點異常日志從理論上來說,不可能。配置建議最后,附上一份配置建議。
1、事發(fā)
我們有個視頻處理程序,基于 SpringBoot,會啟動幾個線程來跑。要退出程序時,會發(fā)送一個信號給程序,每個線程收到信號后會平滑退出,等全部線程都退出后,整個進程再平滑退出。
整個程序平時運行都正常,然后有一天,我們發(fā)送了退出信號給程序后,發(fā)現(xiàn)程序無法自動退出了!腫么回事呢,grep 一下日志看到是這樣的。
# grep "receive exit signal" /PATH/TO/LOG [2019-02-22 09:49:28,884][INFO ][Thread-75][n.p.j.e.l.l.PolyvQueueVideo:83] - receive exit signal ... exit current thread [2019-02-22 09:49:56,271][INFO ][Thread-78][n.p.j.e.l.l.PolyvQueueVideo:83] - receive exit signal ... exit current thread [2019-02-22 09:53:24,943][INFO ][Thread-74][n.p.j.e.l.l.PolyvQueueVideo:83] - receive exit signal ... exit current thread [2019-02-22 09:55:23,317][INFO ][Thread-79][n.p.j.e.l.l.PolyvQueueVideo:83] - receive exit signal ... exit current thread [2019-02-22 09:57:00,196][INFO ][Thread-77][n.p.j.e.l.l.PolyvQueueVideo:83] - receive exit signal ... exit current thread
這里的程序總共啟動了6個線程的,但上面看到只有5個線程退出了,還有一個哪兒去了?不肯輕易就義么?好頑強的線程。。。
2、排查有點小幸運的是,從上面這5個線程的名稱,我們可以推斷出那個頑強的線程的名稱,從74到79,中間唯獨缺了76!那就是你啦 Thread-76!
再查 Thread-76 的日志,確實是我們想找的那個線程,然后發(fā)現(xiàn)原來在好幾天前它 好像就停止了運行 ,不再有日志輸出。也沒有任何異常信息!Thread-76 就這樣悄悄的離開,不帶走一片云彩。我對著日志和代碼大眼瞪小眼看了半個小時,一籌莫展。
此時我想到了福爾摩斯說過的一句話:
“當(dāng)你排除掉各種不可能出現(xiàn)的情況之后,剩下的情況無論多么難以置信,都是真相。” -- 福爾摩斯
冷靜下來想一想,Thread-76 這個線程,有可能靜悄悄地退出了嗎,沒留下半點異常日志?從理論上來說,不可能。一個線程,要么順利地執(zhí)行直到結(jié)束,要么中途出錯退出了,如果這樣肯定有異常信息,但我們并沒看到有異常日志。排除掉 “Thread-76 已經(jīng)退出了” 這個可能性之后,我有個大膽的想法:這個線程還一直運行著!安安靜靜地運行著,持續(xù)著好幾天,沒有半點日志輸出!
是出現(xiàn)了死鎖嗎?不確定,但我們可以驗證一下這個線程是不是真的還存活著。祭出 jstack,把線程信息 dump 出來,一查,果然見到了 Thread-76!
"Thread-76" #141 prio=5 os_prio=0 tid=0x00007f812d7d9800 nid=0x12848 runnable [0x00007f8227cfa000] java.lang.Thread.State: RUNNABLE at java.net.SocketInputStream.socketRead0(Native Method) at java.net.SocketInputStream.socketRead(SocketInputStream.java:116) at java.net.SocketInputStream.read(SocketInputStream.java:170) at java.net.SocketInputStream.read(SocketInputStream.java:141) at java.io.BufferedInputStream.fill(BufferedInputStream.java:246) at java.io.BufferedInputStream.read(BufferedInputStream.java:265) - locked <0x00000005e64cad10> (a java.io.BufferedInputStream) at org.apache.commons.httpclient.HttpParser.readRawLine(HttpParser.java:77) at org.apache.commons.httpclient.HttpParser.readLine(HttpParser.java:105) at org.apache.commons.httpclient.HttpConnection.readLine(HttpConnection.java:1115) at org.apache.commons.httpclient.HttpMethodBase.readStatusLine(HttpMethodBase.java:1832) at org.apache.commons.httpclient.HttpMethodBase.readResponse(HttpMethodBase.java:1590) at org.apache.commons.httpclient.HttpMethodBase.execute(HttpMethodBase.java:995) at org.apache.commons.httpclient.HttpMethodDirector.executeWithRetry(HttpMethodDirector.java:397) at org.apache.commons.httpclient.HttpMethodDirector.executeMethod(HttpMethodDirector.java:170) at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:396) at org.apache.commons.httpclient.HttpClient.executeMethod(HttpClient.java:324) at net.polyv.jet.encoding.legacy.util.IPUtil.delRemoteServerCacheFile(IPUtil.java:175) ***
可以看到,這個線程并沒有發(fā)生死鎖,但卡在了發(fā)送 HTTP 請求這一步??赡苁蔷W(wǎng)絡(luò)有問題,或者是服務(wù)端除了問題,反正我們沒收到響應(yīng),然后線程就一直停在這了。怎么會這樣呢,難道發(fā)送 HTTP 請求時沒有設(shè)置超時時間嗎?我一查代碼,還真的沒設(shè)置。。。這是個低級錯誤啊。
3、總結(jié)弄清楚了原因之后,問題就迎刃而解了??偨Y(jié)一下,有幾個地方可以改進:
客戶端發(fā)送 HTTP 請求時,一定要設(shè)置超時時間,避免出現(xiàn)問題導(dǎo)致請求卡死。
接收 HTTP 請求的服務(wù)端,各級服務(wù)器(例如 Nginx、Tomcat)也都要設(shè)置超時時間,理由同上。
多線程的程序,出問題時進行排查的難度會相對大一些。所以,對于手工啟動、維護的線程,可以的話自定義個線程名稱吧,出問題時也有跡可循。
4、HttpClient 配置建議最后,附上一份 HttpClient 配置建議。
由于各種原因,HttpClient 經(jīng)歷過好幾次版本變更,且這幾次變更導(dǎo)致其 API 用法都不一樣,不了解情況的人往往會覺得懵逼,我到底該用哪個版本呢?到底該用哪種方法做配置呢?到底該配置哪幾種超時時間呢?下面這個例子,應(yīng)該基本上涵蓋了大多數(shù)的應(yīng)用場景了,拿走不謝。對應(yīng)的版本是 HttpClient 4.5.* 。
public static CloseableHttpClient buildHttpClient() throws KeyStoreException, NoSuchAlgorithmException, KeyManagementException { HttpClientBuilder builder = HttpClientBuilder.create(); // 信任全部 HTTPS 證書,避免 HTTPS 請求因為證書問題而失敗。留意,風(fēng)險自擔(dān)。 SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, (arg0, arg1) -> true).build(); // 信任全部證書 builder.setSSLContext(sslContext); HostnameVerifier hostnameVerifier = NoopHostnameVerifier.INSTANCE; // 也信任全部域名 SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); RegistrysocketFactoryRegistry = RegistryBuilder. create() .register("http", PlainConnectionSocketFactory.getSocketFactory()) .register("https", sslSocketFactory).build(); PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry); // 設(shè)置并發(fā)連接數(shù)上限 connectionManager.setMaxTotal(CONNECTION_LIMIT_TOTAL); // 總的并發(fā)連接數(shù)上限 connectionManager.setDefaultMaxPerRoute(CONNECTION_LIMIT_PER_HOST); // 單個域名的并發(fā)連接數(shù)上限 builder.setConnectionManager(connectionManager); // 設(shè)置默認(rèn)的超時時間。具體數(shù)值可按需調(diào)整。 RequestConfig requestConfig = RequestConfig.custom() .setConnectTimeout(CONNECT_TIMEOUT) // 建立連接的超時時間 .setSocketTimeout(SOCKET_TIMEOUT) // 連接建立后,傳輸數(shù)據(jù)時的超時時間 .setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT) // 從連接池中獲取連接時的超時時間 .build(); builder.setDefaultRequestConfig(requestConfig); // 除了 GET、HEAD 之外,也自動跟隨 POST、PUT 的 301、302 重定向。按需使用。 builder.setRedirectStrategy(new LaxRedirectStrategy()); return builder.build(); }
參考:
HttpClient Timeout
How to ignore SSL certificate errors in Apache HttpClient 4.4
如果需要自動重試機制,可以看 這里
當(dāng) POST 遇上302,請看 這里
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77729.html
摘要:首先先解讀下這個報警內(nèi)容,原因活躍線程數(shù)過多,是監(jiān)聽的端口號用來獲取虛擬機各項信息,代表著此時的線程數(shù),是設(shè)置的報警閾值。 前言 前天,一位21世紀(jì)的好好青年正在工位上默念社會主義大法好的時候,釘釘上又報警了(公司項目接入了open-faclon監(jiān)控,指標(biāo)不正常會報警給釘釘?shù)臋C器人),無奈默默流淚揮手告別社會主義大法開始定位線上問題。 報警內(nèi)容 首先我們先來看下報警信息,為防止泄露公...
摘要:展示如下場景再現(xiàn)經(jīng)過分析,最后我們定位到是使用產(chǎn)生的內(nèi)存泄露問題。下面通過一個,來簡單講下具體內(nèi)存泄露的原因。這一次的內(nèi)存泄露問題算是解決了??偨Y(jié)關(guān)于內(nèi)存泄露問題在第一次排查時,往往是有點不知所措的。 記一次 JAVA 的內(nèi)存泄露分析 摘要:本文屬于原創(chuàng),歡迎轉(zhuǎn)載,轉(zhuǎn)載請保留出處:https://github.com/jasonGeng88/blog 當(dāng)前環(huán)境 jdk == 1.8 ...
摘要:當(dāng)然,如果你的核心數(shù)夠多,到個線程的并行度不滿足的話,也可以自定義一個線程池來執(zhí)行,不過這樣的話,要注意自己維護這個線程池的初始化,釋放等等操作了。 事情起源于一個bug排查,一個AsyncTask的子類,執(zhí)行的時候發(fā)現(xiàn)onPreExecute方法執(zhí)行了,doInBackground卻遲遲沒有被調(diào)用。懂AsyncTask一些表面原理的都知道,onPreExecute方法是在主線程執(zhí)行,...
摘要:問題描述最近通知應(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)過一次,看來很有必要徹底排查一次;具體的錯...
閱讀 1230·2023-04-26 00:47
閱讀 3582·2021-11-16 11:53
閱讀 805·2021-10-08 10:05
閱讀 2758·2021-09-22 15:19
閱讀 2989·2019-08-30 15:55
閱讀 2767·2019-08-29 16:55
閱讀 2936·2019-08-29 15:20
閱讀 1121·2019-08-23 16:13