在微服務架構中,因爲某個服務的不可用致使一系列的服務崩潰,被稱之爲雪崩效應。因此防護服務的雪崩效應是必不可少的,在Spring Cloud中防雪崩的利器就是Hystrix,Spring Cloud Hystri是基於Netflix Hystrix實現的。Hystrix的目標在於經過控制那些訪問遠程系統、服務和第三方庫的節點,從而對延遲和故障提供更強大的容錯能力。Hystrix 具有服務降級、服務容錯、服務熔斷、線程和信號隔離、請求緩存、請求合併以及服務監控等強大功能。java
Hystrix中的資源隔離:web
在Hystrix中, 主要經過線程池來實現資源隔離. 一般在使用的時候咱們會根據調用的遠程服務劃分出多個線程池. 例如調用產品服務的Command放入A線程池, 調用帳戶服務的Command放入B線程池. 這樣作的主要優勢是運行環境被隔離開了. 這樣就算調用服務的代碼存在bug或者因爲其餘緣由致使本身所在線程池被耗盡時, 不會對系統的其餘服務形成影響. 可是帶來的代價就是維護多個線程池會對系統帶來額外的性能開銷. 若是是對性能有嚴格要求並且確信本身調用服務的客戶端代碼不會出問題的話, 可使用Hystrix的信號模式(Semaphores)來隔離資源.spring
關於服務降級:後端
本小節咱們來模擬一下觸發服務降級的狀況,首先在訂單服務項目的pom.xml文件中,加入Spring Cloud Hystrix依賴。以下:緩存
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
添加好依賴後修改一下啓動類的註解。修改後代碼以下:bash
package org.zero.springcloud.order.server; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.openfeign.EnableFeignClients; @SpringCloudApplication @EnableFeignClients(basePackages = "org.zero.springcloud.product.client") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
在controller包中,新建一個 HystrixController ,咱們在這個類裏作實驗。在這個類裏,咱們調用了商品服務中的查詢商品信息接口。爲了模擬服務宕機觸發降級,因此此時我已經把商品服務關閉了。具體代碼以下:服務器
package org.zero.springcloud.order.server.controller; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.Collections; /** * @program: sell_order * @description: Hystrix Demo * @author: 01 * @create: 2018-08-28 20:10 **/ @Slf4j @RestController @RequestMapping("/hystrix/demo") public class HystrixController { /** * 經過@HystrixCommand註解指定可降級的服務,fallbackMethod參數指向的是回調函數,函數名稱可自定義 * * @return String */ @HystrixCommand(fallbackMethod = "fallback") @GetMapping("/getProductInfoList") public String getProductInfoList() { RestTemplate restTemplate = new RestTemplate(); return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder", Collections.singletonList("157875196366160022"), String.class); } /** * 觸發降級後的回調函數 * * @return String */ public String fallback() { return "太擁擠了, 請稍後重試~"; } }
啓動項目,訪問結果以下:
網絡
從測試結果能夠看到,因爲商品服務關閉了,致使沒法調用相應的接口。觸發了服務降級後,調用了註解中指定的回調函數,並返回了相應的提示。架構
觸發服務降級不必定是服務調用失敗,由於服務降級的主要觸發緣由是拋出了異常,因此只要這個方法中拋出了未被捕獲的異常都會觸發服務降級。以下示例:app
@HystrixCommand(fallbackMethod = "fallback") @GetMapping("/getProductInfoList") public String getProductInfoList() { throw new RuntimeException("發生了異常"); }
在某些狀況下,咱們可能只須要定義一個默認的回調處理函數便可,那麼咱們就可使用@DefaultProperties
註解來定義默認的回調函數,這樣就不須要每一個 @HystrixCommand
註解都指定一個回調函數了。以下示例:
package org.zero.springcloud.order.server.controller; import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties; import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.client.RestTemplate; import java.util.Collections; /** * @program: sell_order * @description: Hystrix Demo * @author: 01 * @create: 2018-08-28 20:10 **/ @Slf4j @RestController @RequestMapping("/hystrix/demo") @DefaultProperties(defaultFallback = "defaultFallback") public class HystrixController { /** * 定義了@DefaultProperties後,只需經過@HystrixCommand註解指定可降級的服務便可 * * @return String */ @HystrixCommand @GetMapping("/getProductInfoList") public String getProductInfoList() { RestTemplate restTemplate = new RestTemplate(); return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder", Collections.singletonList("157875196366160022"), String.class); } /** * 觸發降級後的回調函數 * * @return String */ public String defaultFallback() { return "太擁擠了, 請稍後重試~"; } }
使用 @HystrixCommand
註解的接口是有一個默認超時時間的,當調用某個服務的耗時超過這個時間也會觸發服務降級,默認的超時時間是1秒。咱們也能夠去自定義這個超時時間,以下示例:
@HystrixCommand(commandProperties = { // 設置超時時間爲3秒 @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000") }) @GetMapping("/getProductInfoList") public String getProductInfoList() { RestTemplate restTemplate = new RestTemplate(); return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder", Collections.singletonList("157875196366160022"), String.class); }
斷路器就像電路中的斷路器同樣,當短路發生時,它第一時刻熔斷,切斷了故障電路,保護其餘用電單元。
在分佈式架構中,斷路器的做用相似,當某個服務單元發生了故障,經過斷路器的故障監控,直接切斷原來的主邏輯調用,強迫之後的多個服務調用再也不訪問遠程服務器,防止應用程序繼續執行或等待超時。熔斷器也能夠監控服務單元的錯誤是否已經修正,若是已經修正,應用程序會再次嘗試調用操做。
在微服務架構中,系統被拆分紅了一個個小的服務單元,各自運行在本身的線程中,各單元之間經過註冊與訂閱的方式互相遠程調用,此時若網絡故障或是某一服務掛掉則會出現調用延遲,進一步致使調用方的對外服務也出現延遲,若是調用方的請求不斷增長,服務單元線程資源沒法釋放,隊列裝滿,最終致使故障的蔓延,故斷路器就是解決這種問題的。
斷路器模式:
當Hystrix Command請求後端服務失敗數量超過必定比例(默認50%), 斷路器會切換到開路狀態(Open). 這時全部請求會直接失敗而不會發送到後端服務. 斷路器保持在開路狀態一段時間後(默認5秒), 自動切換到半開路狀態(HALF-OPEN). 這時會判斷下一次請求的返回狀況, 若是請求成功, 斷路器切回閉路狀態(CLOSED), 不然從新切換到開路狀態(OPEN). 即有自我檢測並恢復的能力.
代碼示例:
@HystrixCommand(commandProperties = { @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), // 開啓熔斷機制 @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), // 設置當請求失敗的數量達到10個後,打開斷路器,默認值爲20 @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), // 設置打開斷路器多久之後開始嘗試恢復,默認爲5s @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), // 設置出錯百分比閾值,當達到此閾值後,打開斷路器,默認50% }) @GetMapping("/getProductInfoList") public String getProductInfoList(@RequestParam("number") Integer number) { if (number % 2 == 0) { return "success"; } RestTemplate restTemplate = new RestTemplate(); return restTemplate.postForObject("http://127.0.0.1:8519/buyer/product/listForOrder", Collections.singletonList("157875196366160022"), String.class); }
在代碼裏寫配置可能不太方便維護,咱們也能夠在配置文件中使用配置項進行配置。例如超時時間配置以下:
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000
若指定配置某一個方法的超時時間,將default換成相應方法名便可。以下示例:
hystrix: command: getProductInfoList: execution: isolation: thread: timeoutInMilliseconds: 3000
斷路器的的配置方式也是同樣的,以下示例:
hystrix: command: default: execution: isolation: thread: timeoutInMilliseconds: 3000 circuitBreaker: enabled: true requestVolumeThreshold: 10 sleepWindowInMilliseconds: 10000 errorThresholdPercentage: 60
咱們在訂單服務中,使用了feign組件去調用商品服務實現服務間的通訊。而feign內部已包含了hystrix,因此也能夠實現服務降級。首先在訂單服務項目的配置文件中,增長以下配置:
feign: hystrix: enabled: true # 開啓hystrix
到商品服務項目的client模塊中,新增一個 ProductClientFallback 類,並實現ProductClient接口。代碼以下:
package org.zero.springcloud.product.client; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.zero.springcloud.product.common.DecreaseStockInput; import org.zero.springcloud.product.common.ProductInfoOutput; import java.util.List; /** * @program: sell_product * @description: 觸發服務降級時會調用相應的方法 * @author: 01 * @create: 2018-08-29 21:39 **/ @Slf4j @Component public class ProductClientFallback implements ProductClient { @Override public List<ProductInfoOutput> productInfoList(List<String> productIdList) { log.info("productInfoList() 觸發了服務降級"); return null; } @Override public void decreaseStock(List<DecreaseStockInput> cartDTOList) { log.info("decreaseStock() 觸發了服務降級"); } }
而後在 ProductClient 接口的@FeignClient
註解裏增長 fallback 屬性,並指定以上編寫的實現類。當某個接口觸發降級時,就會調用實現類裏的方法。代碼以下:
package org.zero.springcloud.product.client; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.zero.springcloud.product.common.DecreaseStockInput; import org.zero.springcloud.product.common.ProductInfoOutput; import java.util.List; /** * @program: sell_order * @description: 配置須要調用的接口地址 * @author: 01 * @create: 2018-08-19 12:14 **/ @FeignClient(name = "PRODUCT", fallback = ProductClientFallback.class) public interface ProductClient { /** * 調用商品服務-按id查詢商品列表 * 注意,接口地址須要填寫完整 * * @param productIdList productIdList * @return List<ProductInfo> */ @PostMapping("/buyer/product/listForOrder") List<ProductInfoOutput> productInfoList(@RequestBody List<String> productIdList); /** * 調用商品服務-扣庫存 * * @param cartDTOList cartDTOList */ @PostMapping("/buyer/product/decreaseStock") void decreaseStock(@RequestBody List<DecreaseStockInput> cartDTOList); }
編寫完以上的代碼後,不要忘了安裝到maven本地倉庫中,安裝命令以下:
mvn clean -Dmaven.test.skip=true install
回到訂單服務,在啓動類上增長@ComponentScan
註解,擴大包掃描範圍:
package org.zero.springcloud.order.server; import org.springframework.boot.SpringApplication; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; @SpringCloudApplication @ComponentScan(basePackages = "org.zero.springcloud") @EnableFeignClients(basePackages = "org.zero.springcloud.product.client") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
重啓訂單服務項目,訪問建立訂單接口,以下:
控制檯輸出以下:
注:此時我已關閉了商品服務,因此纔會觸發服務降級
若是是超時致使服務降級的話,能夠在配置文件中配置feign的超時時間,以下:
feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic
hystrix-dashboard是一個可視化的熔斷監視工具,咱們本小節來看看如何在項目中使用這個工具。咱們以訂單服務項目爲例,首先在pom.xml文件中,增長以下依賴:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>
在啓動類中,增長@EnableHystrixDashboard
註解。代碼以下:
package org.zero.springcloud.order.server; import org.springframework.boot.SpringApplication; import org.springframework.cloud.client.SpringCloudApplication; import org.springframework.cloud.openfeign.EnableFeignClients; import org.springframework.context.annotation.ComponentScan; @EnableHystrixDashboard @SpringCloudApplication @ComponentScan(basePackages = "org.zero.springcloud") @EnableFeignClients(basePackages = "org.zero.springcloud.product.client") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
在config包下新建一個 HystrixConfig 配置類,用於配置 HystrixMetricsStreamServlet 。代碼以下:
package org.zero.springcloud.order.server.config; import com.netflix.hystrix.contrib.metrics.eventstream.HystrixMetricsStreamServlet; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @program: sell_order * @description: 配置HystrixMetricsStreamServlet * @author: 01 * @create: 2018-08-29 22:22 **/ @Configuration public class HystrixConfig { @Bean public HystrixMetricsStreamServlet hystrixMetricsStreamServlet() { return new HystrixMetricsStreamServlet(); } @Bean public ServletRegistrationBean registration(HystrixMetricsStreamServlet servlet) { ServletRegistrationBean<HystrixMetricsStreamServlet> registrationBean = new ServletRegistrationBean<>(); registrationBean.setServlet(servlet); //是否啓用該registrationBean registrationBean.setEnabled(true); registrationBean.addUrlMappings("/hystrix.stream"); return registrationBean; } }
完成以上代碼的編寫後,重啓項目,訪問http://localhost:9080/hystrix
,會進入到以下頁面中:
經過Hystrix Dashboard主頁面的文字介紹,咱們能夠知道,Hystrix Dashboard共支持三種不一樣的監控方式:
我這裏使用的是單體應用的監控,點擊Monitor Stream後,進入到以下頁面,在此頁面能夠看到這個項目的請求信息:
咱們來對這些指標進行一個簡單的說明: