7. 使用Hystrix實現微服務的容錯處理

              使用Hystrix實現微服務的容錯處理

7.1. 實現容錯的手段

  7.1.1. 雪崩效應

                   在微服務架構中一般會有多個服務層調用,基礎服務的故障可能會致使級聯故障,進而形成整個系統不可用的狀況,這種現象被稱爲服務雪崩效應。

                   服務雪崩效應是一種因「服務提供者」的不可用致使「服務消費者」的不可用,並將不可用逐漸放大的過程。

 

 7.1.2. 如何容錯

爲了防止雪崩效應,必須有一個強大的容錯機制。該容錯機制需實現如下兩點:java

       1.爲網絡請求設置超時

         必須爲網絡請求設置超時。web

          正常狀況下,一個遠程調用通常在及時毫秒內就能獲得響應了。若是依賴的服務不可用或者網絡有問題,那麼響應時間就會變得特別長。spring

          一般狀況下,一次遠程調用對應着一個線程/進程。若是響應太慢,這個線程/進程就得不到釋放。而線程/進程又對應着系統資源,若是得不到釋放的線程/進程約積越多,資源就會逐漸被耗盡,最終致使服務的不可用。apache

2.使用斷路器模式

          若是對某個微服務的請求有大量超時(經常說明該微服務不可用),再去讓新的請求訪問該服務已經沒有任何意義,只會無所謂消耗資源。設計模式

          例如,設置了超時時間爲1秒,若是短期內有大量的請求沒法在1秒內獲得響應,就沒有必要再去請求依賴的服務了。服務器

 

          斷路器可理解爲對容易致使錯誤的操做的代理。網絡

          這種代理可以統計一段時間內調用失敗的次數,並決定是正常請求依賴的服務仍是直接返回。架構

 

          斷路器能夠實現快速失敗,若是它在一段時間內檢測到許多相似的錯誤(例如超時),就會在以後的一段時間內,強迫對該服務的調用快速失敗,即再也不請求所依賴的服務。併發

          這樣,應用程序就無需再浪費cpu時間去等待長時間的超時。app

 

          斷路器也可自動診斷是否已經恢復正常。若是發現依賴的服務已經恢復正常,那麼就會恢復請求該服務。

          使用這種方式,就能夠實現微服務的「自我修復」——當依賴的服務不正常打開斷路器時快速失敗,從而防止雪崩效應;

          當發現依賴的服務恢復正常時,又會恢復請求。

   

         斷路器狀態轉換邏輯:

             - 正常狀況下,斷路器關閉,可正常請求依賴的服務

             - 當一段時間內,請求失敗率達到必定閥值(例如錯誤率達到50%,或100次/分鐘等),斷路器就會打開。此時,不會再去請求依賴的服務。

             - 斷路器打開一段時間後,會自動進入「半開」狀態。此時,斷路器可容許一個請求訪問依賴的服務。若是該請求可以調用成功,則關閉斷路器;不然繼續保持打開狀態。

 7.1.3. 熔斷器

            熔斷器的原理很簡單,如同電力過載保護器。

            它能夠實現快速失敗,若是它在一段時間內偵測到許多相似的錯誤,會強迫其之後的多個調用快速失敗,

            再也不訪問遠程服務器,從而防止應用程序不斷地嘗試執行可能會失敗的操做,使得應用程序繼續執行而不用等待修正錯誤,

            或者浪費CPU時間去等到長時間的超時產生。熔斷器也可使應用程序可以診斷錯誤是否已經修正,若是已經修正,應用程序會再次嘗試調用操做。

            熔斷器模式就像是那些容易致使錯誤的操做的一種代理。這種代理可以記錄最近調用發生錯誤的次數,而後決定使用容許操做繼續,或者當即返回錯誤。

 

