在企業業務領域,錦禮是針對福利、營銷、激勵等員工采購場景的一站式解決方案,包含面向員工、會員等彈性激勵SAAS平臺,由于其直接面向公司全體員工,其服務的高可用尤其重要,本文將介紹錦禮商城大促前夕,通過混沌工程實戰演習,降低應用的MTTR,
MTTR(平均恢復時間)是從產品或系統故障中恢復所需的平均時間, 這包括整個中斷時間——從系統或產品出現故障到其恢復完全運行為止,
如何在混沌演練的場景中降低應用的MTTR,必須需要根據監控定位,然后人工進行反饋進行處理嗎?是否可以自動化,是否有方案可以降低混沌演練程序中的影響?以此達到快速止血,進一步提高系統的穩定性,
本篇文章將根據一些思考和實踐來解答以上問題,
故障無處不在,而且無法避免,
我們將從宿主機重啟問題以及底層服務混沌演練的排查與舉措說起,
背景
【客戶端視角】:出現大量介面(包括提單)超時報錯、可用率跳點,部分客戶命中,產生客訴,
通過定位發現大促備戰前期宿主機重啟及底層服務混沌演練原因,較長時間影響我側系統可用率及性能,尤其是核心介面的部署應用,會大范圍的影響到多個介面的可用率,進一步影響采購端客戶的體驗問題,
特別在TOB領域,本身就存在大客戶的口碑效應,如果恰好頭部客戶碰到該問題,那么極易被放大和激化,
臨時舉措
一方面協同運維組確認宿主機重啟未及時通知的情況,另一方面與底層服務提供者同步演練影響,建議其遵守演練原則最小化爆炸半徑,控制影響范圍,保證演練是可控的,
除了以上協同外部的情況外,我們內部也產生了思考,首先情況故障本身就是不可控的,無論宿主機還是混沌演練,真實場景也是有概率發生的(并且已發生),那么我們只能通過監控定位,然后手動摘除機器或者通知服務提供者處理嗎?是否可以自動化,是否有方案可以降低影響?以此達到快速止血,進一步提高系統的穩定性,
長期方案——JSF中間件能力實踐
既然無法避免故障,那么就擁抱故障,通過一些技術手段來構建獲取應用故障的能力,從而保證應用的高可用,
由于內部的呼叫90+%為(JSF)RPC呼叫,所以我們還是把目光放到了JSF中間件的容錯能力上,以下主要介紹通過JSF中間件的超時與重試、自適應負載均衡、服務熔斷來進行故障轉移的理論與實踐,
實踐是檢驗真理的唯一標準,
關于超時和重試
實際開發程序中,相信大家也見過太多由于超時未設定、設定有誤導致的故障,當超時未設定或者設定不合理,會導致請求回應變慢,慢請求的不斷累計疊加,就會引起連鎖反應,甚至產生應用雪崩,
不僅我們自身的服務,還有外部的依賴服務,不僅HTTP服務,還是中間件服務,都應該設定合理的超時重試策略,并且重視起來,
首先讀寫服務的超時重試策略也是大不相同的,讀服務天生適合重試(如設定合理超時時間后重試兩次),但是寫服務大多是不能重試的,不過如果均是冪等設計,也是可以的,
另外設定呼叫方的超時時間之前,需要先了解清楚依賴服務的TP99回應時間是多少(如果依賴服務性能波動大,也可以看TP95),呼叫方的超時時間可以在此基礎上加50%Buff,當然服務的回應時間并不是恒定的,在某些長尾條件下可能需要更多的計算時間,所以為了有足夠的時間等待這種長尾請求回應,我們需要把超時設定足夠合理,
最后重試次數不宜太多(高并發時可能引發一系列問題(一般2次,最多3次),雖然重試次數越大,服務可用性越高,但是高并發情況下會導致多倍的請求流量,類似模擬DDOS攻擊,嚴重情況下甚至于加速故障的連鎖發生,因此超時重試最好是和熔斷、快速失敗等機制配合使用,效果更佳,這個后面會提到,
除了引入手段,重要的是驗證手段的有效性,
模擬場景(后續另兩個手段也是用該場景)
方案:采用故障注入(50%機器網路延遲3000-5000ms)的方式模擬類似場景,并驗證,
機器部署如下:
壓測介面(QPS-300)及故障介面監控Key值:
1、壓測介面:jdos_b2b2cplatform.B2b2cProductProviderImpl.queryProductBpMap
2、服務消費:jdos_b2b2cplatform.ActivityConfigServiceRPCImpl.queryActivityConfig
3、服務提供:jdos_b2b2cshop.com.jd.ka.b2b2c.shop.service.impl.sdk.ActivityConfigServiceImpl.queryActivityConfig
【注意】
網路場景不支持如下情形:
1、應用容器所在機房:lf04, lf05, lf07, ht01, ht02, ht05, ht07, htmysql, lfmysql02, yn02, hk02, hk03
2、物理機的內核版本:2.6x, 3.8x, 3.10x
正常情況(未注入故障)
注入故障——超時設定不合理情況下(超時2000ms,重試2)
注入故障——超時設定合理情況下(超時10ms,重試2)
該介面TP99在6ms,設定超時10ms,重試2,即:jsf:methodname="queryActivityConfig"timeout="10"retries="2"/
超時重試小結
通過合理的超時重試,整體請求平穩,重試后的故障轉移,大幅提升介面可用率,
超時重試補充
在介面維度拆分不合理的情況下,我們可以更細粒度的使用方法維度的超時重試配置,不過這里有一個注意項JSF當前注解方式不支持方法維度的超時重試設定,僅支持介面維度,如已使用注解類,可進行遷移XML方式進行配置使用,
關于自適應負載均衡
對于shortestresponse自適應負載均衡設計目的是解決在 provider 節點能力不均的場景下,讓處理能力較弱的provider少接受些流量,不會因個別性能較差的 provider 影響到 consumer 整體呼叫的請求耗時和可用率,
能者多勞拙者閑,智者多憂愚者無所慮,
但是該策略下也是存在一些問題的:
- 流量的過度集中高性能實體,服務提供者的單機限流或成為瓶頸,
- response的時間長短有時也并不能代表機器的吞吐能力,
- 大多數的場景下,不同provider的response時長在沒有非常明顯的區別時,shortestresponse同random(隨機),
現有的shortestresponse的實作機制,類似P2C(Power of Two Choice)演算法,不過計算方式不是采用當前正在處理的連接數,而是默認隨機選擇兩個服務提供者參與最快回應比較計算,即:統計請求每個provider的請求耗時、訪問量、例外量、請求并發數,比較平均回應時間 * 當前請求數,用于最快回應負載計算,選取優勝者來避免羊群效應,以此自適應的衡量 provider 端機器的吞吐能力,然后將流量盡可能分配到吞吐能力高的機器上,提高系統整體服務的性能,
<jsf:consumer id="activityConfigService"
interface="com.jd.ka.b2b2c.shop.sdk.service.ActivityConfigService"
alias="${jsf.activityConfigService.alias}" timeout = "3000" filter="jsfLogFilter,jsfSwitchFilter"
loadbalance="shortestresponse">
<jsf:method name="queryActivityConfig" timeout="10" retries="2"/>
</jsf:consumer>
注入故障(設定自適應負載均衡)
自適應負載均衡小結
通過引入自適應負載均衡,從介面最初呼叫就開始了”能者多勞“模式,選舉出的機器承載著更高的流量,故障注入后,介面可用率短時間視窗消失,變成可用率跳點,進一步保障了服務的高可用及性能,
關于服務熔斷
當電路發生短路或嚴重過載時,熔斷器中的熔斷體將自動熔斷,對電路進行保護,避免對設備產生重大影響,甚至火災,
服務熔斷是面向不穩定服務場景的一種鏈路保護機制,
其背后的基本思想非常簡單,將受保護的函式呼叫包裝在熔斷物件中,該物件會監視故障,當呼叫鏈路的某個服務不可用或者回應時間太長導致故障達到設定閾值時,會進行服務熔斷,熔斷視窗內不再有該節點服務的呼叫,以此來最大限度避免下游服務不穩定對上游服務帶來的影響,
<!-- 服務熔斷策略配置 -->
<jsf:reduceCircuitBreakerStrategy id="demoReduceCircuitBreakerStrategy"
enable="true" <!-- 熔斷策略是否開啟 -->
rollingStatsTime="1000" <!-- 熔斷器指標采樣滾動視窗時長,單位 ms,默認 5000ms -->
triggerOpenMinRequestCount="10" <!-- 單位時間內觸發熔斷的最小訪問量,默認 20 -->
triggerOpenErrorCount="0" <!-- 單位時間內的請求例外數達到閥值,默認 0,小于等于0 代表不通過例外數判斷是否開啟熔斷 -->
triggerOpenErrorPercentage="50" <!-- 單位時間內的請求例外比例達到閥值,默認 50,即 默認 50% 錯誤率 -->
<!-- triggerOpenSlowRT="0" 判定請求為慢呼叫的請求耗時,單位 ms,請求耗時超過 triggerOpenSlowRT 則認為是慢呼叫 (默認為 0,即默認不判定)-->
<!-- triggerOpenSlowRequestPercentage="0" 采樣滾動周期內觸發熔斷的慢呼叫率(默認為 0,即默認不觸發慢呼叫熔斷 -->
openedDuration="10000" <!-- 熔斷開啟狀態持續時間,單位 ms,默認 5000ms -->
halfOpenPassRequestPercentage="30" <!-- 半閉合狀態,單位時間內放行流量百分比,默認 40-->
halfOpenedDuration="3000" <!-- 半閉合狀態持續時間設定,需要大于等于 rollingStatsTime ,默認為 rollingStatsTime -->
<!-- failBackType="FAIL_BACK_EXCEPTION" failBack策略, 取值:FAIL_BACK_EXCEPTION拋出例外、FAIL_BACK_NULL回傳null、FAIL_BACK_CUSTOM配置自定義策略,配合 failBackRef 屬性 -->
<!-- failBackRef="ref" 如果 failBackStrategy 配置為 FAIL_BACK_CUSTOM 則必填,用戶自定義的failback策略com.jd.jsf.gd.circuitbreaker.failback.FailBack<Invocation> 介面實作類 -->
/>
<jsf:consumerid="activityConfigService"interface="com.jd.ka.b2b2c.shop.sdk.service.ActivityConfigService"
alias="${consumer.alias.com.jd.ka.b2b2c.shop.sdk.service.ActivityConfigService}" timeout="2000"check="false"
serialization="hessian"loadbalance="shortestresponse"
connCircuitBreakerStrategy="demoCircuitBreakerStrategy">
<jsf:methodname="queryActivityConfig"timeout="10"retries="2"/>
</jsf:consumer>
這里來了一個小插曲,由于JSF本身的心跳機制,檢測故障后,自動(30s檢測一次,三次均例外則摘除)摘除了對應的機器,我們自身設定的熔斷機制并不明顯,因此重新設定故障(網路延遲800-1500ms)進行重新演練,
注入故障(服務熔斷)
服務熔斷小結
從可用率上看,確實在視窗內會關閉對例外機器節點的訪問,不過由于并沒有實作failback策略以及熔斷開啟視窗時間較短,可用率還是會在視窗打開后,直接回傳了呼叫失敗資訊,因此影響了可用率,所以相比于熔斷后失敗,最好的方式是配合服務降級能力,通過呼叫預先設定好的服務降級邏輯,以降級邏輯的結果作為最終呼叫結果,以更優雅的回傳給服務呼叫方,
服務熔斷補充
- 集團已搭建了統一的熔斷組件,并且在泰山上建立了對應的平臺能力,如果團隊需要引入熔斷能力,可以直接接入使用,避免重復建設,詳情見:http://taishan.jd.com/flowControl/limitIndex
> 一種機制可能會擊敗另一種機制,
其實為了增強系統的彈性和魯棒性,以應對各種故障和不可預測的情況,在分布式系統中,通常會設計成能夠部分故障(partially fail),即使不能滿足全量客戶,但是仍然可以向某些客戶提供服務,但是熔斷旨在將部分故障轉化為完全故障,以此防止故障進一步擴散,因此服務熔斷和分布式系統的設計原則中存在一種相互制約的關系,所以,在使用前,要進行仔細的分析和思考,以及后續的調優,
結論
能力只是手段,穩定性才是目的,
無論采用什么手段,進行穩定性建設,我們需要時刻思考的是如何在業務需求和穩定性建設中尋找平衡,以建設支持業務長期增長的高可用架構,
本次就寫到這,如有問題,歡迎交流,希望文章中的一些經驗,給大家帶來一些識訓,或者說,大家不妨思考一下你們會采用何種技術方案和手段來解決類似問題,歡迎留言交流,也希望能和更多志同道合的伙伴溝通交流,
參考檔案
外部檔案
The power of two random choices : https://brooker.co.za/blog/2012/01/17/two-random.html
負載均衡:https://cn.dubbo.apache.org/zh-cn/overview/core-features/load-balance/#shortestresponse
作者:京東零售 李孟冬
內容來源:京東云開發者社區
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/555071.html
標籤:其他
上一篇:爬取豆瓣Top250圖書資料
下一篇:返回列表