HttpClient偶爾報NoHttpResponseException: xxx failed to respond 問題分析

HttpClient偶爾報NoHttpResponseException: xxx failed to respond

背景描述

調(diào)用底層服務(wù)偶爾會報以下錯誤

org.apache.http.NoHttpResponseException: submit.10690221.com:9012 failed to respond

    at org.apache.http.impl.conn.DefaultHttpResponseParser.parseHead(DefaultHttpResponseParser.java:141)
    ....

第一次碰到拒课,先google一下切揭,發(fā)現(xiàn)不少相同的情況苇倡,講的也很不錯放刨,但是呢熔号,我想自己復(fù)現(xiàn)一下秕磷,并且自己去分析并解決簸州,這樣能更好的去理解 網(wǎng)絡(luò) 這東西

復(fù)現(xiàn)方法

這個怎么復(fù)現(xiàn)呢缭付,通過google得知柿估,這個只會在服務(wù)器端keep-alive剛好過期的時間我們進(jìn)行訪問才能大概率復(fù)現(xiàn),方法如下:

wireshark進(jìn)行抓包得出底層服務(wù)器的keep-alive時間

寫一段程序陷猫,用于探測底層服務(wù)器的keep-alive秫舌,代碼如下:

@Test
public void test121() throws Exception {
    String url = "http://xxxxxxx:9012/hy/json";
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpPost request = new HttpPost(url);

    httpClient.execute(request, response -> {
        String content = EntityUtils.toString(response.getEntity());
        System.out.println(content);
        return content;
    });

    Thread.sleep(1000000);

}

開啟wireshark進(jìn)行抓包,執(zhí)行程序直到下圖出現(xiàn)即可停止


重點看左下角的紅色框绣檬,時間相差65秒左右,沒錯從而可以得知底層服務(wù)器的keep-alive 是 65秒娇未,也就是當(dāng)一個連接socket 65秒內(nèi)沒有數(shù)據(jù)交互墨缘,底層服務(wù)器就會認(rèn)為這個連接可以關(guān)閉了,因此才會在3分36秒進(jìn)行揮手操作發(fā)送一個FIN包,這時我們稍微改造一下這個程序镊讼,如下:

@Test
public void test121() throws Exception {
    String url = "http://xxxxxxx:9012/hy/json";
    CloseableHttpClient httpClient = HttpClients.createDefault();
    HttpPost request = new HttpPost(url);
    while (true) {//加了一個死循環(huán) ^_^
        httpClient.execute(request, response -> {
            String content = EntityUtils.toString(response.getEntity());
            System.out.println(content);
            return content;
        });

        Thread.sleep(65000); //關(guān)鍵是這里平夜,設(shè)置和底層服務(wù)器keep-alive相同
    }
}

相比第一個蝶棋,有兩個改動

  1. 加了一個循環(huán)
  2. 每次調(diào)用的間隔改成和底層服務(wù)器相同的65秒

我們清空wireshark,運(yùn)行該程序抓包忽妒,結(jié)果如下:


問題分析

首先我們分析一下抓包結(jié)果


  1. 紅色框1:前3個請求是建立連接的過程玩裙,三次握手,接著4個請求就是client和server的數(shù)據(jù)交互段直,著重看最后四個請求
    1. 9012 -> 59233 [FIN, ACK]:服務(wù)器主動進(jìn)行關(guān)閉吃溅,給client發(fā)送了FIN包
    2. 59233 -> 9012 [ACK]:client進(jìn)行回應(yīng)ACK包
    3. 69233 -> 9012 [FIN, ACK]:按照四次揮手原則,client發(fā)現(xiàn)目前數(shù)據(jù)已經(jīng)發(fā)送完畢了鸯檬,因此也發(fā)出FIN包
    4. 9012 -> 59233 [RST]:服務(wù)器直接返回一個RST
  2. 紅色框2:同2
  3. 紅色框3:前面的7個步驟都是相同的决侈,建立連接,數(shù)據(jù)交互喧务,區(qū)別唯獨在于綠色框
    1. 9012 -> 59233 POST /hy/json: client認(rèn)為服務(wù)器端可用颜及,因此給服務(wù)器發(fā)送數(shù)據(jù)
    2. 9012 -> 59233 [FIN, ACK]:服務(wù)器認(rèn)為此連接已經(jīng)失效,因為超過了65的keep-alive時間蹂楣,主動進(jìn)行關(guān)閉,給client發(fā)送了FIN包
    3. 59233 -> 9012 [ACK]:client進(jìn)行回應(yīng)ACK包
    4. 69233 -> 9012 [FIN, ACK]:按照四次揮手原則讯蒲,client發(fā)現(xiàn)目前數(shù)據(jù)已經(jīng)發(fā)送完畢了痊土,因此也發(fā)出FIN包
    5. 9012 -> 59233 [RST]:服務(wù)器直接返回一個RST 通過Seq=188,可判斷這條是給【9012 -> 59233 POST /hy/json】這個請求回的
    6. 9012 -> 59233 [RST]:服務(wù)器直接返回一個RST 通過Seq=189墨林,可判斷這條是給【69233 -> 9012 [FIN, ACK]】回的
    7. 9012 -> 59233 [RST]:服務(wù)器直接返回一個RST 通過Seq=189赁酝,同6

通過分析抓包數(shù)據(jù)犯祠,得出結(jié)果是,當(dāng)client客戶端認(rèn)為這條Socket連接有用酌呆,這時服務(wù)器端卻認(rèn)為該Socket連接無用衡载,并主動關(guān)閉,就會報錯,屬于臨界值沒有處理好的

這時有人就說了隙袁,為什么前兩次就沒有問題呢痰娱,原因是HttpClient會進(jìn)行連接過期是否可用的檢查,那么也就能理解這是httpclient的一個bug菩收,即使httpclient有做這么一件事情梨睁,但是由于網(wǎng)絡(luò)I/O原因,導(dǎo)致httpclient認(rèn)為一個關(guān)閉了的連接是有效的娜饵,才報了這個錯誤

接下來我們看看HttpClient為什么會復(fù)用一個已經(jīng)被關(guān)閉的連接

由于HttpClient代碼有點多坡贺,為了方便快速定位縮小范圍, 我這邊開啟了debug箱舞,并對兩者的日志進(jìn)行了分析
左邊日志是正常交互遍坟、右邊是報錯了


我這邊簡化了一下日志,通過仔細(xì)分析HttpClient打印的debug日志,可發(fā)現(xiàn)左邊正常交互日志 打印了一串 "end of stream" 后進(jìn)行了連接的重新建立晴股, connection established 愿伴,而右邊錯誤日志打印了一串 "[read] I/O error: Read timed out" 后沒有進(jìn)行連接的重新建立,因此就報錯了

那么可以通過打印 "[read] I/O error: Read timed out"日志的上下文日志縮小 排查代碼的范圍队魏,上文日志 Connection request公般,下文日志 Connection leased,進(jìn)行代碼定位


基本上定位到了PooingHttpClientConnectionManager.java這個類胡桨,那么進(jìn)行代碼跟蹤吧


追蹤到了 AbstractConnPool.java類官帘,那么這段代碼什么意思呢,這個就是進(jìn)行連接是否能夠復(fù)用的檢查代碼

對validateAfterInactivity進(jìn)行判斷昧谊,這個是服務(wù)器keep-alive的值

  1. leasedEntry.getUpdated() + validateAfterInactivity <= System.currentTimeMillis():如果連接的最后一次使用時間 + 服務(wù)器keep-alive的時間 小于等于當(dāng)前時間刽虹,那么就認(rèn)為該連接可能已經(jīng)失效了
  2. !validate(leasedEntry): 因此會進(jìn)行連接是否失效的檢查

跟進(jìn)去看看


最終找到"end of stream" and "[read] I/O error: Read timed out" 打印的地方
然后回到如下圖代碼:


可以看到

  • 當(dāng)bytesRead 值為 -1 時,返回true,那么HttpClient就會認(rèn)為該連接失效了呢诬,不能夠復(fù)用涌哲,并進(jìn)行清理操作,
  • 當(dāng)拋出異常是ShockTimeoutException時會返回false尚镰, 那么HttpClient就會認(rèn)為該連接可復(fù)用

