斷路由器模式
在分布式架構中,當某個服務單元發生故障之后,通過斷路由器的故障監控(類似熔斷保險絲),向調用方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得線程因調用故障服務被長時間占用不釋放,避免了故障在分布式系統中的蔓延。
spring cloud hystrix針對上述問題實現了斷路由器、線程隔離等一系列服務保護功能。它是基于netflix hystrix實現,該框架的目標在于通過控制那些訪問遠程系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。
hystrix具備服務降級、服務熔斷、線程和信號隔離、請求緩存、請求合并以及服務監控等強大功能。
快速入門
構建一個如下架構圖的服務調用關系
分析上述架構圖,主要有以下幾項工作:
- eureka-server工程: 服務注冊中心,端口1111hello-service工程:
- hello-service服務單元,啟動兩個實例,端口分別為8081和8082
- ribbon-consumer工程: 使用ribbon實現的服務消費者,端口9000
修改ribbon-consumer模塊
修改pom.xml
首先在pom.xml文件中增加spring-cloud-starter-hystrix依賴
開啟斷路由器功能
在ribbon-consumer主類中使用@enablecircuitbreaker注解開啟斷路由器功能,在這里還有一個小技巧,可以使用@springcloudapplicationd代替@enablecircuitbreaker、@enableeurekaclient、@springbootapplication這三個注解。
改造服務消費方式
改造ribbon-consumer中的helloservice,如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
package cn.sh.ribbon.service; import com.netflix.hystrix.contrib.javanica.annotation.hystrixcommand; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.beans.factory.annotation.autowired; import org.springframework.stereotype.service; import org.springframework.web.client.resttemplate; /** * @author sh */ @service public class helloservice { private static final logger logger = loggerfactory.getlogger(helloservice. class ); @autowired private resttemplate resttemplate; /** * 使用@hystrixcommand注解指定回調方法 * @param name * @return */ @hystrixcommand (fallbackmethod = "ribbonhellofallback" , commandkey = "hellokey" ) public string ribbonhello(string name) { long start = system.currenttimemillis(); string result = resttemplate.getforobject( "http://hello-service/hello?name=" + name, string. class ); long end = system.currenttimemillis(); logger.info( "spend time:" + (end - start)); return result; } public string ribbonhellofallback() { return "hello, this is fallback" ; } } |
改造服務提供者
改造hello-service模塊中的helloservice.java,如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
package cn.sh.hello.service; import org.slf4j.logger; import org.slf4j.loggerfactory; import org.springframework.stereotype.service; import java.util.random; /** * @author sh */ @service public class helloservice { private static final logger logger = loggerfactory.getlogger(helloservice. class ); public string hello(string name) throws interruptedexception { int sleeptime = new random().nextint( 3000 ); logger.info( "sleeptime:" + sleeptime); thread.sleep(sleeptime); return "hello, " + name; } } |
在服務提供者的改造中,我們會讓方法阻塞幾秒中返回內容,由于hystrix默認的超時時間為2000ms,在這里產生0-3000的隨機數可以讓處理過程有一定概率觸發斷路由器。
原理分析
下面根據工作流程圖,我們來分析一下hystrix是如何工作的。
第1步 創建hystrixcommand或hystrixobservablecommand對象
首先,構建一個hystrixcommand或hystrixobservablecommand對象,用來表示對依賴服務的操作請求,同時傳遞所有需要的參數。這兩個對象都采用了命令模式來實現對服務調用操作的封裝,但是這兩個對象分別針對不同的應用場景。
hystrixcommand: 用在依賴的服務返回單個操作結果的時候hystrixobservablecommand: 用在依賴的服務返回多個操作結果的時候
命令模式,將來自客戶端的請求封裝成一個對象,從而讓你可以使用不同的請求對客戶端進行參數化。它可以用于實現行為請求者和行為實現者的解耦,以便使兩者可以適應變化
命令模式的示例代碼在command模塊下
通過命令模式的示例代碼可以分析出命令模式的幾個關鍵點:
- receiver: 接收者,處理具體的業務邏輯
- command: 抽象命令,定義了一個對象應具備的一系列命令操作,如execute()、undo()、redo()等。當命令操作被調用的時候就會觸發接收者做具體命令對應的業務邏輯。
- concretecommand: 具體的命令實現,在這里要綁定命令操作和接收者之間的關系,execute()命令的實現轉交給了receiver的action()方法
- invoker: 調用者,它擁有一個命令對象,可以在需要時通過命令對象完成具體的業務邏輯
命令模式中invoker和receiver的關系非常類似于請求-響應模式,所以它比較適用于實現記錄日志、撤銷操作、隊列請求等。
以下情況我們可以考慮使用命令模式:
- 使用命令模式作為回調在面向對象系統中的替代。
- 需要在不同的時間指定請求、將請求排隊。一個命令對象和原先的請求發出者可以有不同的生命周期。換言之,原先的請求發出者可能已經不在了,但是命令本身仍然是活動的。這時命令的接收者可以是在本地,也可以在網絡的另一個地址。命令對象可以在序列化之后傳送到另一臺機器上。
- 系統需要支持命令的撤銷。命令對象可以把狀態存儲起來,等到客戶端需要撤銷命令所產生的效果時,可以調用undo()方法,把命令所產生的效果撤銷掉。命令對象還提供redo()方法,以供客戶端在需要時再重新實施命令效果。
- 如果要將系統中所有的數據更新到日志里,以便在系統崩潰時,可以根據日志讀回所有的數據更新命令,重新調用execute()方法一條一條執行這些命令,從而恢復系統在崩潰前所做的數據更新。
第2步 命令執行
從圖中我們可以看到一共存在4種命令的執行方式,hystrix在執行時會根據創建的command對象以及具體的情況來選擇一個執行。
hystrixcommand
hystrixcommand實現了兩個執行方式:
- execute(): 同步執行,從依賴的服務返回一個單一的結果對象,或是在錯誤時拋出異常
- queue(): 異步執行,直接返回一個future對象,其中包含了服務執行結束時要返回的單一結果對象。
1
2
|
r value = command.execute(); future<r> fvalue = command.queue(); |
hystrixobservablecommand
hystrixobservablecommand實現了另兩種執行方式:
- observer(): 返回observable對象,它代表了操作的多個結果,是一個hotobservable
- toobservable(): 同樣返回observable對象,也代表操作的多個結果,返回的是一個coldobservable
1
2
|
observable<r> ohvalue = command.observe(); observable<r> ocvalue = command.toobservable(); |
hot observable和cold observable,分別對應了上面command.observe()和command.toobservable的返回對象。
hot observable,不論事件源是否有訂閱者,都會在創建后對事件進行發布,所以對hot observable的每一個訂閱者都有可能是從事件源的中途開始的,并可能只是看到了整個操作的局部過程。
cold observable在沒有訂閱者的時候不會發布事件,而是進行等待,直到有訂閱者后才會發布事件,所以對于cold observable的訂閱者,它可以保證從一開始看到整個操作的全部過程。
hystrixcommand也使用rxjava實現:
- execute():該方法是通過queue()返回的異步對象future<r>的get()方法來實現同步執行的。該方法會等待任務執行結束,然后獲得r類型的結果返回。
- queue():通過toobservable()獲得一個cold observable,并且通過通過toblocking()將該observable轉換成blockingobservable,它可以把數據以阻塞的方式發出來,tofuture方法則是把blockingobservable轉換為一個future,該方法只是創建一個future返回,并不會阻塞,這使得消費者可以自己決定如何處理異步操作。execute()則是直接使用了queue()返回的future中的阻塞方法get()來實現同步操作的。
- 通過這種方式轉換的future要求observable只發射一個數據,所以這兩個實現都只能返回單一結果。
rxjava觀察者-訂閱者模式入門介紹
在hystrix的底層實現中大量使用了rxjava。上面提到的observable對象就是rxjava的核心內容之一,可以把observable對象理解為事件源或是被觀察者,與其對應的是subscriber對象,可以理解為訂閱者或是觀察者。
- observable用來向訂閱者subscriber對象發布事件,subscriber對象在接收到事件后對其進行處理,這里所指的事件通常就是對依賴服務的調用。
- 一個observable可以發出多個事件,直到結束或是發生異常。
- observable對象每發出一個事件,就會調用對應觀察者subscriber對象的onnext()方法。
- 每一個observable的執行,最后一定會通過調用subscriber.oncompleted()或是subscriber.onerror()來結束該事件的操作流。
第3步 結果是否被緩存
若當前命令的請求緩存功能是被啟用的,并且該命令緩存命中,那么緩存的結果會立即以observable對象的形式返回。
第4步 斷路器是否打開
在命令結果沒有緩存命中的時候,hystrix在執行命令前需要檢查斷路器是否為打開狀態:
如果斷路器是打開的,hystrix不會執行命令,而是直接賺到fallback處理邏輯(對應下面第8步)
如果斷路器是關閉的,那么hystrix會跳到第5步,檢查是否有可用資源來執行命令。
第5步 線程池/請求隊列/信號量是否占滿
如果與命令相關的線程池和請求隊列或者信號量(不使用線程池的時候)已被占滿,那么hystrix不會執行命令,轉接到fallback處理邏輯(對應下面第8步)
hystrix所判斷的線程池并非容器的線程池,而是每個依賴服務的專有線程池。hystrix為了保證不會因為某個依賴服務的問題影響到其他依賴服務而采用了艙壁模式來隔離每個依賴的服務。
第6步 hystrixobservablecommand.construct()或hystrixcommand.run()
hystrix會根據我們編寫的方法來決定采取什么樣的方式去請求依賴服務:
- hystrixcommand.run(): 返回一個單一的結果,或者拋出異常
- hystrixobservablecommand.construct(): 返回一個observable對象來發射多個結果,或通過onerror發送錯誤通知
如果run()或construct()方法的執行時間超過了命令設置的超時閥值,當前處理線程會拋出一個timeoutexception(如果該命令不在其自身的線程中執行,則會通過單獨的計時線程拋出)。在這種情況下,hystrix會轉到fallback邏輯去處理(第8步)。同時,如果當前命令沒有被取消或中斷,那么它最終會忽略run()或construct()方法的返回。
如果命令沒有拋出異常并返回了結果,那么hystrix在記錄一些日志并采集監控報告之后將該結果返回。在使用run()時,返回一個observable,它會發射單個結果并產生oncompleted的結束通知,在使用construct()時,會直接返回該方法產生的observable對象。
第7步 計算斷路器的健康度
hystrix會將成功、失敗、拒絕、超時等信息報告給斷路器,斷路器會維護一組計數器來統計這些數據。
斷路器會使用這些統計數據來決定是否要將斷路器打開,來對某個依賴服務的請求進行熔斷/短路,直到恢復期結束。若在恢復期結束后,根據統計數據判斷如果還是未達到健康指標,就再次熔斷/短路。
第8步 fallback處理
當命令執行失敗時,hystrix會進入fallback嘗試回退處理,我們通常也稱之為服務降級。能夠引起服務降級處理的情況主要有以下幾種:
- 第4步,當前命令處于熔斷/短路狀態,斷路器是打開的時候。
- 第5步,當前命令的線程池、請求隊列或者信號量被占滿的時候。
- 第6步,hystrixobservablecommand.construct()或hystrixcommand.run()拋出異常的時候。
在服務降級邏輯中,我們需要實現一個通用的響應結果,并且該結果的處理邏輯應當是從緩存或是根據一些靜態邏輯來獲取,而不是依賴網絡請求獲取。如果一定要在降級邏輯中包含網絡請求,那么該請求也必須被包裝在hystrixcommand或是hystrixobservablecommand中,從而形成級聯的降級策略,而最終的降級邏輯一定不是一個依賴網絡請求的處理,而是一個能夠穩定返回結果的處理邏輯。
hystrixcommand和hystrixobservablecommand中實現降級邏輯時有以下不同:
- 當使用hystrixcommand的時候,通過實現hystrixcommand.getfallback()來實現服務降級邏輯。
- 當使用hystrixobservablecommand的時候,通過hystrixobservablecommand.resumewithfallback()實現服務降級邏輯,該方法會返回一個observable對象來發射一個或多個降級結果。
當命令的降級邏輯返回結果之后,hystrix就將該結果返回給調用者。當使用hystrixcommand.getfallback()時候,它會返回一個observable對象,該對象會發射getfallback()的處理結果。而使用hystrixobservablecommand.resumewithfallback()實現的時候,它會將observable對象直接返回。
如果我們沒有為命令實現降級邏輯或在降級處理中拋出了異常,hystrix依然會返回一個observable對象,但是他不會發射任何結果數據,而是通過onerror方法通知命令立即中斷請求,并通過onerror()方法將引起命令失敗的異常發送給調用者。在降級策略的實現中我們應盡可能避免失敗的情況。
如果在執行降級時發生失敗,hystrix會根據不同的執行方法作出不同的處理:
- execute(): 拋出異常
- queue(): 正常返回future對象,但是調用get()來獲取結果時會拋出異常
- observe(): 正常返回observable對象,當訂閱它的時候,將立即通過訂閱者的onerror方法來通知中止請求
- toobservable(): 正常返回observable對象,當訂閱它的時候,將通過調用訂閱者的onerror方法來通知中止請求
第9步 返回成功的響應
當hystrix命令執行成功之后,它會將處理結果直接返回或是以observable的形式返回。具體的返回形式取決于不同的命令執行方式。
- toobservable(): 返回原始的observable,必須通過訂閱它才會真正觸發命令的執行流程
- observe(): 在toobservable()產生原始observable之后立即訂閱它,讓命令能夠馬上開始異步執行,并返回一個observable對象,當調用它的subscribe時,將重新產生結果和通知給訂閱者。
- queue(): 將toobservable()產生的原始observable通過toblocking()方法轉換成blockingobservable對象,并調用它的tofuture()方法返回異步的future對象
- execute(): 在queue()產生異步結果future對象之后,通過調用get()方法阻塞并等待結果的返回。
代碼地址
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持服務器之家。
原文鏈接:https://segmentfault.com/a/1190000016185647