SpringCloud系列—Spring Cloud Hystrix服务保护机制

老老老JR老北
发布于 2022-7-25 17:32
浏览
0收藏

作者 | 宇木木兮

来源 |今日头条

学习目标

  1. Hystrix到底是什么?它的核心功能是什么

Hystrix的应用案例
第1章 Hystrix简介

git上的介绍:
https://github.com/Netflix/Hystrix

Hystrix目前不再进行开发了,从github的更新来看,已经好久没有更新了,Hystrix推荐使用Resilience4j来替代,但是,国内使用Resilience4j的大厂比较少,更多的大厂使用的是Spring Cloud Alibaba Sentinel来实现熔断与限流,又或者在现有的开源框架上进行包装,自己实现功能。

虽然Hystrix不再更新,但是Hystrix的设计理念值得学习,也为后面学习Spring Cloud Alibaba Sentinel做好铺垫。

1.1 这玩意出现的背景
随着架构的演进,从单体架构演进到了微服务架构,那么就避免不了微服务之间的相互调用,而在调用过程中,就肯定会出现调用失败的情况,特别是在一次调用过程中牵扯到多个服务之间的通信的话,这种失败的概率就更大了,例如现在要完成一个业务需求,而这个需求会牵扯到A系统调用B系统,B系统调用C系统,C系统又调用D等等。

在这么一个调用过程中,实际上必须得每一步都成功,这个流程才算走成功了,一旦某一步出问题,整个请求就算失败了。比如在C系统中,处理请求比较慢,响应时间比较长,这个时候C系统就容易导致超时,当我并发比较大的时候,一下子来了一堆的请求进来,这个时候,C系统一旦超时,完犊子了,后面所有的请求全卡在这一步了。这样就会造成大面积的请求失败,系统崩溃。

那为了解决这个问题,有些大佬也发挥脑洞,想出了很多解决方案,比如说在C系统调用之前增加消息队列,让C系统异步去执行操作,或者增加机器。这些方案不能不行,但是没有从根本上解决当大量请求进来怎么处理的问题,这个时候保险丝(Hystrix)出现了。

1.2 Hystrix是什么
git上面的介绍是:Hystrix是一个延迟和容错库,旨在隔离远程系统、服务和第三方库的访问点,阻止级联故障,并在故障不可避免的复杂分布式系统中实现恢复能力。

从上面的介绍中,可以明确一点,特么地说的啥,完全没明白。OK,老规矩,用最直白的语言翻译一下大佬们装*的语言。

Hystrix就是一个库,这个库呢是开源的,不收费了,当你项目中用到这个库的时候,它可以保护你的后台系统,如果你的系统出故障了,它就会阻止你的请求进来了,然后给你返回一个友好的东西,这个友好的东西可以自己设计,比如一个页面什么的。这个时候就保护了后台系统了,而返回的的逻辑自己也可控了,这样也可以保证在一个系统出现问题的情况下,不会导致整体服务失败,避免了级联故障,从而提高分布式系统的弹性。

在网上很多文章里面都将它称作为“断路器”,当某个服务发生故障的时候之后,通过断路器的故障监控(类似于保险丝熔断),向调用方返回一个符合预期的、可以处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

1.3 核心功能
其实上面对Hystrix的介绍中也牵扯到了它的功能,无非就是做熔断的,保护后台的,那这里总结一下他的功能:

  1. 服务熔断
  2. 服务降级
  3. 服务隔离
    下面给大家解释一下什么是熔断和降级。

1.3.1 服务熔断
break
对于熔断这个概念,我们并不陌生,比如股市熔断机制,当股指波幅达到规定的熔断点时,交易所为控制风险采取的暂停交易措施。亦或者是电流熔断,当通过的电流超出导线所能承载的最大电流时,会触发保险丝熔断,从而避免因为电流过大造成火灾。

在这些场景中,可以发现一个共同的特点,就是熔断在这些场景中都是充当保护机制,避免引发更大的问题。那么在整个微服务架构中,也同样会存在类似的问题。

如下图所示,在微服务架构中,一个请求过来,可能会经过多个服务进行处理,导致整个处理链路会比较长。而在整条调用链路中,可能会因为某个节点因为网络故障导致响应时间比较长,而这个节点的阻塞将会影响这条链路的结果返回。SpringCloud系列—Spring Cloud Hystrix服务保护机制-鸿蒙开发者社区当访问量比较高的请求下,一个后端依赖节点的延迟响应可能导致所有服务器上的所有资源在数秒内饱和。一旦出现这个问题,会导致系统资源被快速消耗,从而导致服务宕机等问题,最坏的情况会导致服务雪崩。如下图:QPS为50的依赖 I 出现不可用,但是其他依赖仍然可用。SpringCloud系列—Spring Cloud Hystrix服务保护机制-鸿蒙开发者社区当依赖I 阻塞时,大多数服务器的线程池就出现阻塞(BLOCK),影响整个线上服务的稳定性.如下图:SpringCloud系列—Spring Cloud Hystrix服务保护机制-鸿蒙开发者社区所以在软件系统中,为了防止这种问题的产生,也引入了熔断的概念。所以,熔断的意义是:如果某个目标服务调用比较慢或者大量的超时,这个时候如果触发熔断机制,则可以保证后续的请求不会继续发送到目标服务上,而是直接返回降级的逻辑并且快速释放资源。如果目标服务的情况恢复了,那么熔断机制又会动态进行关闭。

1.3.2 服务降级

fallback

简单来说,降级的意思就是:如果主方案行不通,改用备用方案。举个例子,如果在分布式架构中,A服务调用B服务的时候,由于B服务宕机了,导致请求失败,那这个时候我们有几种方式来处理,第一种就是返回一个“系统繁忙”的信息给到用户,这种就是属于降级; 另一种就是发起重试,这种就是容错。

降级实际上有很多的方案

  • 如果服务调用异常,则返回一个备用的数据,这儿就是降级,比如在电商系统中,广告推送服务,如果广告推送信息查询出现异常,可以直接返回一个默认的广告数据进行展示。这种叫被动降级
  • 比如双11 订单评论和收藏等功能在这一天暂停 把这些资源分给其它关键服务 比如下单。说白了就是把一些无关紧要的服务卡掉,腾出资源保证核心功能可用,这种叫主动降级
    还有一些自动触发降级的策略,比如熔断触发降级、限流促发降级、访问超时触发降级等。

由于服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩效应”
1.3.3 服务隔离
服务隔离,也叫舱壁模式。怎么理解舱壁模式呢?看下图

造船行业有一个专业术语叫做「舱壁隔离」,利用舱壁将不同的船舱隔离起来,如果某一个船舱进了水,那么就可以立即封闭舱门,形成舱壁隔离,只损失那一个船舱,其他船舱不受影响,整个船只还是可以正常航行。

在微服务架构中,这种设计可以称为服务隔离。简单来说,舱壁模式通过隔离每个工作负载或者服务的关键资源,比如线程池、内存、cpu等,来防止因为一个服务引发的级联故障带来的服务雪崩问题。通过服务隔离,可以将出现故障的点隔离在一个比较小的范围内,避免影响扩大。

举个例子,比如对于某一个资源的远程调用请求分配到同一个线程池中,这个线程池就相当于这个船只中的一个个舱室,同样我们可以针对不同资源的远程访问分配不同的线程池,一旦某个服务出现故障造成请求堆积时,唯一的影响是线程池处理不过来导致响应过慢,最终因为超时而触发熔断降级。但是这个故障只会被隔离在这一个线程池中,不会影响其他资源的访问,从而起到了很好的隔离效果。这就是所谓的线程池隔离。

在Hystrix中,提供了两种隔离方式

  1. 线程池隔离
  2. 信号量隔离
    1.3.3.1 线程池隔离
    在下面第一幅图中,是没有做隔离的情况下,所有的命令都是共享同一个线程池来处理这些请求,这个时候一旦某个服务节点出现故障,就会导致线程池的工作线程快速被消耗,使得后续的请求全部被阻塞,造成大规模的影响。

