1. 程式人生 > >Spring Cloud Hystrix服務容錯 (學習總結)

Spring Cloud Hystrix服務容錯 (學習總結)

一、雪崩效應

“雪崩效應”: 因 '服務提供者' 的不可用導致 '服務呼叫者' 的不可用,並將不可用逐漸放大的過程

二、Hystrix介紹

Spring Cloud中服務之間的呼叫方式主要有兩種,一種是Ribbon+RestTemplate,一種是Feign宣告式服務呼叫,在實際專案中,為了服務高可用,一個服務通常會叢集部署,執行多個例項, 由於網路原因或者服務自身原因,被呼叫的服務並不能保證100%請求成功,如果這時候有大量的請求請求這個故障的服務,由於服務之間的依賴關係,故障會進行蔓延,這時候會導致呼叫服務自身也出現不可用的情況,使用Hystrix可以解決這個問題。當某個服務單元發生故障(類似用電器發生短路)之後,通過斷路器的故障監控(類似熔斷保險絲),向呼叫方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得執行緒因呼叫故障服務被長時間佔用不釋放,避免了故障在分散式系統中的蔓延。

三、準備工作:

本文同樣有三個工程,分別是:

eureka-server: 服務註冊中心,埠1111;

hystrix-service:服務提供者,埠2222和3333,需要啟動多個例項;

ribbon-hystrix: Ribbon服務容錯,埠4444

feign-hystrix: Feign服務容錯,埠5555

對於eureka-server以及hystrix-service的搭建本文不做詳細介紹,需要注意的是hystrix-service需要暴露一個介面/getInfo給外部呼叫:

/**
 * @Title: HystrixServiceController
 * @Description: 服務提供者
 * @Author WeiShiHuai
 * @Date 2018/9/11 9:31
 */
@RestController
public class HystrixServiceController {

    private static Logger logger = LoggerFactory.getLogger(HystrixServiceController.class);

    @Autowired
    private DiscoveryClient discoveryClient;

    @RequestMapping("/getInfo")
    public String getInfo(@RequestParam("name") String name) {
        ServiceInstance serviceInstance = discoveryClient.getLocalServiceInstance();
        String host = serviceInstance.getHost();
        Integer port = serviceInstance.getPort();
        String info = "hello, name = " + name + ", host = " + host + ", port = " + port;
        logger.info(info);
        return info;
    }

}

四、新建springcloud_ribbon_hystrix專案

首先講一下在Ribbon專案中使用Hystrix斷路器的方法:

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>

	<groupId>com.springcloud.wsh</groupId>
	<artifactId>springcloud_ribbon_hystrix</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springcloud_ribbon_hystrix</name>
	<description>Spring Cloud Hystrix Ribbon服務單元</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Camden.SR6</spring-cloud.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.cloud</groupId>
			<artifactId>spring-cloud-starter-hystrix</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-ribbon</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>

五、啟動類加上@EnableCircuitBreaker註解

該註解主要是開啟Hystrix斷路器功能

/**
 * @Description: 應用程式啟動類
 * @Author: WeiShiHuai
 * @Date: 2018/9/11 9:37
 * Spring Cloud中服務之間的呼叫方式主要有兩種,一種是Ribbon+RestTemplate,一種是Feign宣告式服務呼叫,在實際專案中,為了服務高可用,一個服務通常會叢集部署,執行多個例項,
 * 由於網路原因或者服務自身原因,被呼叫的服務並不能保證100%請求成功,如果這時候有大量的請求請求這個故障的服務,由於服務之間的依賴關係,故障會進行蔓延,這時候會導致呼叫服務自身也出現不可用的情況,使用Hystrix可以解決這個問題
 * 當某個服務單元發生故障(類似用電器發生短路)之後,通過斷路器的故障監控(類似熔斷保險絲),向呼叫方返回一個錯誤響應,而不是長時間的等待。這樣就不會使得執行緒因呼叫故障服務被長時間佔用不釋放,避免了故障在分散式系統中的蔓延。
 * <p>
 * “雪崩效應”: 因 '服務提供者' 的不可用導致 '服務呼叫者' 的不可用,並將不可用逐漸放大的過程
 */
