SpringCloud(4)--服务降级(Hystrix、降级、熔断、监控)
MOKE 2020-07-14 AM loading 0条

服务降级

首先需要了解一个概念,服务雪崩

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

Hystrix

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,它能够保证在一个依赖出现问题的情况下,不会导致整体服务失败,避免级联故障(雪崩)。

主要作用

  • 服务降级:比如当某个服务繁忙,不能让客户端的请求一直等待,应该立刻返回给客户端一个备选方案。
  • 服务熔断:当某个服务出现问题,卡死了,不能让用户一直等待,需要关闭所有对此服务的访问然后调用服务降级。
  • 服务限流:比如秒杀场景,不能访问用户瞬间都访问服务器,限制一次只可以有多少请求。
  • 接近实时的监控

Hystrix

服务降级

比如当某个服务繁忙,不能让客户端的请求一直等待,应该立刻返回给客户端一个备选方案。

出现降级的情况:

  • 程序运行异常
  • 超时
  • 发生服务熔断
  • 线程池/信号量打满

创建带降级机制的pay模块

  1. 名称: cloud-provider-hystrix-payment8001

  2. pom文件

         <!-- hystrix-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>
  3. 配置文件,为了减少资源占用,这里使用单机版 Eureka

            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
            #      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
  4. 主启动类

            @SpringBootApplication
            @EnableEurekaClient
            public class PaymentHystrixMain8001 {
                public static void main(String[] args){
                    SpringApplication.run(PaymentHystrixMain8001.class,args);
                }
            }
  5. service,节约时间不写接口

            @Service
            public class PaymentService {
    
                /*
                正确的方法
                 */
                public String paymentInfo_OK(Integer id){
                    return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id:"+id+"\t"+"???";
                }
    
                /*
                会超时报错的方法
                 */
                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"+"!!!";
                }
            }
  6. controller

            @RestController
            @Slf4j
            @RequestMapping("/payment")
            public class PaymentController {
                @Resource
                private PaymentService paymentService;
                @Value("${server.port}")
                private String serverPort;
    
                @GetMapping("/hystrix/ok/{id}")
                public String paymentInfo_OK(@PathVariable("id") Integer id){
                    String result = paymentService.paymentInfo_OK(id);
                    log.info("****result:"+result);
                    return result;
                }
    
                @GetMapping("/hystrix/timeout/{id}")
                public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
                    String result = paymentService.paymentInfo_TimeOut(id);
                    log.info("****result:"+result);
                    return result;
                }
            }
  7. 启动项目,并发度低的情况下可以使用,我们使用 JMeter 并发2w个请求TimeOut接口,然后访问OK接口会发现也需要进行等待。这就是因为被压测的方法它占用了服务器大部分资源, 导致其他请求也变慢了。

    image-20200712174910073


创建带降级的order模块

  1. 名字: cloud-consumer-feign-hystrix-order80

  2. pom

         <!-- openfeign -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-openfeign</artifactId>
            </dependency>
         <!-- hystrix-->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
            </dependency>
  3. 配置文件

            server:
              port: 80
    
            eureka:
              client:
                register-with-eureka: false
                service-url:
                  defaultZone: http://eureka7001.com:7001/eureka
  4. 主启动类

            @SpringBootApplication
            @EnableFeignClients
            public class OrderHystrixMain80 {
                public static void main(String[] args){
                    SpringApplication.run(OrderHystrixMain80.class,args);
                }
            }
  5. 远程调用pay模块的接口

            @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);
            }
  6. controller:

            @RestController
            @Slf4j
            @RequestMapping("/consumer")
            public class OrderHystrixController {
                @Resource
                private PaymentHystrixService paymentHystrixService;
    
                @GetMapping("/payment/hystrix/ok/{id}")
                public String paymentInfo_OK(@PathVariable("id") Integer id){
                    String result = paymentHystrixService.paymentInfo_OK(id);
                    return result;
                }
    
                @GetMapping("/payment/hystrix/timeout/{id}")
                public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
                    String result = paymentHystrixService.paymentInfo_TimeOut(id);
                    return result;
                }
            }
  7. 测试,启动order模块,再次压测2万并发pay模块的timeout接口,发现order访问也变慢了,没有配置Ribbon超时参数还会调用报错。

    image-20200712180843334

原因

