第7章 使用Hystrix实现微服务的容错处理

第七章 使用Hystrix实现微服务的容错处理

实现容错的手段

  • 如果服务提供者响应非常缓慢,那么消费者对提供者的请求就会被强制等待,直到提供者响应或超时。在高负载场景下,如果不做任何处理,此类问题可能会导致服务消费者的资源耗尽甚至整个系统的崩溃。

雪崩效应

  • 我们把“**基础服务故障”**导致“级联故障”的现象称为雪崩效应雪崩效应描述的是提供者不可用导致消费者不可用,并将不可用逐渐放大的过程。

  • 如下:A作为服务提供者(基础服务),BA服务消费者CDB服务消费者。当A不可用引起了B的不可用,并将不可用像滚雪球一样放大到CD,雪崩效应因此形成。

在这里插入图片描述

如何容错

  • 容错机制需要实现以下两点
    1. 为网络请求设置超时:正常情况下,,一个远程调用一般在几十毫秒内就能得到响应。如果依赖的服务不可用或者为网络有问题,那么响应时间就会变得很长(几十秒)。
      1. 通常情况下,一次远程调用对应着一个线程/进程。如果响应太慢,这个线程/进程就得不到释放。而线程/进程又对应着系统资源,如果得不到释放的线程/进程越积越多,资源就会被耗尽,最终导致服务的不可用。
      2. 必须为每个网络请求设置超时,让资源尽快释放。
    2. 使用断路器模式。(如家中的断路器,电路一旦过载就会跳闸,从而可以保护电路的安全。在电路超载的问题被解决后,只需关闭断路器,电路就可以恢复正常)
      1. 如果对某一个微服务的请求有大量超时(常常说明该微服务不可用),再去让新的请求访问该微服务已经没有任何意义,只会无畏消耗资源。
      2. 断路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。
      3. 断路器可理解为对容易导致错误的操作的代理。这种代理能够统计一段时间内调用失败的次数,并决定是正常请求依赖的服务还是直接返回。
      4. 断路器可以实现快速失败,如果在一段时间内检测到许多类似的错误(如:超时)就会在之后的一段时间内,强迫对该服务的快速失败,即不再请求所依赖的服务。这样,应用程序就无需再浪费CPU时间去等待长时间的超时。
      5. 断路器也可以自动诊断依赖的服务是否已经恢复正常。如果发现依赖的服务已经恢复正常,那么就会恢复请求该服务。使用这种方式,就可以实现微服务的“自我修复”——当依赖的服务不正常时,打开断路器时快速失败,从而防止雪崩效应;当发现依赖的服务恢复正常时,又会恢复请求。

断路器状态转换

  • 正常情况下,断路器关闭,可正常请求依赖的服务。
  • 当一段时间内,请求失败率达到一定阈值(如:错误率达到50%,或100次/分钟等),断路器就会打开。此时,不会再去请求依赖的服务。
  • 断路器打开一段时间后,会自动进入“半开”状态。此时,断路器可允许一个请求访问依赖的服务。如果该请求能够调用成功,则关闭断路器;否则继续保持打开状态。

在这里插入图片描述

使用Hystrix实现容错

Hystrix简介

  • Hystrix是由Netflix开源的一个延迟和容错库,用于隔离访问远程系统,服务或者第三方库,防止级联失败,从而提升系统可用性与容错性Hystrix主要通过以下几点实现延迟和容错。

    • 包裹请求:使用HystrixCommand(或HystrixObservableCommand)包裹对依赖的调用逻辑,每个命令在独立线程中执行。这使用了设计模式中的“命令模式”。
    • 跳闸机制:当某服务的错误率超过一定阈值时,Hystrix可以自动或者手动跳闸,停止请求该服务一段时间。
    • 资源隔离Hystrix为每个依赖都维护了一个小型的线程池(或者信号量)。如果该线程池已满,发往该依赖的请求就被立即拒绝,而不是排队等候,从而加速失败判定。
    • 监控Hystrix可以近乎实时地监控运行指标和配置的变化,如成功,失败,超时和被拒绝的请求等。
    • 回退机制:当请求失败,超时,被拒绝,或当断路器打开时,执行回退逻辑。回退逻辑由开发者自行提供,如返回一个缺省值。
    • 自我修复:断路器打开一段时间后,会自动进入“半开”状态。

通用方式整合Hystrix

  • 创建Maven项目(基于microservice-consumer-movie-ribbon)

在这里插入图片描述

  • 添加Hystrix依赖

    • <!--        Hystrix依赖-->
              <dependency>
                  <groupId>org.springframework.cloud</groupId>
                  <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
              </dependency>
      
  • 修改启动类添加注解**@EnableCircuitBreaker@EnableHystrix**

    • @EnableDiscoveryClient
      //在spring cloud Edgware以上版本中上面注解可有可无
      @EnableHystrix
      //@EnableCircuitBreaker || @EnableHystrix,为项目启用断路器支持
      @SpringBootApplication
      public class ConsumerMovieApplication {
          @Bean
          @LoadBalanced
          public RestTemplate restTemplate() {
              return new RestTemplate();
          }
          public static void main(String[] args) {
              SpringApplication.run(ConsumerMovieApplication.class,args);
          }
      }
      
  • 修改MovieController,让其findById方法具备容错能力。

    • @RestController
      public class MovieController{
      
          private static final Logger LOGGER = LoggerFactory.getLogger(MovieController.class);
      
          @Autowired
          private RestTemplate restTemplate;
      
          @Autowired
          private LoadBalancerClient loadBalancerClient;
      
          @HystrixCommand(fallbackMethod = "findByIdFallback")
          /**
           * @HystrixCommand(fallbackMethod = "findByIdFallback")
           * fallbackMethod属性指定回退方法是findByIdFallback
           * 为findById方法编写一个回退方法findByIdFallback,
           * 该方法与findById方法具有相同的参数和返回值类型,该方法返回一个默认的User。
           */
          @GetMapping("/user/{id}")
          public User findById(@PathVariable Long id) {
              return this.restTemplate.getForObject("http://microservice-provider-user/"+id, User.class);
          }
      
          @GetMapping("/log-user-instance")
          public void logUserInstance() {
      
              ServiceInstance serviceInstance =
                      this.loadBalancerClient.choose("microservice-provider-user");
      //         打印当前选择的是哪个节点
              MovieController.LOGGER.info("{}:{}:{}",
                      serviceInstance.getServiceId(),
                      serviceInstance.getHost(),
                      serviceInstance.getPort());
      
          }
      
          public User findByIdFallback(Long id) {
              User user = new User();
              user.setId(-1L);
              user.setName("默认用户");
              return user;
          }
      }
      
  • 测试

    • 启动microservice-discovery-eureka
    • 启动microservice-provider-user
    • 启动microservice-consumer-movie-ribbon-hystrix
    • 访问http://localhost:8010/user/1,如下

    在这里插入图片描述

    • 停止microservice-provider-user
    • 再次访问http://localhost:8010/user/1,如下,说明当前用户微服务不可用,进入了回退方法。

    在这里插入图片描述

  • 当请求失败,被拒绝,超时或者断路器打开时,都会进入回退方法。但进入回退方法并不意味着断路器已经被打开。

  • 在一些场景下,我们需要获得造成回退的原因,此时只需要在fallback方法上添加一个Throwable参数即可。如下:

    •   /**
           *
           * @param id id
           * @param throwable 异常
           * @return 用户
           */
          public User findByIdFallback(Long id,Throwable throwable) {
              LOGGER.error("==========进入回退方法,异常是=========="+throwable);
              User user = new User();
              user.setId(-1L);
              user.setName("默认用户");
              return user;
          }
      
    • 控制台输出异常

    在这里插入图片描述

  • 但在多数场景下,当发生异常是,我们并不想触发fallback。此时该如何处理?

    • Hystrix提供了HystrixBadRequestException类,这是一个特殊的异常类,当该异常发生时,不会触发回退。因此,可将自定义的业务异常继承该类,从而达到业务异常不回退的效果。
  • 同时,@HystrixCommand提供了ignoreExceptions属性,可以使用该属性配置不想执行回退的异常类,如:

    • @HystrixCommand(fallbackMethod = "findByIdFallback"
                  ,ignoreExceptions = IllegalArgumentException.class)
          /**
           * @HystrixCommand(fallbackMethod = "findByIdFallback")
           * fallbackMethod属性指定回退方法是findByIdFallback
           * ignoreExceptions属性配置不想执行回退的异常类
           * 为findById方法编写一个回退方法findByIdFallback,
           * 该方法与findById方法具有相同的参数和返回值类型,该方法返回一个默认的User。
           */
          @GetMapping("/user/{id}")
          public User findById(@PathVariable Long id) {
              return this.restTemplate.getForObject("http://microservice-provider-user/"+id, User.class);
          }
      
    • 这样即使在findById中发生了IllegalArgumentException异常,也不会执行findByFallback方法。

