1. 背景java
微服務化以後,系統分佈式部署,傳統單個流程的本地API調用被拆分紅多個微服務之間的跨網絡調用,因爲引入了網絡通訊、序列化和反序列化等操做,系統發生故障的機率提升了不少。算法
微服務故障,有些是因爲業務自身設計或者編碼不當致使,有些是底層的微服務化框架容錯能力不足致使。在實際項目中,須要從業務和平臺兩方面入手,提高微服務的可靠性。數據庫
1.1. 無處不在的故障編程
1.1.1. 分佈式部署和調用後端
傳統單體架構一個完整的業務流程每每在同一個進程內部完成處理,不須要進行分佈式協做,它的工做原理以下所示:緩存
圖1-1 傳統單體架構本地方法調用服務器
微服務化以後,不一樣的微服務採用分佈式集羣部署方式,服務的消費者和提供者一般運行在不一樣的進程中,須要跨網絡作RPC調用,它的工做原理以下所示:網絡
圖1-2 微服務分佈式RPC調用數據結構
分佈式調用以後,相比於傳統單體架構的本地方法調用,主要引入了以下潛在故障點:多線程
序列化與反序列化:微服務的請求和應答都須要通過序列化和反序列化,作消息的跨網絡通訊,因爲數據結構不一致、不支持的數據類型、對方編解碼錯誤等都會致使序列化和反序列化失敗,進而致使微服務調用失敗。
網絡問題:常見的包括網絡超時、網絡閃斷、網絡單通、網絡擁塞等,均可能會致使微服務遠程調用的失敗。
1.1.2. 大型系統微服務進程內合設
理想狀況下,每一個微服務都獨立打包和部署,微服務之間自然就支持進程級隔離,但事實上,對於一個大規模的企業IT系統、或者大型網站,是由成百上千個微服務組成的,在實踐中,微服務一般是不可能作到百分之百獨立部署的,緣由以下:
一、方便開發:一般會按照業務域劃分團隊,同一個業務域每每包含多個微服務,由一個團隊負責開發。爲了方便 CI/CD,同一業務域的微服務每每打包和部署在一塊兒,而不是每一個微服務獨立打包部署。
二、方便運維:海量的微服務進程(以1000個微服務 * 10個進程實例爲例),會增長部署、數據採集(性能KPI和日誌 等)、告警、問題定位等成本,若是運維自動化程度不高,很難支撐大規模的微服務獨立部署。
三、提高性能:一些業務對時延很是敏感,若是該業務鏈上的全部微服務調用都跨網絡通訊,時延每每沒法知足業務要 求。經過將微服務合設在同一個進程以內,利用路由短路,把RPC調用轉化成本地方法調用,能夠極大的提高性能。
四、簡化分佈式事務處理:分佈式部署以後,會帶來分佈式事務問題。有時候業務爲了簡化分佈式事務的處理,將事務相 關的微服務部署在同一個進程中,把分佈式事務轉換成本地事務,簡化事務處理。
不一樣的微服務合設在同一個進程之中,就會引入一系列潛在的故障點,例如:
處理較慢的微服務會阻塞其它微服務
某個微服務故障蔓延,可能致使整個進程不可用
低優先級的微服務,搶佔高優先級微服務的資源
1.1.3. 微服務健康度
傳統狀況下,每每使用服務註冊中心檢測微服務的狀態,當檢測到服務提供者不可用時,會將故障的服務信息廣播到集羣全部節點,消費者接收到服務故障通知消息以後,根據故障信息中的服務名稱、IP地址等信息,對故障節點進行隔離。它的工做原理以下所示:
圖1-3 微服務狀態檢測
使用基於心跳或者會話的微服務狀態檢測,能夠發現微服務所在進程宕機、網絡故障等問題,但在實際業務中,微服務並不是「非死即活」,它可能處於「亞健康狀態」,服務調用失敗率很高,但又不是所有失敗。或者微服務已經處於過負荷流控狀態,業務質量受損,可是又沒有所有中斷。
使用簡單的微服務狀態檢測,很難應對上述這些場景。經過對微服務的運行質量建模,利用微服務健康度模型,根據採集的各類指標對微服務健康度實時打分,依據打分結果採起相應的可靠性對策,能夠更有針對性的保障系統的可靠性。
1.1.4. 同步的I/O操做
在整個微服務調用過程當中,主要會涉及到三類I/O操做:
網絡I/O操做,涉及到網絡讀寫
磁盤I/O操做,主要是記錄日誌、話單、寫本地文件等
數據庫訪問,例如Java使用JDBC驅動進行數據庫操做
圖1-4 微服務涉及的主要I/O操做
凡是涉及到I/O操做的,若是I/O操做是同步阻塞模式,例如Java的BIO、文件File的讀寫操做、數據庫訪問的JDBC接口等,都是同步阻塞的。只要訪問的網絡、磁盤或者數據庫實例比較慢,都會致使調用方線程的阻塞。因爲線程是Java虛擬機比較重要的資源,當大量微服務調用線程被阻塞以後,系統的吞吐量將嚴重降低。
1.1.5. 第三方SDK API調用
在微服務中,調用第三方SDK API,也可能會引入新的故障點,例如經過FTP客戶端訪問遠端的FTP服務,或者使用MQ客戶端訪問MQ服務,若是這些客戶端API的容錯性設計很差,也會致使調用方的級聯故障,這些故障是潛在和隱性的,在設計的時候每每容易被忽視,但它帶來的風險和危害是巨大的。
1.2. 微服務可靠性
軟件可靠性是指在給定時間內,特定環境下軟件無錯運行的機率。軟件可靠性包含了如下三個要素:
1) 規定的時間:軟件可靠性只是體如今其運行階段,因此將運行時間做爲規定的時間的度量。運行時間包括軟件系統運行後工做與掛起(啓動但空閒)的累計時間。因爲軟件運行的環境與程序路徑選取的隨機性,軟件的失效爲隨機事件,因此運行時間屬於隨機變量。
2) 規定的環境條件:環境條件指軟件的運行環境。它涉及軟件系統運行時所需的各類支持要素,如支持硬件、操做系統、其它支持軟件、輸入數據格式和範圍以及操做規程等。
3) 規定的功能:軟件可靠性還與規定的任務和功能有關。因爲要完成的任務不一樣,則調用的子模塊就不一樣(即程序路徑選擇不一樣),其可靠性也就可能不一樣。因此要準確度量軟件系統的可靠性必須首先明確它的任務和功能。
1.2.1. 關鍵的可靠性因素
微服務的運行質量,除了自身的可靠性因素以外,還受到其它因素的影響,包括網絡、數據庫訪問、其它相關聯的微服務運行質量等。微服務的可靠性設計,須要考慮上述綜合因素,總結以下:
圖1-5 微服務可靠性設計模型
2. 異步I/O操做
2.1. 網絡I/O
2.1.1. 使用同步阻塞I/O的問題
以Java爲例,在JDK 1.4推出JAVA NIO1.0以前,基於JAVA的全部Socket通訊都採用了同步阻塞模式(BIO),這種一請求一應答的通訊模型簡化了上層的應用開發,可是在可靠性和性能方面存在巨大的弊端:
2-1 傳統Java 同步阻塞I/O模型
採用BIO通訊模型的服務端,一般由一個獨立的Acceptor線程負責監聽客戶端的鏈接,接收到客戶端鏈接以後爲客戶端鏈接建立一個新的線程處理請求消息,處理完成以後,返回應答消息給客戶端,線程銷燬,這就是典型的一請求一應答模型。該架構最大的問題就是不具有彈性伸縮能力,當併發訪問量增長後,服務端的線程個數和併發訪問數成線性正比,因爲線程是JAVA虛擬機很是寶貴的系統資源,當線程數膨脹以後,系統的性能急劇降低,隨着併發量的繼續增長,可能會發生句柄溢出、線程堆棧溢出等問題,並致使服務器最終宕機。
2.1.2. 使用非阻塞I/O通訊
微服務進行遠程通訊時,經過使用非阻塞I/O,能夠解決因爲網絡時延大、高併發接入等致使的服務端線程數膨脹或者線程被阻塞等問題。
以Java爲例,從JDK1.4開始,JDK提供了一套專門的類庫支持非阻塞I/O,能夠在java.nio包及其子包中找到相關的類和接口。JDK1.7以後,又提供了NIO2.0類庫,支持異步I/O操做。
利用JDK的異步非阻塞I/O,能夠實現一個I/O線程同時處理多個客戶端鏈路,讀寫操做不會由於網絡緣由被阻塞,I/O線程能夠高效的併發處理多個客戶端鏈路,實現I/O多路複用,它的工做原理以下所示:
2-2 Java非阻塞I/O模型
使用非阻塞I/O進行通訊,以Java語言爲例,建議策略以下:
1) TCP私有協議:建議直接基於Netty開發。
2) HTTP/Restful/SOAP等:選擇支持非阻塞I/O的Web框架。也能夠選擇基於Netty構建的開源應用層協議棧框架,例如支持異步Restful的RestExpress。
2.2. 磁盤I/O
微服務對磁盤I/O的操做分爲兩類:
直接文件操做:例如調用File的open、write、read等接口,進行文件操做。
間接文件操做:例如調用日誌類庫寫日誌,雖然微服務並無直接操做日誌文件,可是日誌類庫底層仍是會進行 文件的讀寫等操做。
在實際項目中,最容易被忽視的就是日誌操做。不一樣的日誌類庫,寫日誌的機制不一樣,以Log4j 1.2.X版本爲例,當日志隊列滿以後,有多種策略:
同步等待,直到新的日誌消息可以入隊列,它會阻塞當前業務線程。
丟棄當前的日誌消息,不會阻塞當前業務線程。
不入隊列,由當前調用寫日誌的業務線程執行日誌I/O操做,若是此時磁盤I/O寫入速度慢,則會阻塞當前業務線 程。
在實際生產環境中,咱們就遇到過相似問題,在某些時段,磁盤WIO達到10+持續幾秒鐘-10幾秒鐘,而後又恢復正常。WIO較高的時段,須要寫接口日誌、話單等,因爲系統默認採用的是同步等待策略,結果致使通訊I/O線程、微服務調度線程等都被阻塞,最終鏈路由於心跳超時被強制關閉、微服務被大量阻塞在消息隊列中致使內存居高不小、響應超時等。
因爲偶現的WIO高致使同步寫日誌被阻塞,繼而引發通訊線程、微服務調用線程級聯故障,定位起來很是困難,平時Code Review也很難被注意到。因此,隱性的磁盤I/O操做,更須要格外關注。
要解決上面的問題,有三種策略:
使用非阻塞I/O,對文件進行異步讀寫操做。
業務層面封裝一個異步的I/O操做,最簡單的策略就是由一個獨立的線程或者線程池來執行磁盤I/O操做。
選擇支持非阻塞方式調用的I/O類庫,例如使用log4j的異步日誌API。
以JDK1.7爲例,它提供了異步的文件I/O操做類庫,基於該類庫,就不須要擔憂磁盤I/O操做被阻塞:
2-3 JDK1.7異步非阻塞文件接口
本身在上層封裝異步I/O操做,也比較簡單,它的優勢是能夠實現磁盤I/O操做與微服務之間的線程隔離,可是底層仍然使用的是同步阻塞I/O,若是此時磁盤的I/O比較高,依然會阻塞寫磁盤的I/O線程。它的原理以下所示:
2-4 應用層封裝的異步文件操做
將文件I/O操做封裝成一個Task或者Event,投遞到文件I/O線程池的消息隊列中,根據投遞結果,構造I/O操做相關聯的Future對象給微服務調用線程。經過向Future對象註冊Listener並實現callback接口,能夠實現異步回調通知,這樣微服務和文件I/O操做就實現了線程隔離。文件I/O操做耗時,並不會阻塞微服務調度線程。
當使用第三方文件I/O操做類庫時,須要注意下相關API,儘可能使用支持異步非阻塞接口的API,若是沒有,則須要考慮是否作上層的異步封裝。
2.3. 數據庫操做
部分數據庫訪問支持非阻塞方式,例如Oracle的OCI,它支持non-blocking模式和blocking模式:阻塞方式就是當調用 OCI操做時,必須等到此OCI操做完成後服務器才返回客戶端相應的信息,無論是成功仍是失敗。非阻塞方式是當客戶端提交OCI操做給服務器後,服務器當即返回OCI_STILL_EXECUTING信息,而並不等待服務端的操做完成。對於non-blocking方式,應用程序若收到一個OCI函數的返回值爲OCI_STILL_EXECUTING時必須再次對每個OCI函數的返回值進行判斷,判斷其成功與否。 可經過設置服務器屬性爲OCI_ATTR_NONBLOCKING_MODE來實現。
對於Java語言而言,因爲JDK自己提供了數據庫鏈接驅動相關的接口定義,JDBC驅動自己就是同步API接口,所以,Java語言的開源ORM框架也都是同步阻塞的,例如MyBatis、Hibernate等。
儘管大部分數據庫訪問接口是同步阻塞的,可是因爲數據庫中間件的超時控制機制都比較成熟,所以經過合理設置超時時間,能夠避免微服務的數據庫訪問被長時間掛住。
也能夠在應用上層封裝異步數據庫操做層,實現微服務調度與數據庫操做的線程級隔離,原理2.2章節已經介紹過,採用該方式一樣存在兩點不足:
排隊現象:若是某個數據庫操做很是耗時,超時時間配置的又比較大(例如30S),會致使後續的數據庫操做在隊 列中排隊。
沒法充分發揮數據庫效能:因爲底層數據庫訪問採用同步阻塞的方式,因此不能高效發揮數據庫的效能。
3. 故障隔離
因爲大部分微服務採用同步接口調用,並且多個領域相關的微服務會部署在同一個進程中,很容易發生「雪崩效應」,即某個微服務提供者故障,致使調用該微服務的消費者、或者與故障微服務合設在同一個進程中的其它微服務發生級聯故障,最終致使系統崩潰。
爲了不「雪崩效應」的發生,須要支持多種維度的依賴和故障隔離,以實現微服務的HA。
3.1. 通訊鏈路隔離
因爲網絡通訊自己一般不是系統的瓶頸,所以大部分服務框架會採用多線程+單個通訊鏈路的方式進行通訊,原理以下所示:
3-1 多線程-單鏈路P2P通訊模式
正如前面章節所述,因爲微服務使用異步非阻塞通訊,單個I/O線程能夠同時併發處理多個鏈路的消息,並且網絡讀寫都是非阻塞的,所以採用多線程+單鏈路的方式進行通訊性能自己問題不大。可是從可靠性角度來看,只支持單鏈路自己又存在一些可靠性隱患,咱們從下面的案例中看下問題所在。
某互聯網基地微服務架構上線以後,發如今一些時段,常常有業務超時,超時的業務沒有固定規律。經定位發現當有較多的批量內容同步、語音和視頻類微服務調用時,系統的總體時延就增高了不少,並且存在較突出的時延毛刺。因爲這些操做獲取的消息碼流每每達到數M到數十兆,微服務之間又採用單鏈路的方式進行P2P通訊,致使大碼流的傳輸影響了其它消息的讀寫效率,增大了微服務的響應時延。
問題定位出來以後,對微服務之間的通訊機制作了優化,節點之間支持配置多鏈路,每一個鏈路之間還能夠實現不一樣策略的隔離,例如根據消息碼流大小、根據微服務的優先級等策略,實現鏈路級的隔離,優化以後的微服務通訊機制:
圖3-2 支持多鏈路隔離
3.2. 調度資源隔離
3.2.1. 微服務之間隔離
當多個微服務合設運行在同一個進程內部時,能夠利用線程實現不一樣微服務之間的隔離。
對於核心微服務,發佈的時候能夠獨佔一個線程/線程池,對於非核心微服務,則能夠共享同一個大的線程池,在實現微服務隔離的同時,避免線程過於膨脹:
圖3-3 微服務之間故障隔離
假如非核心服務3發生故障,長時間阻塞線程池1的工做線程,其它與其共用線程池消息隊列的非核心服務1和服務2只能在隊列中排隊等待,當服務3釋放線程以後,排隊的服務1和服務2可能已經超時,只能被丟棄掉,致使業務處理失敗。
採用線程池隔離的核心服務1和服務2,因爲各自獨佔線程池,擁有獨立的消息隊列,它的執行不受發生故障的非核心服務1影響,所以能夠繼續正常工做。經過獨立線程池部署核心服務,能夠防止故障擴散,保障核心服務的正常運行。
3.2.2. 第三方依賴隔離
在微服務中一般會調用第三方中間件服務,例如分佈式緩存服務、分佈式消息隊列、NoSQL服務等。只要調用第三方服務,就會涉及跨網絡操做,因爲客戶端SDK API的封裝,不少故障都是隱性的,所以,它的可靠性須要額外關注。
總體而言,第三方依賴隔離能夠採用線程池 + 響應式編程(例如RxJava)的方式實現,它的原理以下所示:
1) 對第三方依賴進行分類,每種依賴對應一個獨立的線程/線程池。
2) 微服務不直接調用第三方依賴的API,而是使用異步封裝以後的API接口。
3) 異步調用第三方依賴API以後,獲取Future對象。利用響應式編程框架,能夠訂閱後續的事件,接收響應,針對響應進行編程。
利用Netflix開源的hystrix + RxJava,能夠快速實現第三方依賴的隔離,後續章節咱們會詳細介紹下如何使用。
3.3. 進程級隔離
對於核心的微服務,例如商品購買、用戶註冊、計費等,能夠採用獨立部署的方式,實現高可用性。
3.3.1. 容器隔離
微服務鼓勵軟件開發者將整個軟件解耦爲功能單一的服務,而且這些服務可以獨立部署、升級和擴容。若是微服務抽象的足夠好,那麼微服務的這一優勢將可以提高應用的敏捷性和自治理能力。
利用Docker容器部署微服務,能夠帶來以下幾個優勢:
高效:Docker容器的啓動和中止不須要幾分鐘,只要幾百毫秒就足夠了。使用Docker部署微服務,微服務的啓動 和銷燬速度很是快,在高壓力時,能夠實現秒級彈性伸縮。
高性能:Docker容器的性能接近裸的物理機,比VM平均高20%+。
隔離性:利用Docker,能夠實現0.1 core的隔離。基於細粒度的資源隔離機制,能夠實現高密度的部署微服務,同 時實現它們之間的資源層隔離,保障微服務的可靠性。
可移植性:在基於虛擬機的解決方案中,應用的可移植性一般來講會受到雲提供商所提供的虛擬機格式限制。若是 應用程序須要部署到不一樣類型的虛擬機中,須要針對特定的虛擬機格式作鏡像文件,新增不少額外的開發和測試工 做量。Docker容器的設計理念是「一次編寫,處處運行」,這可使開發者避免上面這種限制。
基於Docker容器部署微服務,實現物理資源層隔離示意圖以下所示:
圖3-4 基於Docker容器的微服務隔離
3.3.2. VM隔離
除了Docker容器隔離,也可使用VM對微服務進行故障隔離,相比於Docker容器,使用VM進行微服務隔離存在以下優點:
微服務的資源隔離性更好,CPU、內存、網絡等能夠實現徹底的資源隔離。
對於已經完成硬件虛擬化的遺留系統,能夠直接使用已有的VM,而不須要在VM中從新部署Docker容器。
4. 集羣容錯
當微服務不可用時,須要根據預置的策略作容錯處理,大部分的容錯能力和策略是公共的,所以能夠下沉到服務框架中實現。
4.1. 路由容錯
當集羣環境中微服務調用失敗以後,利用路由容錯機制,能夠在底層實現微服務的自動容錯處理,提高系統的可靠性。
經常使用的容錯策略包括:
失敗自動切換機制:微服務調用失敗自動切換策略指的是當發生服務調用異常時,從新選路,查找下一個可用的微 服務提供者。微服務發佈的時候,能夠指定服務的集羣容錯策略。消費者能夠覆蓋服務提供者的通用配置,實現個 性化的容錯策略。
失敗回調機制:微服務調用失敗以後,提供異常回調接口,執行微服務消費者自定義的失敗處理邏輯。
快速失敗機制:在業務高峯期,對於一些非核心的服務,但願只調用一次,失敗也再也不重試,爲重要的核心服務節 約寶貴的運行資源。此時,快速失敗是個不錯的選擇。快速失敗策略的設計比較簡單,獲取到服務調用異常以後, 直接忽略異常,記錄異常日誌。
4.2. 服務降級
大促或者業務高峯時,爲了保證核心服務的SLA,每每須要停掉一些不過重要的業務,例如商品評論、論壇或者粉絲積分等。
另一種場景就是某些服務由於某種緣由不可用,可是流程不能直接失敗,須要本地Mock服務端實現,作流程放通。以圖書閱讀爲例,若是用戶登陸餘額鑑權服務不能正常工做,須要作業務放通,記錄消費話單,容許用戶繼續閱讀,而不是返回失敗。
經過服務治理的服務降級功能,便可以知足上述兩種場景的需求。
4.2.1. 強制降級
當外界的觸發條件達到某個臨界值時,由運維人員/開發人員決策,對某類或者某個服務進行強制降級。
強制降級的經常使用策略:
一、不發起遠程服務調用,直接返回空。例如mock = force: return null。
二、不發起遠程服務調用,直接拋出指定異常。例如mock = force: throw Exception。
三、不發起遠程服務調用,直接執行本地模擬接口實現類。mock = force: execute Bean: <Spring beanName>。
4.2.2. 容錯降級
當非核心服務不可用時,能夠對故障服務作業務邏輯放通,以保障核心服務的運行。
容錯降級與屏蔽降級的主要差別是:
一、觸發條件不一樣:容錯講解是根據服務調用結果,自動匹配觸發的;而屏蔽降級每每是經過人工根據系統運行情 況手工操做觸發的。
二、做用不一樣:容錯降級是當服務提供者不可用時,讓消費者執行業務放通;屏蔽降級的主要目的是將原屬於降級 業務的資源調配出來供核心業務使用。
三、調用機制不一樣:一個發起遠程服務調用,一個只作本地調用。
容錯降級的經常使用策略以下:
一、異常轉義:mock = fail: throw Exception。
二、自定義降級邏輯:mock = fail: execute Bean: <beanName>。將異常屏蔽掉,直接執行本地模擬接口實現類, 返回Mock接口的執行結果。
4.2.3. 服務降級Portal
利用服務治理Portal,能夠在線的動態修改微服務的降級策略,實時生效,它的界面以下所示:
圖4-1 服務降級配置界面
4.3. 熔斷機制
熔斷機制(Circuit Breaker),也叫自動停盤機制,是指當股指波幅達到規定的熔斷點時,交易所爲控制風險採起的暫停交易措施。
在微服務領域,熔斷機制是從消費端保護微服務提供者的措施,當微服務的運行質量低於某個臨界值時,啓動熔斷機制,暫停微服務調用一段時間,以保障後端的微服務不會由於持續過負荷而宕機。
4.3.1. 工做原理
微服務的熔斷機制原理以下所示:
微服務調用時,對熔斷開關狀態進行判斷,當熔斷器開關關閉時, 請求被容許經過熔斷器。若是當前微服務健康 度高於指定閾值, 開關繼續保持關閉。不然開關切換爲打開狀態。
當熔斷器開關打開時,微服務調用請求被禁止經過。調用失敗,執行本地降級邏輯,若是沒有實現降級邏輯,默認 返回異常。
當熔斷器開關處於打開狀態時, 通過指定週期T, 熔斷器會自動進入半開狀態, 這時熔斷器會容許請求經過,當請 求調用成功時, 熔斷器恢復到關閉狀態。若失敗, 則繼續保持打開狀態。
它的工做原理示意以下:
圖4-2 微服務熔斷器工做原理
熔斷器機制能保證微服務消費者在微服務運行狀態不佳時,快速返回結果,避免大量的同步等待。而且能在指定週期T後繼續偵測微服務是否可用, 以實現故障恢復以後的自動感知。
4.3.2. 微服務健康度
熔斷器開關的狀態取決於微服務的運行質量,微服務的運行質量一般由多種因素決定,具備多個衡量因子。經過對微服務健康度建模,能夠實現對微服務運行質量的360°實時評估。
微服務健康度模型以下所示:
圖4-3 微服務健康度模型
微服務運維體系經過分佈式日誌採集系統、告警系統、性能KPI數據採集等,利用在線大數據實時分析技術,經過健康度模型,對微服務的健康度按照週期進行實時打分,同時將微服務的得分經過消息隊列訂閱發佈出去,各個節點訂閱微服務的健康度得分,與熔斷器閾值進行比較,修改熔斷器開關的狀態。
5. 流量控制
當資源成爲瓶頸時,服務框架須要對消費者作限流,啓動流控保護機制。流量控制有多種策略,比較經常使用的有:針對訪問速率的靜態流控、針對資源佔用的動態流控等。
在實踐中,各類流量控制策略須要綜合使用才能起到較好的效果。
5.1. 動態流控
動態流控的最終目標是爲了保命,並非對流量或者訪問速度作精確控制。當系統負載壓力很是大時,系統進入過負載狀態,多是CPU、內存資源已通過載,也多是應用進程內部的資源幾乎耗盡,若是繼續全量處理業務,可能會致使消息嚴重積壓或者應用進程宕機。
動態流控檢測的資源包括:
CPU使用率。
內存使用率(對於Java,主要是JVM內存使用率)。
隊列積壓率。
主機CPU、內存使用率採集算法很是多,例如使用java.lang.Process執行top、sar等外部命令獲取系統資源使用狀況,而後解析後計算得到資源使用率。也能夠直接讀取操做系統的系統文件獲取相關數據,須要注意的是,不管是執行操做系統的本地命令,仍是直接讀取操做系統的資源使用率文件,都是操做系統本地相關的,不一樣的操做系統和服務器,命令和輸出格式可能存在很大差別。在計算時須要首先判斷操做系統類型,而後調用相關操做系統的資源採集接口實現類,經過這種方式就能夠支持跨平臺。
動態流控是分級別的,不一樣級別拒掉的消息比例不一樣,這取決於資源的負載使用狀況。例如當發生一級流控時,拒絕掉1/4的消息;發生二級流控時,拒絕掉1/2消息;發生三級流控時,全部的消息都被流控掉。
不一樣的級別有不一樣的流控閾值,系統上線後會提供默認的;流控閾值,不一樣流控因子的流控閾值不一樣,業務上線以後一般會根據現場的實際狀況作閾值調優,所以流控閾值須要支持在線修改和動態生效。
須要指出的是爲了防止系統波動致使的偶發性流控,不管是進入流控狀態仍是從流控狀態恢復,都須要連續採集N次並計算平均值,若是連續N次平均值大於流控閾值,則進入流控狀態;同理,只有連續N次資源使用率平均值低於流控閾值,才能脫離流控恢復正常。
5.2. 靜態流控
靜態流控主要針對客戶端訪問速率進行控制,它一般根據服務質量等級協定(SLA)中約定的QPS作全局流量控制,例如計費服務的靜態流控閾值爲200 QPS,則不管集羣有多少個計費服務實例,它們總的處理速率之和不能超過200 QPS。
因爲微服務具有彈性伸縮、動態上線和下線等特性,所以集羣中某個微服務實例的節點個數是動態變化的,採用傳統的平均分配製沒法作到精準的控制。
在實踐中,比較成熟的集羣靜態流控策略是動態配額申請制,它的工做原理以下:
系統部署的時候,根據微服務節點數和靜態流控QPS閾值,拿出必定比例的配額作初始分配,剩餘的配額放在配額 資源池中。
哪一個微服務節點使用完了配額,就主動向服務註冊中心申請配額。配額的申請策略:若是流控週期爲T,則將週期T 分紅更小的週期T/N(N爲經驗值,默認值爲10),當前的服務節點數爲M個,則申請的配額爲 (總QPS配額 - 已經 分配的QPS配額)/M * T/N。
總的配額若是被申請完,則返回0配額給各個申請配額的服務節點,服務節點對新接入的請求消息進行流控。
5.3. 用戶自定義流控機制
不一樣的業務,存在不一樣的流控策略,例如基於微服務優先級的流控、基於節假日的流控、基於業務字段的流控等。底層的服務框架沒法實現全部業務級的定製流控策略,所以,過於業務化的流控每每由業務經過自定義流控機制定製實現。
服務框架提供服務調用入口的攔截點和切面接口,由業務實現自定義流控。也能夠提供基礎的流控框架,供業務實現流控條件判斷、流控執行策略等,簡化業務的定製工做量。
6. 使用Hystrix提高微服務可靠性
6.1. Hystrix簡介
Hystrix是Netflix開源的一個可靠性組件,主要用於分佈式環境中的依賴解耦,Hystrix library經過添加延遲容忍和容錯邏輯來控制分佈式服務之間的相互影響,經過服務之間訪問的隔離點阻止連鎖故障,並提供了失敗回調機制,來改進系統的可靠性。
Hystrix提供以下機制來提高分佈式系統的可靠性:
保護經過第三方客戶端API依賴訪問,控制其延遲和故障
阻止級聯故障和「雪崩效應」
提供熔斷機制,快速失敗和恢復
失敗回調和優雅降級機制
近實時檢測、報警和KPI指標展現
6.2. Hystrix的核心功能
Hystrix提供了一些很是有價值、與具體微服務框架實現無關的特性,方便不一樣的分佈式系統集成使用。
6.2.1. 依賴隔離
Hystrix使用命令模式HystrixCommand(Command)包裝依賴調用邏輯,每一個命令在單獨線程/信號受權下執行。依賴調用的超時時間可配置,若是超時,則則返回失敗或者執行fallback邏輯。原理以下所示:
圖6-1 基於線程/信號的依賴隔離
6.2.2. 熔斷器
Hystrix會先通過熔斷器,此時若是熔斷器的狀態是打開,則說明已經熔斷,這時將直接進行降級處理,不會繼續將請求發到線程池。
熔斷器的開關狀態由熔斷算法決定,它的原理以下:
判斷是否熔斷:根據bucket中記錄的次數,計算錯誤率。若是錯誤率達到熔斷預置的閾值,則開啓熔斷開關。
熔斷恢復:對於被熔斷的請求,暫停處理一段時間以後 (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()),容許單個請求經過,若請求成功, 則取消熔斷,不然,繼續熔斷。
Hystrix熔斷器的工做原理以下所示:
圖6-2 Hystrix熔斷機制
6.2.3. 優雅降級
當微服務調用異常、超時,或者熔斷時,能夠經過回調Fallback()的方式實現業務的優雅降級,它的原理以下所示:
圖6-3 Hystrix優雅降級機制
6.2.4. Reactive編程
Hystrix支持響應式編程,並提供了相關接口給用戶,以下所示:
利用響應式編程,能夠更加優雅和靈活的實現異步回調邏輯的處理。
6.2.5. 信號量隔離
爲了下降線程資源的開銷,Hystrix提供了信號量Semaphores,用於實現輕量級的依賴隔離。
開發者能夠限制系統對某一個依賴的最高併發數,這個基本上等同於併發流控策略。每次微服務調用依賴時都會檢查一下是否到達信號量的限制值,如達到則拒絕。該隔離策略的優勢是不新起線程,減小上下文切換和線程數,缺點是沒法配置斷路,每次都必定會去嘗試獲取信號量。
6.3. 集成Hystrix
因爲Hystrix與特定的分佈式系統、微服務框架無關,是個通用的分佈式系統可靠性組件,能夠經過類庫集成的方式方便的集成到已有的微服務架構體系中。
6.3.1. 集成架構
在已有微服務體系中集成Hystrix的策略以下:
一、微服務框架中,對於通用的微服務調用、磁盤I/O操做、數據庫操做和網絡I/O操做等使用HystrixCommand作一 層異步包裝,實現業務的微服務調用線程和第三方依賴的線程隔離。
二、對於非通用的第三方依賴,或者業務微服務自身引入的第三方依賴,直接基於HystrixCommand作異步隔離。
三、對第三方依賴進行分類、分組管理,根據依賴的特色設置熔斷策略、優雅降級策略、超時策略等,以實現差別 化的處理。
集成架構示例以下:
圖6-4 集成Hystrix的微服務架構
6.3.2. 集成Hystrix帶來的優勢
第三方依賴隔離具有必定的通用性,例如數據庫隔離、磁盤I/O隔離、第三方服務調用隔離等,若是各自構建一套隔離機制,除了增長工做量以外,後續維護起來也比較麻煩。
另外,業務微服務自身也會引入第三方依賴,若是沒有通用的隔離機制,則業務須要本身構建業務級的隔離體系,相應的開發難度和工做量都較大,架構上也很難統一。
集成Hystrix,能夠快速的構建微服務的隔離、熔斷、優雅降級和響應式編程體系,提高系統的可靠性。
另外,Hystrix很是成熟,在Netflix已經經歷過苛刻的生產環境考驗,它的可靠性和成熟度徹底可以知足大部分業務場景的須要。