天天看點

SpringCloud - (四)斷路器(Hystrix)

在微服務架構中,根據業務來拆分成一個個的服務,服務與服務之間可以互相調用(RPC),在Spring Cloud可以用RestTemplate+Ribbon和Feign來調用。為了保證其高可用,單個服務通常會叢集部署。由于網絡原因或者自身的原因,服務并不能保證100%可用,如果單個服務出現問題,調用這個服務就會出現線程阻塞,此時若有大量的請求湧入,Servlet容器的線程資源會被消耗完畢,導緻服務癱瘓。服務與服務之間的依賴性,故障會傳播,會對整個微服務系統造成災難性的嚴重後果,這就是服務故障的“雪崩”效應。

為了解決這個問題,業界提出了斷路器模型。

斷路器介紹

Netflix開源了Hystrix元件,實作了斷路器模式,SpringCloud對這一元件進行了整合。 在微服務架構中,一個請求需要調用多個服務是非常常見的,如下圖:

SpringCloud - (四)斷路器(Hystrix)

較底層的服務如果出現故障,會導緻連鎖故障。當對特定的服務的調用的不可用達到一個閥值(Hystric 是5秒20次) 斷路器将會被打開。

SpringCloud - (四)斷路器(Hystrix)

斷路打開後,可用避免連鎖故障,fallback方法可以直接傳回一個固定值。

在Ribbon中使用斷路器

改造之前的Ahut-Service-Ribbon項目

SpringCloud - (四)斷路器(Hystrix)

pom.xml檔案中添加hystrix的依賴

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>      

完整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.ahut</groupId>
    <artifactId>service-ribbon</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>service-ribbon</name>
    <description>service ribbon</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.12.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>Edgware.SR3</spring-cloud.version>
    </properties>

    <dependencies>

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

        <!-- eureka discovery -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>

        <!-- 添加hystrix依賴 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>

        <!-- ribbon -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-ribbon</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>      

項目啟動類添加@EnableHystrix注解開啟Hystrix

