SpringBoot + SpringCloud Hystrix 實現服務熔斷
阿新 • • 發佈:2020-08-23
![11111111.jpeg](https://cdn.nlark.com/yuque/0/2020/jpeg/359374/1598144627528-d12375da-11bf-400d-aee4-42d03ef6249f.jpeg#align=left&display=inline&height=853&margin=%5Bobject%20Object%5D&name=11111111.jpeg&originHeight=853&originWidth=1280&size=195057&status=done&style=none&width=1280)
## 什麼是Hystrix
在分散式系統中,每個服務都可能會呼叫很多其他服務,被呼叫的那些服務就是依賴服務,有的時候某些依賴服務出現故障也是很常見的。
Hystrix是Netflix公司開源的一個專案,它提供了熔斷器功能,能夠解決分散式系統中出現聯動故障,Hystrix是通過隔離服務的訪問點阻止故障,並提供故障解決方案,從而提高分散式系統彈性。
Hystrix可以讓我們在分散式系統中對服務間的呼叫進行控制,加入一些呼叫延遲或者依賴故障的容錯機制。Hystrix通過將依賴服務進行資源隔離,進而阻止某個依賴服務出現故障時在整個系統所有的依賴服務呼叫中進行蔓延;同時Hystrix 還提供故障時的 fallback 降級機制。
總而言之,Hystrix 通過這些方法幫助我們提升分散式系統的可用性和穩定性。
## Hystrix解決了什麼問題
在分散式系統中,可能有幾十個服務相互依賴。這些服務由於某些原因導致不可用。如果系統不隔離不可用的服務,則可能會導致整個系統不可用。
在高併發情況下,單個服務的延遲會導致整個請求都處於延遲狀態,可能在幾秒鐘就使整個執行緒處於負載飽和狀態。
某個服務的單點故障會導致使用者的請求處於阻塞狀態,最終的結果就是整個服務的執行緒資源消耗殆盡。由於服務的依賴性,會導致依賴該故障服務的其他服務也處於執行緒阻塞狀態,最終導致這些依賴服務的執行緒資源消耗殆盡,直到不可用,從而導致整個服務系統不可用,這就是雪崩效應。
為了防止雪崩效應,因而產生了熔斷器模型。Hystrix是業界表現非常好的一個熔斷器模型實現的開源元件,是SpringCloud元件不可缺少的一部分。
## Hystrix設計原則
- 防止單個服務故障耗盡整個服務的Servlet容器(Tomcat/Jetty)的執行緒資源。
- 快速失敗機制,如果某個服務出現故障,則呼叫該服務的請求迅速失敗,而不是執行緒等待。
- 提供回退方案(fallback),在請求發生故障時,提供設定好的回退方案。
- 使用熔斷機制,防止故障擴散到其他服務。
- 提供熔斷器的監控元件Hystrix Dashboard,近實時的監控,報警以及運維操作。
## Hystrix工作服原理
![Hystrix工作機制.jpeg](https://cdn.nlark.com/yuque/0/2020/jpeg/359374/1598107918970-931ba711-b23b-4398-966a-d7b982ecdb7c.jpeg#align=left&display=inline&height=1118&margin=%5Bobject%20Object%5D&name=Hystrix%E5%B7%A5%E4%BD%9C%E6%9C%BA%E5%88%B6.jpeg&originHeight=1118&originWidth=2356&size=121543&status=done&style=none&width=2356)
首先,當服務的某個API介面的失敗次數在一定時間內小於設定的閾值時,熔斷器處於關閉狀態,該API介面正常提供服務。當該API介面處理請求失敗的次數大於設定閾值時,Hystrix判定該接口出現了故障,開啟熔斷器。這時請求該API介面會執行快速失敗的邏輯(fallback的邏輯),不執行業務,請求的執行緒不會處於阻塞狀態。 處於開啟狀態的熔斷器,一段時間後會處於半開啟狀態,並將一定數量的請求執行正常邏輯。剩餘的請求會執行快速失敗,若執行正常請求的邏輯失敗了,則熔斷器繼續開啟。如果執行成功了,則將熔斷器關閉,這樣設計的熔斷器則具備了自我修復的能力。
## 在RestTemplate和Ribbon作為服務消費者時使用Hystrix
新建父Module和4個子Module,專案結構如下圖
![截圖2020-08-2223.03.47.png](https://cdn.nlark.com/yuque/0/2020/png/359374/1598108649413-f70a91cb-9fb0-4353-9425-cc75c3e919df.png#align=left&display=inline&height=475&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2020-08-2223.03.47.png&originHeight=950&originWidth=1138&size=147828&status=done&style=stroke&width=569)
父Module主要用來引入DependencyManagement
```java
org.springframework.boot
spring-boot-dependencies
2.2.2.RELEASE
pom
import
org.springframework.cloud
spring-cloud-dependencies
Hoxton.SR1
pom
import
junit
junit
4.12
```
### 建立EurekaServer
建立子Module, try-spring-cloud-eureka-server。
pom.xml
```java
junit
junit
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-eureka-server
```
application.yml
```java
server:
port: 7001
eureka:
client:
service-url:
defaultZone: http://localhost:7001/eureka # 註冊中心埠7001
register-with-eureka: false
fetch-registry: false
```
Main 函式入口
```java
@SpringBootApplication
@EnableEurekaServer
public class MyEurekaServer7001 {
public static void main(String[] args) {
SpringApplication.run(MyEurekaServer7001.class,args);
}
}
```
### 建立Student Server Provider
建立子Module,try-spring-cloud-student-service
pom.xml
```java
junit
junit
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
```
application.yml
```java
server:
port: 8001
spring:
application:
name: CLOUD-STUDENT-SERVICE
eureka:
client:
fetch-registry: false
register-with-eureka: true # 註冊進Eureka Server
service-url:
defaultZone: http://localhost:7001/eureka # 單機版指向7001
```
Main函式
```java
@SpringBootApplication
@EnableEurekaClient
public class MyStudentService8001 {
public static void main(String[] args) {
SpringApplication.run(MyStudentService8001.class,args);
}
}
```
Controller, 我在這裡先返回了一個字串用來測試使用。
```java
@RestController
@RequestMapping("/student")
public class StudentController {
@GetMapping("/version")
public String version(){
return "8001,202008182343";
}
}
```
### 建立Ribbon Client
建立子Module,try-spring-cloud-ribbon-hystrix。 該模組下,我們基於RestTemplate和Ribbon作為消費者呼叫服務,先測試下服務正常時訪問邏輯, 和前面兩個Module不同,這個Module裡需要引入spring-cloud-starter-netflix-hystrix依賴。
pom.xml
```java
junit
junit
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-netflix-hystrix
```
application.yml
```java
server:
port: 8087
spring:
application:
name: STUDENT-CONSUMER
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka # 服務註冊中心地址
```
Main函式,這裡增加EnableHystrix註解,開啟熔斷器。
```java
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class MyStudentRibbonHystrix {
public static void main(String[] args) {
SpringApplication.run(MyStudentRibbonHystrix.class,args);
}
}
```
Ribbon呼叫配置,關鍵註解LoadBalanced。
```java
@Configuration
public class MyWebConfig {
@Bean
@LoadBalanced
public RestTemplate restTemplate(){
return new RestTemplate();
}
}
```
Controller, 這裡呼叫CLOUD-STUDENT-SERVICE的version()介面,同時增加了HystrixCommand註解,設定了屬性fallbackMethod, 如果方法呼叫失敗則執行快速失敗方法getErrorInfo。
```java
@RestController
@RequestMapping("/student")
public class StudentController {
@Autowired
RestTemplate restTemplate;
/**
* 使用HystrixCommand註解,設定服務呼叫失敗時回撥方法(getErrorInfo)
* **/
@GetMapping("/version")
@HystrixCommand(fallbackMethod = "getErrorInfo")
public String version() {
System.out.println("Ribbon呼叫前");
String result = restTemplate.getForObject("http://CLOUD-STUDENT-SERVICE/student/version", String.class);
System.out.println("Ribbon呼叫後,返回值:" + result);
return result;
}
public String getErrorInfo(){
return "Network error, please hold on...";
}
}
```
依此啟動Eureka-Server、Student-Service、Ribbon-Hystrix先測試下服務正常呼叫邏輯。
訪問[http://localhost:8087/student/version](http://localhost:8087/student/version), 如果正常輸出8001,202008182343字串,則說明服務目前正常。然後我們在停止Student-Service(8001)的服務,再訪問下8087服務, 結果輸出Network error, please hold on... 則說明熔斷器已經生效。
![截圖2020-08-2223.33.00.png](https://cdn.nlark.com/yuque/0/2020/png/359374/1598110455000-3896df49-e45d-4e0e-9189-385a6b4674f2.png#align=left&display=inline&height=143&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2020-08-2223.33.00.png&originHeight=286&originWidth=524&size=20206&status=done&style=none&width=262)
### HystrixCommand註解引數
```java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface HystrixCommand {
#配置全域性唯一標識服務的名稱
String groupKey() default "";
#配置全域性唯一標識服務分組的名稱,比如,庫存系統、訂單系統、系統使用者就是一個單獨的服務分組
String commandKey() default "";
#對執行緒池進行設定,細粒度的配置,相當於對單個服務的執行緒池資訊進行設定,也可多個服務設定同一個threadPoolKey構成執行緒組
String threadPoolKey() default "";
#執行快速失敗的回撥函式,@HystrixCommand修飾的函式必須和這個回撥函式定義在同一個類中,因為定義在了同一個類中,所以fackback method可以是public/private均可
String fallbackMethod() default "";
#配置該命令的一些引數,如executionIsolationStrategy配置執行隔離策略,預設是使用執行緒隔離
HystrixProperty[] commandProperties() default {};
#執行緒池相關引數設定,具體可以設定哪些引數請見:com.netflix.hystrix.HystrixThreadPoolProperties
HystrixProperty[] threadPoolProperties() default {};
#呼叫服務時,除了HystrixBadRequestException之外,其他@HystrixCommand修飾的函式丟擲的異常均會被Hystrix認為命令執行失敗而觸發服務降級的處理邏輯(呼叫fallbackMethod指定的回撥函式),所以當需要在命令執行中丟擲不觸發降級的異常時來使用它,通過這個引數指定,哪些異常丟擲時不觸發降級(不去呼叫fallbackMethod),而是將異常向上丟擲
Class extends Throwable>[] ignoreExceptions() default {};
#定義hystrix observable command的模式
ObservableExecutionMode observableExecutionMode() default ObservableExecutionMode.EAGER;
#任何不可忽略的異常都包含在HystrixRuntimeException中
HystrixException[] raiseHystrixExceptions() default {};
#預設的回撥函式,該函式的函式體不能有入參,返回值型別與@HystrixCommand修飾的函式體的返回值一致。如果指定了fallbackMethod,則fallbackMethod優先順序更高
String defaultFallback() default "";
}
```
## 在OpenFeign作為服務消費者時使用Hystrix
建立子Module, try-spring-cloud-openfeign-hystrix,這個節點使用OpenFeign作為服務消費者時使用Hystrix, OpenFeign裡已經依賴了Hystrix,所以這裡不需要再單獨引入,只需要引入spring-cloud-starter-openfeign即可。
pom.xml
```java
junit
junit
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
org.springframework.boot
spring-boot-starter-actuator
org.springframework.cloud
spring-cloud-starter-netflix-eureka-client
org.springframework.cloud
spring-cloud-starter-openfeign
```
```java
server:
port: 8087
spring:
application:
name: STUDENT-OPENFEIGN-CONSUMER
eureka:
client:
register-with-eureka: false
fetch-registry: true
service-url:
defaultZone: http://localhost:7001/eureka # 服務註冊中心地址
feign:
hystrix:
enabled: true #在feign中開啟hystrix
```
定義呼叫CLOUD-STUDENT-SERVICE的介面,也就是增加註解FeignClient,設定value和fallback屬性。
```java
@FeignClient(value = "CLOUD-STUDENT-SERVICE",fallback = StudentFallbackService.class)
public interface StudentService {
@GetMapping("/student/version")
String version();
}
```
StudentFallbackService
```java
@Component
public class StudentFallbackService implements StudentService {
@Override
public String version() {
return "Network Error, I am callback service...";
}
}
```
Controller
```java
@RestController
@RequestMapping("/student")
public class StudentController {
@Autowired
StudentService studentService;
@GetMapping("/version")
public String version(){
System.out.println("===openfeign 呼叫===");
return studentService.version();
}
}
```
Main函式
```java
@SpringBootApplication
@EnableFeignClients
@EnableDiscoveryClient
public class MyStudentOpenfeignHystrix {
public static void main(String[] args) {
SpringApplication.run(MyStudentOpenfeignHystrix.class,args);
}
}
```
依次啟動Eureka-Server, Student-Service,OpenFeign-Hystrix-Client,和上面Ribbon一樣,先來測試下正常邏輯,訪問http://localhost:8087/student/version. 輸出8001,202008182343字串說明服務呼叫正常,然後停掉Student-Service,再次訪問介面,輸出Network Error, I am callback service... 則說明基於OpenFeign的熔斷器已經生效。
![截圖2020-08-2307.40.04.png](https://cdn.nlark.com/yuque/0/2020/png/359374/1598139647560-24bec9c8-641b-4110-b84d-9935926a6ad5.png#align=left&display=inline&height=152&margin=%5Bobject%20Object%5D&name=%E6%88%AA%E5%B1%8F2020-08-2307.40.04.png&originHeight=304&originWidth=628&size=22378&status=done&style=none&width=314)
## 請求快取功能@CacheResult
Hystrix還提供了請求快取功能,當一些查詢服務不可用時,可以呼叫快取查詢。@CacheResult需要和@HystrixCommand組合使用
| **註解** | **描述** | **屬性** |
| --- | --- | --- |
| @CacheResult | 該註解用來標記請求命令返回的結果應該被快取,它必須與@HystrixCommand註解結合使用 | cacheKeyMethod |
| @CacheRemove| 該註解用來讓請求命令的快取失效,失效的快取根據定義Key決定 | commandKey, cacheKeyMethod |
| @CacheKey | 該註解用來在請求命令的引數上標記,使其作為快取的Key值,如果沒有標註則會使用所有引數。如果同事還是使用了@CacheResult和@CacheRemove註解的cacheKeyMethod方法指定快取Key的生成,那麼該註解將不會起作用 | value |
## 總結
Hystrix現在已經宣佈停止更細了,進入了維護模式,但是其效能也比較穩定了,spring官方推薦了[Resilience4J](https://github.com/resilience4j/resilience4j)、阿里的[Sentinel](https://github.com/alibaba/Sentinel)、[Spring Retry](https://github.com/spring-projects/spring-retry)作為替代