Hystrix断路器的状态监控与深入理解

  • 之前在项目中引入的Spring Boot Actuator依赖,断路器的状态也会暴露在Actuator提供的==/health==中,这样就可以直观地了解断路器的状态。

  • 测试

    • 启动microservice-discovery-eureka
    • 启动microservice-provider-user
    • 启动microservice-consumer-movie-ribbon-hystrix
    • 访问http://localhost:8010/user/1
    • 访问http://localhost:8010/health,如下

    在这里插入图片描述

    • 停止microservice-provider-user
    • 访问http://localhost:8010/user/1,如下

    在这里插入图片描述

    • 此时访问http://localhost:8010/health,得到结果和第一次访问一样?
      • 这是因为我们的失败率还没有达到阈值(默认是5秒内失败20次),注意:执行回退逻辑并不代表断路器已经打开。请求失败,超时,被拒绝以及断路器打开时等都会执行回退逻辑。
    • 此时快速的访问http://localhost:8010/user/1,以达到阈值。
    • 访问http://localhost:8010/health

    在这里插入图片描述

    • 此时Hystrix的状态是CIRCUIT_OPEN,说明断路器已经打开,不会再去请求用户用户微服务了。

Hystrix线程隔离策略与传播上下文

  • Hystrix的隔离策略有THREADSEMAPHORE两种,默认是THREAD
  • 正常情况下保持默认即可。
  • 如果发生找不到上下文的运行时异常,可考虑将隔离策略设置为SEMAPHORE

在这里插入图片描述

Feign使用Hystrix

  • 前面介绍的回退是使用**@HystrixCommandfallbackMethod属性实现回退的,但是Feign是以接口的形式存在的,没有方法体,所以前面讲解的方式不适合于Feign**。

  • Spring Cloud已经为Feign整合了Hystrix,要想为Feign打开Hystrix的支持,只需要配置feign.hystrix.enabled=true

  • 注:在Spring Cloud Dalston之前的版本中,Feign默认开启了Hystrix支持,无需设置feign.hystrix.enabled=true;从Spring Cloud Dalston开始,FeignHystrix支持默认关闭,必须设置该属性。

  • 创建Maven项目(基于microservice-consumer-movie-feign)

在这里插入图片描述

  • 修改application.yml

    • server:
        port: 8010
      spring:
        application:
          name: microservice-consumer-movie
      eureka:
        client:
          serviceUrl:
            defaultZone: http://localhost:8761/eureka/
        instance:
          prefer-ip-address: true
      feign:
        client:
          config:
            microservice-provider-user:
              loggerLevel: full
        hystrix:
          enabled: true
          #开启Feign的Hystrix支持
      logging:
        level:
          com.ym.cloud.study.feign.UserFeignClient: DEBUG
      
      
  • 修改UserFeignClient接口

    • /**
       * 使用@FeignClient的fallback属性指定回退类
       */
      @FeignClient(name = "microservice-provider-user", fallback = FeignClientFallback.class)
      public interface UserFeignClient {
          @RequestMapping("/{id}")
          public User findById(@PathVariable("id")Long id);
      }
      
      /**
       * 创建回退类需要实现UserFeignClient接口
       */
      @Component
      class FeignClientFallback implements UserFeignClient{
          @Override
          public User findById(Long id) {
              User user = new User();
              user.setId(1L);
              user.setUsername("默认用户");
              return user;
          }
      }
      
  • 测试

    • 启动microservice-discovery-eureka
    • 启动microservice-provider-user
    • 启动microservice-consumer-movie-feign-hystrix-fallback
    • 访问http://localhost:8010/user/1,正常返回如下。
    image-20200214135810793
    • 停止microservice-provider-user
    • 再次访问http://localhost:8010/user/1,如下

    在这里插入图片描述

通过Fallback Factory检查回退原因
  • 前面在使用**@HystrixCommand时,如需获得造成回退的原因,只需在回退的方法上添加一个Throwable**参数。
  • Feign中,可使用**@FeignClientfallbackFactory**属性获得造成回退的原因。
实践
  • 创建Maven项目(基于microservice-consumer-movie-feign)

