• SpringCloud搭建微服务之Hystrix熔断器


    1. 概述

    1.1. 分布式系统需要解决的问题

    复杂分布式体系结构中的应用程序有数十个依赖服务,每个依赖服务在某时将不可避免发生异常或失败。多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他微服务,这就叫扇出,如果扇出的链路上某个微服务的调用响应时间过长或者不可使用,对微服务A的调用就会占用越来越多的系统资源,进而引发整个系统的崩溃,称为雪崩效应,因此就需要一个组件来解决这个问题

    1.2. 什么是Hystrix

    Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统中,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整个系统服务失败,避免级联故障发生,以提高分布式系统的弹性。当某个服务发生故障后,通过断路器的故障监控向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩

    1.3. 重要概念

    服务降级:当程序运行异常、超时、服务熔断触发、线程池/信号量打满等情况下会让客户端不再等待并返回一个友好提示
    服务熔断:请求量达到最大服务访问后,直接拒绝访问,然后调用服务降级的方法并返回友好提示
    服务限流:高并发操作时,严禁一瞬间过来海量请求,要求一秒钟多少个请求有序进行

    2. 服务提供端集成Hystrix

    2.1. 引入核心依赖

    在pom.xml文件中引入Hystrix依赖

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    2.2. 业务方法配置服务降级

    @HystrixCommand(fallbackMethod = "timeOutHandler", commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
    })
    @GetMapping(value = "/getProviderInfoForHystrix/{message}")
    public String getProviderInfoForHystrix(@PathVariable("message") String message) {
        try {
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "Hello" + message + ", This is provider hystrix, The current time is " + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
    }
    
    public String timeOutHandler(String message) {
        return "Hello " + message + "调用服务接口超时或异常!";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.3. 主启动类引入Hystrix配置

    @SpringBootApplication
    @EnableEurekaClient
    @EnableCircuitBreaker
    public class ProviderApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(ProviderApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    3. 服务消费端集成Hystrix

    3.1. 引入核心依赖

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-hystrixartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-openfeignartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.2. 配置application.yml文件

    server:
      port: 8800
    spring:
      application:
        name: cloud-hystrix
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8761/eureka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.3. 编写主启动类

    @SpringBootApplication
    @EnableEurekaClient
    @EnableFeignClients
    @EnableHystrix
    public class HystrixApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(HystrixApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3.4. 编写业务接口

    @RestController
    @RequestMapping("/hystrix")
    public class HystrixController {
        
        @Autowired
        private ProviderClient providerClient;
    
        @GetMapping(value = "/getHystrixInfoTimeOut/{message}")
        @HystrixCommand(fallbackMethod = "timeOutFallback", commandProperties = {
                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
        })
        public String getHystrixInfoTimeOut(@PathVariable("message") String message) {
            return providerClient.getProviderInfoForHystrix(message);
        }
    
        public String timeOutFallback(String message) {
            return "This is Hystrix consumer, The request Provider time out, The message is " + message;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3.5. 业务类配置默认返回类

    由于每个方法都配置一个异常处理类,会导致代码膨胀,可以在类上配置默认的异常处理类,在需要特殊处理的方法上再配置单独的异常处理方法

    @RestController
    @RequestMapping("/hystrix")
    @DefaultProperties(defaultFallback = "providerDefaultFallback")
    public class HystrixController {
    
        @Autowired
        private ProviderClient providerClient;
        
        @HystrixCommand
        @GetMapping(value = "/getHystrixInfo/{message}")
        public String getHystrixInfo(@PathVariable("message") String message) {
            return providerClient.getProviderInfoForHystrix(message);
        }
    
        public String providerDefaultFallback() {
            return "This is Hystrix consumer The global error fallback";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    3.6. 编写异常请求类

    上面两种方法都是在业务代码中处理异常业务,这样容易导致异常代码和业务代码混淆在一起,可以单独将异常处理拧出来,在Feign接口上引用该异常类即可

    @Component
    public class ProviderClientFallback implements ProviderClient {
    
        @Override
        public String getProviderInfoForHystrix(String message) {
            return "failBack provider info rest service call failed!";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在Feign接口上引用该异常类

    @Component
    @FeignClient(value = "CLOUD-PROVIDER", configuration = ProviderClientFallback.class)
    public interface ProviderClient {
    
        @GetMapping(value = "/provider/getProviderInfoForHystrix/{message}")
        String getProviderInfoForHystrix(@PathVariable("message") String message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在yml配置中开启Feign的hystrix支持

    feign:
      hystrix:
        enabled: true
    
    • 1
    • 2
    • 3

    3.7. 编写异常回退工厂类

    定义和使用一个Fallback回退处理工厂类

    @Component
    public class ProviderClientFallbackFactory implements FallbackFactory<ProviderClient> {
        
        @Override
        public ProviderClient create(Throwable throwable) {
            return new ProviderClient() {
                @Override
                public String getProviderInfoForHystrix(String message) {
                    return "FallbackFactory fallback: provider rest service call failed!";
                }
            };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在Feign接口引入该工厂类

    @Component
    @FeignClient(value = "CLOUD-PROVIDER", configuration = FeignConfiguration.class, fallbackFactory = ProviderClientFallbackFactory.class)
    public interface ProviderClient {
    
        @GetMapping(value = "/provider/getProviderInfoForHystrix/{message}")
        String getProviderInfoForHystrix(@PathVariable("message") String message);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    回退类和回退工厂类区别

    • 使用回退类时,远程调用RPC过程中所引发的异常已经被回退逻辑彻底屏蔽掉了,应用程序不方便干预,也看不到RPC过程中的具体异常
    • 使用回退工厂类时,应用程序可以通过Java代码对RPC异常进行拦截和处理

    4. 服务熔断

    当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制

    4.1. 注解配置

    在服务端新增测试熔断方法

    @GetMapping(value = "/getProviderInfoForCircuitBreaker/{id}")
    @HystrixCommand(fallbackMethod = "circuitBreakerFallback", commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60")
    })
    public String getProviderInfoForCircuitBreaker(@PathVariable("id") Integer id) {
        if (id < 0) {
            throw new RuntimeException("id不能为负数!");
        }
        return "The id is " + id;
    }
    
    public String circuitBreakerFallback(@PathVariable("id") Integer id) {
        return "id 不能为负数,请稍后再试! id is " + id;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    4.2. 三个重要参数

    快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近10秒
    请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断,默认20,意味着在10秒内,如果该hystrix命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开
    错误百分比阈值:当请求总数在快照时间窗内超过了阈值,比如发生了30次调用,如果在这30次调用中有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阈值情况下,这时就会将断路器打开
    断路器开启或关闭的条件
    当满足一定的阈值的时候(默认10秒内超过20个请求次数)
    当失败率达到一定的时候(默认10秒内超过50%的请求失败)
    到达以上阈值,断路器将会开启
    当开启的时候,所有请求都不会进行转发
    一段时间后(默认5秒),此时断路器是半开状态,会让其中一个请求进行转发,如果成功,断路器会关闭,若失败,继续开启

    4.3. yml文件配置

    在yml文件中也可以进行服务熔断相关配置

    hystrix:
      command:
        default:
          circuitBreaker:
            enabled: true #是否开启熔断器
            requestVolumeThreshold: 20 #窗口时间内最小请求数
            sleepWindowInMilliseconds: 5000 #打开后允许一次尝试的睡眠时间,默认5秒
            errorThresholdPercentage: 50 #窗口时间内熔断器开启的错误比例,默认50
          metrics:
            rollingStats:
              timeInMilliseconds: 1000 #滑动窗口时间
              numBuckets: 10 #滑动窗口的时间桶数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5. RPC保护之舱壁模式

    对于不同的服务提供者可以设置不同的RPC调用线程池,让不同RPC通过专门的线程池请求到各自的Provider服务提供者,像舱壁一样对Provider进行隔离,对于不同的服务提供者设置不同的RPC调用线程池,这种模式被称为舱壁模式
    优点
    避免对单个Provider的RPC的消耗掉所有资源,从而防止由于某一个服务性能低而引起的级联故障和雪崩效应
    Hystrix提供两种RPC隔离方式:线程池隔离和信号量隔离

    5.1. 线程池隔离

    每一个线程池都有一个Key,名为Thread Pool Key(线程池名)。如果没有为HystrixCommand指定线程池,Hystrix就会为HystrixCommand创建一个与Group Key(命令组Key)同名的线程池,当然,如果与Group Key同名的线程池已经存在,就直接进行关联。
    线程池隔离配置如下:

    hystrix:
      threadpool:
        default:
          coreSize: 10 #线程池核心线程数
          maximumSize: 20 线程池最大线程数
          allowMaximumSizeToDivergeFromCoreSize: true #线程池maximumSize最大线程数是否生效
          keepAliveTimeMinutes: 10 #可空闲时间,分钟
      command:
        default:
          execution:
            isolation:
              strategy: THREAD #配置请求隔离方式为线程池
              thread:
                timeoutInMilliseconds: 100000 #RPC执行超时时间,默认1000毫秒
                interruptOnTimeout: true #超时后是否中断方法的执行,默认true
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.2. 信号量隔离

    信号量所起到的作用就像一个开关,而信号量的值就是每个命令的并发执行数量,当并发数高于信号量的值时就不再执行命令。信号量可以细分为run执行信号量和fallback回退信号量
    IO线程在执行HystrixCommand命令之前需要抢到run执行信号量,成功之后才允许执行HystrixCommand.run()方法。如果争抢失败,就准备回退,但是在执行HystrixCommand.getFallback()回退方法之前,还需要争抢fallback回退信号量,成功之后才允许执行HystrixCommand.getFallback()回退方法。如果都获取失败,操作就会直接终止。
    信号量隔离配置如下

    hystrix:
      command:
          fallback:
            isolation:
              semaphore:
                maxConcurrentRequests: 10 #回退信号量大小,默认为10,信号量大小不能大于容器线程池大小
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    5.3. 线程池隔离与信号量隔离区别

    线程池隔离信号量隔离
    调用线程RPC线程与Web容器IO线程相互隔离RPC线程与Web容器IO线程相同
    开销存在请求排队、线程调度、线程上下文切换无线程切换,开销低
    异步支持不支持
    并发量最大线程池大小最大信号量上限,且最大信号量需要小于IO线程数

    6. RPC保护之熔断器模式

    统计最近RPC调用发生错误的次数,然后根据统计值中的失败比例等信息决定是否允许后面的RPC调用继续,或者快速地失败回退
    熔断器的3种状态如下:
    关闭(closed):熔断器初始状态,RPC调用正常放行
    开启(open):失败比例到一定的阈值后,熔断器进入开启状态,RPC将会快速失败,然后执行失败回退逻辑
    半开启(half-open):打开一定时间后(睡眠窗口结束),熔断器进入半开启状态,小流量尝试进行RPC调用放行,如果尝试成功,熔断器就变为关闭状态,RPC调用正常,如果尝试失败,熔断器就变为开启状态,RPC调用快速失败

    6.1. 熔断器配置

    包含滑动窗口配置和熔断器自身配置

    hystrix:
      command:
        default:
          circuitBreaker:
            enabled: true #是否开启熔断器
            requestVolumeThreshold: 20 #窗口时间内最小请求数
            sleepWindowInMilliseconds: 5000 #打开后允许一次尝试的睡眠时间,默认5秒
            errorThresholdPercentage: 50 #窗口时间内熔断器开启的错误比例,默认50
          metrics:
            rollingStats:
              timeInMilliseconds: 1000 #滑动窗口时间
              numBuckets: 10 #滑动窗口的时间桶数
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    6.2. HystrixCommand工作流程

    1. 判断是否使用缓存响应请求,若启用了缓存,且缓存可用,则直接使用缓存响应请求,Hystrix支持请求缓存,但需要用户自定义启动
    2. 判断熔断器是否开启,如果熔断器处于open状态,则跳至第5步
    3. 若使用线程池进行请求隔离,则判断线程池是否已占满,若已满则跳至第5步;若使用信号量进行请求隔离,则判断信号量是否耗尽,若耗尽则跳至第5步
    4. 使用HystrixCommand.run()方法执行具体业务逻辑,如果执行失败或者超时,就跳至第5步,否则跳至第6步
    5. 执行HystrixCommand.getFallback()服务降级处理逻辑
    6. 返回请求响应

    7. 服务监控

    Hystrix提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等

    7.1. 引入核心依赖

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4

    7.2. 编写application.yml文件

    server:
      port: 8801
    spring:
      application:
        name: cloud-hystrix-dashboard
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8761/eureka
    hystrix:
      dashboard:
        proxy-stream-allow-list: '*'
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    7.3. 编写主启动类

    @SpringBootApplication
    @EnableHystrixDashboard
    public class HystrixDashboardApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(HystrixDashboardApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    主要新增注解@EnableHystrixDashboard

    7.4. 验证

    依次启动Eureka Server、Provider和HystrixDashboard consumer微服务
    浏览器地址输入http://localhost:8801/hystrix
    hystrix dashboard
    Delay:用来控制服务器上轮询监控信息的延迟时间,默认2000毫秒,可以通过配置该属性来降低客户端的网络和CPU消耗
    Title:对应头部标题Hystrix Stream之后的内容,默认会使用具体监控实例的URL,可以通过配置该信息来展示更合适的标题
    填写监控地址http://localhost:8770/actuator/hystrix.stream、延迟时间和应用名称
    微服务监控
    点击Monitor Stream
    监控页面
    浏览器输入地址http://localhost:8801/hystrix/dashboard/getProviderInfoForCircuitBreaker/100,多次访问
    访问正常监控
    再次在浏览器地址输入http://localhost:8801/hystrix/dashboard/getProviderInfoForCircuitBreaker/-100,多次访问
    错误监控
    实心圆:通过颜色的变化代表了实例的健康程度,健康度从绿色<黄色<橙色<红色递减。其大小会根据实例的请求流量发生变化,流量越大该实心圆就越大
    曲线:用来记录2分钟内流量的相对变化,通过其来观察到流量的上升和下降趋势
    错误解析

    8. 聚合监控

    dashboard用于监控单个微服务,如果需要监控多个微服务,就需要用到turbine

    8.1. 引入核心依赖

    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
    dependency>
    <dependency>
        <groupId>org.springframework.cloudgroupId>
        <artifactId>spring-cloud-starter-netflix-turbineartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    8.2. 编写application.yml文件

    server:
      port: 8802
    spring:
      application:
        name: cloud-turbine
    eureka:
      client:
        register-with-eureka: false
        fetch-registry: true
        service-url:
          defaultZone: http://localhost:8761/eureka
    hystrix:
      dashboard:
        proxy-stream-allow-list: '*'
    turbine:
      aggregator:
        cluster-config: default
      app-config: cloud-provider8771,cloud-provider8772 #服务列表
      cluster-name-expression: new String('default')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    8.3. 编写主启动类

    @EnableTurbine
    @SpringBootApplication
    @EnableHystrixDashboard
    public class TurbineApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(TurbineApplication.class, args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    8.4. 验证

    启动两个微服务,在浏览器输入地址http://localhost:8802/hystrix
    turbine监控面板
    多次访问两个微服务接口,在面板地址栏输入http://localhost:8802/turbine.stream,点击Monitor Stream查看监控界面信息
    turbine监控详情

  • 相关阅读:
    02_Java基础语法
    2022-08-01 C++并发编程(四)
    微信小程序修改vant组件样式
    计算机毕业设计Java钢材出入库管理系统(源码+系统+mysql数据库+lw文档)
    ps gif动图怎么做,教你一招更简单
    性能优于BERT的FLAIR:一篇文章入门Flair模型
    Springboot餐饮点餐系统毕业设计源码301749
    Java入门基础第2天(java jdk下载与安装教程)
    LeetCode【2251. 花期内花的数目】
    mysql中的各种日志文件redo log、undo log和binlog
  • 原文地址:https://blog.csdn.net/liu320yj/article/details/126329287