生產(chǎn)環(huán)境NoHttpResponseException異常排查解決記錄分析
業(yè)務(wù)背景
公司最近正在準(zhǔn)備為郵儲(chǔ)銀行開展一個(gè)營銷活動(dòng),活動(dòng)規(guī)則是:用戶使用郵儲(chǔ)銀行卡在線上支付一分錢,就可以領(lǐng)取50元現(xiàn)金券,卡券領(lǐng)取完畢后,系統(tǒng)會(huì)自動(dòng)退還消費(fèi)者的1分錢。(相當(dāng)于免費(fèi)給郵儲(chǔ)用戶發(fā)放50元現(xiàn)金券),因?yàn)榘l(fā)券的入口要做在小程序里面,于是這個(gè)需求就落到了C端這邊(公司的另一個(gè)業(yè)務(wù)小組),而我主要負(fù)責(zé)B端支付模塊。經(jīng)過我們商討后,初步制定的業(yè)務(wù)邏輯為:用戶打開C端小程序進(jìn)行支付、然后C端將支付請求轉(zhuǎn)給B端支付模塊、B端支付模塊向微信下單、等待消費(fèi)者完成支付后B端支付模塊通知C端交易完成并返回其支付方式、C端判斷支付方式是否為郵儲(chǔ)銀行卡(是郵儲(chǔ)銀行卡則發(fā)券)、然后C端調(diào)用B端支付模塊進(jìn)行退款。為了方便大家理解,我嘔心瀝血的畫出了系統(tǒng)調(diào)用的時(shí)序圖。

生產(chǎn)環(huán)境發(fā)現(xiàn)的問題
1、NoHttpResponseException導(dǎo)致退款失敗
功能上線后,我便開始監(jiān)控B端支付模塊的交易數(shù)據(jù),前兩天的數(shù)據(jù)并沒有什么異常,支付完成的訂單都已經(jīng)退款完成。然后在第三天快下班時(shí),我又統(tǒng)計(jì)了一遍數(shù)據(jù),發(fā)現(xiàn)竟然存在一筆沒退款的訂單,我整個(gè)人一下子就支棱了起來(不會(huì)又寫了個(gè)Bug吧~),我先在數(shù)據(jù)庫中查到訂單號,然后找運(yùn)維同事拿了一下日志,發(fā)現(xiàn)支付回調(diào)是正常的,并且下游系統(tǒng)也響應(yīng)了success,但是卻沒有調(diào)用退款接口進(jìn)行退款。排查到這里基本已經(jīng)可以確定不是支付模塊這邊的問題了,但問題畢竟還是要解決的,于是我聯(lián)系了C端的同事,暫時(shí)先通過接口的方式把消費(fèi)者的錢進(jìn)行退款。然后開始排查C端系統(tǒng)的問題,通過C端的日志發(fā)現(xiàn),在請求支付模塊進(jìn)行退款時(shí)存在一個(gè)異常信息,報(bào)錯(cuò)信息如下

看到這個(gè)報(bào)錯(cuò),我不禁陷入了思考:C端這個(gè)日志表明確實(shí)是發(fā)起了退款請求,但是B端支付模塊根本沒收到這個(gè)退款請求,這樣一來就比較尷尬了,雙方系統(tǒng)竟然都沒問題,那只能是網(wǎng)絡(luò)問題了(找不到人背鍋,只能推給網(wǎng)絡(luò)了~~哈哈),剛開始只有一筆,我沒怎么在意,過了幾天后,陸陸續(xù)續(xù)發(fā)現(xiàn)了好幾筆類似的情況,平均幾千筆訂單就會(huì)出現(xiàn)一筆退款失敗的,并且這些訂單之間毫無規(guī)律,搞得我這幾天是干啥啥不香,于是痛下決心要深入研究一下這個(gè)問題。
2、 異常情況分析
目前能夠提供幫助的信息并不多,只有這一個(gè)報(bào)錯(cuò)日志,通過在網(wǎng)上收集到的一些相關(guān)資料,發(fā)現(xiàn)了幾篇比較有借鑒價(jià)值的文章,他們的觀點(diǎn)也都幾乎一致:服務(wù)端主動(dòng)斷開TCP鏈接,然后客戶端使用半斷開的鏈接發(fā)起請求時(shí),服務(wù)端響應(yīng)RST包導(dǎo)致此異常情況的發(fā)生。 大多數(shù)文章的建議是:捕獲NoHttpResponseException異常進(jìn)行重試。
3、驗(yàn)證思路
既然有了上述猜想,那么下一步肯定是要做驗(yàn)證的,驗(yàn)證一下在這個(gè)場景下確實(shí)會(huì)出現(xiàn)此現(xiàn)象。剛開始的驗(yàn)證思路比較簡單,就是在服務(wù)端通過工具模擬FIN包,然后再用HttpClient繼續(xù)請求,觀察其結(jié)果,然而抓包結(jié)果顯示Httpclient會(huì)創(chuàng)建一個(gè)新的tcp鏈接進(jìn)行請求,木得辦法,解鈴還須系鈴人,恐怕要看一下HttpClient源碼才能解釋這個(gè)現(xiàn)象了。
通過閱讀HttpClient源碼,大致找到了兩個(gè)比較關(guān)鍵的邏輯點(diǎn)
- HttpClient建立tcp鏈接的時(shí)機(jī)(三次握手的時(shí)機(jī))
- 發(fā)送http請求的時(shí)機(jī)
tip:在三次握手之前會(huì)檢查當(dāng)前tcp鏈接是否處于Open狀態(tài),若處于Open狀態(tài)則復(fù)用此鏈接,若不處于Open狀態(tài)則打開一個(gè)新的tcp鏈接,這樣一來就解釋的通為什么之前HttpClient又重新創(chuàng)建了一個(gè)TCP鏈接的現(xiàn)象了。
4、NoHttpResponseException復(fù)現(xiàn)
然后接下來是要做的就是根據(jù)之前的猜想來復(fù)現(xiàn)NoHttpResponseException場景,具體的思路如下
- 在Httpclient源碼中,等待tcp鏈接建立完成后,打上斷點(diǎn)
- 等服務(wù)器主動(dòng)發(fā)送FIN包斷開鏈接后,再發(fā)起請求,然后觀察結(jié)果