在这里插入图片描述

  • 修改application.yml,添加feign.hystrix.enabled: true

    • server:
        port: 8010
      spring:
        application:
          name: microservice-consumer-movie
      eureka:
        client:
          serviceUrl:
            defaultZone: http://localhost:8761/eureka/
        instance:
          prefer-ip-address: true
      feign:
        client:
          config:
            microservice-provider-user:
              loggerLevel: full
      #        开启feign对Hystrix的支持
        hystrix:
          enabled: true
      logging:
        level:
          com.ym.cloud.study.feign.UserFeignClient: DEBUG
      
  • 修改UserFeignClient

    • /**
       * 使用@FeignClient的fallbackFactory属性指定回退类
       */
      @FeignClient(name = "microservice-provider-user"
              ,fallbackFactory = FeignClientFallbackFactory.class)
      public interface UserFeignClient {
          @RequestMapping("/{id}")
          public User findById(@PathVariable("id")Long id);
      }
      
      /**
       * UserFeignClient的fallbackFactory类,该类需实现FallbackFactory接口,并重写create()
       */
      @Component
      class FeignClientFallbackFactory implements FallbackFactory<UserFeignClient> {
          private static final Logger LOGGER = LoggerFactory
                  .getLogger(FeignClientFallbackFactory.class);
          @Override
          public UserFeignClient create(Throwable throwable) {
              return new UserFeignClient() {
                  @Override
                  public User findById(Long id) {
                      /**
                       * 日志最好放在各个fallback方法中,而不要直接放在create方法中,
                       * 否则在引用启用时,就会打印该日志
                       */
                      FeignClientFallbackFactory.LOGGER.info("========产生回退原因:",throwable);
                      User user = new User();
                      user.setId(1L);
                      user.setName("默认用户");
                      return user;
                  }
              };
          }
      }
      
  • 测试

    • 启动microservice-discovery-eureka
    • 启动microservice-provider-user
    • 启动microservice-consumer-movie-feign-hystrix-fallback-factory
    • 访问http://localhost:8010/user/1,正常访问。
    • 停止microservice-provider-user
    • 再次访问http://localhost:8010/user/1,如下

    在这里插入图片描述

    • 控制台如下,说明已经进入回退的方法
    2020-02-14 15:52:06.098  INFO 12248 --- [provider-user-1] c.y.c.s.f.FeignClientFallbackFactory     : ========产生回退原因:
    
    java.lang.RuntimeException: com.netflix.client.ClientException: Load balancer does not have available server for client: microservice-provider-user
    

Hystrix的监控

  • Hystrix还为我们提供了近乎实时的监控。HystrixCommandHystrixObservableCommand在执行时,会生成执行结果和运行指标,如:每秒执行的请求数,成功数等,这对于我们分析应用系统的状况很有用。

  • 使用Hystrix的模块hystrix-metrics-event-stream,就可将这些监控的指标信息以text/event-stream的格式暴露给外部系统。Spring-cloud-starter-netflix-hystrix已经包含该模块,在此基础上,只需为项目添加spring-boot-starter-actuator,就可以使用hystrix.stream端点获得Hystrix的监控信息了。

  • 测试

    • 启动microservice-discovery-eureka
    • 启动microservice-provider-user
    • 启动microservice-consumer-movie-ribbon-hystrix
    • 访问http://localhost:8010/hystrix.stream,此时浏览器一直处于请求的状态,页面为空,这是因为此时项目中的注解了**@HystrixCommand**的方法还美誉被执行,因此也没有任何的监控数据
    • 访问http://localhost:8010/user/1后,再次访问http://localhost:8010/hystrix.stream,如下(注:火狐会弹出一个下载,建议用Google)

    在这里插入图片描述

Feign项目的Hystrix监控

  • 启动之前的microservice-consumer-movie-feign-hystrix-fallback,使用类似的方法进行测试,会404

  • 解决方案

    • 为项目添加spring-cloud-starter-netflix-hystrix依赖

      • <dependency>
        	<groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        
    • 在启动类上添加**@EnableCircuitBreaker**

使用Hystrix Dashboard可视化监控数据

  • 前面介绍的Hystrix的监控,访问**/hystrix.stream**端点获得的数据是以文字形式展示的。很难一眼看出当前系统的运行状态。

  • Hystrix Dashboard是一个可视化工具,让监控数据图形化,可视化。

  • 创建Maven项目(microservice-hystrix-dashboard)

在这里插入图片描述

  • 添加依赖

    <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
    </dependency>
    
  • 编写启动类添加==@EnableHystrixDashboard==

    @SpringBootApplication
    @EnableHystrixDashboard
    //启用Hystrix Dashboard
    public class HystrixDashboardApplication {
        public static void main(String[] args) {
            SpringApplication.run(HystrixDashboardApplication.class,args);
        }
    }
    
  • application.yml中添加端口

    server:
    	port: 8030
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值