而在下面第二幅图所示中,针对不同的资源实现线程池的隔离,在这种模式下,一旦某个远程资源访问出现异常,只会影响到这个资源对应的线程池范围,从而很好的实现了隔离的效果。SpringCloud系列—Spring Cloud Hystrix服务保护机制-鸿蒙开发者社区SpringCloud系列—Spring Cloud Hystrix服务保护机制-鸿蒙开发者社区

默认情况下,Hystrix使用的是线程池隔离

线程池隔离,在绝大部分场景中都适合使用,比如对于依赖外部第三方服务需要网络通信的接口,或者需要对目标服务做超时控制等场景。基于线程池隔离可以避免所有业务被同一个服务阻塞造成较大影响。

1.3.3.2 信号量隔离
信号量隔离,相当于一个计数器限流的概念。

如下图所示,假设我们针对A服务设置信号量大小为10,那么说明它同时只允许有10个tomcat线程来访问A服务,其他的请求都会被拒绝。

通过这种方式实现资源隔离和限流保护的作用。SpringCloud系列—Spring Cloud Hystrix服务保护机制-鸿蒙开发者社区信号量隔离,适合非外部依赖的访问,但是在业务逻辑较为复杂的场景中使用,信号量相当于实现了一个普通的限流场景。
1.3.3.3 对比
线程池隔离的优点:

  • 使用线程池隔离可以完全隔离依赖的服务,请求线程可以快速放回。
  • 当线程池出现问题时,线程池是完全隔离状态的,是独立的,不会影响到其他服务的正常执行。
  • 当崩溃的服务恢复时,线程池可以快速清理并恢复,不需要相对漫长的恢复等待。
  • 独立的线程池也提供了并发处理能力。
    线程池隔离的缺点:

线程池隔离机制,会导致服务硬件计算开销加大(CPU计算、调度等),每个命令的执行都涉及到排队、调度、上下文切换等,这些命令都是在一个单独的线程上运行的
1.3.3.4 线程池隔离的配置方式

hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          strategy: THREAD
          thread:
            timeoutInMilliseconds: 3000
    OrderServiceClient#orderLists(int): ## 指定服务#方法,如果有参数,需要增加参数类型
      execution:
        isolation:
          strategy: SEMAPHORE
          semaphore:
            maxConcurrentRequests: 10
    OrderServiceClient#orderLists(int):
      execution:
        isolation:
          strategy: THREAD
  threadpool:
    order: ## 针对指定服务设置线程池隔离的大小
      coreSize: 2
      maxQueueSize: 1000
      queueSizeRejectionThreshold: 800
ribbon:
  ReadTimeout: 10000
  ConnectTimeout: 10000​
  • 其中,threadpoll.(order-service/default),表示针对全局配置或者针对指定的服务配置线程池大小。
  • hystrix.command.(service-id/default),表示针对指定的服务配置相关属性或者配置全局的属性。
  • 熔断的配置的方式如下,同样,default表示全局配置,其中default可以通过commandKey来设置,但是在OpenFeign中,默认是采用类名#方法名()
  • hystrix提供了queueSizeRejectionThreshold属性(hystrix.threadpool.default.queueSizeRejectionThreshold)来动态控制线程池队列的上限。
    1.3.3.5 基于信号量隔离配置实战
    在User这个项目中,增加如下配置。
hystrix.command.default.execution.timeout.enabled=true
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=4000
hystrix.command.OrderServiceClient#orderLists(int).execution.isolation.strategy=SEMAPHORE
hystrix.command.OrderServiceClient#orderLists(int).execution.isolation.semaphore.maxConcurrentRequests=1

访问:
http://localhost:8080/get/2, 由于这个接口默认会睡眠4s,所以我们连续访问两次的情况下,会触发熔断限流。

