分佈式系統中常常會出現某個基礎服務不可用形成整個系統不可用的狀況,這種現象被稱爲服務雪崩效應。爲了應對服務雪崩,一種常見的作法是手動服務降級。而 Hystrix 的出現,給咱們提供了另外一種選擇。java
在微服務架構中, 咱們將系統拆分紅了不少服務單元, 各單元的應用間經過服務註冊 與訂閱的方式互相依賴。 因爲每一個單元都在不一樣的進程中運行,依賴經過遠程調用的方式 執行, 這樣就有可能由於網絡緣由或是依賴服務自身間題出現調用故障或延遲, 而這些問 題會直接致使調用方的對外服務也出現延遲, 若此時調用方的請求不斷增長, 最後就會因 等待出現故障的依賴方響應造成任務積壓, 最終致使自身服務的癱瘓。 舉個例子, 在一個電商網站中, 咱們可能會將系統拆分紅用戶、 訂單、 庫存、 積分、 評論等一系列服務單元。 用戶建立一個訂單的時候, 客戶端將調用訂單服務的建立訂單接 口,此時建立訂單接口又會向庫存服務來請求出貨(判斷是否有足夠庫存來出貨)。 此時若 庫存服務因自身處理邏輯等緣由形成響應緩慢, 會直接致使建立訂單服務的線程被掛起, 以等待庫存申請服務的響應, 在漫長的等待以後用戶會由於請求庫存失敗而獲得建立訂單 失敗的結果。 若是在高併發狀況之下,因這些掛起的線程在等待庫存服務的響應而未能釋 放, 使得後續到來的建立訂單請求被阻塞, 最終致使訂單服務也不可用。即雪崩效應。web
服務雪崩效應是一種因 服務提供者 的不可用致使 服務調用者 的不可用,並將不可用 逐漸放大 的過程。spring
服務雪崩產生的過程分爲如下三個階段來分析造成的緣由:express
服務雪崩的每一個階段均可能由不一樣的緣由形成,好比形成 服務不可用 的緣由有:apache
而造成 重試加大流量 的緣由有:json
服務調用者不可用 產生的主要緣由有:後端
針對形成服務雪崩的不一樣緣由,可使用不一樣的應對策略:瀏覽器
流量控制 的具體措施包括:緩存
改進緩存模式 的措施包括:服務器
服務調用者降級服務 的措施包括:
咱們根據具體業務,將依賴服務分爲: 強依賴和若依賴。強依賴服務不可用會致使當前業務停止,而弱依賴服務的不可用不會致使當前業務的停止。
不可用服務的調用快速失敗通常經過 超時機制, 熔斷器 和熔斷後的 降級方法 來實現。
對於查詢操做,咱們能夠實現一個 fallback 方法,當請求後端服務出現異常的時候,可使用 fallback 方法返回的值。fallback 方法的返回值通常是設置的默認值或者來自緩存。
在 Hystrix 中,主要經過線程池來實現資源隔離。一般在使用的時候咱們會根據調用的遠程服務劃分出多個線程池。例如調用產品服務的 Command 放入 A 線程池,調用帳戶服務的 Command 放入 B 線程池。這樣作的主要優勢是運行環境被隔離開了。這樣就算調用服務的代碼存在 bug 或者因爲其餘緣由致使本身所在線程池被耗盡時,不會對系統的其餘服務形成影響。 經過實現對依賴服務的線程池隔離, 能夠帶來以下優點:
Hystrix 中除了使用線程池以外,還可使用信號量來控制單個依賴服務的併發度,信號量的開銷要遠比線程池的開銷小得多,可是它不能設置超時和實現異步訪問。因此,只有在依賴服務是足夠可靠的狀況下才使用信號量。在 HystrixCommand 和 HystrixObservableCommand 中 2 處支持信號量的使用:
信號量的默認值爲 10,咱們也能夠經過動態刷新配置的方式來控制併發線程的數量。對於信號量大小的估算方法與線程池併發度的估算相似。僅訪問內存數據的請求通常耗時在 1ms 之內,性能能夠達到 5000rps,這樣級別的請求咱們能夠將信號量設置爲 1 或者 2,咱們能夠按此標準並根據實際請求耗時來設置信號量。
在分佈式架構中,斷路器模式的做用也是相似的,當某個服務單元發生故障(相似用電器發生短路)以後,經過斷路器的故障監控(相似熔斷保險絲),直接切斷原來的主邏輯調用。可是,在 Hystrix 中的斷路器除了切斷主邏輯的功能以外,還有更復雜的邏輯,下面咱們來看看它更爲深層次的處理邏輯。 斷路器開關相互轉換的邏輯以下圖:
當 Hystrix Command 請求後端服務失敗數量超過必定閾值,斷路器會切換到開路狀態 (Open)。這時全部請求會直接失敗而不會發送到後端服務。
這個閾值涉及到三個重要參數:快照時間窗、請求總數下限、錯誤百分比下限。這個參數的做用分別是: 快照時間窗:斷路器肯定是否打開須要統計一些請求和錯誤數據,而統計的時間範圍就是快照時間窗,默認爲最近的 10 秒。 請求總數下限:在快照時間窗內,必須知足請求總數下限纔有資格進行熔斷。默認爲 20,意味着在 10 秒內,若是該 Hystrix Command 的調用此時不足 20 次,即時全部的請求都超時或其餘緣由失敗,斷路器都不會打開。 錯誤百分比下限:當請求總數在快照時間窗內超過了下限,好比發生了 30 次調用,若是在這 30 次調用中,有 16 次發生了超時異常,也就是超過 50% 的錯誤百分比,在默認設定 50% 下限狀況下,這時候就會將斷路器打開。
斷路器保持在開路狀態一段時間後 (默認 5 秒),自動切換到半開路狀態 (HALF-OPEN)。這時會判斷下一次請求的返回狀況,若是請求成功,斷路器切回閉路狀態 (CLOSED),不然從新切換到開路狀態 (OPEN)。
由於熔斷只是做用在服務調用這一端,所以咱們根據上一篇的示例代碼只須要改動 server-businessb-woqu 項目相關代碼就能夠。
feign: hystrix: enabled: true
/** * @author orrin */ @Component(value = "additionHystrix") public class AdditionHystrix implements Addition { private static final Logger LOGGER = LoggerFactory.getLogger(AdditionHystrix.class); @Override public Integer add(int x, int y) { LOGGER.error(" Addition is disabled "); return 0; } }
@FeignClient(serviceId = "business-a-woqu", fallback = AdditionHystrix.class) public interface Addition { @GetMapping("/add") public Integer add(@RequestParam("x") int x, @RequestParam("y") int y); }
GET http://192.168.2.102:9002/area?length=1&width=2&heigh=3 HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Mon, 19 Nov 2018 07:39:24 GMT 0 Response code: 200; Time: 80ms; Content length: 1 bytes
Hystrix還有如下使用方式:
咱們提到斷路器是根據一段時間窗內的請求狀況來判斷並操做斷路器的打開和關閉狀態的。而這些請求狀況的指標信息都是 HystrixCommand 和 HystrixObservableCommand 實例在執行過程當中記錄的重要度量信息,它們除了 Hystrix 斷路器實現中使用以外,對於系統運維也有很是大的幫助。這些指標信息會以 「滾動時間窗」 與 「桶」 結合的方式進行彙總,並在內存中駐留一段時間,以供內部或外部進行查詢使用,Hystrix Dashboard 就是這些指標內容的消費者之一。
下面咱們基於以前的示例來結合 Hystrix Dashboard 實現 Hystrix 指標數據的可視化面板,這裏咱們將用到下以前實現的幾個應用,包括:
建立一個標準的 Spring Boot 工程,命名爲:hystrix-dashboard-woqu
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>common-server-woqu</artifactId> <groupId>com.orrin</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>hystrix-dashboard-woqu</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <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> <dependency> <groupId>org.jolokia</groupId> <artifactId>jolokia-core</artifactId> </dependency> </dependencies> </project>
import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard; /** * @author orrin */ @EnableHystrixDashboard @SpringBootApplication public class HystrixDashboardApplication { public static void main(String[] args) { SpringApplication.run(HystrixDashboardApplication.class, args); } }
spring: application: name: hystrix-dashboard-woqu server: port: 7002
啓動應用,而後再瀏覽器中輸入 http://localhost:7002/hystrix 能夠看到以下界面:
經過 Hystrix Dashboard 主頁面的文字介紹,咱們能夠知道,Hystrix Dashboard 共支持三種不一樣的監控方式:
前二者都對集羣的監控,須要整合 Turbine 才能實現。這一部分咱們先實現對單體應用的監控,這裏的單體應用就用咱們以前使用 Feign 和 Hystrix 實現的服務消費者——server-businessb-woqu。
既然 Hystrix Dashboard 監控單實例節點須要經過訪問實例的/actuator/hystrix.stream接口來實現,天然咱們須要爲服務實例添加這個 endpoint。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency>
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients(basePackages = "com.woqu") @EnableHystrix @ComponentScan(value = "com.woqu") public class BusinessBApp { public static void main(String[] args) { SpringApplication.run(BusinessBApp.class, args); } @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
management: endpoints: web: exposure: include: hystrix.stream
以上圖來講明其中各元素的具體含義:
建立一個標準的 Spring Boot 工程,命名爲:turbine-woqu。
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>common-server-woqu</artifactId> <groupId>com.orrin</groupId> <version>0.0.1-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>turbine-woqu</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-turbine</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-consul-discovery</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.jolokia</groupId> <artifactId>jolokia-core</artifactId> </dependency> </dependencies> </project>
package com.woqu.common.turbine; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.client.discovery.EnableDiscoveryClient; import org.springframework.cloud.netflix.turbine.EnableTurbine; /** * @author orrin */ @EnableTurbine @EnableDiscoveryClient @SpringBootApplication public class TurbineApplication { public static void main(String[] args) { SpringApplication.run(TurbineApplication.class, args); } }
spring: application: name: common-server-turbine cloud: consul: host: woqu.consul port: 8500 discovery: instance-id: ${spring.application.name} instance-group: ${spring.application.name} register: true service-name: ${spring.application.name} applications: common-server-gateway,business-b-woqu,business-a-woqu turbine: aggregator: cluster-config: ${applications} app-config: ${applications} #cluster-name-expression: new String("default") combine-host-port: true management: endpoints: web: exposure: include: "*" exclude: dev server: port: 7003
參數說明
注意:new String("default")這個必定要用 String 來包一下,不然啓動的時候會拋出異常。
在瀏覽器中訪問http://localhost:7002/hystrix,經過/clusters獲取能夠監控的集羣
GET http://127.0.0.1:7003/clusters HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Transfer-Encoding: chunked Date: Tue, 20 Nov 2018 05:47:18 GMT [ { "name": "business-b-woqu", "link": "http://127.0.0.1:7003/turbine.stream?cluster=business-b-woqu" }, { "name": "common-server-gateway", "link": "http://127.0.0.1:7003/turbine.stream?cluster=common-server-gateway" }, { "name": "business-a-woqu", "link": "http://127.0.0.1:7003/turbine.stream?cluster=business-a-woqu" } ] Response code: 200; Time: 12ms; Content length: 304 bytes
在Hystrix Dashboard輸入http://127.0.0.1:7003/turbine.stream?cluster=business-b-woqu,請求GET http://192.168.2.102:9002/area?length=1&width=2&heigh=3,便可查看到監控效果
Spring Cloud 在封裝 Turbine 的時候,還實現了基於消息代理的收集實現。因此,咱們能夠將全部須要收集的監控信息都輸出到消息代理中,而後 Turbine 服務再從消息代理中異步的獲取這些監控信息,最後將這些監控信息聚合並輸出到 Hystrix Dashboard 中。
這裏咱們使用RabbitMQ來實現基於消息代理的 Turbine 聚合服務。
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-turbine-stream</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
@SpringBootApplication @EnableTurbineStream @EnableDiscoveryClient public class TurbineStreamRabbitmqApplication { public static void main(String[] args) { SpringApplication.run(TurbineStreamRabbitmqApplication.class, args); } @Bean public ConfigurableCompositeMessageConverter integrationArgumentResolverMessageConverter(CompositeMessageConverterFactory factory) { return new ConfigurableCompositeMessageConverter(factory.getMessageConverterForAllRegistered().getConverters()); } }
spring: application: name: common-server-turbine-stream cloud: consul: host: woqu.consul port: 8500 discovery: instance-id: ${spring.application.name} instance-group: ${spring.application.name} register: true service-name: ${spring.application.name} stream: rabbit: bindings: test: consumer: prefix: z bindings: input: group: default rabbitmq: addresses: rabbitmq.server port: 5672 username: test password: password virtualHost: /test applications: common-server-gateway,business-b-woqu,business-a-woqu turbine: aggregator: cluster-config: ${applications} app-config: ${applications} #cluster-name-expression: new String("default") combine-host-port: true stream: port: 18888 # 這是turbine的端口即暴露監控數據的端口,跟server.port不一樣 management: endpoints: web: exposure: include: "*" exclude: dev server: port: 7004
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-netflix-hystrix-stream</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-stream-rabbit</artifactId> </dependency>
再在啓動類上加上@EnableHystrix註解
@SpringBootApplication @EnableDiscoveryClient @EnableFeignClients(basePackages = "com.woqu") @EnableHystrix @ComponentScan(value = "com.woqu") public class BusinessBApp { public static void main(String[] args) { SpringApplication.run(BusinessBApp.class, args); } @LoadBalanced @Bean public RestTemplate restTemplate() { return new RestTemplate(); } }
添加配置文件
spring: rabbitmq: addresses: rabbitmq.server port: 5672 username: test password: password virtualHost: /test cloud: stream: rabbit: bindings: test: consumer: prefix: z bindings: input: group: default