7.2. 使用Hystrix實現容錯

 7.2.1. Hystrix簡介

   Hystrix是一個實現了超時機制和斷路器模式的工具類庫。

   是由Netfix開源的一個延遲和容錯庫,用於隔離訪問遠程系統、服務或者第三方庫,防止級聯失敗,從而提高系統可用性與容錯性。

   Hystrix主要經過如下幾點實現延遲和容錯。

  • 包裹請求:使用HystrixCommand(或者HystrixObservableCommand)包裹對依賴的調用邏輯,每一個命令在獨立線程中執行。這使用了設計模式中的「命令模式」。
  • 跳閘機制:當某服務的錯誤率超過必定閥值時,Hystrix能夠自動或者手動跳閘,中止請求該服務一段時間。
  • 資源隔離:Hystrix爲每一個依賴都維護了一個小型的線程池(或者信號量)。若是該線程池已滿,發往該依賴的請求就被當即拒絕,而不是排隊等候,從而加速失敗判斷
  • 監控:Hystrix能夠近乎實時地監控運行指標和配置的變化,例如成功、失敗、超時、以及被拒絕的請求等。
  • 回退機制:當請求失敗、超時、被拒絕,或當斷路器打開時,執行回退邏輯。回退邏輯可由開發人員自行提供,例如返回一個缺省值。
  • 自我修復:斷路器打開一段時間後,會自動進入「半開」狀態。

 7.2.2. 通用方式整合Hystrix(Ribbon使用Hystrix)

 

 7.2.2.1. Ribbon添加回退

 

        pom.xml

<?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>

	<artifactId>microservice-consumer-movie-ribbon-with-hystrix</artifactId>
	<packaging>jar</packaging>

	<parent>
		<groupId>com.itmuch.cloud</groupId>
		<artifactId>microservice-spring-cloud</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</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-hystrix</artifactId>
		</dependency>
	</dependencies>
</project>

  配置文件:

spring:
  application:
    name: microservice-consumer-movie-ribbon-with-hystrix
server:
  port: 8010
eureka:
  client:
    healthcheck:
      enabled: true
    serviceUrl:
      defaultZone: http://user:password123@localhost:8761/eureka
  instance:
    prefer-ip-address: true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000

  啓動類

package com.itmuch.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class ConsumerMovieRibbonApplication {

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

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

  業務類:

package com.itmuch.cloud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.itmuch.cloud.entity.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;

@RestController
public class MovieController {
  @Autowired
  private RestTemplate restTemplate;

  @GetMapping("/movie/{id}")
  @HystrixCommand(fallbackMethod = "findByIdFallback")
  public User findById(@PathVariable Long id) {
    return this.restTemplate.getForObject("http://microservice-provider-user/simple/" + id, User.class);
  }

  public User findByIdFallback(Long id) {
    User user = new User();
    user.setId(0L);
    return user;
  }
}

  實體類:

package com.itmuch.cloud.entity;

import java.math.BigDecimal;

public class User {
  private Long id;

  private String username;

  private String name;

  private Short age;

  private BigDecimal balance;

  public Long getId() {
    return this.id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getUsername() {
    return this.username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Short getAge() {
    return this.age;
  }

  public void setAge(Short age) {
    this.age = age;
  }

  public BigDecimal getBalance() {
    return this.balance;
  }

  public void setBalance(BigDecimal balance) {
    this.balance = balance;
  }

}

  測試:

1 啓動eureka
2 啓動user微服務
3 啓動movie微服務
4 訪問http://localhost:8010/user/1,

結果以下

{"id":1,"username":"user1","name":"張三","age":20,"balance":100.00}

5 中止user微服務
6 再次訪問http://localhost:8010/user/1,

結果以下

{"id":0,"username":null,"name":null,"age":null,"balance":null}


說明當前微服務不可用,進入回退方法。

7.2.2.2. Ribbon的propagation

    pom.xml

<?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>

	<artifactId>microservice-consumer-movie-ribbon-with-hystrix-propagation</artifactId>
	<packaging>jar</packaging>

	<parent>
		<groupId>com.itmuch.cloud</groupId>
		<artifactId>microservice-spring-cloud</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-eureka</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-hystrix</artifactId>
		</dependency>
	</dependencies>
</project>

  配置文件:

spring:
  application:
    name: microservice-consumer-movie-ribbon-with-hystrix-propagation
server:
  port: 8010
eureka:
  client:
    healthcheck:
      enabled: true
    serviceUrl:
      defaultZone: http://user:password123@localhost:8761/eureka
  instance:
    prefer-ip-address: true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 5000

  啓動類:

package com.itmuch.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.circuitbreaker.EnableCircuitBreaker;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class ConsumerMovieRibbonApplication {

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

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

  業務類:

package com.itmuch.cloud.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

import com.itmuch.cloud.entity.User;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;

@RestController
public class MovieController {
  @Autowired
  private RestTemplate restTemplate;
//表示@HystrixCommand與findById方法會在同一個線程中調用
//若是不配合的話findById是一個線程,@HystrixCommand是一個隔離的線程至關於兩個線程
//正常狀況下不須要配置,等拋異常了在配置 @GetMapping("/movie/{id}") @HystrixCommand(fallbackMethod = "findByIdFallback", commandProperties = @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE")) public User findById(@PathVariable Long id) { return this.restTemplate.getForObject("http://microservice-provider-user/simple/" + id, User.class); } public User findByIdFallback(Long id) { User user = new User(); user.setId(0L); return user; } }

  實體類:

package com.itmuch.cloud.entity;

import java.math.BigDecimal;

public class User {
  private Long id;

  private String username;

  private String name;

  private Short age;

  private BigDecimal balance;

  public Long getId() {
    return this.id;
  }

  public void setId(Long id) {
    this.id = id;
  }

  public String getUsername() {
    return this.username;
  }

  public void setUsername(String username) {
    this.username = username;
  }

  public String getName() {
    return this.name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public Short getAge() {
    return this.age;
  }

  public void setAge(Short age) {
    this.age = age;
  }

  public BigDecimal getBalance() {
    return this.balance;
  }

  public void setBalance(BigDecimal balance) {
    this.balance = balance;
  }

}

 7.2.3. Hystrix斷路器的狀態監控與深刻理解

需導入以下jar:

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>

 訪問: http://localhost:8010/movie/1

    結果以下:{"id":1,"username":"user1","name":"張三","age":20,"balance":100.00}

    訪問: http://localhost:8010/health

    獲得以下結果:

Hystrix的狀態是UP,也就是一切正常,此時斷路器是關閉的。

 中止user微服務,
訪問:http://localhost:8010/user/1,
可得到以下結果:{"id":0,"username":null,"name":null,"age":null,"balance":null}
訪問: http://localhost:8010/health

咱們發現,儘管執行了回退邏輯,返回了默認用戶,但此時Hystrix的狀態依然是UP,這是由於咱們的失敗率還沒達到閾值(默認是5秒內20次失敗),

這裏再次強調,執行回退邏輯並不表明斷路器已經打開。請求失敗、超時、被拒絕以及斷路器打開時都會執行回退邏輯。

 
訪問: http://localhost:8010/health
 
能夠看到,Hystrix的狀態是CiRCUIT_OPEN,說明斷路器已經打開,不會再去請求微服務了。

 7.2.4. Hystrix線程隔離策略與傳播上下文

Hystrix的隔離策略有兩種:分別是線程隔離和信號量隔離。

  • THREAD(線程隔離):使用該方式,HystrixCommand將會在單獨的線程上執行,併發請求受線程池中的線程數量的限制。
  • SEMAPHONRE(信號量隔離):使用該方式,HystrixCommand將會在調用線程上執行,開銷相對較小,併發請求受到信號量個數的限制。

Hystrix中默認而且推薦使用線程隔離(THREAD),由於這種方式有一個除網絡超時之外的額外保護層。

        通常來講,只有當調用負載很是高時(例如每一個實例每秒調用數百次)才須要使用信號量隔離,由於這種場景下使用THREAD開銷會比較高。

       信號量隔離通常僅適用於非網絡調用的隔離。

 7.2.5. Feign使用Hystrix

  7.2.5.1. 爲Feign添加回退

  7.2.5.2. 經過Fallback Factory檢查回退緣由

  7.2.5.3. 爲Feign禁用Hystrix

7.3. Hystrix的監控

7.4. 使用Hystrix Dashboard可視化監控數據

7.5. 使用Turbine聚合監控數據

 7.5.1. Turbine簡介

 7.5.2. 使用Turbine監控多個微服務

 7.5.3. 使用消息中間件收集數據

 7.5.3.1. 安裝RabbitMQ

 7.5.3.2. 改造微服務

 7.5.3.3. 改造Turbine

相關文章
相關標籤/搜索