@SpringBootApplication
@EnableDiscoveryClient
// @EnableHystrix 與 @EnableCircuitBreaker註解用於開啟Hystrix斷路器功能
//@EnableHystrix
@EnableCircuitBreaker
//@EnableHystrixDashboard註解開啟Hystrix儀表盤功能,監控請求情況
//通過Hystrix Dashboard可以直接看到各個Hystrix Command的請求響應時間,請求成功率等資料
@EnableHystrixDashboard
//@SpringCloudApplication = @SpringBootApplication + @EnableDiscoveryClient + @EnableCircuitBreaker
public class SpringcloudRibbonHystrixApplication {

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

    /**
     * 開啟Ribbon負載均衡能力,並注入spring容器
     */
    @Bean
    @LoadBalanced
    RestTemplate restTemplate() {
        return new RestTemplate();
    }

}

六、新建HystrixService

  • 改造原來的服務消費方式,在使用ribbon消費服務的函式上增加@HystrixCommand註解來指定回撥方法。
/**
 * @Title: HystrixService
 * @ProjectName springcloud_hystrix
 * @Description: 實現Hystrix斷路器
 * @Author WeiShiHuai
 * @Date 2018/9/11 9:52
 */
@Service
public class HystrixService {

    private static Logger logger = LoggerFactory.getLogger(HystrixService.class);

    @Autowired
    RestTemplate restTemplate;

    //@HystrixCommand註解主要對getInfo()開啟熔斷器的功能,並指定fallbackMethod熔斷方法(服務不可用時執行熔斷方法)
    @HystrixCommand(fallbackMethod = "getInfoErrorFallBack")
    public String getInfo(String name) {
        String info = restTemplate.getForObject("http://hystrix-service/getInfo?name={name}", String.class, name);
        logger.info(info);
        return info;
    }

    /**
     * Hystrix熔斷方法(即呼叫失敗回撥方法)
     *
     * @param name
     * @return
     */
    public String getInfoErrorFallBack(String name) {
        return "sorry, " + name + ", the hystrix service is not available! ";
    }

}

七、新建HystrixController

注入HystrixService,呼叫hystrixService.getInfo()介面進行斷路器測試:

/**
 * @Title: HystrixController
 * @ProjectName springcloud_hystrix
 * @Description: 測試
 * @Author WeiShiHuai
 * @Date 2018/9/11 9:48
 */
@RestController
public class HystrixController {

    private static Logger logger = LoggerFactory.getLogger(HystrixController.class);

    @Autowired
    private HystrixService hystrixService;

    @RequestMapping("/getInfo")
    public String getInfo(@RequestParam("name") String name) {
        return hystrixService.getInfo(name);
    }

}

八、啟動專案

依次啟動eureka-server埠1111、hystrix-service可以啟動兩個例項2222和3333,ribbon-hystrix埠4444,

介面成功呼叫,同時看後臺列印的日誌,也實現了服務的負載均衡呼叫

可以看到,當hystrix-service服務不可用時,我們訪問其中的介面,執行了我們制定的fallback熔斷方法。這樣在Ribbon中使用Hystrix斷路器功能已經實現。接下來看一下在Feign中使用Hystrix,因為Feign預設集成了Hystrix,因此不需要新增Hystrix的依賴。

九、新建springcloud_feign_hystrix工程

pom.xml只需要引入主要的Feign依賴:

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

	<groupId>com.sprincloud.wsh</groupId>
	<artifactId>springcloud_feign_hystrix</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>jar</packaging>

	<name>springcloud_feign_hystrix</name>
	<description>Spring Cloud Feign Hystrix</description>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>1.5.2.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
		<java.version>1.8</java.version>
		<spring-cloud.version>Camden.SR6</spring-cloud.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.cloud</groupId>
			<artifactId>spring-cloud-starter-feign</artifactId>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</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>

十、啟動類加上@EnableFeignClients註解

該註解主要是開啟Feign遠端服務呼叫的功能

/** 
 * @Description: 應用程式啟動類
 * @Author: WeiShiHuai  
 * @Date: 2018/9/11 10:23
 *
 * Feign預設集成了Hystrix,所以只需要加入Feign的依賴即可。
 * Feign是通過@FeignClient()中指定fallback來實現Hystrix斷路器功能的,當遠端服務呼叫失敗的時候就會執行這個回撥方法。
 *
*/
@SpringBootApplication
@EnableDiscoveryClient
//開啟Feign宣告式服務呼叫功能
@EnableFeignClients
public class SpringcloudFeignHystrixApplication {

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

新建配置檔案application.yml:

server:
  port: 5555
spring:
  application:
    name: feign-hystrix
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1111/eureka/

十一、新建Feign介面HystrixFeign

/**
 * @Title: HystrixFeign
 * @ProjectName springcloud_hystrix
 * @Description:
 * @Author WeiShiHuai
 * @Date 2018/9/11 10:25
 */
@FeignClient(value = "hystrix-service", fallback = HystrixFeignFallback.class)
public interface HystrixFeign {

