springcloud-Hystrix断路器02(六)

服务降级

  1. 主要用到@HystrixCommand注解.
  • 先从cloud-provider-hystrix-payment8001自身找问题
  1. 设置自身调用超时时间的峰值, 超过了需要有兜底的方法处理, 作服务降级fallback.
  2. 修改8001部分代码
    • 主启动类激活: @EnableCircuitBreaker
      @EnableCircuitBreaker
      @EnableEurekaClient
      @SpringBootApplication
      public class PaymentHystrixMain8001 {
      
          public static void main(String[] args) {
              SpringApplication.run(PaymentHystrixMain8001.class, args);
          }
      }
    • service业务类
      • 一旦调用服务方法失败并抛出了错误信息后, 会自动调用@HystrixCommand标注好的fallback方法
        @Service
        public class PaymentServiceImpl implements PaymentService {
        
            @Override
            public String paymentInfo_OK(Integer id) {
                return "线程池: " + Thread.currentThread().getName() + " paymentInfo_OK, id: " + id;
            }
        
            //fallbackMethod属性中填写兜底方法
            @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {
                    //3秒内完成即为正常逻辑
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
            })
            @Override
            public String paymentInfo_Timeout(Integer id) {
        
                //这里测试, 超时会调用服务降级, 出错也会
                int timeNumber = 1;
                int age = 10 / 0;
                try{
                    TimeUnit.SECONDS.sleep(timeNumber);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        
                return "线程池: " + Thread.currentThread().getName() + " paymentInfo_Timeout, id: " + id + ", 耗时: " + timeNumber;
            }
        
        
            public String paymentInfo_TimeoutHandler(Integer id) {
        
                return "线程池: " + Thread.currentThread().getName() + " 系统繁忙, 请稍后再试, id: " + id + "/(ㄒoㄒ)/~~";
            }
        }
  • 在消费者cloud-consumer-feign-hystrix-order80这里作服务降级.
  1. 其实一般服务降级都是在客户端做的, 这样可以更好的保护自己.
  2. yml中要开启Hystrix
    feign:
      hystrix:
        enabled: true
  3. 主启动类: @EnableHystrix
    @EnableHystrix
    @SpringBootApplication
    @EnableFeignClients
    public class OrderHystrixMain80 {
        public static void main(String[] args) {
            SpringApplication.run(OrderHystrixMain80.class,args);
        }
    }
  4. 业务类
    • controller
      @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;
          }
      
          @HystrixCommand(fallbackMethod = "paymentTimeoutFallbackMethod", commandProperties = {
                  @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
          })
          @GetMapping("/consumer/payment/hystrix/timeout/{id}")
          public String paymentInfo_Timeout(@PathVariable("id") Integer id) {
              String result = paymentHystrixService.paymentInfo_Timeout(id);
              return result;
          }
      
          public String paymentTimeoutFallbackMethod(@PathVariable("id") Integer id) {
              return "我是消费者80, 对方支付系统繁忙, 请10秒后重试 /(ㄒoㄒ)/~~";
          }
      }
  • 解决膨胀与耦合
  1. 目前出现的问题
    • 兜底的代码跟业务逻辑混到一起了, 代码耦合度低.
    • 每个业务方法对应一个兜底的方法, 代码太膨胀
  2. 解决代码膨胀
    • 用到注解@DefaultProperties
    • 在具体的方法中, 如果只标有@HystrixCommand, 则使用defaultFallback
    • 但若在@HystrixCommand中指明了fallbackMethod, 则使用自己指明的.
    • 这样通用的和独享的各自分开, 避免了代码膨胀, 合理减少了代码量.
      @RestController
      @Slf4j
      @DefaultProperties(defaultFallback = "payment_Global_FallbackMethod", commandProperties = {
              @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
      })
      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;
          }
      
          @HystrixCommand
          @GetMapping("/consumer/payment/hystrix/timeout/{id}")
          public String paymentInfo_Timeout(@PathVariable("id") Integer id) {
              String result = paymentHystrixService.paymentInfo_Timeout(id);
              return result;
          }
      
          public String payment_Global_FallbackMethod() {
      
              //下面是全局fallback方法
              return "Global异常处理信息,请稍后再试,(┬_┬)";
          }
      }
  3. 解决代码耦合
    • 只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦合
    • 本例在客户端80完成服务降级处理.
    • 首先写一个类继承PaymentHystrixService, 在其中填写各个功能的降级处理方法.
      @Component
      public class PaymentFallbackService implements PaymentHystrixService {
          @Override
          public String paymentInfo_OK(Integer id) {
              return "---PaymentFallbackService fall back paymentInfo_OK";
          }
      
          @Override
          public String paymentInfo_Timeout(Integer id) {
              return "------PaymentFallbackService paymentInfo_Timeout";
          }
      }
    • 然后在PaymentHystrixService的@FeignClient的fallback属性中填上该实现类
      @Component
      @FeignClient(value="cloud-provider-hystrix-payment", fallback = PaymentFallbackService.class )
      public interface PaymentHystrixService {
      
          @GetMapping("/payment/hystrix/ok/{id}")
          String paymentInfo_OK(@PathVariable("id") Integer id);
      
          @GetMapping("/payment/hystrix/timeout/{id}")
          String paymentInfo_Timeout(@PathVariable("id") Integer id);
      }
    • 这样就实现了解耦合, 
  4. 注: 我们面对的异常通常有
    • 运行时异常
    • 超时
    • 宕机

 服务熔断

  • 熔断机制概述
  1. 熔断机制是应对雪崩效应的一种微服务链路保护机制. 当扇出链路的某个微服务出错不可用或响应时间太长时, 会进行服务的降级, 进而熔断该节点微服务的调用, 快速返回错误的响应信息.
  2. 当检测到该节点微服务调用响应正常后, 会恢复调用链路.
  3. Hystrix实现熔断机制
    • Hystrix会监控微服务间调用的状况, 当失败的调用到一定阙值, 缺省是5秒内20次调用失败, 就会启动熔断机制.
    • 熔断机制的注解也是@HystrixCommand
  • 实操
  1. 修改支付微服务: cloud-provider-hystrix-payment8001
    • service方法
          //服务熔断
          //服务熔断
          @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",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 paymentCircuitBreaker(@PathVariable("id") Integer id){
              if (id < 0){
                  throw new RuntimeException("*****id 不能负数");
              }
              String serialNumber = IdUtil.simpleUUID(); // UUID.randomUUID().toString()
      
              return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
          }
      
          public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
              return "id 不能负数,请稍候再试,(┬_┬)/~~     id: " +id;
          }
    • 在其对应的controller方法中PaymentController添加
          //---服务熔断
          @GetMapping("/payment/circuit/{id}")
          public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
              String result = paymentService.paymentCircuitBreaker(id);
              log.info("****result" + result);
              return result;
          }
  2. 测试
    • 正确的: http://localhost:8001/payment/circuit/11
    • 错误的: http://localhost:8001/payment/circuit/-11
    • 我们多次访问错误的, 之后访问正确的, 发现刚开始也不能显示正确页面, 再访问几次正确的后, 正确率高了, 才慢慢可以显示正确页面.
    • 这就是 服务的熔断 -> 服务降级 -> 恢复调用链路
  • 服务熔断总结
  1. 熔断类型
    • 熔断打开: 请求不再进行调用当前服务, 内部设置时钟一般为MTTR(平均故障处理时间), 当打开时长达到所设时钟则进入熔断状态
    • 熔断关闭
    • 熔断半开: 部分请求根据规则调用当前服务, 如果请求成功且符合规则则认为当前服务恢复正常, 关闭熔断.
  2. 断路器在什么情况下开始起作用
    • 快照时间窗口: 断路器确定是否打开需要统计一些请求和错误数据, 而统计的时间范围就是快照时间窗, 默认10秒
    • 请求总数阈值: 在快照时间窗内,, 必须满足请求总数阈值才有资格熔断. 默认为20
      • 意味着在10秒内, 如果该hystrix命令的调用次数不足20次, 即使所有的请求都超时或其他原因失败, 断路器也不会打开.
    • 错误百分比阈值: 当请求总数在快照时间窗内超过了阈值, 比如发生30次调用, 15次失败, 也就是超过了50%的错误百分比, 在默认设定50%的情况下, 这时断路器就会打开了.
  3. 服务熔断步骤
    • 当满足一定阀值的时候(默认10秒内超过20个请求次数)
    • 当失败率达到一定的时候(默认10秒内超过50%请求失败)
    • 到达以上阀值,断路器将会开启
    • 当开启的时候,所有请求都不会进行转发
    • 一段时间之后(默认是5秒), 这个时候断路器是半开状态, 会让其中一个请求进行转发. 如果成功, 断路器会关闭, 若失败, 继续开启. 重复4和5
  4. 在断路器打开之后
    • 再有请求调用的时候, 将不会调用主逻辑, 而是直接调用降级fallback, 通过断路器, 实现了自动地发现错误并将降级逻辑切换为主逻辑, 减少响应延迟的效果.
    • 原来的主逻辑如何恢复?
      • 当断路器打开, 对主逻辑熔断后, hystrix会启动一个休眠时间窗, 在这个时间窗内, 降级逻辑临时成为主逻辑.
      • 当休眠时间窗到期, 断路器将进入半开状态, 释放一次请求到原来的主逻辑上, 如果此次请求正常返回, 则断路器将闭合, 主逻辑恢复. 但如果该请求依然有问题, 断路器继续进入打开状态, 休眠时间窗口重新计时.

