淺談微服務中的熔斷,限流,降級
https://www.cnblogs.com/raoshaoquan/articles/6636067.html
1.1 名詞解釋
consumer 表示服務調用方
provider 标示服務提供方,dubbo裡面一般就這麼講。
下面的A調用B服務,一般是泛指調用B服務裡面的一個接口。
Provider是出接口的那一方,消費者是進行調用的一方
1.2 拓撲圖
大寫字母表示不同的服務,後面的序号表示同一個服務部署在不同機器的執行個體。
2 從微觀角度思考
2.1 逾時(timeout)
在接口調用過程中,consumer調用provider的時候,provider在響應的時候,有可能會慢,如果provider 10s響應,那麼consumer也會至少10s才響應。如果這種情況頻度很高,那麼就會整體降低consumer端服務的性能。
這種響應時間慢的症狀,就會像一層一層波浪一樣,從底層系統一直湧到最上層,造成整個鍊路的逾時。
是以,consumer不可能無限制地等待provider接口的傳回,會設定一個時間門檻值,如果超過了這個時間門檻值,就不繼續等待。
這個逾時時間選取,一般看provider正常響應時間是多少,再追加一個buffer即可。
2.2 重試(retry)
逾時時間的配置是為了保護服務,避免consumer服務因為provider 響應慢而也變得響應很慢,這樣consumer可以盡量保持原有的性能。
但是也有可能provider隻是偶爾抖動,那麼逾時後直接放棄,不做後續處理,就會導緻目前請求錯誤,也會帶來業務方面的損失。
那麼,對于這種偶爾抖動,可以在逾時後重試一下,重試如果正常傳回了,那麼這次請求就被挽救了,能夠正常給前端傳回資料,隻不過比原來響應慢一點。
重試時的一些細化政策:
重試可以考慮切換一台機器來進行調用,因為原來機器可能由于臨時負載高而性能下降,重試會更加劇其性能問題,而換一台機器,得到更快傳回的機率也更大一些。
2.2.1 幂等(idempotent)
如果允許consumer重試,那麼provider就要能夠做到幂等。
即,同一個請求被consumer多次調用,對provider産生的影響(這裡的影響一般是指某些寫入相關的操作) 是一緻的。
而且這個幂等應該是服務級别的,而不是某台機器層面的,重試調用任何一台機器,都應該做到幂等。
舉個例子:
同一筆訂單的支付資訊,假設被Consumer多次調用,
我在口碑網買了一杯奶茶,下了訂單,下訂單本身是一次接口調用,從流程上看是口碑網将使用者選擇的奶茶的資訊,比如奶茶品類編号,大杯,去冰,半糖,發送到指定店鋪
從接口調用上來看是:口碑網調用了奶茶店的訂單詳情接口,将生成訂單序号,單品資訊傳輸給奶茶店
奶茶店調用口碑網的接口,擷取了客戶的訂單詳情
使用者在微信頁面上進行注冊,頁面捕獲了會員資訊,組裝成指定的格式去調用服務端的注冊接口
頁面是consumer方,而服務端的接口,即服務端是我們常說的Prodiver端,
頁面即使多次調用,需要對服務端的DB資料是一緻的。
比如會員A進行1次,在進行注冊1次,服務端需要隻生成一條資料。是以說影響一般指的是某些寫入操作。
2.3 熔斷(circuit break)
重試是為了應付偶爾抖動的情況,以求更多地挽回損失。
可是如果provider持續的響應時間超長呢?
如果provider是核心路徑的服務,down掉基本就沒法提供服務了,那我們也沒話說。 如果是一個不那麼重要的服務,卻因為這個服務一直響應時間長導緻consumer裡面的核心服務也拖慢,那麼就得不償失了。
單純逾時也解決不了這種情況了,因為一般逾時時間,都比平均響應時間長一些,現在所有的打到provider的請求都逾時了,那麼consumer請求provider的平均響應時間就等于逾時時間了,負載也被拖下來了。
而重試則會加重這種問題,使consumer的可用性變得更差。
是以就出現了熔斷的邏輯,也就是,如果檢查出來頻繁逾時,就把consumer調用provider的請求,直接短路掉,不實際調用,而是直接傳回一個mock的值。
等provider服務恢複穩定之後,重新調用。
2.3.1 簡單的熔斷處理邏輯
目前我們架構有通過注解使用的熔斷器,大家可以參考應用在項目中。
2.4 限流(current limiting)
上面幾個政策都是consumer針對provider出現各種情況而設計的。
而provider有時候也要防範來自consumer的流量突變問題。
這樣一個場景,provider是一個核心服務,給N個consumer提供服務,突然某個consumer抽風,流量飙升,占用了provider大部分機器時間,導緻其他可能更重要的consumer不能被正常服務。
是以,provider端,需要根據consumer的重要程度,以及平時的QPS大小,來給每個consumer設定一個流量上線,同一時間内隻會給A consumer提供N個線程支援,超過限制則等待或者直接拒絕。
2.4.1 資源隔離
provider可以對consumer來的流量進行限流,防止provider被拖垮。
同樣,consumer 也需要對調用provider的線程資源進行隔離。 這樣可以確定調用某個provider邏輯不會耗光整個consumer的線程池資源。
2.4.2 服務降級
降級服務既可以代碼自動判斷,也可以人工根據突發情況切換。
2.4.2.1 consumer 端
consumer 如果發現某個provider出現異常情況,比如,經常逾時(可能是熔斷引起的降級),資料錯誤,這是,consumer可以采取一定的政策,降級provider的邏輯,基本的有直接傳回固定的資料。
2.4.2.2 provider 端
當provider 發現流量激增的時候,為了保護自身的穩定性,也可能考慮降級服務。
比如,1,直接給consumer傳回固定資料,2,需要實時寫入資料庫的,先緩存到隊列裡,異步寫入資料庫。
3 從宏觀角度重新思考
宏觀包括比A -> B 更複雜的長鍊路。
長鍊路就是 A -> B -> C -> D這樣的調用環境。
而且一個服務也會多機部署,A 服務實際會存在 A1,A2,A3 …
微觀合理的問題,宏觀未必合理。
下面的一些讨論,主要想表達的觀點是:如果系統複雜了,系統的容錯配置要整體來看,整體把控,才能做到更有意義。
3.1 逾時
如果A給B設定的逾時時間,比B給C設定的逾時時間短,那麼肯定不合理把,A逾時時間到了直接挂斷,B對C支援太長逾時時間沒意義。
R表示服務consumer自身内部邏輯執行時間,TAB表示consumer A開始調用provider B到傳回的時間 。
那麼那麼TAB > RB + TBC 才對。
3.2 重試
重試跟逾時面臨的問題差不多。
B服務一般100ms傳回,是以A就給B設定了110ms的逾時,而B設定了對C的一次重試,最終120ms正确傳回了,但是A的逾時時間比較緊,是以B對C的重試被白白浪費了。
A也可能對B進行重試,但是由于上一條我們講到的,可能C确實性能不好,每次B重試一下就OK,但是A兩次重試其實都無法正确的拿到結果。
N标示設定的重試次數
修正一下上面section的公式,TAB > RB+TBC * N。
雖然這個公式本身沒什麼問題,但是,如果站在長鍊路的視角來思考,我們需要整體規劃每個服務的逾時時間和重試次數,而不是僅僅公式成立即可。
比如下面情況:
A -> B -> C。
RB = 100ms,TBC=10ms
B是個核心服務,B的計算成本特别大,那麼A就應該盡量給B長一點的逾時時間,而盡量不要重試調用B,而B如果發現C逾時了,B可以多調用幾次C,因為重試C成本小,而重試B成本則很高。 so …
3.3 熔斷
A -> B -> C,如果C出現問題了,那麼B熔斷了,則A就不用熔斷了。
3.4 限流
B隻允許A以QPS<=5的流量請求,而C卻隻允許B以QPS<=3的qps請求,那麼B給A的設定就有點大,上遊的設定依賴下遊。
而且限流對QPS的配置,可能會随着服務加減機器而變化,最好是能在叢集層面配置,自動根據叢集大小調整。
3.5 服務降級
服務降級這個問題,如果從整體來操作,
1,一定是先降級優先級低的接口,兩權相害取其輕
2,如果服務鍊路整體沒有性能特别差的點,比如就是外部流量突然激增,那麼就從外到内開始降級。
3如果某個服務能檢測到自身負載上升,那麼可以從這個服務自身做降級。
3.6 漣漪
A -> B -> C,如果C服務出現抖動,而B沒有處理好這個抖動,造成B服務也出現了抖動,A調用B的時候,也會出現服務抖動的情況。
這個暫時的不可用狀态就想波浪一樣從底層傳遞到了上層。
是以,從整個體系的角度來看,每個服務一定要盡量控制住自己下遊服務的抖動,不要讓整個體系跟着某個服務抖動。
3.7 級聯失敗(cascading failure)
系統中有某個服務出現故障,不可用,傳遞性地導緻整個系統服務不可用的問題。
跟上面漣漪(自造詞)的差別也就是嚴重性的問題。
漣漪描述服務偶發的不穩定層層傳遞,而級聯失敗基本是導緻系統不可用。 一般,前者可能會因為短時間内恢複而未引起重視,而後者一般會被高度重視。
3.8 關鍵路徑
關鍵路徑就是,你的服務想正常工作,必須要完整依賴的下遊服務鍊,比如資料庫一般就是關鍵路徑裡面的一個節點。
盡量減少關鍵路徑依賴的數量,是提高服務穩定性的一個措施。
資料庫一般在服務體系的最底層,如果你的服務可以會自己完整緩存使用的資料,解除資料庫依賴,那麼資料庫挂掉,你的服務就暫時是安全的。
3.9 最長路徑
想要優化你的服務的響應時間,需要看服務調用邏輯裡面的最長路徑,隻有縮短最長時間路徑的用時,才能提高你的服務的性能。