    @RequestMapping("/getInfo")
    String getInfo(@RequestParam("name") String name);

}

在feign介面中,我們通過指定fallback來實現Hystrix服務容錯功能

新建HystrixFeignFallback回撥類,並且實現@FeignClient修飾的介面:

/**
 * @Title: HystrixFeignFallback
 * @ProjectName springcloud_feign_hystrix
 * @Description: FeignClient失敗回撥方法
 * @Author WeiShiHuai
 * @Date 2018/9/11 10:28
 * FeignClient失敗回撥方法必須實現使用@FeignClient標識的介面(implements HystrixFeign),實現其中的方法
 */
@Component
public class HystrixFeignFallback implements HystrixFeign {

    /**
     * 由於某種原因使得服務呼叫不成功時會執行該回調方法
     *
     * @param name
     * @return
     */
    @Override
    public String getInfo(String name) {
        return "sorry " + name + ", feign client error";
    }

}

十二、新建FeignHystrixController

暴露一個getInfo介面給外部呼叫,如下:

/**
 * @Title: HystrixFeignController
 * @ProjectName springcloud_feign_hystrix
 * @Description: 測試Feign
 * @Author WeiShiHuai
 * @Date 2018/9/11 10:28
 * 注入FeignClient,呼叫feignClient的方法實現遠端方法呼叫
 */
@RestController
public class HystrixFeignController {

    private static Logger logger = LoggerFactory.getLogger(HystrixFeignController.class);

    @Autowired
    private HystrixFeign hystrixFeign;

    /**
     * 使用http://localhost:5555/getInfo?name=xxx訪問,實際上會通過FeignClient呼叫服務hystrix-service提供的getInfo介面
     *
     * @param name
     * @return
     */
    @GetMapping("/getInfo")
    public String getInfo(@RequestParam("name") String name) {
        String info = hystrixFeign.getInfo(name);
        logger.info(info);
        return info;
    }

}

接下來,同樣啟動eureka-server、以及hystrix-service兩個例項,還有啟動feign-hystrix,訪問http://localhost:5555/getInfo?name=weixiaohuai,結果如下:

介面已經成功呼叫,通過檢視後臺日誌,同樣feign也實現對服務的負載均衡呼叫

由此證明Hystrix斷路器起作用了。

十三、總結

以下內容,摘自網上總結:

雪崩效應的原因:

1) 服務提供者不可用
  a.硬體故障
  b.程式Bug
  c.使用者大量請求:在秒殺和大促開始前,如果準備不充分,使用者發起大量請求造成服務提供者的不可用

2) 重試加大流量
  a.使用者重試:使用者由於忍受不了介面上長時間的等待,而不斷重新整理頁面甚至提交表單
  b.程式碼邏輯重試:服務呼叫端的會存在大量服務異常後的重試邏輯

3) 服務呼叫者不可用
  a.同步等待造成的資源耗盡:使用 同步呼叫 時, 會產生大量的等待執行緒佔用系統資源. 一旦執行緒資源被耗盡,服務呼叫者提供的服務也將處於不可用狀態, 造成服務雪崩效應產生


雪崩效應的解決措施:
1) 流量控制
  a.閘道器限流
  因為Nginx的高效能, 目前一線網際網路公司大量採用Nginx+Lua的閘道器進行流量控制, 由此而來的OpenResty也越來越熱門.
  b.使用者互動限流
  具體措施:
    a21. 採用載入動畫,提高使用者的忍耐等待時間.
    a22. 提交按鈕新增強制等待時間機制.
  c.關閉重試

2) 改進快取模式
  a.快取預載入
  b.同步改為非同步重新整理

3) 服務自動擴容
  a.AWS的auto scaling

4) 服務呼叫者降級服務
  a.資源隔離:主要是對呼叫服務的執行緒池進行隔離.
  b.對依賴服務進行分類
  依賴服務分為: 強依賴和若依賴. 強依賴服務不可用會導致當前業務中止,而弱依賴服務的不可用不會導致當前業務的中止.
  c.不可用服務的呼叫快速失敗

一般通過 超時機制, 熔斷器 和熔斷後的 降級方法 來實現