1 寫在前面html
1.1 名詞解釋前端
consumer表示服務調用方 數據庫
provider標示服務提供方,dubbo裏面通常就這麼講。緩存
下面的A調用B服務,通常是泛指調用B服務裏面的一個接口。安全
1.2 拓撲圖app
大寫字母表示不一樣的服務,後面的序號表示同一個服務部署在不一樣機器的實例。框架
2 從微觀角度思考異步
2.1 超時(timeout)ide
在接口調用過程當中,consumer調用provider的時候,provider在響應的時候,有可能會慢,若是provider 10s響應,那麼consumer也會至少10s才響應。若是這種狀況頻度很高,那麼就會總體下降consumer端服務的性能。post
這種響應時間慢的症狀,就會像一層一層波浪同樣,從底層系統一直涌到最上層,形成整個鏈路的超時。
因此,consumer不可能無限制地等待provider接口的返回,會設置一個時間閾值,若是超過了這個時間閾值,就不繼續等待。
這個超時時間選取,通常看provider正常響應時間是多少,再追加一個buffer便可。
2.2 重試(retry)
超時時間的配置是爲了保護服務,避免consumer服務由於provider 響應慢而也變得響應很慢,這樣consumer能夠儘可能保持原有的性能。
可是也有可能provider只是偶爾抖動,那麼超時後直接放棄,不作後續處理,就會致使當前請求錯誤,也會帶來業務方面的損失。
那麼,對於這種偶爾抖動,能夠在超時後重試一下,重試若是正常返回了,那麼此次請求就被挽救了,可以正常給前端返回數據,只不過比原來響應慢一點。
重試時的一些細化策略:
重試能夠考慮切換一臺機器來進行調用,由於原來機器可能因爲臨時負載高而性能降低,重試會更加重其性能問題,而換一臺機器,獲得更快返回的機率也更大一些。
2.2.1 冪等(idempotent)
若是容許consumer重試,那麼provider就要可以作到冪等。
即,同一個請求被consumer屢次調用,對provider產生的影響(這裏的影響通常是指某些寫入相關的操做) 是一致的。
並且這個冪等應該是服務級別的,而不是某臺機器層面的,重試調用任何一臺機器,都應該作到冪等。
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 最長路徑
想要優化你的服務的響應時間,須要看服務調用邏輯裏面的最長路徑,只有縮短最長時間路徑的用時,才能提升你的服務的性能。
來源 : https://www.cnblogs.com/raoshaoquan/articles/6636067.html