• 快速入门Spring Cloud Hystrix(服务降级、服务熔断、服务监控)



    前言

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


    提示:以下是本篇文章正文内容,下面案例可供参考

    一、服务雪崩

    1.服务雪崩概述

    在微服务之间进行服务调用是由于某一个服务故障,导致级联服务故障的现象,称为雪崩效应。
    雪崩效应描述的是提供方不可用,导致消费方不可用并将不可用逐渐放大的过程。      
    
    • 1
    • 2

    2. 造成服务雪崩的原因

     - 服务提供者不可用
         a)硬件故障:硬件损坏造成的服务器主机宕机, 网络硬件故障造成的服务提供者的不可访问
         b)程序Bug:
         c) 缓存击穿:缓存击穿一般发生在缓存应用重启, 所有缓存被清空时,以及短时间内大量缓存失效时. 大量的缓存不命中, 使请求直击后端,造成服务提供者超负荷运行,引起服务不可用
         d)用户大量请求:在秒杀和大促开始前,如果准备不充分,用户发起大量请求也会造成服务提供者的不可用
     - 重试加大流量
         a)用户重试:在服务提供者不可用后, 用户由于忍受不了界面上长时间的等待,而不断刷新页面甚至提交表单
         b)代码逻辑重试: 服务调用端的会存在大量服务异常后的重试逻辑
     - 服务调用者不可用
         a)同步等待造成的资源耗尽:当服务调用者使用同步调用 时, 会产生大量的等待线程占用系统资源. 一旦线程资源被耗尽,服务调用者提供的服务也将处于不可用状态, 于是服务雪崩效应产生了。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    3. 如何防止雪崩

    使用Spring Cloud Hystrix进行服务熔断、降级。
    然而Hystrix已经停更,进入了维护模式,Hystrix官方推荐的替代产品:Resilience4J 
    但我们需要知道Hystrix是干嘛,怎么用的,它的思想
    
    • 1
    • 2
    • 3

    在这里插入图片描述

    二、Spring Cloud Hystrix

    1.什么是Spring Cloud Hystrix(豪猪哥)

    Spring Cloud Hystrix 是基于 Netflix 公司的开源组件 Hystrix 实现的,
    它提供了熔断器功能,能够有效地阻止分布式微服务系统中出现联动故障,以提高微服务系统的弹性。         
    Spring Cloud Hystrix 具有服务降级、服务熔断、线程隔离、请求缓存、请求合并以及实时故障监控等强大功能。
    
    • 1
    • 2
    • 3

    Hystrix 的使用文档:https://github.com/Netflix/Hystrix/wiki/How-To-Use

    • 服务熔断

    熔断机制是应对雪崩效应的⼀种微服务链路保护机制。当扇出链路的某个微服务不可⽤或者响应时间太⻓时,熔断该节点微服务的调⽤,进⾏服务的降级,快速返回错误的响应信息。当检测到该节点微服务调⽤响应正常后,恢复调⽤链路。【通常与服务降级一起使用】

    • 服务降级

    服务降级是从系统整体考虑,当某个服务熔断之后,服务器不再被调⽤时,客户端可以为发送的请求准备⼀个本地的fallback回调,返回⼀个与方法返回值类型相同的缺省值,这样做,虽然服务水平下降,但整体仍然可用,比直接熔断要好。

    • 服务限流

    秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。

    2.搭建测试环境

    (1)创建cloud-provider-hystrix-payment8003支付服务

    改pom
    在原有支付服务的依赖下,添加hystrix依赖

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

    添加yml

    server:
      port: 8003
    #服务名称
    spring:
      application:
        name: cloud-provider-hystrix-payment
    
    eureka:
      client:
        #表示是否将自己注册进Eurekaserver默认为true。
        register-with-eureka: true
        #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
        fetchRegistry: true
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    启动类

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

    业务类

    @Service
    public class PaymentService {
        public String paymentInfo_OK(Integer id){
            return "线程池:  "+Thread.currentThread().getName()+"  paymentInfo_OK,id:  "+id+"\t"+"O(∩_∩)O哈哈~";
        }
    
        public String paymentInfo_TimeOut(Integer id)
        {
            try { TimeUnit.MILLISECONDS.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
            return "线程池:  "+Thread.currentThread().getName()+" id:  "+id+"\t"+"O(∩_∩)O哈哈~"+"  耗时(秒): 3";
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    controller类

    @RestController
    @Slf4j
    public class PaymentController {
        @Resource
        private PaymentService paymentService;
    
        @Value("${server.port}")
        private String serverPort;
    
        @GetMapping("/payment/hystrix/ok/{id}")
        public String paymentInfo_OK(@PathVariable("id") Integer id)
        {
            String result = paymentService.paymentInfo_OK(id);
            log.info("*****result: "+result);
            return result;
        }
    
        @GetMapping("/payment/hystrix/timeout/{id}")
        public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
        {
            String result = paymentService.paymentInfo_TimeOut(id);
            log.info("*****result: "+result);
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    (2)创建cloud-consumer-feign-hystrix-order订单服务

    改pom

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

    添加yml

    server:
      port: 80
    eureka:
      client:
        #表示是否将自己注册进Eurekaserver默认为true。
        register-with-eureka: false
        #是否从EurekaServer抓取已有的注册信息,默认为true。单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    启动类

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

    业务类

    @Component
    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
    public interface PaymentHystrixService {
    
        @GetMapping("/payment/hystrix/ok/{id}")
        public String paymentInfo_OK(@PathVariable("id") Integer id);
    
        @GetMapping("/payment/hystrix/timeout/{id}")
        public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    controller类

    @RestController
    @Slf4j
    public class OrderHystirxController {
    
        @Resource
        private PaymentHystrixService paymentHystrixService;
    
        @GetMapping("/consumer/payment/hystrix/ok/{id}")
        public String paymentInfo_OK(@PathVariable("id") Integer id)
        {
            String result = paymentHystrixService.paymentInfo_OK(id);
            return result;
        }
    
        @GetMapping("/consumer/payment/hystrix/timeout/{id}")
        public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
            String result = paymentHystrixService.paymentInfo_TimeOut(id);
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    (3)jmeter压力测试

    压测40000并发,访问支付模块
    在这里插入图片描述
    在这里插入图片描述
    此时80调用8003,发现订单模块访问变慢。
    原因:8003同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
    解决方案:使用服务降级

    • 超时导致服务器变慢(转圈) - 超时不再等待
    • 出错(宕机或程序运行出错) - 出错要有兜底

    3.服务降级实例

    为了测试比较清晰,因此故意制造两种异常

    • 制造超时异常
    • int age = 10/0,计算异常
    (1)支付服务做服务降级

    更改支付服务业务类
    在这里插入图片描述
    主启动类添加@EnableCircuitBreaker注解来激活Hystrix的功能

    @SpringBootApplication
    @EnableEurekaClient
    @EnableCircuitBreaker   //此处添加
    public class PaymentHystrixMain8003 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentHystrixMain8003.class,args);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    修改代码造成计算异常
    在这里插入图片描述

    测试:两种异常都是跳转到了兜底的方法上
    在这里插入图片描述

    (2)@EnableHystrix 注解

    @EnableHystrix注解它继承了@EnableCircuitBreaker,并对它进行了在封装。
    这两个注解都是激活hystrix的功能,

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @EnableCircuitBreaker
    public @interface EnableHystrix {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    (3)全局服务降级DefaultProperties

    每个方法上配置一个服务降级方法。太麻烦,只需要对核心业务有专属
    其他普通的可以进行全局降级

    @Service
    @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
    public class PaymentService {
       
        @HystrixCommand
        public String paymentInfo_TimeOut(Integer id)
        {    int age=10/0;
            return "线程池:  "+Thread.currentThread().getName()+" id:  "+id+"\t"+"O(∩_∩)O哈哈~"+"  耗时(秒): 3";
        }
        public String payment_Global_FallbackMethod(){
            return "全局异常处理信息,请稍后再试,/(ㄒoㄒ)/~~";
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在这里插入图片描述

    (4)通配服务降级FeignFallback

    降级方法与业务方法写在了一块,耦合性太高

    修改order模块,这里开始,pay模块就不服务降级了,服务降级写在order模块即可
    创建PaymentHystrixService接口实现类

    @Component
    public class PaymentFallbackService implements PaymentHystrixService {
        @Override
        public String paymentInfo_OK(Integer id) {
            return "paymentInfo_OK出现异常";
        }
    
        @Override
        public String paymentInfo_TimeOut(Integer id) {
            return "paymentInfo_TimeOut出现异常";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    让PayService的实现类生效:

    @Component
    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",
              fallback = PaymentFallbackService.class)
    public interface PaymentHystrixService {
    
        @GetMapping("/payment/hystrix/ok/{id}")
        public String paymentInfo_OK(@PathVariable("id") Integer id);
    
        @GetMapping("/payment/hystrix/timeout/{id}")
        public String paymentInfo_TimeOut(@PathVariable("id") Integer id);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    配置文件中开启hystrix

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

    测试:支付服务报错,首先会运行支付服务中该方法的兜底方法
    在这里插入图片描述
    当我们关掉支付服务后,重新发起请求
    在这里插入图片描述
    可以看到,并没有报500错误,而是降级访问实现类的同名方法

    这样,即使服务器挂了,用户要不要一直等待,或者报错

    4.服务熔断实例

    (1)服务熔断原理剖析

    服务熔断:就类似于保险丝
    熔断这个概念由martin fowler 提出的,在他的博客中对熔断的原理进行了概述
    在这里插入图片描述
    简单来说,断路器有三种状态熔断打开熔断关闭熔断半开

    正常调用时,断路器是关闭的。
    当出现高并发等情况,导致某个服务瘫痪,此时断路器打开,服务发生熔断,会进行服务的降级,快速返回错误的信息。
    当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果能够正常运行,则恢复正常链路,此时断路器关闭

    (2)服务熔断实例

    修改cloud-hystrix-pay8003模块

    • 修改业务类
    @HystrixCommand(fallbackMethod = "payment_Hander",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 paymentInfo_TimeOut(Integer id)
        {   if(id<0){
             throw new RuntimeException("id不能为负数");
        }
            //try { TimeUnit.MILLISECONDS.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }
            return "线程池:  "+Thread.currentThread().getName()+" id:  "+id+"\t"+"O(∩_∩)O哈哈~"+"  耗时(秒): 3";
        }
        public  String payment_Hander(Integer id){
    
            return "线程池:  "+Thread.currentThread().getName()+"  8003系统繁忙或者运行报错,请稍后再试,id:  "+id;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 测试
      正确 - http://localhost:8001/payment/circuit/1
      错误 - http://localhost:8001/payment/circuit/-1
      多次错误,再来次正确,但错误得显示
      在这里插入图片描述
      HystrixCommandProperties 这个类规定了HystrixCommand注解下的HystrixProperty属性里的值。
      在这里插入图片描述在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    5.Hystrix工作流程

    官网的工作流程:
    在这里插入图片描述
    步骤说明:

    1. 创建HystrixCommand (用在依赖的服务返回单个操作结果的时候)或HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候)对象。

    2. 若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。

    3. 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)。

    4. 线程池/请求队列信号量是否占满。如果命令依赖服务的专有线程地和请求队列,或者信号量(不使用线程的时候)已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理理辑(第8步) 。

    5. Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。
      HystrixCommand.run():返回一个单一的结果,或者抛出异常。
      HystrixObservableCommand.construct():返回一个Observable对象来发射多个结果,或通过onError发送错误通知。

    6. Hystix会将“成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行"熔断/短路"。

    7. 当命令执行失败的时候,Hystix会进入fallback尝试回退处理,我们通常也称波操作为“服务降级”。

    8. 当Hystrix命令执行成功之后,它会将处理结果直接返回或是以Observable的形式返回。

    6.服务监控

    (1)Hystrix图形化Dashboard搭建

    创建模块:cloud-consumer-hystrix-dashboard-9001
    改pom:

    <dependencies>
            <dependency>
                <groupId>org.springframework.cloudgroupId>
                <artifactId>spring-cloud-starter-netflix-hystrix-dashboardartifactId>
            dependency>
            //必须要添加监控依赖
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-actuatorartifactId>
            dependency>
    
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-devtoolsartifactId>
                <scope>runtimescope>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <optional>trueoptional>
            dependency>
            <dependency>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-starter-testartifactId>
                <scope>testscope>
            dependency>
        dependencies>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    添加yml

    server:
      port: 9001
    
    • 1
    • 2

    启动类 @EnableHystrixDashboard注解

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

    测试:浏览器输入:http://localhost:9001/hystrix
    在这里插入图片描述
    出现这个则配置成功

    (2)图形化Dashboard监控实战

    对监控的服务主启动类添加以下内容

    @SpringBootApplication
    @EnableEurekaClient
    @EnableHystrix  //此处添加
    public class PaymentHystrixMain8003 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentHystrixMain8003.class, args);
        }
    
        /**
         * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
         * ServletRegistrationBean因为springboot的默认路径不是"/hystrix.stream",
         * 只要在自己的项目里配置上下面的servlet就可以了
         * 否则,Unable to connect to Command Metric Stream 404
         */
        @Bean
        public ServletRegistrationBean getServlet() {
            HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
            ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);
            registrationBean.setLoadOnStartup(1);
            registrationBean.addUrlMappings("/hystrix.stream");
            registrationBean.setName("HystrixMetricsStreamServlet");
            return registrationBean;
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    监控测试:
    启动注册中心、9003、9001服务
    填写监控地址 http://localhost:8003/hystrix.stream 到 http://localhost:9001/hystrix页面的输入框
    在这里插入图片描述
    监控说明:
    在这里插入图片描述

    在这里插入图片描述

  • 相关阅读:
    Java复习六:内部类+异常处理
    交换机与路由技术-31-扩展ACL
    DVWA 靶场之 Command Injection(命令执行)middle&high
    MySQL 8.0 主从复制重建流程(从主库数据文件备份恢复)
    考古:MFC界面的自适应缩放(代码示例)
    【ASM】字节码操作 工具类与常用类 InstructionAdapter 介绍
    SqlBoy:间隔连续问题
    CSDN每日一题学习训练——Python版(搜索插入位置、最大子序和)
    java: 无法访问org.mybatis.spring.annotation.MapperScan
    猿创征文 |【算法面试入门必刷】动态规划-线性dp(二)
  • 原文地址:https://blog.csdn.net/qq_45637894/article/details/126421001