一個隱藏在支付系統很長時間的雷


這個案例是最近剛發生不久的,只是這個雷的歷史實在是久遠。

公司在3月底因為一次騰訊雲專線故障,整個支付系統在高峰期停止服務將近10分鍾。而且當時為了快速解決問題止損,重啟了支付服務,事后也就沒有了現場。我們支付組在技術架構上原先對專線故障的場景做了降級預案,但故障時預案並沒有生效,所以這次我們需要排查清楚降級沒有生效的原因(沒有現場的事后排查,挑戰非常大)。

 

 

微信支付流程

首先回顧一下微信支付的流程(也可以參考https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_4):

 

 

這個過程是同步的,如果我們的支付系統因為網絡問題,沒有取到prepay_id,那么用戶就無法支付;

 

我們的預案

我們的預案非常簡單,就是在請求api.mch.weixin.qq.com時,在HTTPClient中設置了一個超時時間,當支付請求超時時,我們就請求微信支付的另外一個備用域名api2.mch.weixin.qq.com,我們的超時時間設置的是3秒;

 

故障現象

每次網絡抖動的時候,我們從監控中都能發現,我們的超時時間並沒有完全起作用。從故障后的監控看平均執行時間達到了10秒,超時時間(3秒)完全不管用:從日志中進一步分析到,很多請求都是在10秒以上,甚至10分鍾后才報超時異常。10分鍾后再降級到備份域名顯然已經沒有什么意義了。這讓我們開發很不解,為什么HttpClient的超時設置沒有生效,難道是HttpClient的bug?

以前我們也懷疑過自己封裝的HTTPClient組件有問題,但是我們寫了一個並發程序測試過,當時並沒有測試出有串行問題或者不支持並發的問題;

 

 

真相-系統層面瓶頸點HttpClient

最近通過我們測試(我們組其中一個開發在測試環境對故障進行了復現)和調研后,我們發現支付系統使用的封裝后的HttpsClient工具,同一時間最多只允許發起兩個微信支付請求;當這兩個請求沒有迅速返回的時候(也就是網絡抖動的時候),后面新的請求,只能排隊等候,進而block住線程耗盡tomcat的線程;超時未生效的原因是因為CloseableHttpClient默認的實現對網絡連接采用了連接池技術,當連接數達到最大連接數時,后續的請求只能排隊等待連接,根本就無法取得發起網絡請求的機會,所以也談不上連接超時和響應超時;

系統本來應該這樣:

實際卻是這樣:

 

參考和論證

我們從HttpClient的官方文檔中證實了這一點,同時也寫程序進行了驗證(這其中的配置比較復雜和深入,計划后續再寫一篇文章進行說明,請持續關注汪汪隊);

官方文檔:http://hc.apache.org/httpcomponents-client-4.5.x/tutorial/html/connmgmt.html 2.3.3. Pooling connection manager

我們訪問微信支付域名api.mch.weixin.qq.com,無論我們發起多少個請求, 在httpclient中就是對應一個route(一個host和port對應一個route),而每個route默認最多只有兩個connection;而這個Route的默認值,我們代碼中沒有修改。所以,一台tomcat,實際上同一時間最多只會有兩個請求發送到微信。網絡抖動的時候,請求都會需要很長時間才能返回,因為我們設置的是3秒響應超時,所以,當網絡抖動時,我們單台機器的qps就是3秒2個,極限情況下一分鍾最多40個請求;更糟糕的情況,我們的程序中微信退款的超時時間設置的是30秒,所以如果是退款請求,那就是1分鍾只能處理4個請求,10台服務器一分鍾也就只能處理40個請求;因為支付和退款都是共用的一個HttpClient連接池,所以退款和支付會互相影響;

按照HttpClient的設計,支付系統真實請求過程大概如下:

 

經驗教訓

1、對於微信支付,缺少壓測。之前壓測都是基於支付寶,而支付寶的調用模式和微信完全不一樣,導致無法及時發現這個瓶頸;

2、研發對HttpClient等使用池技術的組件,原理了解不夠深入,沒有修改默認策略,最終形成了瓶頸;

3、對報警細節觀察不是很到位,每次網絡抖動我們只看到了網絡方面的問題,卻忽略了程序中超時參數未生效的細節,從而多次錯失發現程序缺陷的機會,所以“細節決定成敗”;

 

知識點

1、HttpClient,Route

2、微信支付

3、池技術

 

更多案例請關注微信公眾號猿界汪汪隊

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM