SpringBoot + SpringCloud Hystrix 實現服務熔斷


11111111.jpeg

什么是Hystrix

在分布式系統中,每個服務都可能會調用很多其他服務,被調用的那些服務就是依賴服務,有的時候某些依賴服務出現故障也是很常見的。
Hystrix是Netflix公司開源的一個項目,它提供了熔斷器功能,能夠解決分布式系統中出現聯動故障,Hystrix是通過隔離服務的訪問點阻止故障,並提供故障解決方案,從而提高分布式系統彈性。
    Hystrix可以讓我們在分布式系統中對服務間的調用進行控制,加入一些調用延遲或者依賴故障的容錯機制。Hystrix通過將依賴服務進行資源隔離,進而阻止某個依賴服務出現故障時在整個系統所有的依賴服務調用中進行蔓延;同時Hystrix 還提供故障時的 fallback 降級機制。
    總而言之,Hystrix 通過這些方法幫助我們提升分布式系統的可用性和穩定性。

Hystrix解決了什么問題

   在分布式系統中,可能有幾十個服務相互依賴。這些服務由於某些原因導致不可用。如果系統不隔離不可用的服務,則可能會導致整個系統不可用。
   在高並發情況下,單個服務的延遲會導致整個請求都處於延遲狀態,可能在幾秒鍾就使整個線程處於負載飽和狀態。
   某個服務的單點故障會導致用戶的請求處於阻塞狀態,最終的結果就是整個服務的線程資源消耗殆盡。由於服務的依賴性,會導致依賴該故障服務的其他服務也處於線程阻塞狀態,最終導致這些依賴服務的線程資源消耗殆盡,直到不可用,從而導致整個服務系統不可用,這就是雪崩效應。
   為了防止雪崩效應,因而產生了熔斷器模型。Hystrix是業界表現非常好的一個熔斷器模型實現的開源組件,是SpringCloud組件不可缺少的一部分。

Hystrix設計原則

  • 防止單個服務故障耗盡整個服務的Servlet容器(Tomcat/Jetty)的線程資源。
  • 快速失敗機制,如果某個服務出現故障,則調用該服務的請求迅速失敗,而不是線程等待。
  • 提供回退方案(fallback),在請求發生故障時,提供設定好的回退方案。
  • 使用熔斷機制,防止故障擴散到其他服務。
  • 提供熔斷器的監控組件Hystrix Dashboard,近實時的監控,報警以及運維操作。

Hystrix工作服原理

Hystrix工作機制.jpeg
首先,當服務的某個API接口的失敗次數在一定時間內小於設定的閾值時,熔斷器處於關閉狀態,該API接口正常提供服務。當該API接口處理請求失敗的次數大於設定閾值時,Hystrix判定該接口出現了故障,打開熔斷器。這時請求該API接口會執行快速失敗的邏輯(fallback的邏輯),不執行業務,請求的線程不會處於阻塞狀態。 處於打開狀態的熔斷器,一段時間后會處於半打開狀態,並將一定數量的請求執行正常邏輯。剩余的請求會執行快速失敗,若執行正常請求的邏輯失敗了,則熔斷器繼續打開。如果執行成功了,則將熔斷器關閉,這樣設計的熔斷器則具備了自我修復的能力。

在RestTemplate和Ribbon作為服務消費者時使用Hystrix

新建父Module和4個子Module,項目結構如下圖
截屏2020-08-2223.03.47.png
父Module主要用來引入DependencyManagement

  <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.2.2.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR1</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

創建EurekaServer

創建子Module, try-spring-cloud-eureka-server。
pom.xml

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
    </dependencies>

application.yml

server:
  port: 7001
eureka:
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka # 注冊中心端口7001
    register-with-eureka: false
    fetch-registry: false

Main 函數入口

@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

 <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
    </dependencies>

application.yml

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函數

@SpringBootApplication
@EnableEurekaClient
public class MyStudentService8001 {

    public static void main(String[] args) {
        SpringApplication.run(MyStudentService8001.class,args);
    }
}

Controller, 我在這里先返回了一個字符串用來測試使用。

@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

<dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </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-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>

application.yml

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注解,開啟熔斷器。

@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class MyStudentRibbonHystrix {

    public static void main(String[] args) {
        SpringApplication.run(MyStudentRibbonHystrix.class,args);
    }

}

Ribbon調用配置,關鍵注解LoadBalanced。

@Configuration
public class MyWebConfig {
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

Controller, 這里調用CLOUD-STUDENT-SERVICE的version()接口,同時增加了HystrixCommand注解,設置了屬性fallbackMethod, 如果方法調用失敗則執行快速失敗方法getErrorInfo。

@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, 如果正常輸出8001,202008182343字符串,則說明服務目前正常。然后我們在停止Student-Service(8001)的服務,再訪問下8087服務, 結果輸出Network error, please hold on... 則說明熔斷器已經生效。
截屏2020-08-2223.33.00.png

HystrixCommand注解參數

@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

   <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </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>
    </dependencies>
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屬性。

@FeignClient(value = "CLOUD-STUDENT-SERVICE",fallback = StudentFallbackService.class)
public interface StudentService {
    
    @GetMapping("/student/version")
    String version();
}

StudentFallbackService

@Component
public class StudentFallbackService implements StudentService {
    @Override
    public String version() {
        return "Network Error, I am callback service...";
    }
}

Controller

@RestController
@RequestMapping("/student")
public class StudentController {

    @Autowired
    StudentService studentService;

    @GetMapping("/version")
    public String version(){
        System.out.println("===openfeign 調用===");
        return studentService.version();
    }
}

Main函數

@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

請求緩存功能@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)作為替代方案。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM