SpringCloud-2.0:(11. 服务降级 - Hystrix - 解决问题)

Posted ABin-阿斌

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringCloud-2.0:(11. 服务降级 - Hystrix - 解决问题)相关的知识,希望对你有一定的参考价值。

上一篇 :10. 服务降级 - Hystrix - 引出问题

下一篇 :12. 服务网关 - Gateway

  • 声明:
    • 原文作者:csdn:yuan_404

文章目录

1 . 上一篇的问题与解决办法

1.1 问题

  1. 超时导致服务器变慢(转圈)

  2. 出错(宕机或程序运行出错)

1.2 解决

  1. 对方服务(8007)超时了,调用者(80)不能一直卡死等待,必须有服务降级

  2. 对方服务(8007)宕机了,调用者(80)不能一直卡死等待,必须有服务降级

  3. 对方服务(8007)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

  4. 服务降级
    ========

  • 哪些情况会触发服务降级
  1. 程序运行异常
  2. 服务超时
  3. 服务熔断触发服务降级
  4. 线程池 / 信号量打满

2.1 配置 Provider 提供端

  • 服务降级的注解 @HystrixCommand

  • 先从自身找问题,设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要服务降级 fallback

  • 所以需要对 Provider - 8007 进行超时设置

  1. 在业务类中启用 Fallback

    //失败
    @Override
    /** 配置需要进行 FallBack 的方法 */
    @HystrixCommand(
            // fallbackMethod :出错后处理方法的方法名
            fallbackMethod = "paymentInfo_TimeOutHandler",
            // 方法设置超时时间为 2000 ms => 2 s
            // 也即该方法在给时间内执行完即正常,超过该时间则执行下面的异常方法
            commandProperties = @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
                                value = "2000")
    )
    public String paymentInfo_TimeOut(Integer id)
        int timeNumber = 3;
        try  TimeUnit.SECONDS.sleep(timeNumber); catch (Exception 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ㄒ)/~~";
    
    
    

  2. 在主启动类上,添加:@EnableCircuitBreaker

  3. 启动测试

  • 总结

    • 不管是服务超时 ,还是运行异常都会执行这个异常处理的方法。
    • 如果对@HystrixCommand内属性的修改建议直接重启微服务,不使用热部署,因为有的时候热部署识别不到这一块内容的改变

2.2 配置 Consumer 消费端

  • Consumer 消费端,也需要更好的保护自己,也需要进行客户端降级保护
  • 对 Consumer-feign-hystrix-80 进行修改
  1. 修改 YML

    feign:
      hystrix:
        #如果处理自身的容错就开启。开启方式与生产端不一样。
        enabled: true
    
    
  2. 在主启动类上配置 :@EnableHystrix

  3. 修改 Controller

    @GetMapping("/consumer/payment/hystrix/timeout/id")
    @HystrixCommand(
            fallbackMethod = "paymentTimeOutFallbackMethod",
            commandProperties = @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",
                    value = "1500")
    )
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id)
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        log.info("*******result:"+result);
        return result;
    
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id)
        return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
    
    
    

  4. 启动测试

上述方法产生的问题
- 每个业务方法对应一个处理的方法,代码膨胀
- 业务方法和降级的方法放在一起,代码混乱

2.3 解决办法 - 代码膨胀

  • 在 Controller 上配置默认处理方法

    @DefaultProperties(defaultFallback = “”)

  • 配置这个注解之后,当前 Controller 中的方法

  • 如果有具体配置对应的处理方法 (就像 2.1、2.2 中配置的那样) 就执行 配置的 对应的 处理方法
  • 如果没有执行处理方法,就都执行一个默认的处理方法
  • 在实际使用中,一般只有很关键的方法才配置专门的处理方法,其他大多数都是用默认的。
  1. 修改 Consumer-feign-hystrix-80 的 Controller

    @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
    public class OrderHystrixController 
    
      	 ……
      	 
        /** 全局的 默认的 FallBack 处理方法 */
        public String payment_Global_FallbackMethod()
            return "Global异常处理信息,请稍后再试,(┬_┬)";
        
    
    
    

  2. 重启测试

2.4 解决办法 - 代码混乱

  • 所有的业务逻辑和 FallBack 方法都写在 Controller 中,耦合度很高。
  • 因为之前 运行时异常 和 超时异常的处理都演示了,下面就换一个异常情况 —— Provider 突然宕机了(解耦的思路都是一样的)。
  • 实际中常遇到的异常情况 :运行时异常、超时异常、宕机
  • 所以下面的修改都在 Consumer-feign-hystrix-80 中
  • 思路

为Feign客户端定义的 Service 接口添加一个服务降级处理的实现类即可实现解耦

  1. 新建一个 Service 接口 :PaymentFallbackService

    根据 cloud-consumer-feign-hystrix-order80 已经有的 PaymentHystrixService 接口,重新新建一个类 PaymentFallbackService 实现该接口,统一为接口里面的方法进行异常处理

    /** 用于处理 FallBack  */
    @Component
    public class PaymentFallbackService implements PaymentHystrixService
        @Override
        public String paymentInfo_OK(Integer id) 
            return "---PaymentFallbackService - FallBack - paymentInfo_OK - /(ㄒoㄒ)/~~";
        
    
        @Override
        public String paymentInfo_TimeOut(Integer id) 
            return "---PaymentFallbackService - FallBack - paymentInfo_TimeOut - /(ㄒoㄒ)/~~";
        
    
    
    
  2. 修改 YML

    这个之前已经添加过了,但是实际使用中不能忘记这一步

    feign:
      hystrix:
        #如果处理自身的容错就开启。开启方式与生产端不一样。
        enabled: true
    
    
  3. 修改原有的 Service 接口 :PaymentHystrixService

    @FeignClient(
        value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",
        // 这个属性的值就是刚刚写的 FallBack Service 的类对象
        fallback= PaymentFallbackService.class)
    
    

  4. 重启测试

