Hystrix 断路器

什么是Hystrix

Hystrix是一个用于处理分布式系统的 延迟 和 容错 的开源库,在分布式系统里,许多依赖不可避免的会调用失败,
比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下, 不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性,稳定性。

雪崩效应

复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

  多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的 “扇出” 。
如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”. 

  例如曾经发生的一个案例,某个电子商务网站在某个星期五发生过载,过多的并法请求,
导致用户支付请求延迟很久没有响应,在等待很长时间后最终失败,支付失败又导致用户重新刷新页面再次尝试支付,
进一步增加了服务器的负载,最终整个系统崩溃了。

如何容错


想要避免雪崩效应,必须有强大的容错机制,该容错机制需要实现以下两点

- 为网络请求设置超时机制

  即便是没有请求到数据 也强制结束  避免请求堆积 

- 使用断路器模式

  正常情况下断路器关闭可正常请求服务,当一定时间内请求达到了一定的阈(yu)值(请求失败率达到百分之50或者100次/分钟),断路器就会打开此时不会再去请求依赖服务。

  断路器打开一段时间之后,会自动进入半开的状态。此时断路器允许一个请求请求服务实例,如果该服务可以调用成功,则关闭断路器,否则继续保持打开状态。

通用方式整合Hystrix(Ribbon)

  • 导入依赖

<?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 https://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.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>SpringCloud-OrdersForGoods</groupId>
    <artifactId>ordersforgoods</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>ordersforgoods</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!--hystrix 熔断器-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

        <!--ribbon-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
        </dependency>
        <!--        引入SpringCloud Eureka client的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.0.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </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>
    </dependencies>
    <!--    约束整个项目的SpringCloud版本-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>


  • 入口类
package springcloudordersforgoods;

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.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
/**
 * 声明当前客户端为可被发现
 * 不仅可以注册到Eureka Server  也可以注册到其他注册中心
 */
@EnableDiscoveryClient
/**
 * 开启熔断器
 */
@EnableHystrix
public class OrdersforgoodsApplication {

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

    /**
     * @LoadBalanced 声明当前项目可以使用ribbon负载均衡
     * @return
     */
    @Bean
    @LoadBalanced
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}
  • 配置文件
server:
  port: 8002

# 指定当前服务名称  这个名称会注册到注册中心
spring:
  application:
    name: ordersforgoods

#  指定 服务注册中心的地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8888/eureka,http://localhost:8889/eureka
  • 服务调用
package springcloudordersforgoods.Conterller;


import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

@RestController
public class Conterllers {
    @Value("${server.port}")//获取配置文件数据
    private String post;

    @Autowired
    private RestTemplate restTemplate;

    /**
     * 当前方法设置断路器设置
     * @HystrixCommand(fallbackMethod = "gettHystrixCommand")
     * 在当前方法上开启断路器的设置
     * fallbackMethod 回调的方法,如果方法调用失败则执行回调方法,代替当前方法
     * @param id
     * @return
     */

    @HystrixCommand(fallbackMethod = "gettHystrixCommand")// 配置替代方案方法

    @GetMapping("orderidpost")
    public String getproductbyid(Integer id){
        /**
         * 调用商品服务一起返回
         */
        /**
         * get 开头都为get请求
         *
         * getForObject (请求地址,
         *               响应参数得类型,
         *               请求参数(可不写))
         *      返回值为请求接口响应的数据
         *
         * getForEntity(请求地址,
         *              响应参数得类型,
         *              请求参数(可不写))
         *      返回结果为响应体对象
         *      响应体对象  不仅有相应数据  还有响应状态码
         * 相对于getForObject 信息更全面
         */
        // 服务调用
        /**
         * 通过RestTemplate 将服务调用  IP+端口号  是写死的
         * 这样不能负载均衡
         *
         * 如 负载均衡不能写成 IP+端口号  需要写成被调用的服务名称
         */
        String url = "http://SpringBoot-Cloud/getidpost?id="+id;
        String forObject = restTemplate.getForObject(url, String.class);


        return "查询成功: 商品信息为:"+forObject;
    }

    // 替代方案方法,如果调用服务失败, 断路器会执行替代方案方法
    public String getHystrixCommand(){
        return "服务器忙,请稍后重试.";
    }
}