第2章 Hystrix基本应用
Hystrix使用有两种方式:1.基于注解的方式;2.基于OpenFeign的方式
2.1 注解使用
建立两个项目,一个用户系统,一个订单系统,然后模拟请求链,通过前端页面发送请求到用户系统,用户系统再通过OpenFeign去请求订单系统。

2.1.1 父项目
1.创建Maven-quickstart的父项目hystrix-eclipse-demo

2.修改pom文件

<?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.example</groupId>
  <artifactId>hystrix-eclipse-demo</artifactId>
  <packaging>pom</packaging>
  <version>1.0-SNAPSHOT</version>
  <modules>
    <module>hystrix-eclipse-api</module>
    <module>hystrix-eclipse-user</module>
    <module>hystrix-eclipse-order</module>
  </modules>

  <name>hystrix-eclipse-demo</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring-boot.version>2.3.2.RELEASE</spring-boot.version>
    <spring-cloud.version>Hoxton.SR9</spring-cloud.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <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>
      <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-dependencies</artifactId>
        <version>${spring-boot.version}</version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
    </dependencies>
  </dependencyManagement>

</project>

2.1.2 api项目
1.在父项目中再创建一个新的module,hystrix-eclipse-api

2.配置pom

<?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">
    <parent>
        <artifactId>hystrix-eclipse-demo</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>hystrix-eclipse-api</artifactId>

    <name>hystrix-eclipse-api</name>
    <!-- FIXME change it to the project's website -->
    <url>http://www.example.com</url>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>

    <build>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
            <plugins>
                <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
                <plugin>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.1.0</version>
                </plugin>
                <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <version>3.8.0</version>
                </plugin>
                <plugin>
                    <artifactId>maven-surefire-plugin</artifactId>
                    <version>2.22.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-jar-plugin</artifactId>
                    <version>3.0.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-install-plugin</artifactId>
                    <version>2.5.2</version>
                </plugin>
                <plugin>
                    <artifactId>maven-deploy-plugin</artifactId>
                    <version>2.8.2</version>
                </plugin>
                <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
                <plugin>
                    <artifactId>maven-site-plugin</artifactId>
                    <version>3.7.1</version>
                </plugin>
                <plugin>
                    <artifactId>maven-project-info-reports-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>
</project>

3.创建接口

public interface OrderServiceDemo01 {
    @GetMapping("/orders")
    String orderLists(@RequestParam(value = "num") int num);
}

4.定义FeignClient

@FeignClient(name = "order")
public interface OrderServiceClient extends OrderServiceDemo01 {
}

2.1.3 服务提供端
1.在父项目下创建子模块hystrix-eclipse-order

2.配置pom

<?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>
        <artifactId>hystrix-eclipse-demo</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>hystrix-eclipse-order</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hystrix-eclipse-order</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <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-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>hystrix-eclipse-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>
</project>

3.实现api的接口

@RestController
public class OrderServiceImpl implements OrderServiceDemo01 {
    @Value("${server.port}")
    private int port;
    @Override
    public String orderLists(int num) {
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return port+":返回订单信息——"+num;
    }
}

4.启动类

@SpringBootApplication
@EnableDiscoveryClient //服务提供端,所以要注册到Eureka
public class HystrixEclipseOrderApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixEclipseOrderApplication.class, args);
    }
}

5.配置文件

spring.application.name=order
server.port=8099
#Eureka注册地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
eureka.instance.hostname=127.0.0.1
eureka.instance.instance-id=${eureka.instance.hostname}:${server.port}

2.1.4 服务消费端
1.在父项目下创建子模块hystrix-eclipse-user

2.配置pom

<?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>
        <artifactId>hystrix-eclipse-demo</artifactId>
        <groupId>com.example</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>hystrix-eclipse-user</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>hystrix-eclipse-user</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <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-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>hystrix-eclipse-api</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <!-- Hystrix核心包 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
    </dependencies>
</project>

3.controller