出现这种现象的原因是,8001的tomcat容器中线程池的工作线程被timeout接口挤占完毕,80此时调用8001中的ok接口就无法得到处理。

解决

  • 8001超时/宕机:80不能一直等待,需要进行服务降级
  • 8001没问题:80自己出故障或需要自己的等待时间小于服务提供者,则需要自己处理降级

配置服务降级

修改pay模块
  1. 为service会延迟的方法添加 @HystrixCommand 注解

            /*
            会超时报错的方法
             */
            @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
                @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "3000")
         })//设置调用超时时间的峰值,超时则会调用设置的paymentInfo_TimeOutHandler方法
            public String paymentInfo_TimeOut(Integer id){
                int timeNumber = 5;
                //int age = 10/0;
                //其他异常也会触发服务降级
                try{
                    TimeUnit.SECONDS.sleep(timeNumber);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "线程池:"+Thread.currentThread().getName()+" paymentInfo_TimeOut,id:"+id+"\t"+"!!!";
            }
            public String paymentInfo_TimeOutHandler(Integer id){
                return "线程池:"+Thread.currentThread().getName()+" 系统繁忙,请稍后再试,id:"+id+"\t"+"。。。";
            }
  2. 主启动类上,添加激活hystrix的注解@EnableCircuitBreaker

            @SpringBootApplication
            @EnableEurekaClient
            @EnableCircuitBreaker
            public class PaymentHystrixMain8001 {
                public static void main(String[] args){
                    SpringApplication.run(PaymentHystrixMain8001.class,args);
                }
            }
  3. 访问8001的timeout接口,可以看到触发了降级

    image-20200712183040684


修改order模块

一般服务降级,都是放在客户端。此外,对于@HystrixCommand内的修改,建议重启微服务。

  1. 修改配置文件,feign开启hystrix支持

            feign:
              hystrix:
                enabled: true
  2. 主启动类添加@EnableHystrix,启用hystrix

            @SpringBootApplication
            @EnableFeignClients
            @EnableHystrix
            public class OrderHystrixMain80 {
                public static void main(String[] args){
                    SpringApplication.run(OrderHystrixMain80.class,args);
                }
            }
  3. 修改controller【pay模块timeout改为3s,限制5s】

            @GetMapping("/payment/hystrix/timeout/{id}")
            @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")
            })
            public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
                String result = paymentHystrixService.paymentInfo_TimeOut(id);
                return result;
            }
            public String paymentInfo_TimeOutHandler(Integer id){
                return "本80系统繁忙,请稍后再试,id:"+id+"\t"+"。。。";
            }
  4. 测试,order访问pay需要3s,而自己只运行1.5s,所以会在order中降级

    image-20200712185843234

  5. 目前两个降级模块出现的问题

    • 降级方法与业务方法写在了一块,耦合度
    • 每个业务方法都写了一个降级方法,重复代码

重复代码的问题

解决:

配置一个全局的降级方法,所有方法都可以走这个降级方法,至于某些特殊创建,再单独创建方法。

  1. 创建一个全局方法

            //全局降级方法
            public String paymentInfo_Global_FallbackMethod(){
                return "Global异常处理信息,请稍后再试,。。。";
            }
  2. 使用@DefaultProperties注解指定其为全局降级方法(默认降级方法)

            @RestController
            @Slf4j
            @RequestMapping("/consumer")
            @DefaultProperties(defaultFallback = "paymentInfo_Global_FallbackMethod")
            public class OrderHystrixController {
                ...
            }
  3. 业务方法不指定具体降级方法,就会使用默认降级方法

            @HystrixCommand
            public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
                String result = paymentHystrixService.paymentInfo_TimeOut(id);
                return result;
            }
  4. 测试

    image-20200713150127160


代码耦合度的问题

解决:

修改order模块,这里开始,pay模块就不服务降级了,服务降级写在order模块即可。

  1. PaymentHystrixService接口是远程调用pay模块的,我们这里创建一个类实现改接口进行统一降级处理。

            @Component
            public class PaymentFallbackService implements PaymentHystrixService{
                @Override
                public String paymentInfo_OK(@PathVariable("id") Integer id) {
                    return "----PaymentFallbackService fall back-paymentInfo_OK,...";
                }
    
                @Override
                public String paymentInfo_TimeOut(@PathVariable("id") Integer id) {
                    return "----PaymentFallbackService fall back-paymentInfo_TimeOut,...";
                }
            }
  2. 确保配置文件中开启了feign的hystrix支持,最后让PaymentHystrixService的实现类生效,修改接口的@FeignClient注解

    @FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)
  3. 启动测试,启动order和pay正常访问OK接口

    image-20200713151605380

    如果将pay服务关闭,order再次访问OK接口(没有其他降级方法)

    image-20200713151631251

解决耦合后又出现的问题:

这样虽然解决了代码耦合度问题,但是又出现了过多重复代码的问题,每个方法都有一个降级方法


服务熔断

当某个服务出现问题:

  • 需要关闭所有对此服务的访问【断路器】,然后调用服务降级

  • 当检测到该服务响应正常后,恢复调用链路

继续修改pay模块

  1. 修改Payservice接口,添加服务熔断相关的方法

            //服务熔断
            @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
                    @HystrixProperty(name = "circuitBreaker.enable",value = "true"),//是否启用断路器
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),//请求次数
                    @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();//hutool工具
                return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
            }
            public String paymentCircuitBreaker_fallback(Integer id){
                return "id:"+id+",不能为负数,请稍后再试,。。。";
            }

    配置服务降级方法就不说了,对于配置的参数表示:

    • 如果并发超过10个或者10个并发中失败了6个,就会开启断路器
    • 在时间窗口期10秒之内会尝试请求,如果请求成功就会关闭断路器
  2. 修改controller,添加一个测试方法

            //服务熔断
            @GetMapping("/circuit/{id}")
            public String paymentCircuitBreaker(@PathVariable("id") Integer id){
                String result = paymentService.paymentCircuitBreaker(id);
                log.info("****result:"+result);
                return result;
            }
  3. 测试:

    • 多次访问,并且错误率超过60%:image-20200713160340776

    • 此时服务熔断,此时即使访问正确的也会报错:

      image-20200713160412692

    • 等待窗口期过了,取消熔断

      image-20200713160441201


总结补充

Hystrix所有可配置的属性

此外,所有的参数配置可以查看HystrixCommandProperties类当中的成员变量。

熔断整体流程

  1. 请求进来,首先查询缓存,如果缓存有,直接返回;如果缓存没有,则到2.

  2. 查看断路器是否开启,如果开启的,Hystrix直接将请求转发到降级返回,然后返回;如果断路器是关闭的,则到3。

  3. 判断线程池等资源是否已经满了,如果已经满了也会走降级方法,如果资源没有满,则到4。

  4. 判断我们使用的什么类型的Hystrix,决定调用构造方法还是run方法,然后处理请求

  5. 然后Hystrix将本次请求的结果信息汇报给断路器,断路器收到信息,判断是否符合开启或关闭断路器的条件。

  6. 如果本次请求处理失败,又会进入降级方法;如果处理成功,判断处理是否超时,如果超时了也进入降级方法。没有超时,则本次请求处理成功,将结果返回给controller。


服务监控

HystrixDashboard

HystrixDashboard是Hystrix提供的准实时调用监控,它记录了所有Hystrix发起的请求的执行信息,并以图形化的形式显示。

HystrixDashboard的使用

  1. 创建项目,名称为cloud-consumer-hystrix-dashboard9001

  2. pom文件

         <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
            </dependency>
  3. 配置文件

            server:
              port: 9001
  4. 主启动类

            @SpringBootApplication
            @EnableHystrixDashboard
            public class HystrixDashboarMain9001 {
                public static void main(String[] args){
                    SpringApplication.run(HystrixDashboarMain9001.class,args);
                }
            }
  5. 修改所有pay模块(8001,8002,8003...)添加一个pom依赖,我们之前都配过了

         <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
  6. 启动9001即可,访问: http://localhost:9001/hystrix

    image-20200713164722127

  7. 此时仅仅是可以访问HystrixDashboard,并不代表已经监控了8001,8002。如果要监控,还需要配置(8001为例):

    • 8001的主启动类添加

         /**
           * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
           * ServletRegistrationBean因为SpringBoot的默认路径不是 “/hystrix.stream"
           * 只要在自己的项目里配置上下的servlet就可以了
           */
          @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;
          }
  8. 到此,可以启动服务启动7001,8001,9001,然后在web界面,指定9001要监控8001:

    image-20200713165237608

    image-20200713165616190

标签: SpringCloud

非特殊说明,本博所有文章均为博主原创。

评论啦~


召唤看板娘