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

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

公司在3月底由於一次騰訊雲專線故障,整個支付系統在高峯期中止服務將近10分鐘。並且當時爲了快速解決問題止損,重啓了支付服務,過後也就沒有了現場。咱們支付組在技術架構上原先對專線故障的場景作了降級預案,但故障時預案並無生效,因此此次咱們須要排查清楚降級沒有生效的緣由(沒有現場的過後排查,挑戰很是大)。html

 

 

微信支付流程

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

 

 

這個過程是同步的,若是咱們的支付系統由於網絡問題,沒有取到prepay_id,那麼用戶就沒法支付;api

 

咱們的預案

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

 

故障現象

每次網絡抖動的時候,咱們從監控中都能發現,咱們的超時時間並無徹底起做用。從故障後的監控看平均執行時間達到了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的設計,支付系統真實請求過程大概以下:

 

經驗教訓

一、對於微信支付,缺乏壓測。以前壓測都是基於支付寶,而支付寶的調用模式和微信徹底不同,致使沒法及時發現這個瓶頸;

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

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

 

知識點

一、HttpClient,Route

二、微信支付

三、池技術

 

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

相關文章
相關標籤/搜索