@RestController
public class UserController {
    @Autowired
    OrderServiceClient orderServiceClient;
    @HystrixCommand(commandProperties = {
        	//最小请求数
            @HystrixProperty(name="circuitBreaker.requestVolumeThreshold",value = "10"),
        	//熔断5s
            @HystrixProperty(name="circuitBreaker.sleepWindowInMilliseconds",value="5000"),
        	//50s内,最少请求10次。弱百分比超过50则触发熔断
            @HystrixProperty(name="circuitBreaker.errorThresholdPercentage",value="50"),
    },fallbackMethod = "fallback")//熔断机制
    @GetMapping("/get/{num}")
    public String get(@PathVariable("num") int num){
        if(num%2==0){
            return "正常访问";
        }
        return orderServiceClient.orderLists(num);
    }
    public String fallback(int num){
        return "触发降级";
    }

4.启动类

@SpringBootApplication
@EnableFeignClients("com.example.clients")
//@EnableDiscoveryClient //注销表示User服务不注册
@EnableHystrix //注解方式开启Hystrix
public class HystrixEclipseUserApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixEclipseUserApplication.class, args);
    }

}

5.配置文件

spring.application.name=user
server.port=8080
#Eureka注册地址
eureka.client.service-url.defaultZone=http://localhost:8761/eureka/
#eureka.instance.hostname=127.0.0.1
#eureka.instance.instance-id=${eureka.instance.hostname}:${server.port}
eureka.client.register-with-eureka=false

并配置一个num%2模拟请求成功和失败的场景,来触发熔断机制。

如果不清楚属性名字怎么写,可以找到HystrixCommandProperties这个类,查看可以配置的所有属性

  • circuitBreaker.requestVolumeThreshold 断路器的最小请求次数,10次
  • circuitBreaker.sleepWindowInMilliseconds 睡眠的时间窗口,也就是断路器触发熔断之后,直接对请求进行拒绝的持续时间。
  • circuitBreaker.errorThresholdPercentage 错误百分比。
    6.降级测试
  • http://localhost:8080/get/2 ,正常
  • http://localhost:8080/get/1,访问超时请求失败,触发降级
    7.熔断测试

http://localhost:8080/get/2 , 连续刷新访问10次以上
当熔断打开之后,再次访问,http://localhost:8080/get/2
8.总结

引入在user端引入依赖包

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

 

在启动类上开启Hystrix:@EnableHystrix
在controller里面使用注解
2.2 集成OpenFeign
2.2.1 api项目修改
1.在api模块中添加hystrix的jar包依赖

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

2.在api模块中,创建一个包fallback,并添加专门处理熔断的配置类

@Component
public class OrderServiceFallBack implements OrderServiceClient {
    @Override
    public String orderLists(int num) {
        return "查询Order信息失败,触发熔断";
    }
}

3.修改api模块中的FeignClient接口,增加fallback的配置

@FeignClient(name = "order",fallback = OrderServiceFallBack.class)
public interface OrderServiceClient extends OrderServiceDemo01 {
}

2.2.2 消费端修改
1.增加Hystrix的jar包依赖

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

2.修改application.properties文件,开启Hystrix

feign.hystrix.enabled=true

3.由于OrderServiceFallBack是一个Bean对象,需要加载到User项目中的IOC容器中,所以通过下面代码实现bean注册

@Configuration
public class HystrixConfiguration {
    @Bean
    OrderServiceFallBack orderServiceFallBack(){
        return new OrderServiceFallBack();
    }
}

4.修改Controller

@RestController
public class UserController {
    @Autowired
    OrderServiceClient orderServiceClient;
    @GetMapping("/get/{num}")
    public String get(@PathVariable("num") int num){
        if(num%2==0){
            return "正常访问";
        }
        return orderServiceClient.orderLists(num);
    }
}

2.2.3 触发降级原因详解
默认情况下,Hystrix请求超时时间为1s,由于我们在代码中让程序等待了3s,因此会触发熔断从而返回fallback中定义的降级内容。

在User模块中,增加下面这个配置项,即可修改超时时间

# default : #全局生效,service id表示指定应用生效
# true #为true,表示超时作为熔断依据、否则,则请求超时交给ribbon控制
hystrix.command.default.execution.timeout.enabled=true
# 超时时间为3秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=4000
# 下面表示ribbon配置的超时时间
ribbon.ReadTimeout=10000
ribbon.ConnectTimeout=10000

关于上述配置的作用说明如下:

  • 如果hystrix.command.default.execution.timeout.enabled为true,则会有两个执行方法超时的配置,一个就是ribbon的ReadTimeout,一个就是熔断器hystrix的timeoutInMilliseconds, 此时谁的值小谁生效。
  • 如果hystrix.command.default.execution.timeout.enabled为false,则熔断器不进行超时熔断,而是根据ribbon的ReadTimeout抛出的异常而熔断,也就是取决于ribbon
  • ribbon的ReadTimeout, 表示建立连接之后从服务器读取到可用资源所消耗的时间。
  • ribbon的ConnectTimeout,配置的是请求服务的超时时间,适用于网络状况正常的情况下。
  • ribbon还有MaxAutoRetries对当前实例的重试次数,MaxAutoRetriesNextServer对切换实例的重试次数, 如果ribbon的ReadTimeout超时,或者ConnectTimeout连接超时,会进行重试操作
  • 由于ribbon的重试机制,通常熔断的超时时间需要配置的比ReadTimeout长,ReadTimeout比ConnectTimeout长,否则还未重试,就熔断了
    通过上述配置把超时时间修改成4秒后,再次访问
    http://localhost:8080/get/2,就会正常等待4秒后才返回结果。
    2.3 HystrixCommand使用
    上文中我们通过Hystrix 中的核心注解 @HystrixCommand, 通过它创建了 HystrixCommand 的实现,同时利用 fallback 属性指定了服务降级的实现方法。然而这些还只是 Hystrix 使用的一小部分,在实现 一个大型分布式系统时,往往还需要更多高级的配置功能。 接下来我们将详细介绍 Hystrix 各接口和注解的使用方法。创建请求命令:

  Hystrix 命令就是我们之前所说的 HystrixCommand, 它用来封装具体的依赖服务调用逻辑。我们可以通过继承的方式来实现, 比如:

1.在api项目中的接口增加方法

@GetMapping("/hello")
String hello(@RequestParam(value = "name") String name);

在OrderServiceFallBack增加降级实现

@Override
public String hello(String name) {
    return "别你好了,烦不烦啊,降级";
}

2.Order实现类增加代码

@Override
public String hello(String name) {
    return port+"你好啊,亲爱的"+name;
}

3.User增加如下代码

package com.example.hystrixeclipseuser.service;
import com.example.clients.OrderServiceClient;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
public class HelloCommand extends HystrixCommand<String> {
    private String name;
    private OrderServiceClient orderServiceClient;
    public HelloCommand(String name, OrderServiceClient orderServiceClient) {
        super(com.netflix.hystrix.HystrixCommand.Setter.withGroupKey(
                HystrixCommandGroupKey.Factory.asKey("")).andCommandPropertiesDefaults(
                HystrixCommandProperties.Setter().withExecutionTimeoutInMilliseconds(5000)));
        this.name = name;
        this.orderServiceClient = orderServiceClient;
    }
    @Override
    protected String run() throws Exception {
        return orderServiceClient.hello(name);
    }
    @Override
    protected String getFallback() {
        return "服务降级";
    }
}
package com.example.hystrixeclipseuser.controller;
import com.example.clients.OrderServiceClient;
import com.example.hystrixeclipseuser.service.HelloCommand;
import com.netflix.hystrix.HystrixCommand;
import com.netflix.hystrix.HystrixCommandGroupKey;
import com.netflix.hystrix.HystrixCommandProperties;
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 rx.Observable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
@RestController
public class HelloController {
    @Autowired
    OrderServiceClient orderServiceClient;
    @GetMapping("/hello/{name}")
    public String get(@PathVariable("name") String name){
        String result = "";
        //同步
        result = new HelloCommand(name,orderServiceClient).execute();
        //异步
//        Future<String> res = new HelloCommand(name, orderServiceClient).queue();
//        try {
//            result = res.get();
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        }
        //响应式执行方式
//        Observable<String> hotObserve = new HelloCommand(name, orderServiceClient).observe();
//        Observable<String> coldObservable = new HelloCommand(name, orderServiceClient).toObservable();
        return result;
    }
}