Hystrix工作流程

  •  官方架构图
  1. 创建HyustrixCommand对象 - 用在依赖的服务返回单个操作结果的时候
  2. 命令执行, 其中HystrixCommand实现下面两种执行方式.
    • execute(): 同步执行, 从依赖的服务返回一个单一的结果对象, 或是发生错误的时候抛出异常.
    • queue(): 异步执行, 直接返回一个Future对象, 其中包含了服务执行结束时要返回的单一结果对象.
  3. 若当前命令的请求缓存功能是被启用的, 并且该命令缓存命中, 那么缓存结果会立即以Observable对象的形式返回.
  4. 检查断路器是否为打开状态, 如果断路器是打开的, 那么Hystrix不会执行, 而是转到fallback处理逻辑(第8步), 如果断路器是关闭的, 则继续执行命令.
  5. 线程池/请求队列/信号量是否占满, 如果命令依赖服务的专有线程池和请求队列, 或信号量(不用线程池的时候)已被占满, 那么Hystrix也不会继续执行, 而是转到fallback处理逻辑(第8步).
  6. Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务,
    • HystrixCommand.run(): 返回单一结果, 或抛出异常.
  7. Hystrix会将"成功", "失败", "拒绝", "超时"等信息报告给断路器, 而断路器会维护一组计数器来统计这些数据, 断路器会使用这些统计数据决定是否要打开断路器, 来对某个依赖服务的请求进行"熔断/短路".
  8. 当命令执行失败的时候, Hystrix会进入fallback尝试回退处理, 我们通常也称该操作为"服务降级", 而能够引起服务降级处理的情况有3种
    • 当命令处于"熔断/短路"状态, 断路器是打开的时候.
    • 当前命令的线程池/请求队列/信号量被占满的时候.
    • HystrixCommand.run()抛出异常的时候.
  9. 当Hystrix命令执行成功后, 它会将处理结果直接返回或以Observable的形式返回.
  • 注意: 如果我们没有为命令实现降级逻辑或在降级处理逻辑中抛出了异常, Hystrix依然会返回一个Observable对象, 但不会发射任何结果数据, 而是通过onError方法通知命令立即中断请求, 并通过onError()方法将引起命令失败的异常发送给调用者.

Hystrix图形化Dashboard

  • 搭建仪表盘9001
  1. 新建cloud-consumer-hystrix-dashboard9001 Module
  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. 主启动类: 新注解@EnableHystrixDashboard
    @EnableHystrixDashboard
    @SpringBootApplication
    public class HystrixDashboard9001 {
    
        public static void main(String[] args) {
            SpringApplication.run(HystrixDashboard9001.class, args);
        }
    }
  5. 注意: 所有Provider微服务提供类(8001/8002/...)都需要监控配置依赖
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-actuator</artifactId>
    </dependency>
  6. 启动该微服务, 并访问: http://localhost:9001/hystrix, 如果出现下图页面, 则表示环境搭建成功
  • 图形监控演示
  1. 新版本的Hystrix需要在主启动类 PaymentHystrixMain8001 中指定监控路径
        /**
         * 此配置是为了服务监控而配置, 与服务容错本身无关
         * ServerletReegistrationBean是因为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;
        }
    
    不指定会报错: Unable to connect to Command Metric Stream
  2. 监控测试
    • 填写监控地址
    • 图形说明
posted @ 2020-07-01 10:41  yellowstreak  阅读(191)  评论(0编辑  收藏  举报