3 . 服务熔断

3.1 什么是服务熔断

  • 类比保险丝

  • 熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。

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

  • 在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,
    当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand。

  • 大神论文 :https://martinfowler.com/bliki/CircuitBreaker.html

3.2 使用

  • 修改 cloud-provider-payment-Hystrix-8007
  1. 修改 PaymentService

    // ========= 服务熔断 =========
    public String paymentCircuitBreaker(@PathVariable("id") Integer id);
    
    
  2. 修改 PaymentServiceImpl

    // ========= 服务熔断 =========
    @Override
    @HystrixCommand(
            // 兜底的方法
            fallbackMethod = "paymentCircuitBreaker_fallback",
            commandProperties = 
                // 是否开启断路器
                @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),
                // 请求次数,表示每 10 次请求失败率达到下面的配置失败率,则熔断
                @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),
                // 时间范围
                @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),
                // 失败率达到多少后跳闸,这里表示 60 %
                @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 不能负数,请稍候再试,(┬_┬)/~~     id: " +id;
    
    
    

    电路断开和闭合的精确方式如下:

    1. 假设整个电路的 请求次数 是否达到某个阈值
      (HystrixCommandProperties.circuitBreakerRequestVolumeThreshold() )…
    2. 假设 误差百分比 超过阈值误差百分比
      (HystrixCommandProperties.circuitBreakerErrorThresholdPercentage() )…
    3. 然后断路器从 闭合断开
    4. 当它断开时,它会使对该断路器的 所有请求短路
    5. 过了一段时间
      ( HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds() ),下一个单一请求被允许通过(这是半开放状态)。
      - 如果请求失败,断路器将在休眠窗口期间返回打开状态
      - 如果请求成功,断路器将转换为闭合状态,1 中的逻辑将再次接管
  3. 修改 PaymentController

    // ====== 服务熔断 =====
    @GetMapping("/payment/circuit/id")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id)
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("*******result:"+result);
        return result;
    
    
    
  4. 启动服务,测试

  • 测试输入正数

  • 测试输入负数

  • 执行 10 次,让错误率超过 60 %,故意让断路器打开

    当过了 10000 ms 之后,就进入了半开阶段

3.3 总结

  • 之前给的 “大神论文” 中提到

  • 熔断类型
  1. 熔断打开
    请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入熔断状态
  2. 熔断关闭
    熔断关闭不会对服务进行熔断
  3. 熔断半开
    部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断
  • 断路器在什么情况下起作用

涉及到断路器的三个重要参数: 快照时间窗请求总数阀值错误百分比阀值

  1. 快照时间窗 : 断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
  2. 请求总数阀值 : 在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystri命令的调用次数不足20次,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  3. 错误百分比阀值 : 当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。
  • 断路器默认开启或者关闭的条件
  1. 当满足一定阀值的时候(默认10秒内超过20个请求次数)
  2. 当失败率达到一定的时候(默认10秒内超过50%请求失败)
  3. 到达以上阀值,断路器将会开启
  4. 当开启的时候,所有请求都不会进行转发
  5. 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5
  • 断路器打开之后
  1. 再有请求调用的时候,将不会调用主逻辑,而是直接调用降级falback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,以减少相应延迟的效果。
  2. 原来的主逻辑要如何恢复呢?
    对于这一问题,hystrix也为我们实现了自动恢复功能。
    当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,
    当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时。
  • 官网断路器流程图

  • 所有的配置


4 . 服务监控

  • HystrixDashboard
  • Hystri 会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。
  • Netflix 通过 hystrix-metrics-event-stream 项目实现了对以上指标的监控。Spring Cloud 也提供了 Hystrix Dashboard 的整合,对监控内容转化成可视化界面。

4.1 环境搭建

  1. 新建一个模块 :cloud-consumer-hystrix-dashboard-9001

  2. 修改 POM

    <dependencies>
        <!--新增hystrix dashboard-->
        <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-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

    server:
      port: 9001
    
    
  4. 编写主启动类

    @SpringBootApplication
    @EnableHystrixDashboard
    public class HystrixDashboardMain9001 
        public static void main(String[] args) 
            SpringApplication.run(HystrixDashboardMain9001.class, args);
        
    
    
    
  5. 启动该模块,测试

    访问 :http://localhost:9001/hystrix

4.2 修改被监控的 Provider

  • 修改 cloud-provider-payment-Hystrix-8007
  1. 确保所有需要监控的服务模块,都进入该依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
    
    
  2. 修改主启动类

    直接添加到 main 方法的下面

    @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;
    
    
    

    如果不修改,就会报错

  3. 启动服务,测试

    http://localhost:8001/hystrix.stream

4.3 仪表盘含义

5 . Hystrix 流程图

以上是关于SpringCloud-2.0:(11. 服务降级 - Hystrix - 解决问题)的主要内容,如果未能解决你的问题,请参考以下文章

SpringCloud-2.0-周阳:(12. 服务网关 - Gateway)

SpringCloud-2.0-周阳(23. 熔断降级 - Sentinel)

SpringCloud-2.0 (9. 负载均衡 - OpenFeign)

SpringCloud-2.0-周阳(22. 流量监控 - Sentinel)

SpringCloud-2.0-周阳(24. 分布式事务 - Seata)

SpringCloud-2.0(7. 服务注册发现 - Consul)