本篇博客主要講解Hystrix的理論以及實現。
首先先來講熔斷器Hystrix的理論以及兩種實現方法。
熔斷器理論
爲什麼使用熔斷器
分佈式系統面臨的問題:雪崩效應
在微服務架構中通常會有多個服務層調用,基礎服務的故障可能會導致級聯故障,進而造成整個系統不可用的情況,這種現象被稱爲服務雪崩效應。服務雪崩效應是一種因"服務提供者"的不可用導致"服務消費者"的不可用,並將不可用逐漸放大的過程。
如下圖所示:多個服務調用,A作爲服務提供者,B爲A的服務消費者,C和D是B的服務消費者。微服務 A 調用微服務 B ,微服務 B 調用微服務 C和D,這就是所謂的“扇出”。如果扇出的鏈路上某個微服務的調用響應時間過長或者不可用,對微服務 A 的調用就會佔用越來越多的系統資源,進而引起系統崩潰,也就是A不可用引起了B的不可用,並將不可用像滾雪球一樣放大到C和D時,雪崩效應就形成了。
對於流量高的應用來說,單一的後端依賴可能會導致所有服務器上的所有資源都在幾秒鐘內飽和。比失敗更加糟糕的是,這些應用程序還可能導致服務之間的延遲增加,備份隊列,線程和其他系統資源緊張,導致整個系統發生更多的級聯故障。這些都表示需要對故障和延遲隔離和管理,以便單個依賴關係的失敗不能取消整個應用程序或系統。
熔斷器(CircuitBreaker)
熔斷器的原理很簡單,如同電力過載保護器。它可以實現快速失敗,如果它在一段時間內偵測到許多類似的錯誤,會強迫其以後的多個調用快速失敗,不再訪問遠程服務器,從而防止應用程序不斷地嘗試執行可能會失敗的操作,使得應用程序繼續執行而不用等待修正錯誤,或者浪費CPU時間去等到長時間的超時產生。熔斷器也可以使應用程序能夠診斷錯誤是否已經修正,如果已經修正,應用程序會再次嘗試調用操作。
熔斷器模式就像是那些容易導致錯誤的操作的一種代理。這種代理能夠記錄最近調用發生錯誤的次數,然後決定使用允許操作繼續,或者立即返回錯誤。
熔斷器開關相互轉換的邏輯如下圖:
熔斷器是保護服務高可用的最後一道防線。
什麼是Hystrix
Hystrix 是一個用於處理分佈式系統的 延遲 和 容錯 的開源庫,在分佈式系統裏,許多依賴會不可避免的調用失敗,比如超時,異常, Hystrix 能夠保證在一個依賴出現問題的情況下,不會導致整體服務的失敗,避免級聯故障,以提高分佈式系統的彈性。
Hystrix特性
1.斷路器機制
(是應對雪崩效應的一種微服務鏈路的保護機制)
斷路器很好理解, 當Hystrix Command請求後端服務失敗數量超過一定比例(默認50%), 斷路器會切換到開路狀態(Open). 這時所有請求會直接失敗而不會發送到後端服務. 斷路器保持在開路狀態一段時間後(默認5秒), 自動切換到半開路狀態(HALF-OPEN). 這時會判斷下一次請求的返回情況, 如果請求成功, 斷路器切回閉路狀態(CLOSED), 否則重新切換到開路狀態(OPEN). Hystrix的斷路器就像我們家庭電路中的保險絲, 一旦後端服務不可用, 斷路器會直接切斷請求鏈, 避免發送大量無效請求,也就是不長時間的等待或者拋出異常影響系統吞吐量, 它會向調用方返回一個符合預期的、可處理的備選響應。並且斷路器有自我檢測並恢復的能力,這樣就保證了服務調用方的線程不會被長時間、不必要的佔用,從而避免了故障在分佈式系統中的蔓延,乃至雪崩。
2.Fallback
Fallback相當於是降級操作. 對於查詢操作, 我們可以實現一個fallback方法, 當請求後端服務出現異常的時候, 可以使用fallback方法返回的值. fallback方法的返回值一般是設置的默認值或者來自緩存.
3.資源隔離
在Hystrix中, 主要通過線程池來實現資源隔離. 通常在使用的時候我們會根據調用的遠程服務劃分出多個線程池. 例如調用產品服務的Command放入A線程池, 調用賬戶服務的Command放入B線程池. 這樣做的主要優點是運行環境被隔離開了. 這樣就算調用服務的代碼存在bug或者由於其他原因導致自己所在線程池被耗盡時, 不會對系統的其他服務造成影響. 但是帶來的代價就是維護多個線程池會對系統帶來額外的性能開銷. 如果是對性能有嚴格要求而且確信自己調用服務的客戶端代碼不會出問題的話, 可以使用Hystrix的信號模式(Semaphores)來隔離資源.
Hystrix的兩種熔斷實現方式
本次的實現方法都是在之前的博客“Spring Cloud的Eureka實現服務發現註冊和Feign實現服務調用:https://blog.csdn.net/weixin_42236165/article/details/92842773 的項目上改動實現的。
第一種
Feign Hystrix
因爲熔斷只是作用在服務調用這一端,因此我們根據之前“Spring Cloud的Eureka實現服務發現註冊和Feign實現服務調用:https://blog.csdn.net/weixin_42236165/article/details/92842773
的示例代碼只需要改動springcloud-eurekastudent項目相關代碼就可以。因爲,Feign中已經依賴了Hystrix所以在maven配置上不用做任何改動。
1.配置文件
在application.properties文件中添加開啓熔斷器代碼
feign.hystrix.enabled=true
application.properties代碼如下:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/studentdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.application.name=eureka-student
server.port=8091
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka/
#開啓熔斷器
feign.hystrix.enabled=true
2.創建回調類
創建GradeClientImpl類繼承GradeClient接口實現回調的方法
package com.szh.eurekastudent.client.impl;
import com.szh.eurekastudent.client.GradeClient;
import com.szh.eurekastudent.pojo.Grade;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.List;
@Component
public class GradeClientImpl implements GradeClient {
@Override
public List<Grade> findStudents() {
//打印日誌
//使用消息隊列發送消息
//發送短信
List<Grade> list = new ArrayList<>();
Grade grade = new Grade();
grade.setGradeId(0);
grade.setGradeName("出錯了,執行熔斷器");
list.add(grade);
return list;
}
}
3、添加fallback屬性
在GradeClient接口添加指定fallback類,在服務熔斷的時候返回fallback類中的內容。
package com.szh.eurekastudent.client;
import com.szh.eurekastudent.client.impl.GradeClientImpl;
import com.szh.eurekastudent.pojo.Grade;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import java.util.List;
@FeignClient(name = "eureka-grade",fallback = GradeClientImpl.class)
public interface GradeClient {
@GetMapping("/grades")
public List<Grade> findStudents();
}
4.測試
依次啓動springcloud-eureka-server、springcloud-eureka-grade、spring-eureka-student三個項目。
首先在瀏覽器中輸入:http://localhost:8091/grades
說明加入熔斷相關信息後,不影響正常的訪問。接下來我們手動停止springcloud-eureka-grade項目再次測試:
在瀏覽器中輸入:http://localhost:8091/grades
“出錯了,執行熔斷器”
根據返回結果說明熔斷成功。
第二種
@HystrixCommand註解
在 Spring Cloud 框架裏熔斷機制通過 Hystrix 實現。Hystrix 會監控微服務間調用狀況,當失敗的調用到一定的閾值,缺省值是 5s 內 20 次調用失敗就會啓動熔斷機制。熔斷機制的註解是 @HystrixCommand
1.pom.xml文件
記得引入hystrix依賴
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.szh</groupId>
<artifactId>eureka-student</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>eureka-student</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Greenwich.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.13</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- hystrix -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2.application.properties文件
記得註釋掉第一種方法
#開啓熔斷器
#feign.hystrix.enabled=true
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/studentdb?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=root
spring.application.name=eureka-student
server.port=8091
eureka.client.serviceUrl.defaultZone=http://localhost:8080/eureka/
#開啓熔斷器
#feign.hystrix.enabled=true
3.項目啓動類
加上註解
@EnableHystrix
package com.szh.eurekastudent;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableDiscoveryClient
@EnableFeignClients
@EnableHystrix
@MapperScan("com.szh.eurekastudent.mapper")
@SpringBootApplication
public class EurekaStudentApplication {
public static void main(String[] args) {
SpringApplication.run(EurekaStudentApplication.class, args);
}
}
4.controller層的業務代碼
調用方法上加
@HystrixCommand(fallbackMethod = “findGrade”)註解
增加方法findGrade
package com.szh.eurekastudent.controller;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.szh.eurekastudent.client.GradeClient;
import com.szh.eurekastudent.pojo.Grade;
import com.szh.eurekastudent.pojo.Student;
import com.szh.eurekastudent.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@Autowired
private GradeClient gradeClient;
@GetMapping("/students")
public List<Student> findStudents(){
return studentService.selAllStudent();
}
@GetMapping("/grades")
@HystrixCommand(fallbackMethod = "findGrade")
public List<Grade> findGrades(){
return gradeClient.findStudents();
}
public List<Grade> findGrade() {
//打印日誌
//使用消息隊列發送消息
//發送短信
List<Grade> list = new ArrayList<>();
Grade grade = new Grade();
grade.setGradeId(0);
grade.setGradeName("出錯了,執行熔斷器");
list.add(grade);
return list;
}
}
5.測試
仍然依次啓動springcloud-eureka-server、springcloud-eureka-grade、spring-eureka-student三個項目。
首先在瀏覽器中輸入:http://localhost:8091/grades
說明加入熔斷相關信息後,不影響正常的訪問。接下來我們手動停止springcloud-eureka-grade項目再次測試:
在瀏覽器中輸入:http://localhost:8091/grades
“出錯了,執行熔斷器”
根據返回結果說明熔斷成功。