在分佈式架構中,當某個服務單元發生故障以後,經過斷路由器的故障監控(相似熔斷保險絲),向調用方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得線程因調用故障服務被長時間佔用不釋放,避免了故障在分佈式系統中的蔓延。java
Spring Cloud Hystrix針對上述問題實現了斷路由器、線程隔離等一系列服務保護功能。它是基於Netflix Hystrix實現,該框架的目標在於經過控制那些訪問遠程系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。git
Hystrix具有服務降級、服務熔斷、線程和信號隔離、請求緩存、請求合併以及服務監控等強大功能。github
構建一個以下架構圖的服務調用關係
分析上述架構圖,主要有如下幾項工做:web
首先在pom.xml文件中增長spring-cloud-starter-hystrix依賴spring
在ribbon-consumer主類中使用@EnableCircuitBreaker註解開啓斷路由器功能,在這裏還有一個小技巧,可使用@SpringCloudApplicationd代替@EnableCircuitBreaker、@EnableEurekaClient、@SpringBootApplication這三個註解。緩存
改造ribbon-consumer中的HelloService,以下網絡
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,以下:架構
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是如何工做的。dom
首先,構建一個HystrixCommand或HystrixObservableCommand對象,用來表示對依賴服務的操做請求,同時傳遞全部須要的參數。這兩個對象都採用了命令模式來實現對服務調用操做的封裝,可是這兩個對象分別針對不一樣的應用場景。
命令模式,未來自客戶端的請求封裝成一個對象,從而讓你可使用不一樣的請求對客戶端進行參數化。它能夠用於實現行爲請求者和行爲實現者的解耦,以便使二者能夠適應變化
命令模式的示例代碼在command模塊下
經過命令模式的示例代碼能夠分析出命令模式的幾個關鍵點:
命令模式中Invoker和Receiver的關係很是相似於請求-響應模式,因此它比較適用於實現記錄日誌、撤銷操做、隊列請求等。
如下狀況咱們能夠考慮使用命令模式:
從圖中咱們能夠看到一共存在4種命令的執行方式,Hystrix在執行時會根據建立的Command對象以及具體的狀況來選擇一個執行。
HystrixCommand
HystrixCommand實現了兩個執行方式:
R value = command.execute(); Future<R> fValue = command.queue();
HystrixObservableCommand
HystrixObservableCommand實現了另兩種執行方式:
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實現:
在Hystrix的底層實現中大量使用了RxJava。上面提到的Observable對象就是RxJava的核心內容之一,能夠把Observable對象理解爲事件源或是被觀察者,與其對應的是Subscriber對象,能夠理解爲訂閱者或是觀察者。
若當前命令的請求緩存功能是被啓用的,而且該命令緩存命中,那麼緩存的結果會當即以Observable對象的形式返回。
在命令結果沒有緩存命中的時候,Hystrix在執行命令前須要檢查斷路器是否爲打開狀態:
若是與命令相關的線程池和請求隊列或者信號量(不使用線程池的時候)已被佔滿,那麼Hystrix不會執行命令,轉接到fallback處理邏輯(對應下面第8步)
Hystrix所判斷的線程池並不是容器的線程池,而是每一個依賴服務的專有線程池。Hystrix爲了保證不會由於某個依賴服務的問題影響到其餘依賴服務而採用了艙壁模式來隔離每一個依賴的服務。
Hystrix會根據咱們編寫的方法來決定採起什麼樣的方式去請求依賴服務:
若是run()或construct()方法的執行時間超過了命令設置的超時閥值,當前處理線程會拋出一個TimeoutException(若是該命令不在其自身的線程中執行,則會經過單獨的計時線程拋出)。在這種狀況下,Hystrix會轉到fallback邏輯去處理(第8步)。同時,若是當前命令沒有被取消或中斷,那麼它最終會忽略run()或construct()方法的返回。
若是命令沒有拋出異常並返回告終果,那麼Hystrix在記錄一些日誌並採集監控報告以後將該結果返回。在使用run()時,返回一個Observable,它會發射單個結果併產生onCompleted的結束通知,在使用construct()時,會直接返回該方法產生的Observable對象。
Hystrix會將成功、失敗、拒絕、超時等信息報告給斷路器,斷路器會維護一組計數器來統計這些數據。
斷路器會使用這些統計數據來決定是否要將斷路器打開,來對某個依賴服務的請求進行熔斷/短路,直到恢復期結束。若在恢復期結束後,根據統計數據判斷若是仍是未達到健康指標,就再次熔斷/短路。
當命令執行失敗時,Hystrix會進入fallback嘗試回退處理,咱們一般也稱之爲服務降級。可以引發服務降級處理的狀況主要有如下幾種:
在服務降級邏輯中,咱們須要實現一個通用的響應結果,而且該結果的處理邏輯應當是從緩存或是根據一些靜態邏輯來獲取,而不是依賴網絡請求獲取。若是必定要在降級邏輯中包含網絡請求,那麼該請求也必須被包裝在HystrixCommand或是HystrixObservableCommand中,從而造成級聯的降級策略,而最終的降級邏輯必定不是一個依賴網絡請求的處理,而是一個可以穩定返回結果的處理邏輯。
HystrixCommand和HystrixObservableCommand中實現降級邏輯時有如下不一樣:
當命令的降級邏輯返回結果以後,Hystrix就將該結果返回給調用者。當使用HystrixCommand.getFallback()時候,它會返回一個Observable對象,該對象會發射getFallback()的處理結果。而使用HystrixObservableCommand.resumeWithFallback()實現的時候,它會將Observable對象直接返回。
若是咱們沒有爲命令實現降級邏輯或在降級處理中拋出了異常,Hystrix依然會返回一個Observable對象,可是他不會發射任何結果數據,而是經過onError方法通知命令當即中斷請求,並經過onError()方法將引發命令失敗的異常發送給調用者。在降級策略的實現中咱們應儘量避免失敗的狀況。
若是在執行降級時發生失敗,Hystrix會根據不一樣的執行方法做出不一樣的處理:
當Hystrix命令執行成功以後,它會將處理結果直接返回或是以Observable的形式返回。具體的返回形式取決於不一樣的命令執行方式。