通过上面实现的HelloCommand , 我们既可以实现请求的同步执行也可以实现异步执行。除了传统的同步执行与异步执行之外, 我们还可以将 HystrixComrnand 通过Observable 来实现响应式执行方式。通过调用 observe()和toObservable ()方法可以返回 Observable 对象。observe ()和toObservable ()虽然都返回了 Observable, 但是它们略有不同,前者返回的是一 个Hot Observable, 该命令会在 observe ()调用的时候立即执行, 当Observable 每次被订阅的时候会重放它的行为;而后者返回的是一 个Cold Observable,toObservable ()执行之后,命令不会被立即执行,只有当所有订阅者都订阅它之后才会执行。

异步执行的时候, 可以通过对返回的 result 调用 get 方法来获取结果。

2.4 异常处理:
异常传播:

在 HystrixComrnand 实现的 run() 方法中抛出异常时, 除了
HystrixBadRequestException 之外,其他异常均会被 Hystrix 认为命令执行失败并触发服务降级的处理逻辑,所以当需要在命令执行中抛出不触发服务降级的异常时来使用它。而在使用注册配置实现 Hystrix 命令时,它还支持忽略指定异常类型功能, 只需要通过设置 @HystrixComrnand 注解的 ignoreExceptions 参数, 比如:@HystrixCommand(ignoreExceptions = {BadRequestException.class}).当方法抛出了类型为 BadRequestException的异常时, Hystrix 会将它包装在 HystrixBadRequestException 中抛出, 这样就不会触发后续的 fallback 逻辑。

异常获取:

当 Hystrix 命令因为异常(除了
HystrixBadRequestException 的异常)进入服务降级逻辑之后, 往往需要对不同异常做针对性的处理, 那么我们如何来获取当前抛出的异常呢?在以传统继承方式实现的 Hystrix 命令中, 我们可以用 getFallback ()方法通过 getExecutionException() 方法来获取具体的异常, 通过判断来进入不同的处理逻辑。

除了传统的实现方式之外,注解配置方式也同样可以实现异常的获取。 它的实现也非常简单, 只需要在 fallback 实现方法的参数中增加 Throwable e 对象的定义, 这样在方法内部就可以获取触发服务降级的具体异常内容了, 比如:fallbackl(Throwable e)

2.5 请求缓存:
在高并发的场景之下, Hystrix 中提供了请求缓存的功能, 我们可以方便地开启和使用请求缓存来优化系统, 达到减轻高并发时的请求线程消耗、 降低请求响应时间的效果。

开启请求缓存功能 :Hystrix 请求缓存的使用非常简单, 我们只需要在实现 HystrixCommand 或 HystrixObservableCommand 时, 通过重载 getCacheKey ()方法来开启请求缓存。通过开启请求缓存可以让我们实现的 Hystrix 命令具备下面几项好处:

  • 减少重复的请求数, 降低依赖服务的并发度。
  • 在同一 用户请求的上下文中, 相同依赖服务的返回数据始终保持 一 致。
  • 请求缓存在 run() 和 construct ()执行之前生效, 所以可以有效减少不必要的线程开销。
    清理失效缓存功能

清除缓存有两个方式

//刷新缓存,根据id进行清理 自己写一个flush方法。通过idzuoweikey清除
HystrixRequestCache.getInstance(GETTER_KEY,HystrixConcurrencyStrategyDefault.getinstance()).clear(String.valueOf(id));
//刷新缓存, 清理缓存中失效的User,直接调用flush方法
HelloCommand.flushCache(id);

2.6 请求合并
微服务架构中的依赖通常通过远程调用实现, 而远程调用中最常见的问题就是通信消耗与连接数占用。 在高并发的情况之下, 因通信次数的增加, 总的通信时间消耗将会变得不那么理想。 同时, 因为依赖服务的线程池资源有限,将出现排队等待与响应延迟的清况。为了优化这两个问题, Hystrix 提供了 HystrixCollapser 来实现请求的合并,以减少通信消耗和线程数的占用。

HystrixCollapser 实现 了在 HystrixCommand 之前放置 一 个合并处理器, 将处于一个很短的时间窗(默认 10 毫秒)内对同 一 依赖服务的多个请求进行整合并以批量方式发起请 求 的功能(服 务提供方也需 要 提供相应的批 量实 现 接口)。 通 过HystrixCollapser 的封装, 开发者不需要关注线程合并的细节过程, 只需关注批量化服务和处理。 下面我们从 HystrixCollapser 的使用实例 中对其合并请求的过程 一 探究竟。

public abstract class HystrixCollapser<BatchReturnType, ResponseType, RequestArgumentType> implements HystrixExecutable<ResponseType>, HystrixObservable<ResponseType> {  
    //BatchReturnType: 合并后批量请求的返回类型。
    // ResponseType: 单个请求返回的类型。
    //RequestArgumentType: 请求参数类型。
    //该函数用来定义获取请求参数的方法。
    public abstract RequestArgumentType getRequestArgument();
    //合并请求产生批量命令的具体实现方法。
    protected abstract HystrixCommand<BatchReturnType> createCommand(Collection<CollapsedRequest<ResponseType, RequestArgumentType>> requests);
    //批量命令结果返回后 的处理, 这里需要实现将批量结果拆分并传递给合并前的各个原子请求命令的逻辑。
    protected abstract void mapResponseToRequests(BatchReturnType batchResponse, Collection<CollapsedRequest<ResponseType, RequestArgumentType>> requests);
}

2.7 熔断触发原理分析
Hystrix提供了三种降级策略: 并发数量(SEMAPHORE)、耗时(默认是1s)、错误率(熔断),对于并发数量和耗时这种降级策略,实际上不用看代码基本上也能理解它的实现,所以我们主要去看一下熔断这个机制,这块会涉及到一些很有意思的设计。

每当Hystrix发起请求遇到一个服务错误时,就会开始一个10s的计时器,这个计时器用来检查服务调用失败的频率。

  • Hystrix触发问题时,做的第一件事情就是查看最近10s内发生的调用次数,如果调用次数少于这个窗口内发生的最小调用次数,那么这个时候Hystrix不会采取行动。比如默认情况下Hystrix需要满足10s内进行20次调用,那即便是10s内发生了15次请求并且15次都是 失败的,只要次数没达标,也都不会触发熔断。
  • 当在10s内达到20次之后,hystrix开始检查整体故障的百分比,如果整体的百分比超过阈值,默认是50%,hystrix将会触发熔断,熔断之后后续对于这个服务的请求都会失败。
  • Hystrix熔断之后,在5s内,所有请求都不会发送到远程服务器上,而是直接设置为请求失败。同时,Hystrix每隔5s会发送一个测试的请求,如果测试请求返回成功,则Hystrix就会尝试关闭断路器,使得后续请求继续正常访问,否则,hystrix在下一个5s进行访问。
    SpringCloud系列—Spring Cloud Hystrix服务保护机制-鸿蒙开发者社区熔断器有三种状态,
  • open状态说明打开熔断,也就是服务调用方执行本地降级策略,不进行远程调用。(当客户端向服务端发送请求,如果在10s内超过20个请求发送,且有50%的请求是失败时,那么Hystrix就会开启熔断)
  • closed状态说明关闭了熔断,这时候服务调用方直接发起远程调用。
  • half-open状态,则是一个中间状态,当熔断器处于这种状态时候,直接发起远程调用。
    SpringCloud系列—Spring Cloud Hystrix服务保护机制-鸿蒙开发者社区

分类
已于2022-7-25 17:32:39修改
收藏
回复
举报
回复
    相关推荐