彈性設計為系統穩定性建設提供了一種新的視角和方法,它有助於提高系統的可用性、性能和安全性,同時也降低了維護和修複的成本和風險 ...
背景
隨著業務的快速變化和技術的不斷發展,系統面臨著諸多挑戰,例如流量峰值、依賴服務故障、硬體故障、網路中斷、軟體缺陷等,這些因素都可能影響到系統的正常運行。在這種背景下,彈性設計(Resilience Design)應運而生。彈性設計是一種系統的設計和構建方法,系統的設計原則應該本著不信任外部資源(外部API服務、網路設備、存儲、消息等)100%可用的原則,在關鍵處理路徑上針對上述可能發生故障的點進行容錯加固設計,保護系統自身的可用性。它的目標是使系統能夠在面臨壓力和不確定性時,保持服務可用性和性能,而不是簡單地在問題出現後進行修複。彈性設計考慮到了系統可能會遭受的各種攻擊,包括物理攻擊、網路攻擊、軟體錯誤等,並採取了相應的預防措施。
彈性設計的核心思想是預見並應對系統可能面臨的風險和挑戰,這需要對系統的需求、架構、組件和服務進行深入的理解和管理。彈性設計還強調了自動化和快速響應的重要性,以便在問題發生時,能夠迅速地檢測到問題並採取相應的措施。
總的來說,彈性設計為系統穩定性建設提供了一種新的視角和方法,它有助於提高系統的可用性、性能和安全性,同時也降低了維護和修複的成本和風險。
一、故障隔離標準
1、故障隔離概念
故障隔離是指當系統中某些模塊或者組件出現異常故障時,把這些故障通過某種方式隔離起來,讓其不和其他的系統產生聯繫,即使這個被隔離的應用或者系統出現了問題,也不會波及其他的應用。故障隔離方案是一個應用系統實現高可用的重要組成部分。
2、故障隔離原因
系統必須具備防止故障從一個系統/組件傳播到另一個系統/組件的能力。故障從一個系統/組件傳播到另一個系統/組件通常有以下兩種原因。
1.系統/組件間強依賴:如果系統/組件間存在強依賴,當一個系統/組件發生故障時,強依賴它的組件將無法正常工作。防止強依賴引發的故障傳播,通常的手段是將強依賴轉化為弱依賴或最弱依賴,比如設置合適的超時、捕獲異常、同步依賴轉非同步依賴、提供備份組件等。
2.系統/組件間共用資源:如果系統/組件間存在共用的資源(如線程池、資料庫連接池、網路連接池、記憶體區等),當一個系統/組件因為故障耗盡了共用的資源後,所有依賴該資源的系統/組件也都會發生故障。防止共用資源引發的故障傳播,通常的手段是對組件的資源使用建立配額體系,或者為重要組件提供專用資源。
二、訪問量控制標準
訪問量控制是指服務提供者或者服務使用者對服務資源有效的SLA控制,在做訪問量控制設計時,需要關註以下幾方面:
1.服務提供者必須給出本服務(包括系統調用服務、頁面服務等)的訪問策略,包括最大的訪問能力、其它訪問約束(如參數約束、單賬戶訪問約束等),說明違反服務訪問策略的後果。
2.服務提供者需要對違反服務訪問策略的情況,實施管控措施。我們要求所有對外提供服務的系統(如對外服務的網關係統、對外服務的web系統等)必須具有防止外部訪問過載的能力(即具備限流能力)。
3.渠道入口系統需要具備能夠降級入口服務的能力,確保入口功能服務在出現異常時,在交易鏈路的最前段截斷異常,防止影響擴大。
4.服務調用方需要對關鍵場景下的非關鍵服務訪問進行容錯設計,常用的手段包括(熔斷、降級),確保在非關鍵服務訪問出現異常的情況下,迅速切斷該服務訪問,保證關鍵交易成功率。
5.服務調用方在調用第三方服務時,需要明確外部服務能力,並具備相應手段可以進行訪問控制。
6.原則上所有控制訪問量的手段(如限流、熔斷、降級)均應具備實時調整的能力,以保證在異常訪問下系統的動態性能餘量充足。
三、服務降級、限流、熔斷
熔斷和限流都可以認為是降級的一種方式
1、服務降級
服務降級是當出現系統/組件故障後,以犧牲某些業務功能或者犧牲某些客戶群體為代價,保障更關鍵的業務、客戶群體服務質量的應急措施。服務降級可以是人工觸發的,也可以是系統自動執行的。所有核心交易場景下的非關鍵服務訪問均應進行服務降級設計,以保證核心交易成功率。在有限的資源情況下,對系統做出一些犧牲,有點“棄卒保帥”的意思。放棄一些功能,保證整個系統能平穩運行。
降級的註意點
1.每個服務都需要制定自己的降級策略,根據服務不同的優先順序來設定降級方案,緊急情況下可以通過啟用降級開關關閉非核心功能,損失一定的客戶體驗,以確保核心關鍵業務的服務可用性。
2.對業務進行仔細的梳理和分析,哪些是核心流程必須保證的,哪些是可以犧牲的,幹掉一些次要功能。比如電商功能大促期間可以把評論關閉或者簡化評論流程
3.什麼指標下能進行降級:吞吐量、響應時間、失敗次數等達到一個閾值才進行降級處理
4.降級最簡單的就是在業務代碼中配置一個開關或者做成配置中心模式,直接在配置中心上更改配置,推送到相應的服務。比如DUCC開關技術。
2、服務限流
服務限流是分散式系統中一種常見的保護機制。當負載超出系統/組件的處理能力上限時,可能會造成系統響應時間增加或部分業務失敗,需要通過業務限流來防止系統響應進一步嚴重惡化。例如,在一個分散式系統中,最大tps為2000。這意味著每秒最多只能處理2000個請求。為了防止系統過載,可以設置規則限制上游服務每秒調用的tps。當請求量超過2000tps時,可以通過隨機或選擇性拋棄一些請求來實現限流。
具體實現方式可以有很多種,比如:
- 隨機拋棄一部分請求:當請求量超過2000tps時,從所有請求中隨機選擇一部分進行拒絕處理。這樣可以確保每個請求都有平等的機會被處理,同時也可以在一定程度上避免單個請求對整個系統造成過大的影響。
- 選擇性拋棄一部分請求:與隨機拋棄不同,選擇性拋棄是針對某些特定的請求進行處理。例如,可以根據請求的來源、參數等信息來判斷是否需要拋棄該請求。這種方式可以更加靈活地控制限流策略,但也需要更多的資源和技術支持。
服務限流是一種有效的保護機制,可以幫助我們應對高併發場景下的挑戰。通過合理設置業務限流規則,我們可以保證系統的穩定運行,提高用戶體驗和滿意度。
主流的限流演算法有:
(1)、計數器法
計數器法是限流演算法里最簡單也是最容易實現的一種演算法。比如我們規定,對於A介面來說,我們1分鐘的訪問次數不能超過100個,分散式計數器一般會用自研計數服務或者 redis 等組件來做。
(2)、滑動時間視窗演算法(JSF限流模式之一)
為瞭解決計數器法統計精度太低的問題,引入了滑動視窗演算法。下麵這張圖,很好地解釋了滑動視窗演算法:
在上圖中,整個紅色的矩形框表示一個時間視窗,在我們的例子中,一個時間視窗就是一分鐘。然後我們將時間視窗進行劃分,比如圖中,我們就將滑動視窗劃成了6格,所以每格代表的是10秒鐘。每過10秒鐘,我們的時間視窗就會往右滑動一格。每一個格子都有自己獨立的計數器counter,比如當一個請求 在0:35秒的時候到達,那麼0:30~0:39對應的counter就會加1。
計數器演算法其實就是滑動視窗演算法。只是它沒有對時間視窗做進一步地劃分,所以只有1格。
由此可見,當滑動視窗的格子劃分的越多,那麼滑動視窗的滾動就越平滑,限流的統計就會越精確。
(3)、漏桶演算法
漏桶演算法思路很簡單,水(請求)先進入到漏桶里,漏桶以一定的速度出水,當水流入速度過大會直接溢出,可以看出漏桶演算法能強行限制數據的傳輸速率。
從圖中我們可以看到,整個演算法其實十分簡單。首先,我們有一個固定容量的桶,有水流進來,也有水流出去。對於流進來的水來說,我們無法預計一共有多少水會流進來,也無法預計水流的速度。但是對於流出去的水來說,這個桶可以固定水流出的速率。而且,當桶滿了之後,多餘的水將會溢出。
我們將演算法中的水換成實際應用中的請求,我們可以看到漏桶演算法天生就限制了請求的速度。當使用了漏桶演算法,我們可以保證介面會以一個常速速率來處理請求。所以漏桶演算法天生不會出現臨界問題。
(4)、令牌桶演算法(JSF限流模式之一)
令牌桶演算法是網路流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種演算法。典型情況下,令牌桶演算法用來控制發送到網路上的數據的數目,並允許突發數據的發送。
從圖中我們可以看到,令牌桶演算法比漏桶演算法稍顯複雜。首先,我們有一個固定容量的桶,桶里存放著令牌
(token)。桶一開始是空的,token以 一個固定的速率r往桶里填充,直到達到桶的容量,多餘的令牌將會被丟棄。每當一個請求過來時,就會嘗試從桶里移除一個令牌,如果沒有令牌的話,請求無法通過。
(5)、限流演算法小結
計數器 VS 滑動視窗:
1 計數器演算法是最簡單的演算法,可以看成是滑動視窗的低精度實現。
2 滑動視窗由於需要存儲多份的計數器(每一個格子存一份),所以滑動視窗在實現上需要更多的存儲空間。
3 如果滑動視窗的精度越高,需要的存儲空間就越大。
漏桶演算法 VS令牌桶演算法:
1 漏桶演算法和令牌桶演算法最明顯的區別是令牌桶演算法允許流量一定程度的突發。
2 因為預設的令牌桶演算法,取走token是不需要耗費時間的,也就是說,假設桶內有100個token時,那麼可以瞬間允
3 當然我們需要具體情況具體分析,只有最合適的演算法,沒有最優的演算法。
3、服務熔斷
服務與服務之間的依賴性,故障會傳播,會對整個微服務系統造成災難性的嚴重後果,這就是服務故障的“雪崩”效應。為瞭解決這個問題,業界提出了斷路器模型。
服務熔斷是在分散式系統中避免從系統局部的、小規模的故障,最終導致全局性的後果的手段。它是通過快速失敗(Fail Fast)的機制,避免請求大量阻塞,從而保護調用方。比如:一個服務A調用當下游B超時或失敗時,會導致請求超時引起堆積隊列,從而導致下游B系統壓力越來越大而無法恢復。如果我們我們還是盲目地去請求,即時失敗了多次,還是傻傻的去請求,去等待。這樣一來增加了整個鏈路的請求時間,同時下游B系統本身就出現了問題,不斷的請求又把系統問題加重了,恢復困難。如果使用了熔斷功能,則當觸發熔斷後,到下游B服務的壓力減小,從而保護了存活的系統。原理圖如下:
業內目前流行的熔斷器很多,例如Sentinel,以及最多人使用的Hystrix,同時京東內部的JSF也支持熔斷
關於熔斷原理詳見這篇文章
【源碼】SpringCloud-Hystrix服務熔斷與降級工作原理&源碼
四、容錯設計
容錯設計對系統穩定性至關重要,在容錯設計中,首先應確定系統容錯等級策略,策略分級可參考如下表
容錯設計等級 | 等級描述 |
---|---|
無容錯性設計 | 所依賴的外部資源訪問出錯,本應用未能檢測識別到,導致應用處理數據出錯,造成臟數據的 |
弱容錯性設計 | 所依賴的外部資源訪問出錯,本應用服務不可用且難以恢復的 |
基本容錯性設計 | 所依賴的外部資源訪問出錯,本應用服務不可用,但是由人工操作後可恢復的 |
較強容錯性設計 | 所依賴的外部資源訪問出錯,本應用服務不可用,但可自動恢復的 |
強容錯性設計 | 所依賴的外部資源訪問出錯,本應用不受影響並正常對外提供服務的 |
(容錯等級設計)
確定好容錯策略分級之後,開始系統容錯設計。系統需要提供充足的容錯機制,以應對所依賴的外部服務或其他依賴資源發生故障情況;系統的設計原則應該本著不信任外部資源(外部服務、DB、網路設備、存儲、消息等)100%可用的原則,在關鍵處理路徑上針對上述可能發生故障的點進行容錯加固設計,保護系統自身的可用性。
1、服務不可用容錯設計
服務不可用容錯設計:跨系統服務調用,調用端必須保障請求準確送達、服務端必須保障響應準確返回;基於此原則,某些場景下可能發生請求送達或響應返回丟失的,必須使用重試機制來彌補,如通過非同步確保消息通知機制來解決跨系統、一次性調用場景下請求無法確保送達問題。服務提供方系統發佈中、或其他不可預知的服務訪問超時,都有可能導致客戶端請求失敗,此時客戶端應用若無任何容錯機制,則業務處理異常中斷。
探活檢測:是故障隔離的基礎,通過應用的健康檢查可以識別出異常的服務節點。負載均衡和服務註冊中心可以及時地將這些異常節點剔除,從而阻止請求流量再次分發到這些節點,實現節點維度的故障隔離。同樣地,資料庫連接探活也是類似的思路。
重試邏輯:在分散式系統中,微服務系統的重試操作可能會觸發多個其他請求或重試操作,並導致級聯效應。為了避免這種影響,我們應該儘量減少重試的次數,並使用指數退避演算法來逐漸增加重試之間的延遲時間,直到達到最大限制。重試的另外一個要求是服務的冪等設計。
故障轉移:對於冪等服務,調用出現故障,系統不會立即向調用者返回失敗結果,而是自動切換到其他服務副本,嘗試其他副本能否返回成功調用的結果,從而保證了整體的高可用性。
快速失敗:快速失敗是指依賴方不可用時,不能耗費大量的寶貴系統資源(比如線程、連接數等),再等待其恢復,而是要在其達不到合理的功能和性能要求時快速失敗,避免占用資源,甚至拖垮系統。
對於非冪等的服務,重覆調用就可能產生臟數據,引起的麻煩遠大於單純的某次服務調用失敗,此時就應該以快速失敗作為首選的容錯策略。我們可以使用基於操作的成功或失敗統計次數的熔斷模式,而不是使用超時。
同步轉非同步:同步調用意味著強依賴。消息隊列起到很好的削峰填谷的作用,且一般都具備很大規模的消息堆積能力。
資源隔離:線程池隔離:我們可以通過線程池隔離的方式來實現資源的隔離,不同的請求使用相應的線程池來處理,即便出現請求資源time out的情況,最多影響當前線程池的資源,而不會影響整個服務的線程資源。類似船艙中的隔離區域。
信號量隔離:信號量主要是用來控制線程數的,規定好一些調用最大的併發量,超過指定的信號量後,可以將請求丟棄或者延時處理,防止線程的不斷增長導致的服務異常。
降級、限流、熔斷:參考上一章節
2、數據容錯設計
資料庫容錯設計:業務在正常執行的時候,如果遇到某個資料庫故障,需要具備快速的容錯機制,保證後續業務的正常進行。這種容錯機制包括資料庫的災難轉移或切換方案。
緩存數據設計:依賴本地緩存是一種數據的降級方案,當依賴方(服務或資料庫)出現故障時,可以一定程度地保障業務可用。一般是應用於核心、決不能出錯的業務場景。緩存的問題是數據實時更新的一致性問題,但在很多場景下,對於客戶的體驗,讀到舊的數據往往要比系統無法響應要好很多。
參考:
中國信通院分散式系統穩定性建設指南
本文所述技術仍有待進一步研究和探討,希望能為相關領域的研究者提供一些啟示。文章中難免會有不足之處,希望讀者能給予寶貴的意見和建議。謝謝!
作者:京東物流 馮志文
來源:京東雲開發者社區 自猿其說Tech 轉載請註明來源