成功復(fù)現(xiàn)了NoHttpResponseException現(xiàn)象,抓包結(jié)果如下所示

通過抓包結(jié)果分析,可以得出"服務(wù)端主動(dòng)斷開TCP鏈接,然后客戶端使用半斷開的鏈接發(fā)起請求"確實(shí)會(huì)導(dǎo)致NoHttpResponseException現(xiàn)象,至于服務(wù)端什么情況下會(huì)主動(dòng)斷開tcp鏈接?間隔多久主動(dòng)斷開tcp鏈接?這里就不再討論了,讀者可以自行了解一下keep-alive機(jī)制。分析到這里,問題基本上算是解決了,生產(chǎn)環(huán)境出現(xiàn)此問題的執(zhí)行時(shí)序應(yīng)該如下所示
- 客戶端HttpClient復(fù)用之前已經(jīng)Open的鏈接
- 然后進(jìn)行檢查(因?yàn)榇藭r(shí)服務(wù)端還未關(guān)閉tcp鏈接,所以鏈接可用)
- 緊接著服務(wù)端主動(dòng)關(guān)閉鏈接導(dǎo)致鏈接不可用
- 服務(wù)端針對客戶端的請求響應(yīng)了RST包
5、解決方案
從業(yè)務(wù)層面考慮,即使修復(fù)了這個(gè)問題,也還是會(huì)有很大的風(fēng)險(xiǎn),畢竟網(wǎng)絡(luò)是未知的,因此我建議C端同事做一個(gè)補(bǔ)償機(jī)制,用來處理退款失敗情況。
當(dāng)然網(wǎng)絡(luò)層面該優(yōu)化的也得優(yōu)化,具體步驟是在HttpClient初始化時(shí)添加重試策略。
private static CloseableHttpClient init() {
// 配置請求的超時(shí)設(shè)置
RequestConfig requestConfig = RequestConfig.custom()
.setConnectionRequestTimeout(CONNECTION_REQUEST_TIMEOUT)
.setConnectTimeout(CONNECT_TIMEOUT)
.setSocketTimeout(SOCKET_TIMEOUT)
.build();
// 重試策略 RETRY_COUNT=3 代表NoHttpResponseException異常重試3次
HttpRequestRetryHandler retryHandler = (exception, executionCount, context) -> {
return executionCount <= RETRY_COUNT && exception instanceof NoHttpResponseException;
};
return HttpClients.custom()
.setConnectionManager(new PoolingHttpClientConnectionManager())
.setRetryHandler(retryHandler)
.setDefaultRequestConfig(requestConfig)
.build();
}
6、引發(fā)的思考
- HttpClientPool的鏈接管理策略(復(fù)用、回收等等)。
- Keep-alive機(jī)制
- 計(jì)算機(jī)網(wǎng)絡(luò)
以上就是生產(chǎn)環(huán)境NoHttpResponseException異常排查解決記錄分析的詳細(xì)內(nèi)容,更多關(guān)于生產(chǎn)環(huán)境NoHttpResponseException的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
POI導(dǎo)出Excel報(bào)錯(cuò)No such file or directory的解決方法
這篇文章主要為大家詳細(xì)介紹了POI導(dǎo)出Excel報(bào)錯(cuò)No such file or directory的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
spring+hibernate 兩種整合方式配置文件的方法
本篇文章主要介紹了spring+hibernate 兩種整合方式配置文件的方法,主要有兩種方式 1、注解方式 2、xml方式實(shí)現(xiàn),有興趣的可以了解一下。2017-04-04
在SpringBoot中整合使用Netty框架的詳細(xì)教程
這篇文章主要介紹了在SpringBoot中整合使用Netty框架的教程,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
Java中BM(Boyer-Moore)算法的圖解與實(shí)現(xiàn)
本文主要介紹了兩個(gè)大的部分,第一部分通過圖解的方式講解BM算法,第二部分則代碼實(shí)現(xiàn)一個(gè)簡易的BM算法,感興趣的小伙伴可以學(xué)習(xí)一下2022-05-05
一個(gè)簡單的java學(xué)生寢室查詢系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了一個(gè)簡單的java學(xué)生寢室查詢系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10

