近日我司進行雲服務商更換,恰逢由我負責新上線的三方呼叫 api 維護管理,在將服務由阿里雲部署到騰訊雲過程中,我們壓測發現在騰訊雲呼叫京東介面時 TP999 抖動十分劇烈,儘管業務層有重試操作但是超時依然較多,並不滿足業務要求…… 接下來針對過程中發現的種種問題我們便踏上了優化之路。
開端
那還是普通的一夜,突然群裡蹦出一些報警訊息,多條業務線呼叫外部 api 超時,dubbo 執行緒池數量不足,還好很快服務恢復了正常,不過這也為我提了個大醒,我維護的服務作為業務呼叫的聚合出口,一旦服務發生異常將會導致很多業務方失敗。儘管在壓測過程中我們便知道了遷移雲後的 api 呼叫可用性會有明顯下降,但在 C 端業務還未遷移時便出了這麼大影響確實是前期準備不足。
基於表面問題分析
出現問題先從最容易下手的地方開始解決,先分析問題,執行緒池飆高報警,結合日誌、APM(skywalking) 工具進行排查,發現報警時刻執行緒數量和 cpu 均出現飆升情況,由於服務主要提供的功能就是三方呼叫,很多執行緒卡在呼叫三方 api 等待響應結果步驟上,並且有響應結果的呼叫消耗在 http 的時間也特別長,這就很容易理解了,是由於三方 api 抖動加之服務建立 http 連線過多先是導致應用響應緩慢,再之服務超時觸發重試瞬間導致應用負載翻倍,進而加劇了服務緩慢問題。在不具備限流降級能力時服務一旦出現這種情況很容易導致雪崩,萬幸沒有太多業務損失,作為一個成熟的程式設計師總不能給自己留坑啊,趕緊來填坑!
多方位填坑
問題找到了,方案自然不缺了
- 修改 http 呼叫方式,引入 http 連線池
這個方案其實在之前壓測過程中便考慮採用了,由於使用的三方 sdk 在 http 呼叫中均使用 java 原生的 HttpURLConnection ,這在介面高併發情況下不停建立連線,效能實在太差了。考慮團隊內大家熟悉程度直接用 HttpClinet 覆蓋三方 SDK 中的 HttpUtil 實現,稍加修改即可。修改好重新進行壓測,TP999 指標、cpu 使用率均有明顯下降,這波漂亮,投入產出 ++!
- 第一個方案就萬事大吉了嗎?支援請求超時分級,執行緒隔離
在第一個方案開發上線後我們又發現了新問題,業務側超時情況依然明顯!這個原因很容易想到,針對不同業務方我們只配置了一份超時配置,儘管部分業務方(主要是 C 端業務,對介面超時十分敏感)會提前超時然後觸發重試,但是我的應用執行緒依然在阻塞請求,這是資源的浪費啊,仔細想想,之前的問題還是沒有解決,在客戶端不停重試下服務的 dubbo 執行緒豈不是還得爆了啊。再優化,這次我想到了請求分級,由於不同業務方對介面的響應時效要求不同,我們進行了連線池隔離,針對不同的業務方呼叫可以在呼叫時設定請求分級,我們大致分類下建立了三種分級 ‘FAST’,'STANDARD','SLOW',不同分級由服務提供者去配置使用不同超時方案。服務穩定性又進一步,問題再一次得到了解決。
這裡還要提醒下大家 HttpClinet 使用過程中一般會配置連線池使用,切記搭配連線池使用時超時引數要配置三個,分別為
ConnectionRequestTimeout 獲取連線池連線的超時時間(這個大家最容易忽略了)
ConnectTimeout 連線超時時間
SocketTimeout socket 讀取超時時間
合理應用重試(消費者重試)
儘管在提供者服務上已進行程式碼優化,但是為了提升業務成功率,合理的使用重試也是很有必要的。此處要注意如果服務響應較慢千萬避免消費者的多級重試,如果我們的整個業務呼叫鏈每一層都做了重試那麼就會導致鏈路中響應慢的服務壓力愈發增長,嚴重的引發重試風暴,直接壓垮服務,所以合理設定重試也是很關鍵的一環,這裡我們後續也要考慮引入熔斷降級方案,避免意外發生。
在上述三招連環出擊後,問題基本告一段落,2C4G 200 dubbo 執行緒,30 http 執行緒,吞吐量固定 1k,服務的 TP999 在壓測過程中基本可以穩定在 500 毫秒以下,滿足各方要求。
問題再起
伴隨著應用接入量越來越大,我們的 C 端業務方又給提出了一個新問題,服務的超時情況怎麼和阿里的服務差那麼大啊(此處僅是指京東介面),由於是新業務直接上線到騰訊雲,懷疑可能是我們服務的效能問題,這個在之前只有一些 B 端業務我確實沒有過多注意,必須把這個問題好好查一查,還是從 APM 工具側檢視,果然,介面的 TP999 抖動十分劇烈,很多達到超時閾值,而 C 端業務十分重視這些指標,這也值得我們去學習,往往這些指標上線很容易暴露出根本問題,在高併發下看 TP90、TP99 時指標看起來還是挺正常的,那是因為超高請求量將響應時間給平均下去了。
糾結求解
這次的問題不再簡單,面對這些 TP999 的超時響應,我們在觀測了應用的整體流量、JVM 情況等等,愣是沒有找到瓶頸,有之前阿里雲的應用對比我們堅信問題是存在的,可能是在網路請求層面。接下來我們一步步出擊。
0、檢視 APM 工具發現部分 HTTP 請求確實超時,還有一部分是 HTTP 請求耗時不算長,但是從 APM 統計的呼叫鏈路來看耗費在提供者服務的時間比較長導致超時了。上述為兩個問題,分別來看,請求可以確定是超時的我懷疑是不是由於使用了 http 連線池,池中的連結是不是超時了,從 httpclient 的日誌中我分析了一下並沒有這個問題,因為三方響應的 header 只返回了 keepalived,並沒有返回其有超時時間。第二個問題我反覆查詢當時並沒有找到原因,服務壓力不大,也沒有發生長耗時的 GC,實在不好解釋這個問題,但是後來問題找到了,繼續看結果在下文。
1、為了觀察應用的請求響應資訊,我們對 http 出口進行了抓包,通過對大量請求的抓包分析,我們找到了在響應比較高的時候抓包的 ip 中竟然有香港的 ip,為了驗證這個問題,我們去百度了 dns 解析,發現該 ip 確實為該域名所有。
2、我們的伺服器和出口 ip 都是北京的為什麼 dns 解析出來的 ip 會返回香港的呢?帶著問題我們求助了騰訊雲官方,在官方問題排查過程中,我們又做了其他測試,在伺服器上寫了一個 python 小指令碼定時進行指定域名的 dns 域名解析,果然在跑了一段時間後出現了幾次解析結果為香港的 ip,狂喜。此時騰訊雲官方也有了回應,由於京東自己搭建的 DNS 解析伺服器,但是無法準確識別騰訊雲的 ip 地域,導致偶爾會解析到香港,解決方案是他們推動京東去識別騰訊雲的 ip。
3、等官方解決進度遲緩啊,我們能有什麼臨時的解決辦法嗎?糾結之時我們也接入了架構組為我們搭建的 kong 出口閘道器,有專門的 net 出口,更高的頻寬,完善的 granfa 監控,結合監控請求抖時展現更明顯了,於是我們想到了固定 host,更改 dns 方案,由於固定 host 風險太大了,我們採用了更改 dns 的方案嘗試。原來機器上預設是騰訊雲的 dns,我們分別更換為阿里的 dns 和 114 的 dns 進行了測試,測試結果是差距不大最終我們採用了阿里的 dns,在更換阿里的 dns 後效果明顯,原來 TP999 的尖刺明顯減少,觀察 kong 日誌也不存在香港的 ip 了,這裡就有點詫異騰訊不是說和 dns 沒關係嗎,為什麼換了別人家的 dns 情況就好了很多(這個問題的結果是明確的,但是原因目前還是停留在猜測階段,猜測是 114 和阿里 dns 對此有優化,所以在該場景下優於騰訊 dns,若大家有方案可以驗證此問題歡迎提出來哦)。
小插曲,kong 閘道器的 dns 解析器是寫在 kong.conf 配置檔案中的,如果未配置該項則會讀取系統的 /etc/resolv.conf 檔案,每次 kong 啟動後會讀取配置並快取到記憶體中,如果啟動 kong 後再修改系統的 nameserver 對 kong 是不管用的!還好我們之前有在專門的機器中測試 dns 更換效果,所以才敢肯定是 kong 配置問題,但是來回也折騰了不少時間才找到問題。
在這裡我也想告訴大家,和別人協作,如果可以請一定準備好對比資料,避免他人因質疑而不配合,或者合作結果並不如預期時可以有個對照。
4、記得最開始有一個問題當時沒找到原因嗎,‘一部分 HTTP 請求耗時不算長,但是從 APM 統計的呼叫鏈路來看耗費在提供者服務的時間比較長導致超時了。’ 這個的問題最後還是 http 請求超時的原因,我對照了 kong 的日誌和服務的 httpClient 日誌,發現出現這種問題的請求有個共同點就是請求頭很快接收到了,但是 body 遲遲未讀取到然後觸發超時了。skyWaling 中展示的 HTTP 耗時很短的原因是因為只要開始有響應就算請求結束了,然後為什麼會有隻返回 header 的情況呢?我 ping 了一下出現問題的 ip 是香港的,響應時間明顯要比北京的慢,而且偶爾會丟包。這個問題解釋通了。
終是踏平
經過又一輪的問題解決,測試加驗證效果耗費了近兩週時間,服務的可用性也提升了一個層次,算是一個小坑,不過這也讓我們對 http 的整個請求流程做了很多梳理和二次認知,以上便是我在解決這個超時問題的整個解決路徑和心路歷程,感謝看我的叨叨叨,希望我們可以一起共同進步。
更多精品文章,歡迎大家掃碼關注我的公眾號「碼海」