package com.ahut;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @author cheng
 * @className: ServiceRibbonApplication
 * @description: service ribbon
 * @dateTime
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
public class ServiceRibbonApplication

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

    /**
     * @description: 注入restTemplate, 并且開啟負載均衡
     * @author cheng
     * @dateTime
    @Bean
    @LoadBalanced
    public RestTemplate createRestTemplate() {
        return new      

改造HelloServiceImpl類,在showPort方法上加上@HystrixCommand注解。該注解對該方法建立了熔斷器的功能,并指定了fallbackMethod熔斷方法,熔斷方法直接傳回了一個字元串,代碼如下:

package com.ahut.serviceImpl;

import com.ahut.service.HelloService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

/**
 * @author cheng
 * @className: HelloServiceImpl
 * @description:
 * @dateTime
@Service
public class HelloServiceImpl implements HelloService

    @Autowired
    private RestTemplate restTemplate;

    /**
     * @description: 展示伺服器端口
     * @author cheng
     * @dateTime
    @HystrixCommand(fallbackMethod = "handleError")
    @Override
    public String showPort() {
        return restTemplate.getForObject("http://AHUT-EUREKA-CLIENT/v1/showPort", String.class);
    }

    /**
     * @description: 斷路器方法
     * @author cheng
     * @dateTime
    public String handleError() {
        return "I am so sorry,Server has error";
    }

}      

啟動項目:

  • Ahut-Eureka-Server(注冊中心)
  • Ahut-Eureka-Client-One(AHUT-EUREKA-CLIENT服務一)
  • Ahut-Service-Ribbon(AHUT-SERVICE-RIBBON服務消費者)

通路eureka:

SpringCloud - (四)斷路器(Hystrix)

通路Ahut-Service-Ribbon ​​http://localhost:8940/v1/hello​​

SpringCloud - (四)斷路器(Hystrix)

此時,關閉Ahut-Eureka-Client-One服務,再次通路Ahut-Service-Ribbon ​​http://localhost:8940/v1/hello​​

SpringCloud - (四)斷路器(Hystrix)

這就說明當 Ahut-Eureka-Client-One 工程不可用的時候,Ahut-Service-Ribbon調用 Ahut-Eureka-Client-One的API接口時,會執行快速失敗,直接傳回一組字元串,而不是等待響應逾時,這很好的控制了容器的線程阻塞。

此時的項目架構

SpringCloud - (四)斷路器(Hystrix)

在Feign中使用斷路器

Feign是自帶斷路器的,在D版本的Spring Cloud中,它沒有預設打開。需要在配置檔案中配置打開它。

改造之前的Ahut-Service-Feign項目

在application.properties配置檔案中打開hystrix配置:

# 配置啟動斷路器
feign.hystrix.enabled=true      

在FeignClient的HelloService接口的注解中加上fallback的指定類就行了:

package com.ahut.service;

import com.ahut.hystrix.HelloServiceHystrix;
import org.springframework.cloud.netflix.feign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author cheng
 * @interfaceName: HelloService
 * @description:
 * @dateTime
@FeignClient(value = "AHUT-EUREKA-CLIENT", fallback = HelloServiceHystrix.class)
public interface HelloService

    @RequestMapping(value = "/v1/showPort")
    String showPort();

    /**
     * @description: 示範帶參數
     * @author cheng
     * @dateTime
    //@GetMapping(value = "/demo")
    //void demo(@RequestParam(value = "name") String name);      

HelloServiceHystrix需要實作HelloService 接口,并注入到Ioc容器中,代碼如下:

package com.ahut.hystrix;

import com.ahut.service.HelloService;
import org.springframework.stereotype.Component;

/**
 * @author cheng
 * @className: HelloServiceHystrix
 * @description: 斷路器
 * @dateTime
@Component
public class HelloServiceHystrix implements HelloService

    /**
     * @description: 斷路器方法
     * @author cheng
     * @dateTime
    @Override
    public String showPort() {
        return "I am so sorry,Server has error";
    }
}      

啟動項目:

  • Ahut-Eureka-Server(注冊中心)
  • Ahut-Eureka-Client-One(AHUT-EUREKA-CLIENT服務一)
  • Ahut-Service-Feign(AHUT-SERVICE-FEIGN服務消費者)

通路eureka

SpringCloud - (四)斷路器(Hystrix)

通路Ahut-Service-Feign項目 ​​http://localhost:8930/v1/hello​​:

SpringCloud - (四)斷路器(Hystrix)

此時關閉Ahut-Eureka-Client-One項目,再次通路Ahut-Service-Feign項目 ​​http://localhost:8930/v1/hello​​:

SpringCloud - (四)斷路器(Hystrix)

斷路器起作用

此時的項目架構

SpringCloud - (四)斷路器(Hystrix)

Hystrix Dashboard (斷路器:Hystrix 儀表盤)

改造Ahut-Service-Ribbon項目,Ahut-Service-Feign項目的改造和這個一樣

添加依賴:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix-dashboard</artifactId>
</dependency>

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

完整的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.ahut</groupId>
    <artifactId>service-ribbon</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>service-ribbon</name>
    <description>service ribbon</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.12.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>Edgware.SR3</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-hystrix-dashboard</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-ribbon</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>      

在主程式啟動類中加入@EnableHystrixDashboard注解,開啟hystrixDashboard:

package com.ahut;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.cloud.netflix.hystrix.EnableHystrix;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * @author cheng
 * @className: ServiceRibbonApplication
 * @description: service ribbon
 * @dateTime
@SpringBootApplication
@EnableDiscoveryClient
@EnableHystrix
@EnableHystrixDashboard
public class ServiceRibbonApplication

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

    /**
     * @description: 注入restTemplate, 并且開啟負載均衡
     * @author cheng
     * @dateTime
    @Bean
    @LoadBalanced
    public RestTemplate createRestTemplate() {
        return new      

啟動項目:

  • Ahut-Eureka-Server(注冊中心)
  • Ahut-Eureka-Client-One(AHUT-EUREKA-CLIENT服務一)
  • Ahut-Service-Ribbon(AHUT-SERVICE-RIBBON服務消費者)

通路eureka:

SpringCloud - (四)斷路器(Hystrix)

通路hystrix儀表盤 ​​http://localhost:8940/hystrix​​

SpringCloud - (四)斷路器(Hystrix)

修改以下部分:

  • 複制浏覽器的位址欄,粘貼到輸入框,并且加上.stream
  • 自定義一個Title

    修改完後,點選monitor stream,進入下一個頁面

SpringCloud - (四)斷路器(Hystrix)

中間通路一下Ahut-Service-Ribbon項目 ​​http://localhost:8940/v1/hello​​

SpringCloud - (四)斷路器(Hystrix)