Feign的方式整合Hystrix

  • 导入依赖
<?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 https://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.0.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>EurekaClient</groupId>
    <artifactId>eurekaclient</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>eurekaclient</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <!-- feign服务调用 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.0.3.RELEASE</version>
        </dependency>

        <!--        引入SpringCloud Eureka client的依赖-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            <version>2.0.3.RELEASE</version>
        </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>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
        <!-- client端,开启健康检查(需要spring-boot-starter-actuator依赖)-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    </dependencies>

    <!--    约束整个项目的SpringCloud版本-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Finchley.SR2</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>
  • 配置文件
server:
  port: 8001

# 指定当前服务名称  这个名称会注册到注册中心
spring:
  application:
    name: eureka-client

#  指定 服务注册中心的地址
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8888/eureka,http://localhost:8889/eureka

# Feign日志服务配置
logging:
  level:
    # 全局日志
    # root: debug
    # 配置服务调用类的所在的包名
    eurekaclient.eurekaclient.Service: debug
    eurekaclient.eurekaclient.Conterller: debug

# Feign是自带断路器的,不需要导入额外依赖,需要打开配置
feign:
  hystrix:
    enabled: true
  • conterller
package eurekaclient.eurekaclient.Conterller;

import eurekaclient.eurekaclient.Entity.Entity;
import eurekaclient.eurekaclient.Service.userfeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;

@RestController
public class Clientterller {


    //@Qualifier("SpringBoot-Cloud")
    @Autowired
    private userfeign userfeign; // 自动注入接口对象

    @GetMapping("userConterller")
    public String userConterller(Integer id){
        /**
         * 和使用其他接口一样, 使用Feign接口中的服务方法
         *
         * 服务调用细节,如 请求的发送 负载均衡 都被封装了
         */

        return "用户服务调用成功: \t"+userfeign.getproductbyid(id);
    }

    @GetMapping("postuserConterller")
    public String postuserConterller(Entity entity){
        /**
         * 和使用其他接口一样, 使用Feign接口中的服务方法
         *
         * 服务调用细节,如 请求的发送 负载均衡 都被封装了
         */
        HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
        objectObjectHashMap.put("msg",entity);
        return "用户服务调用成功: \t"+userfeign.postproductbyid(objectObjectHashMap);
    }
}
  • 服务调用接口
package eurekaclient.eurekaclient.Service;


import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * FeignClient声明当前接口为Feign的客户端
 *   当前接口就具备了发送HTTP请求的能力
 *
 * FeignClient(value = "SpringBoot-Cloud")  vlaue = 为被调用服务的名字
 */
@FeignClient(value = "SpringBoot-Cloud", fallback = HystrixService.class)
/**
 * 在调用时出错,就根据fallback的配置执行替代方案
 */
public interface userfeign {
    /***
     * 定义服务调用的方法
     *
     * 当前方法对应被调用的方法-->>需添加@GetMapping注解  参数为被调用方法的路径(全路径)
     * 且方法的形参和返回值类型必须和被调用方法一致
     *
     * 注意! 因为请求方式为Rest风格 所以参数前必须加 @RequestParam注解
     * @param id
     * @return
     */
    //@GetMapping("getidpost")
    @GetMapping(path = "getidpost")
    String getproductbyid(@RequestParam Integer id);


    @PostMapping(path = "postgetidpost")
    /**
     * 如为Get请求则需要添加@RequestParam注解
     * Post请求 添加 @RequestBody
     */
    String postproductbyid(@RequestBody Map map);
}

  • 替代方案
package eurekaclient.eurekaclient.Service;

import org.springframework.stereotype.Service;

import java.util.Map;

/**
 * 替代方案
 */
@Service
//这里需要实现服务调用接口,重写其方法
public class HystrixService implements userfeign {
    @Override
    public String getproductbyid(Integer id) {
        return "服务调用失败,参数id为: "+id;
    }

    @Override
    public String postproductbyid(Map map) {
        return "服务调用失败,参数id为: "+map;
    }
}
posted @ 2020-12-25 21:44  花红  阅读(128)  评论(0编辑  收藏  举报