不斷的學習新東西,不斷的思考更多,不斷的對原有自己造成更大的衝擊。如果要給我遷移 FaaS 期間的感受下一個總結,那麼一定是:“在撕裂中成長”。
微服務架構下,穩定性和高可用性一個永恆的話題,在實際的治理過程中,我們有可能會遇到以下場景:
- 某個應用灰度發佈,先上了幾臺機器,由於代碼邏輯寫的有問題,造成線程池滿,出現運行異常。
- 服務端集羣中,某幾臺機器由於磁盤滿,或者是宿主機資源爭搶導致 load 過高,客戶端出現調用超時。
- 服務端集羣中,某幾臺機器由於線程池滿,造成 Full Garbage Collection。
在以上 3 種場景中,由於客戶端並不法感知已經出現問題的那些服務端,依然會發送請求到這些機器上,造成業務調用報錯,上游的機子將會被下游的某臺機子的短暫故障拖垮,造成應用雪崩的風險。
面對這種場景,如果僅僅爲此而進行服務降級,對應用的傷害未免過大,但如果我們可以檢測出服務集羣中某些故障機子,並對其進行短暫隔離,即可有效保障服務的高可用與系統的穩定性,同時給運維人員提供了寶貴的緩衝時間,用於問題定位,排除故障。
本文將作爲《微服務治理實踐》系列篇的第一篇,爲大家介紹如何實現離羣實例摘除。該系列文章是基於阿里雲商業化產品 EDAS 的微服務實踐,如果您團隊具備較強的微服務治理能力,那麼希望我們在微服務治理方面的實踐和背後的思考,可以爲您提供一些參考。
Microservice Outlier Ejection (微服務離羣實例摘除)
- 什麼是離羣實例摘除
當單點發生夯機異常時,consumer 能主動判斷,並將對應的 provider 實例短時間剔除,不再請求,在一定時間間隔後再繼續訪問。同時,具有全局異常判斷能力,當 provider 異常實例的數量過多時,並且超過一定的控制比例,說明此時 provide 整體服務質量低下,該機制僅保持摘除一定的比例。
- 離羣實例摘除的功能
從服務層容錯能力上,對業務穩定性進行增強,有效解決單點故障的問題。
- 與熔斷的區別
熔斷是指當服務的輸入負載激增時, 避免服務被迅速壓垮導致雪崩效應,而對負載進行斷路的一種方式 。熔斷一般由熔斷請求判斷算法,熔斷恢復機制,熔斷報警等模塊組成。隔離是指,爲了避免在依賴的服務故障時候造成的故障擴散,而採取的將系統進行單元化設計的一種架構方法。
若僅僅由於服務端集羣中單點異常問題,就採用熔斷降級方案,將會對應用的傷害過大,離羣實例摘除可以有效地解決單點異常問題從而保證服務質量。若 provider 整體服務質量低下時,離羣摘除效果不再明顯,此時可以採用熔斷降級功能。
- 離羣實例摘除支持的版本
只要您的應用版本在列表中,您無需改動一行代碼就可以使用到離羣實例摘除功能。
目前已經覆蓋了市面上大部分微服務場景,後續我們將會持續支持開源最新的 Dubbo/Spring Cloud 版本。
我們提供了 Dubbo 和 Spring Cloud 兩種場景的離羣摘除功能,本文將先介紹一下 Dubbo Microservice Outlier Ejection 的實踐與效果。
示例
下面將通過在 EDAS 上通過演示 Dubbo 離羣摘除功能及效果。
企業級分佈式應用服務 EDAS(Enterprise Distributed Application Service)是一個應用託管和微服務管理的 PaaS 平臺,提供應用開發、部署、監控、運維等全棧式解決方案,同時支持 Dubbo、Spring Cloud 等微服務運行環境。
準備
接下來以微服務 Demo 爲例子示範離羣摘除功能,讀者可以從 github 中下載驗證
https://github.com/aliyun/alibabacloud-microservice-demo/tree/master/src
微服務 Demo 是一個簡單的電商項目,下圖爲項目結構,cartservice 爲 Dubbo 框架的購物車服務 provider,productservice 爲 Spring Cloud 提供的商品詳情服務 provider,frontend 爲 web controller 即前端展示頁面,可以理解爲 consumer 。
我們將以 cartservice 服務即 Dubbo 服務端爲例子,展示離羣實例摘除功能。
EDAS 上部署微服務 Demo
首先 cd cartservice 切換到 cartservice 目錄下,再通過 mvn clean install 打包,通過 cd cartservice-provider/target 切換到 target 目錄下,我們可以看到新生成的 cartservice-provider-1.0.0-SNAPSHOT.jar 包,然後在 EDAS上 創建一個 cartservice 應用。
點擊下一步後,上傳剛纔打包的jar包,即 cartservice-provider/target/ cartservice-provider-1.0.0-SNAPSHOT.jar 然後下一步,記住登陸密碼,直到創建應用成功。
然後啓動應用,到目前爲止,我們啓動了一個 cartservice-provider。點擊按此實例規格擴容,該服務我們部署在兩個實例上。
我們在這個 provider 的com.alibabacloud.hipstershop.provider.CartServiceImpl 類中可以看到,這個 provider 是提供了viewCart 和 addItemToCart 的兩個關於購物車的服務,我們在 viewCart 中加入一些模擬運行時異常的邏輯。
@Value("${exception.ip}") private String exceptionIp; @Override public List<CartItem> viewCart(String userID) { if (exceptionIp != null && exceptionIp.equals(getLocalIp())) { throw new RuntimeException("運行時異常"); } return cartStore.getOrDefault(userID, Collections.emptyList()); }
其中 exceptionIp 爲 ACM 配置中心的 exception.ip 的配置項,若該項配置爲本機 Ip 時,該服務 throw RuntimeException ,用於模擬業務異常的場景。
- 爲什麼將 cartservice 擴容到兩個實例,想必大家也猜到了,運行時通過配置ACM配置中心指定其中一個實例的IP,模擬出一個實例異常的場景。
接下來,我們需要部署 frontend / productservice 兩個服務,方式一樣,分別上傳 frontend/target/frontend-1.0.0-SNAPSHOT.jar 和 productservice/productservice-provider/target/productservice-provider-1.0.0-SNAPSHOT.jar。
從下圖可以看到,我們的微服務 Demo 在 EDAS 部署上去了。
模擬業務異常
進入到 frontend 應用中,我們看到其實例的公網 Ip 爲 47.99.150.33。
進入瀏覽器訪問 :
點擊 View Cart 訪問至 :
可以看到,此時服務都是正常的。
我們去 ACM 配置中心 配置 exception.ip 爲 172.16.205.180(即cartservice的其中某個實例的IP)。
然後繼續訪問 :
http://47.99.150.33:8080/cart,
發現 50 % 的概率錯誤頁面。
此時,我們寫一個腳本,定時大量訪問 :
模擬請求。
while : do result=`curl $1 -s` if [[ "$result" == *"500"* ]]; then echo `date +%F-%T` $result else echo `date +%F-%T` "success" fi sleep 0.1 done
然後 sh curlservice.sh http://47.99.150.33:8080/cart。
我們看到不斷重複的每秒鐘 10 次的 50% 的調用成功率。
其實也可以理解到,下游的服務質量隨着上游的某臺機子的異常而急劇下降,甚至可能導致下游服務被上游某些機子的(系統、業務)異常給拖垮。
開啓離羣摘除策略
下面我將演示離羣摘除的策略的開啓及其效果的展示。
創建
我們進入到 EDAS 左側列表的 [微服務管理] 下的 [離羣實例摘除] 界面中,並選擇創建離羣實例摘除策略。
然後按照提示一步步創建離羣摘除的策略。
基本信息
如上圖可以選擇命名空間、填寫策略名稱、選擇該策略支持的框架類型(Dubbo/Spring Cloud)。
選擇生效應用
按照目前的調用方式,我們只需要配置 frontend 應用,保護下游應用 consumer。
配置策略
這些參數都提供了默認值,需要根據自己應用的具體情況調整最合適的值,由於需要保護的 RuntimeException 屬於業務異常於是選上 網絡異常+業務異常。(需要注意的是即使摘除實例比例上限配得特別低,向下取整數小於1,當集羣中實例數目大於1,且某一實例異常,我們也會摘除一實例)。
創建完成
可以看到策略的信息,創建完成。
策略
看到了我們創建的離羣摘除策略,且是針對 Dubbo 框架,並且針對的是 網絡異常+業務異常 的異常類型。
驗證離羣摘除效果
這時,我們看到,再感知到異常後,離羣摘除功能生效,請求調用一陣子後,均返回正確結果。
不斷刷新瀏覽器訪問:
也均正常。
客戶端感知到某臺服務端機子異常後,主動摘除。僅僅調用業務正常的 Provider 實例,同時我們也可以通 ARMS(EDAS監控系統) 監控看到服務質量的上升,以及流量從異常 Provider 中摘除。
Dubbo 框架可以從 /home/admin/.opt/ArmsAgent/logs 目錄下的日誌中,搜索日誌中的 “OutlierRouter” 關鍵字可以看到一系列離羣實例摘除的事件日誌。
修改/關閉離羣摘除策略
對於 EDAS 的應用我們支持通過控制檯動態修改和刪除離羣摘除策略。
- 對應策略規則的修改
點擊 修改生效應用 或者 編輯策略。
然後增加刪除應用或者調整參數,確定後均立即生效
- 刪除對應策略
控制檯的操作,對應用中的配置都是實時生效的,若刪除策略後,默認關閉相關策略。
若我們打開 ARMS 監控觀察具體的調用情況。
ARMS監控
若我們開啓監控,將會直觀看到流量與請求錯誤等信息。
開啓離羣摘除前
如下圖方式開啓,然後跳轉至 ARMS( EDAS 監控系統)應用監控頁面,我們需要把三個應用都開啓高級監控。
我們可以從下圖即 ARMS( EDAS 監控系統)應用監控頁面直觀地看到結果。
從以下拓撲圖中我們看到,流量不斷地訪問到 cartservice 服務上。
開啓離羣摘除後
離羣摘除效果通過簡單的例子就看到了,當然可以通過 ARMS ( EDAS 監控系統)的監控可以明顯觀察到服務質量的提升。
可以看到,在開啓了離羣摘除的那個點只後,錯誤率從 50% 明顯下降。
其中兩個小的起伏毛刺是因爲,離羣摘除一段時間後會重新嘗試訪問被摘除的 endPoint ,若依舊錯誤率高於閾值,繼續隔離,且間隔時間更長。
離羣實例摘除具體控制邏輯
前面我們看到了,離羣實力摘除對應用穩定性提高帶來的幫助,下面我們將具體分析離羣實例摘除的控制邏輯,有助於您更好地理解其各種參數的意義,以及可以根據自己的應用情況,通過調整參數,配置出最合適自己的離羣摘除策略。
對於 Dubbo/SpringCloud 框架:
- 默認 QPS 下限爲 1
只有當前某實例的調用 QPS 大於 1 纔會開始離羣實例摘除保護。
- 默認錯誤率下限 50%
只有當前某實例的調用錯誤率高於 50% ,則系統會認爲該服務端集羣的當前某實例處於異常狀態。
- 默認摘除實例比例上限 20%
若當前服務集羣中,有大於20%的實例節點處於異常狀態,則系統只會摘除異常狀態的實例數佔集羣總數的 50% 。
- 異常類型
若異常類型爲 網絡異常 ,則系統僅僅把網絡異常的錯誤算進錯誤率統計中,忽略掉業務異常;反之,若選擇了 網絡異常 + 業務異常 則系統會將所有異常當成錯誤算進錯誤率統計中。
- 關於恢復檢測單位時間(默認 30000ms 即 30s )與未恢復累計次數上限(默認 40 )的解釋
其中第一次摘除時長爲 0.5 分鐘,時間到了之後 consumer 會繼續訪問該 provider ,若該 provider 服務質量依舊低下,則會繼續摘除,摘除時長隨着連續被摘除次數的增加線性遞增每次增加 0.5 分鐘,每次最多摘除 20 分鐘。當然,若繼續調用之後,服務質量恢復了,則會當成健康服務,下一次又出現異常導致服務質量低下問題時,會重新隔離 0.5 分鐘,並繼續上述規則。
- 兜底
但是當客戶端調用的服務僅有 1 個實例提供服務提供則不會隔離這個實例。
若當前客戶端調用的服務實例大於 1 個,且當前離羣摘除隔離比例計算出的實例數小於1,若服務端集羣出現單點故障,則會摘除1個實例。
上面所有的實例可以理解爲 endpoint(ip+port爲緯度)
- 通用最佳實踐
可以配置相對的錯誤率閾值(50%)與過低的摘除實例比例上限(10%),全鏈路開啓。
離羣實例摘除技術細節
無侵入技術
無侵入方案即通過 agent 技術來實現,一句話來說就是通過字節碼增強技術,運行時插入我們的代碼,改變應用的原有邏輯,可以理解爲運行時 AOP ,通過在 Dubbo 的鏈路中插入 Filter/Router ,在 Spring Cloud 中增強 LoadBalance 邏輯,來實現我們期望的路由控制邏輯。同時因爲是 agent 增強的,再加上 Dubbo 各個版本的鏈路整體基本沒大的變化,Spring Cloud 模型的統一性,因此我們可以花少的代價將能力基本覆蓋到所有版本。
Dubbo Agent 方案技術架構
對於用戶來說,無需改動一行代碼,一行配置,即可享受到穩定性增強的能力。
離羣實例摘除技術
Outlier Detection 離羣檢測
均是基於時間窗口的數據統計。兩種實現如下:
1、Dubbo 2.7 版本通過向鏈路中嵌入一個 MetricsFilter ,對於鏈路的每個 request/response 做打點處理,統計rt、調用成功與否、異常類型,並且已 endpoint(ip+port) 爲 key 存儲。
2、在 Agent 底座中統計經過的 http 請求,通過 url、rt、狀態碼、異常類型等數據結果,統計最近時間窗口的數據(目前寫死 10 秒,暫時不透出)。
實時統計前 N 秒的調用信息,作爲離羣實例摘除動作的依據。
Outlier Ejection 離羣摘除
Dubbo 基於 Dubbo Router 實現,對於調用的上游服務對應的所有 invokers 中,拉黑掉“不健康”的節點,同時記錄拉黑的信息。
Dubbo-Router 控制邏輯
每次請求過來僅僅 check 一下並標記狀態,後臺有專門兩個線程將標記的流量進行判斷是否進入隔離列表或從中剔除,修改拉黑信息等耗時操作,最大程度上保證請求的實時性。
Spring Cloud 基於 擴展 LoadBalace 實現,原理相似。
作者介紹:
泮聖偉,花名十眠,中間件技術-微服務產品團隊研發工程師,負責 Dubbo / Spring Cloud 商業化產品開發相關工作,目前主要關注雲原生、微服務等技術方向。
本文轉載自公衆號阿里巴巴中間件(ID:Aliware_2018)。
原文鏈接: