首页 > 技术文章 > 5--SpringCloud:Hystrix 断路器周阳老师

coderD 2021-01-30 19:16 原文

2021:5--SpringCloud:Hystrix 断路器

https://www.cnblogs.com/coderD/p/14350076.html SpringCloud

https://www.cnblogs.com/coderD/p/14350073.html SpringCloud 和 Eureka

https://www.cnblogs.com/coderD/p/14350082.html SpringCloud 和 Zookeeper

https://www.cnblogs.com/coderD/p/14350086.html SpringCloud-Ribbon/OpenFeign

https://www.cnblogs.com/coderD/p/14350091.html SpringCloud:Hystrix 断路器

https://www.cnblogs.com/coderD/p/14350097.html SpringCloud:服务网关 gateway

https://www.cnblogs.com/coderD/p/14350099.html SpringCloud:Config/Bus

https://www.cnblogs.com/coderD/p/14350103.html SpringCloud:Stream/Sleuth

https://www.cnblogs.com/coderD/p/14350110.html SpringCloud Alibaba:Nacos

https://www.cnblogs.com/coderD/p/14350114.html SpringCloud Alibaba:Sentinel

https://www.cnblogs.com/coderD/p/14350119.html SpringCloud Alibaba:Seata

代码:https://gitee.com/xue--dong/spring-cloud

阳哥脑图:https://gitee.com/xue--dong/spring-cloud

背景和概述

几个微服务之间的相互调用,肯定会导致链路越来越长。一个出事了,就会导致整条链路上的
服务都出事。

所以分布式系统实际面临一个问题:
    复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地
    失败:网络故障,出错,宕机,断电...
    
    很可能会导致服务雪崩。
    
服务雪崩:
    
    多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其
    他的微服务,这就是所谓的“扇出”。
    
    如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越
    多的系统资源,进而引起系统崩溃,所谓雪崩效应。
    
对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒内饱和。
比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增肌,备份队列,线程和其他系统资源紧张,
导致整个系统发生更多的级联故障。
这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不会取消整个应用程序或系统。

所以:
    通常当你发现一个模块的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块
    还调用了其他的模块,这样就会发生级联故障,或者叫雪崩。
    

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

    
Hystrix的主要功能:
    
    服务降级
    服务熔断
    接近实时的监控
    
    限流
    隔离
    ...
    
    有一个图形化的服务监控:HystrixDashboard
复制代码

1. Hystrix

官网资料

1.1 Hystrix 的几个概念

    1.  服务降级:fallback
        
        假如对方的系统不可用了,你需要给我一个兜底的解决办法或备选响应。
        
    1.1 哪些情况下会发生服务降级?
        
        程序运行异常
        
        超时
        
        服务熔断触发服务降级
        
        线程池/信号量打满也会导致服务降级
        
    2.  服务熔断:break
        
        类似保险丝达到最大访问服务后,直接拒绝访问,拉闸断电,然后调用服务降级的方法并返回友好提示。
        先熔断,拒绝访问,再调用服务降级返回备选响应(友好提示),再恢复调用链路。
    
    
    3.  服务限流:flowlimit
    
        秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行。
复制代码

2 构建 cloud-provider-hystrix-payment8001

    单机版的注册服务中心7001:
复制代码

img

2.1 新建 mudule

        cloud-provider-hystrix-payment8001     
复制代码

2.2 pom

    <dependencies>
            <!--引入hystrix-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>
    
            <!--引入我们自定义的公共api jar包-->
            <dependency>
                <groupId>com.atguigu.springcloud</groupId>
                <artifactId>cloud-api-commons</artifactId>
                <version>${project.version}</version>
            </dependency>
    
            <!--web/actuator这两个一般一起使用,写在一起-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
            <!--监控-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>

            <!--eureka client-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
            </dependency>
    
            <!--热部署-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
                <scope>runtime</scope>
                <optional>true</optional>
            </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>
复制代码

2.3 yml

    server:
      port: 8001
    
    spring:
      application:
        name: cloud-provider-hystrix-payment
    
    eureka:
      client:
        register-with-eureka: true
        fetch-registry: true
        service-url: 
          # defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7003/eureka,
          # 为了方便测试,我就不用集群了
          defaultZone: http://eureka7001.com:7001/eureka
复制代码

2.4 主启动类

    @SpringBootApplication
    @EnableEurekaClient
    public class PaymentHystrixMain8001 {
        public static void main(String[] args) {
            SpringApplication.run(PaymentHystrixMain8001.class, args);
        }
    }
复制代码

2.5 业务类

1.  service
复制代码
    @Service
    public class PaymentService {
    
        //正常访问,肯定OK的方法
        public String paymentInfo_OK(Integer id){
            return "线程池:  "+ Thread.currentThread().getName()+"   paymentInfo_OK,  id:  "+id
                    +"\t"+"O(∩_∩)O哈哈~";
        }
    
        //模拟拥堵的情况
        public String paymentInfo_Timeout(Integer id){
    
            int timeNumber = 3;
    
            try {
                TimeUnit.SECONDS.sleep(timeNumber);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            return "线程池:  "+ Thread.currentThread().getName()+"   paymentInfo_Timeout,  id:  "+id
                    +"\t"+"╭(╯^╰)╮" + "耗时"+timeNumber+"秒钟";
        }
    }
复制代码
2.  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;
        }
    }
复制代码

2.6 测试

启动eureka7001
启动cloud-provider-hystrix-payment8001

访问模拟健康的方法:success的方法
    http://localhost:8001/payment/hystrix/ok/1 
复制代码

img

访问模拟有问题的方法:timeout的方法,耗时3秒
    http://localhost:8001/payment/hystrix/timeout/1
复制代码

img

2.7 以上述为根基平台,从正确 --> 错误 --> 降级熔断 --> 恢复。

    上述在非高并发情形下,还能勉强满足,
    
    1.  Jmeter压测测试:
    
        开启Jmeter,来20000个并发压死8001,20000个请求都去访问:
        http://localhost:8001/payment/hystrix/timeout/1
        
    2.  演示结果
    
        发现原来迅速响应的http://localhost:8001/payment/hystrix/ok/1 也开始卡顿起来。
        
        两个都在转圈圈。
        
    3.  因为
    
        http://localhost:8001/payment/hystrix/ok/1
        http://localhost:8001/payment/hystrix/timeout/1
        
        都在一个微服务里面,现在大量的资源被timeout占用,微服务必须集中资源去处理这些
        高并发的请求。那么ok方法,得到的资源就少,进而被影响。
        
        SpringBoot默认集成的是tomcat容器,里面有一个tomcat的线程池,高并发下没有多余的
        线程来分解压力和处理ok方法。
        每个线程都要等待3秒钟,才能拿到timeout的结果响应。那么2000个线程一起过来,
        Tomcat池子里的线程,马上就会被抢占完。
        
    4.  Jmeter压测结论:
    
        上面还只是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,
        最终导致消费端80不满意,服务端8001直接被拖死。
复制代码

3 新建消费者模块加入:cloud-consumer-fegin-hystrix-order80

        feign是服务调用,负载均衡工具。
        
        80用feign调用8001
        
        Hystrix一般用于80客户侧降级:本案例消费侧/服务侧都演示
复制代码

3.1 建 module

    cloud-consumer-feign-hystrix-order80
复制代码

3.2 pom

    <dependencies>
        <!--引入hystrix-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>com.atguigu.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</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-actuator</artifactId>
        </dependency>

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

        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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>
复制代码

3.3 yml

    server:
      port: 80
    
    eureka:
      client:
        # 表示不讲其注入Eureka作为微服务,它就是个客户端
        register-with-eureka: false
        service-url:
          defaultZone: http://eureka7001.com:7001/eureka/
    
      instance:
        instance-id: hystrix-order80
        prefer-ip-address: true
复制代码

3.4 主启动类

    @SpringBootApplication
    // @EnableEurekaClient 不向Eureka注册了,不是Eureka客户端
    @EnableFeignClients //作为Feign客户端
    public class OrderHystrixMain80 {
        public static void main(String[] args) {
            SpringApplication.run(OrderHystrixMain80.class, args);
        }
    }
复制代码

3.5 业务类

1.  PaymentHystrixService
复制代码
    @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);
    }
复制代码
2.  OrderHystrixController
复制代码
    @RestController
    @Slf4j
    public class OrderHystrixController {
    
        @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;
        }
    }
复制代码

3.6 测试

1.  http://localhost/consumer/payment/hystrix/timeout/1

    由于使用的是Feign作为客户端,默认1s没有得到响应就会报超时错误。
    这是正常的,我们本案例不去改正。
复制代码

img

2.  http://localhost/consumer/payment/hystrix/ok/1
    成功
复制代码

img

3.  http://localhost:8001/payment/hystrix/ok/1
复制代码

img

    http://localhost:8001/payment/hystrix/timeout/1
    
    自测成功:由于8001用的是eureka客户端,所以延迟三秒也不会报错。
复制代码

img

3.7 高并发测试

        2W个线程压8001
        
        消费端80微服务再去访问正常的OK微服务8001地址
        
        http://localhost/consumer/payment/hystrix/ok/1:
    
        要么转圈圈等待,要么消费端报超时错误。
        
        
        原因:
            8001同一层次的其他接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
            
            所以80此时调用8001,客户端访问响应缓慢,转圈圈。
            
        
        正因为有上述故障或不佳表现,才有了我们的降级/容错/限流等技术诞生。
复制代码

4 解决维度

    如何解决,解决的要求?
    
        超时导致服务器变慢(转圈)
            不再等待:系统繁忙,稍后再试。
            
        出错(宕机或程序运行出错)
            出错要有兜底:兜底的响应
            
    解决维度:
    
        对方服务8001超时了,调用者80不能一直卡死等待,必须有服务降级
        
        对方服务8001down机了,调用者80不能一直卡死等待,必须有服务降级
        
        对方服务8001 OK,调用者80自己出故障或有自我要求:自己的等待时间小于服务提供者
        自己处理降级。
复制代码

4.1 服务降级 : 服务生产者

    1.  服务降级:支付侧(生产者服务)
    
        设置自身调用超时时间的峰值,峰值内可以正常运行。
        超过了就需要有兜底的方法处理,做服务降级fallback。
        
        向调用方返回一个符合预期的,可处理的备选响应(FallBack)
        
    
    2.  业务类上启动@HystrixCommand 
复制代码
        //模拟拥堵的情况
        /**
         * 规定这个线程的超时时间是3s,3s后就由fallbackMethod指定的方法帮我“兜底”(服务降级)
         * @param id
         * @return
         */
        @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {
                @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",
                value = "3000")
        }) 
        public String paymentInfo_Timeout(Integer id){
            int timeNumber = 5;
            try {
                TimeUnit.SECONDS.sleep(timeNumber);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "线程池:  "+ Thread.currentThread().getName()+"   paymentInfo_Timeout,  id:  "+id
                    +"\t"+"╭(╯^╰)╮" + "耗时"+timeNumber+"秒钟";
        }
    
        public String paymentInfo_TimeoutHandler(Integer id){
            return "线程池:  "+ Thread.currentThread().getName()+"   paymentInfo_TimeoutHandler,  id:  "+id
                    +"\t"+"o(╥﹏╥)o";
        }
复制代码
    /**
     * 规定这个线程的超时时间是3s,3s后就由fallbackMethod指定的方法帮我“兜底”
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",
            value = "3000")
    }) 
    
    规定这个线程的超时时间是3s,超过3s就由fallbackMethod指定的方法帮我“兜底”
    服务降级:会自动调用@HystrixCommand中fallbackMethod指定的方法。
    
    3.  主启动类上要激活
        
        @EnableCircuitBreaker
复制代码
        @SpringBootApplication
        @EnableEurekaClient
        @EnableCircuitBreaker
        public class PaymentHystrixMain8001 {
            public static void main(String[] args) {
                SpringApplication.run(PaymentHystrixMain8001.class, args);
            }
        }

复制代码
    4.  测试:
        
        启动7001,80
复制代码

img

        超过3秒后,判断当前线程异常,执行服务降级。
    
    5.  好处
        系统就不会被超时程序越拖越慢。
        如果有拖慢系统的程序,就不会让他在继续执行下去了。
复制代码

4.2 程序报错时,能否服务降级

    1.  程序异常
复制代码
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public String paymentInfo_Timeout(Integer id){
    
        int age = 10/0;
        
        return "线程池:  "+ Thread.currentThread().getName()+"   paymentInfo_Timeout,  id:  "+id
                +"\t"+"╭(╯^╰)╮";
    }
    //服务降级
    public String paymentInfo_TimeoutHandler(Integer id){
        return "线程池:  "+ Thread.currentThread().getName()+"  系统繁忙请稍后再试,  id:  "+id
                +"\t"+"o(╥﹏╥)o";
    }
复制代码
    2.  测试:
复制代码

img

        遇到异常,直接执行服务降级。
    
    
    3.  结论:
        
        我们故意制造的两个异常:
            1.  int age = 10/0 计算异常
            2.  我们定义超时时间3s,服务运行5s的超时异常
        
        这些都会造成当前服务不可用了,就会进行服务降级,执行兜底的方案:
复制代码
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
复制代码
        fallbackMethod指定的方法
复制代码

4.3 服务降级 : 服务消费者

    cloud-consumer-fegin-hystrix-order80

    80订单微服务,也可以更好的保护自己,自己也一样可以进行客户端降级保护。
    
    即:服务降级即可以放在消费端/客户端也可以放在服务端。
    
    一般是放在客户端。
    
    注意:
        我们自己配置的热部署方式对java代码的改动明显,但是对
        @HystrixCommand内属性的修改,建议重启微服务。
        
    
    1.  修改yml文件
        开启feign客户端支持hystrix属性
复制代码

img

    2.  主启动类:@EnableHystrix
        开启Hystrix支持
复制代码

img

        注意:@EnableHystrix注解包含了在8001主配置类上加的@EnableCircuitBreaker注解
复制代码

img

    3.  业务类
复制代码
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public String paymentInfo_Timeout(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_Timeout(id);

        return result;
    }
    public String paymentInfo_TimeoutHandler(@PathVariable("id") Integer id){
        return "我是消费者80,对方支付系统繁忙,请10秒后再试,o(╥﹏╥)o";
    }
复制代码
    4.  测试
    
    我们先分析一下:
复制代码

img

    生产者微服务定义的规则:
        方法会休眠3秒,但是只要在5s内走完,就不会执行服务降级。
        
    消费者微服务定义的规则:
        在1.5s内,我没有得到响应,就会执行方法降级。
        
    两个服务的规则不同,结果会怎样呢?    
复制代码

img

    结果:消费者微服务在1.5s后执行服务降级。
复制代码

img

    5.  测试一下消费者微服务自己运行出错
复制代码

img

        测试结果:
            瞬间报错。
复制代码

img

5. 改进

5.1 面临的问题

    1.  我们现在服务降级的方法和业务处理的方法混杂在了一块,耦合度高。
    2.  如果每个接口,每个方法都需要一个“兜底”的方法,那么就会造成代码臃肿。
    
    所以我们需要一个:全局的服务降级,global fallback。
    需要特殊照顾的方法,我们再精确的配置服务降级。
复制代码

5.2 解决代码臃肿的问题

    没有配置过fallback,就找在该类上配置的defaultfallback,配置过的就找自己单独配置的。
    
    需要使用服务降级的方法还是要加上@HystrixCommand,只是不再单独指定一个降级方法。
复制代码

img

    这种解决思想:
    
        1:1每个方法配置一个服务降级方法,技术上可以,实际上**违规词**
        1:N除了各别重要核心业务有专属,其他普通的可以通过@DefaultProperties(defaultFallback = "")
        统一跳到一个服务降级方法处。
        
        通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量。
        
    测试:
复制代码

img

5.2 解决代码耦合度高的问题

    1.  抓住关键点
    
        在这个80微服务中,它没有注册进eureka注册中心成为eureka客户端。
复制代码

img

        而是通过openFeign,成为一个Feign客户端,通过Feign来进行微服务的调用和负载均衡。
        通过@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT") 这个注解
        找CLOUD-PROVIDER-HYSTRIX-PAYMENT这个微服务中的方法进行调用。
复制代码

img

        即:
            你只要是用Feign进行微服务的调用,那么一定有这个
            @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")注解的接口,
            这个接口就能实现调用其他微服务的操作.
            
    2.  我们将这个接口中的全部方法来进行统一的fallback服务降级
    
    
    3.  服务降级:
        客户端去调用服务端,碰上服务端宕机或关闭
        
        本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系。
        只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦。
复制代码

img

        未来我们可能遇到的异常:
            运行
            超时
            宕机
            
    4.  具体操作
        根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,
        重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里的方法进行异常处理。
        
        想要实现的效果是:
            正常的话就找指定的微服务中的方法。
            异常的话就找PaymentFallbackService,由它来进行统一的处理。
            
        修改PaymentHystrixService类:
复制代码

img

        将新建的用来处理服务降级的类,添加到IOC中并且配置到Feign客户端定义的接口中。
复制代码

img

    5.  测试:启动8001,7001,80
    
        都测试OK方法:是没有任何故障的
    
        1.  8001自测成功
复制代码

img

        2.  测试80调用8001的方法
复制代码

img

        3.  模拟宕机:关闭8001
            
            测试80调用8001的方法
复制代码

img

            发现执行的服务降级,而且我们没有在业务层为paymentInfo_OK()方法进行任何的服务降级处理。
            也没有加@HystrixCommand注解。
复制代码

img

            在业务层paymentInfo_OK()方法没有耦合的代码
            
        4.  刚才我们故意关闭8001,模拟宕机。
            80客户端回自己调用服务降级,返回提示。
            
            此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端
            不可用时也会获得提示信息而不会挂起耗死服务器。
            
        @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT", 
        fallback = PaymentFallbackService.class)和
        @Component
        public class PaymentFallbackService implements PaymentHystrixService{}
        结合解决服务降级在业务层产生耦合代码的情况。
复制代码

6 Hystrix 的服务熔断理论

    服务熔断也会触发服务降级
    
    类比保险丝,达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示。
    
    流程:服务降级--->进而熔断--->恢复调用链路
复制代码

6.1 熔断机制概述

    熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者
    响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。
    
    当再次检测到该节点微服务调用响应正常后,恢复调用链路。
    
    在SpringCloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况。
    
    当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。
    熔断机制的注解是@HystrxiCommand
        
    半开状态:
        比如说一个微服能承受100的并发量,某一时刻有500人同时访问。该服务就会崩掉,熔断,直接当掉。
        
        外部此时不能访问了,过了一会,发现没有那么高的并发量了。感觉并发量在我的承受之内了(100)。
        比如1s/72次,那么就试着这放开这些并发请求。
        
        放着放着发现能够适应当前的并发量了,我再把闸道合上。
        
        放着放着的状态就是半开状态,然后再把断路器合上变成打开的状态。
复制代码

6.2 Hystrix 服务熔断案例

    1.  修改模块:cloud-provider-hystrix-payment8001中的service层:
        
        正数:方法正常执行
        负数:抛出一个运行时异常,然后执行服务降级
复制代码
    //==================服务熔断
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback", commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled", value = "true"), //是否开启断路器
            
            ////当在配置时间窗口内达到此数量的失败后,进行短路。10个/10s 注意分母是10s。 默认20个/10s
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), 
            //短路多久以后开始尝试是否恢复,默认5s
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"), 
            //失败率达到多少后跳闸
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "60"), 
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        if(id < 0){
            throw new RuntimeException("******id 不能为负数");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName()+"\t"+"调用成功,流水号: "+serialNumber;
    }
    //服务降级方法
    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
        return "id 不能为负数,请稍后再试, o(╥﹏╥)o  id: "+id;
    }
复制代码
    2.  修改模块:cloud-provider-hystrix-payment8001中的controller层:   
    
        调用service中的熔断(CircuitBreaker)案例
复制代码
    //==========服务熔断
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        String result = paymentService.paymentCircuitBreaker(id);

        log.info("**********result: " + result);
        
        return result;
    }
复制代码
    3.  测试
        
        启动8001和7001
    
        1.  自测cloud-provider-hystrix-payment8001
        
            http://localhost:8001/payment/hystrix/circuit/1
            
            自测成功:
复制代码

img

        2.  模拟异常:参数传递一个负数
复制代码

img

            执行服务降级。
            
    4.  测试一下单位时间出错达到10次/10s以上
    
        在10s内执行了超过10次的异常请求:
        http://localhost:8001/payment/hystrix/circuit/-1
复制代码
    //当在配置时间窗口内达到此数量的失败后,进行短路。10个/10s 注意分母是10s。 默认20个/10s
    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"), 
复制代码
        发现触发了Hystrix的断路器,导致正确的请求也会触发服务降级。
复制代码

img

img

//短路多久以后开始尝试是否恢复:10s,默认5s
@HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds", value = "10000"),

        10s后再次尝试发送:http://localhost:8001/payment/hystrix/circuit/1
        
        又回复连接。
复制代码

img

6.3 Hystrix 服务熔断总结

    1. 熔断类型
    
        1.  熔断打开:
        请求不再进行调用当前服务,再有请求调用时将不会调用主逻辑,而是直接调用降级fallback。实现
        了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟效果。
        
        内部设置时钟一般为MTTR(平均故障处理时间),当打开熔断时长达到所设时钟则进入半熔断状态。
        
        
        2.  熔断关闭
        熔断关闭后,服务正常开始调用。
        
        3.  熔断半开
        部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务回复正常,关闭熔断。
        
    2.  断路器默认开启的条件
    
        1.  当(失败频率)满足一定的阈值时: > 20次/10s
        
        2.  当失败率达到一定的时候:10s内50%的失败请求。
        
        注意:两个条件要都满足,才会开启断路器。
            比如10s内出错21次,正确100次。
            满足情况1,不满足情况2.
            
            断路器也不会开启。
            
        3.  一段时间后,默认是5s,这个时候断路器从开启变成半开状态。会放行一些请求,
            如果成功,断路器关闭,若失败继续开启。
        
        
    3.  熔断器开启式,主逻辑怎么恢复呢?
复制代码

img

7. 服务监控 HystrixDashboard

流程:官网

    官网流程图
复制代码

7.1 HystrixDashboard 模块

    除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(HyStrix Dashboard)。Hystrix
    会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括
    每秒执行多少请求,多少成功,多少失败等。
    NetFlix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。
    SpringCloud也提供了HystrixDashBoard的整合,对监控内容转换成可视化界面。
    

1.  新建Module

    cloud-consumer-hystrix-dashboard
    
2.  POM
复制代码
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
        </dependency>

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

        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </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>
复制代码
3.  yml:加个端口
复制代码

img

4.  主启动类

    @EnableHystrixDashboard //开启Hystrix仪表盘
复制代码
    @SpringBootApplication
    @EnableHystrixDashboard //开启Hystrix仪表盘
    public class HystrixDashboardMain9001 {
        public static void main(String[] args) {
            SpringApplication.run(HystrixDashboardMain9001.class, args);
        }
    }
复制代码
5.  所有的Provider微服务提供类(8001/8002/8003)都需要监控依赖配置
复制代码
        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
复制代码
    都配置过了。
    
    后续我么就可以用9001监控微服务的提供者,来看看它同一时刻被哪些服务调用。成功多少,
    失败多少,健康状况等。
    
6.  启动9001
    http://localhost:9001/hystrix
复制代码

img

7.2. Hystrixdashboard 案例

    1.  微服务要想被监控一掉要有这个依赖
复制代码
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

复制代码
    2.  新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路劲。
        
        否则会报错:
        Unable to connect to Command Metric Stream
复制代码

img

        在被监控的微服务出添加如下配置
复制代码
    /**
     * 此配置是为了服务监控而配置,与服务容错本身无关,SpringCloud升级后的坑
     * ServletRegistrationBean因为springboot的默认路径不是“/hystrix.stream”,
     * 只要在自己的项目里配置夏敏的servlet即可。
     * @return
     */
    @Bean
    public ServletRegistrationBean getServlet(){
        HystrixMetricsStreamServlet streamServlet = new HystrixMetricsStreamServlet();
        ServletRegistrationBean registrationBean = new ServletRegistrationBean(streamServlet);

        registrationBean.setLoadOnStartup(1);
        //9001监控8001的监控地址
        registrationBean.addUrlMappings("/hystrix.stream");
        registrationBean.setName("HystrixMetricsStreamServlet");

        return registrationBean;
    }
复制代码

img

    3.  测试监控
    
        1.  启动7001 Eureka服务器
            8001,9001
        
        2.  9001监控8001
            http://localhost:8001/hystrix.stream
            
            测试平台取名:T3
复制代码

img

        3.  测试地址
            http://localhost:8001/payment/hystrix/circuit/-1
            http://localhost:8001/payment/hystrix/circuit/1
            
            8001即被正确的访问过,也被错误的访问过。
复制代码

img

            多点几次

        4.  打开监测
复制代码

img

img

img

        多点几次错误的请求:断路器会打开
        
        会有个红色的圈
复制代码

img

        5.  如何看这个图
        
            7色1圈
复制代码

img

            每一种故障对应一个名字
复制代码

img

            实心圆:
            颜色变化代表了实例的健康程度,它的健康程度从绿色<黄色<橙色<红色递减。
            实心圆越大说明流量越大,所以通过该实心圆的展示,就可以在大量的实例中快速的
            发现故障实例和高压力实例。
            
            曲线/折线:
            用来记录两分钟内流量的相对变化,可以通过它观察到流量的上升和下降趋势。
            
        6.  例子
复制代码

img

推荐阅读