分析到這阀圾,相信大部分人都已經(jīng)知道為什么會保證錯了,不過還是強(qiáng)烈建議自己動手分析一下狗唉,另外大家可去了解一下初烘,為什么會輸出"end of stream" and "[read] I/O error: Read timed out"兩種不同的結(jié)果,快去暢游底層Socket編程相關(guān)的原理吧,這有助于你更加理解

解決方案

其實當(dāng)你知道原因后肾筐,也能想出對應(yīng)的解決方案哆料,不過我這邊還是收集列出來了一些

  1. 禁用HttpClient的連接復(fù)用(有點扯淡)
  2. 重試方案:http請求使用重發(fā)機(jī)制,捕獲NohttpResponseException的異常吗铐,重新發(fā)送請求东亦,重發(fā)3次后還是失敗才停止
  3. 根據(jù)keep Alive時間,調(diào)整validateAfterInactivity小于keepAlive Time,但這種方法依舊不能避免同時關(guān)閉
  4. 系統(tǒng)主動檢查每個連接的空閑時間唬渗,并提前自動關(guān)閉連接典阵,避免服務(wù)端主動斷開

推薦使用重試方案

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市谣妻,隨后出現(xiàn)的幾起案子萄喳,更是在濱河造成了極大的恐慌,老刑警劉巖蹋半,帶你破解...
    沈念sama閱讀 211,194評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件他巨,死亡現(xiàn)場離奇詭異,居然都是意外死亡减江,警方通過查閱死者的電腦和手機(jī)染突,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來辈灼,“玉大人份企,你說我怎么就攤上這事⊙灿ǎ” “怎么了司志?”我有些...
    開封第一講書人閱讀 156,780評論 0 346
  • 文/不壞的土叔 我叫張陵,是天一觀的道長降宅。 經(jīng)常有香客問我骂远,道長,這世上最難降的妖魔是什么腰根? 我笑而不...
    開封第一講書人閱讀 56,388評論 1 283
  • 正文 為了忘掉前任激才,我火速辦了婚禮,結(jié)果婚禮上额嘿,老公的妹妹穿的比我還像新娘瘸恼。我一直安慰自己,他們只是感情好册养,可當(dāng)我...
    茶點故事閱讀 65,430評論 5 384
  • 文/花漫 我一把揭開白布东帅。 她就那樣靜靜地躺著,像睡著了一般球拦。 火紅的嫁衣襯著肌膚如雪靠闭。 梳的紋絲不亂的頭發(fā)上邓夕,一...
    開封第一講書人閱讀 49,764評論 1 290
  • 那天,我揣著相機(jī)與錄音阎毅,去河邊找鬼。 笑死点弯,一個胖子當(dāng)著我的面吹牛扇调,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抢肛,決...
    沈念sama閱讀 38,907評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼狼钮,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了捡絮?” 一聲冷哼從身側(cè)響起熬芜,我...
    開封第一講書人閱讀 37,679評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎福稳,沒想到半個月后涎拉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,122評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡的圆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,459評論 2 325
  • 正文 我和宋清朗相戀三年鼓拧,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片越妈。...
    茶點故事閱讀 38,605評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡季俩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出梅掠,到底是詐尸還是另有隱情酌住,我是刑警寧澤,帶...
    沈念sama閱讀 34,270評論 4 329
  • 正文 年R本政府宣布阎抒,位于F島的核電站酪我,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏挠蛉。R本人自食惡果不足惜祭示,卻給世界環(huán)境...
    茶點故事閱讀 39,867評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谴古。 院中可真熱鬧质涛,春花似錦、人聲如沸掰担。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽带饱。三九已至毡代,卻和暖如春阅羹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背教寂。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評論 1 265
  • 我被黑心中介騙來泰國打工捏鱼, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酪耕。 一個月前我還...
    沈念sama閱讀 46,297評論 2 360
  • 正文 我出身青樓导梆,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迂烁。 傳聞我的和親對象是個殘疾皇子看尼,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,472評論 2 348

推薦閱讀更多精彩內(nèi)容