关于 Hystrix

  1. 熔断

    • 熔断是 consumer 角色一侧的功能。
    • 当请求某一 provider 的失败情况达到某一阀值后,断路器会切换到 open 状态,请求将不再发往这个 provider 。
    • 断路器打开一段时间后,会切换到 half-open 状态,此时断路器的状态将取决于下一次请求是否成功。如果成功,则切换到 closed 状态;如果失败,则切换到 open 状态。
  2. 服务降级 Fallback
    当处理出现异常,或不具备处理能力时(负载过高、线程池用完),则会快速返回一个预先指定的值。
  3. 资源隔离
    资源隔离主要用来预防雪崩的发生,限制某一资源不可用引起的全局资源挂起。可选的隔离方式有 线程隔离、信号量隔离。

熔断 和 降级

在一般方法上应用

  1. 创建模块 spring-cloud.s05.store-hystrix
  2. pom 中添加依赖

      <dependencies>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-netflix-hystrix</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>
      </dependencies>
  3. 添加配置文件

    server:
      port: 31003
    spring:
      application:
        name: store-hystrix
    eureka:
      client:
        serviceUrl:
          defaultZone: http://user:123123@localhost:34001/eureka/
      instance:
        instance-id: ${spring.cloud.client.ip-address}:${server.port}
        prefer-ip-address: true
        lease-renewal-interval-in-seconds: 10
        lease-expiration-duration-in-seconds: 30
    info:
      app.name: ${spring.application.name}
      compony.name: me.xhy
      build.artifactId: $project.artifactId$
      build.modelVersion: $project.modelVersion$
  4. 创建启动类 ..StoreHystrix

    @SpringBootApplication
    @EnableEurekaClient
    // 开启降级
    @EnableHystrix
    public class StoreHystrix {
    
      public static void main(String[] args) {
        SpringApplication.run(StoreHystrix.class, args);
      }
    
      @Bean
      @LoadBalanced
      RestTemplate restTemplate() {
        return new RestTemplate();
      }
    }
    如果启动类中 不写 @EnableHystrix 注解,那么当方法失败时,是不会执行降级方法的;但是注解了 @HystrixCommand 的方法是可以有熔断功能的,默认值也依然是 3次, 当失败次数达到3次时,在 ribbon 中剔除该资源。
  5. 创建程序访问入口..ConsumerCircuitController

    @RestController
    @RequestMapping("/hystrix")
    public class CircuitFallbackController {
    
      @Autowired
      RestTemplate restTemplate;
    
      /*
      新增了 @HystrixCommand
      @HystrixCommand(fallbackMethod = "getMoviesError") 默认键为 fallbackMethod
      表示当调用 provider 失败,调用的补偿方法
      默认情况下,发生3次错误(补偿方法调用了3次),该 provider 将被熔断
      当被熔断的 provider 重新上线,将被重新加回到服务列表
      */
      @RequestMapping("/cf/ribbon")
      @HystrixCommand(fallbackMethod = "getServerPortError")
      public String getServerPort() {
        return restTemplate.getForObject("http://album-provider/album/server-port", String.class);
      }
    
      public String getServerPortError() {
        return "ribbon method getMovies occur hystrix";
      }
    
      @RequestMapping("/cf/always-exception")
      @HystrixCommand(fallbackMethod = "getServerPortError")
      public String alwaysException() {
        if(true) throw new RuntimeException();
        return "hi";
      }
    }

    这个 Controller 中有两个访问入口, @RequestMapping("/circuit/always-exception") 始终都会发生异常;@HystrixCommand(fallbackMethod = "getServerPortError") 则需要在正常访问所有 provider 后,停掉至少一个 provider 来观察结果。

  6. 验证熔断和降级

    • http://localhost:31003/hystrix/circuit/always-exception 由于该 Controller 始终报错,所以始终使用降级方法的返回值。
    • http://localhost:31003/hystrix/circuit/ribbon 使用 ribbon 的负载均衡访问 provider ,下线至少一个 provider 来观察效果。
    启动类注解了 @EnableHystrix 服务降级才会生效。 @HystrixCommand 只有在应用 ribbon 的方法上,才会有熔断效果。

在 Feign 上应用

Feign 是自带断路器的,但是它没有默认打开。需要在配置文件中配置打开它

  1. pom 中引入 Feign

        <dependency>
          <groupId>org.springframework.cloud</groupId>
          <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
  2. 在配置文件中打开断路器

    feign:
      hystrix:
        enabled: true # 这是默认值,如果觉得每个 Feign 方法都加上 Hystrix 影响效率,可以在这里关闭。
  3. 启动类增加注解

    @EnableFeignClients // Feign 需要
  4. 创建 Feign 接口

    // 直接在 FeignClient 注解中使用 fallback 指定降级方法是有弊端的,这种方式的弊端在于,无论是`原方法`还是`降级方法`都无法获取异常信息
    @FeignClient(value = "album-provider",
    //    fallback = CircuitFallbackFeignServiceFallback.class,
      fallbackFactory = CircuitFallbackFeignServiceFallbackFactory.class)
    public interface CircuitFallbackFeignService {
    
      @RequestMapping("/album/server-port")
      String getServerPort();
    }
    
    /*
    该类提供发生异常时的补偿方法
    必须要有 @Component , 可以是外部类
     */
    /*
    @Component
    弃用,存在无法获取异常信息的弊端
    */
    class CircuitFallbackFeignServiceFallback implements CircuitFallbackFeignService {
      @Override
      public String getServerPort() {
        return "feign method getServerPort occur hystrix fallback";
      }
    }
    
    @Component
    class CircuitFallbackFeignServiceFallbackFactory implements FallbackFactory<CircuitFallbackFeignService> {
    
      // 原方法异常时,调用该方法
      @Override
      public CircuitFallbackFeignService create(Throwable throwable) {
        return new CircuitFallbackFeignService() {
    
          @Override
          public String getServerPort() {
            String exMsg = throwable.getMessage();
            return "feign method getServerPort occur hystrix fallback, exception is " + exMsg;
          }
        };
      }
    }
  5. 创建访问程序入口 ..CircuitFallbackFeignController

    @RestController
    @RequestMapping("/hystrix")
    public class CircuitFallbackFeignController {
    
      @Autowired
      CircuitFallbackFeignService service;
    
      @RequestMapping("/cf/feign")
      public String getServerPort() {
        return service.getServerPort();
      }
    }
  6. 访问 http://localhost:31003/hystrix/cf/feign

    如果配置文件中不开启断路器,降级方法是不会执行的
    如果配置文件中不设置重重试次数,feign 默认会重试 1 次

熔断配置

circuitBreaker:
  enabled: true # 是否开启熔断,默认值为 true
  requestVolumeThreshold: 20 # 设置在一个滚动窗口中,打开断路器的最少请求数。 在已给窗口期失败多少个才会打开断路器。窗口设置属性 metrics.rollingStats.timeInMilliseconds
  sleepWindowInMilliseconds: 5000 # 同上面 hystrix 中的配置
  errorThresholdPercentage: 50 # 开启熔断的失败率,默认值为 50%
metrics:
  rollingStats:
    timeInMilliseconds: 10000 # 滚动窗口,默认为 10000 ms

熔断器的状态

当在滚动时间窗口内达到失败次数,熔断状态为 熔断开启;在经过休眠期后,熔断状态变为 熔断半开,此时该方法可以再次被调用;如果熔断半开状态下,下一个请求调用成功,熔断状态变为 关闭 ,如果下一个请求

隔离

隔离可以防止雪崩的出现。当 provider 没有足够的处理能力时, consumer 的请求无法返回导致线程挂起,逐层影响,最终雪崩。如果限制 consumer 请求某个 provider 的 , consumer 就不会因为 provider 丧失处理能力而挂起过多的线程, consumer 仍然可以保持服务能力,去做其他任务。

线程方式牺牲空间,信号量方式牺牲时间。

使用线程隔离

  1. 创建业务实现类 ..IsolationServiceImpl

    @Component
    public class IsolationServiceImpl {
    
      /*
      在调用 isolationConsume 方法是,先要执行 HystrixCommand 逻辑,通过后才会调用 isolationConsume
       */
      @HystrixCommand(
          fallbackMethod = "isolationFallback", // 降级方法
          commandKey = "isolationByThread", // 配置属性的参数(该值将作为配置 key 的一部分,出现在配置文件中,起到定位局部配置的作用)
          groupKey = "isolationByThreadGroup", // threadPoolKey 和 groupKey 的作用都是用来指定线程池的。
          /*
            先参考 threadPoolKey 后参考 groupKey。
            threadPoolKey 相同的方法,使用同一线程池;
            threadPoolKey 没有设置, groupKey 相同的方法,使用同一线程池。
           */
          commandProperties = {
              @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"), // 线程池模式, 默认值是 THREAD
              /*@HystrixProperty(name = "execution.timeout.enable", value = "true"), // HystrixCommand.run()的执行是否启用超时时间,默认为 true。
              @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" ), // hystrix 超时时间
              @HystrixProperty(name = "circuitBreaker.enabled", value = "true" ), //开启熔断器
              @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3" ), // 窗口时间内多少个请求失败了,会打开断路器*/
          },
          threadPoolKey = "isolationByThreadPool",
          threadPoolProperties = {
              @HystrixProperty(name = "coreSize", value = "10"), // 线程池和信号量的默认值都是 10
              /*@HystrixProperty(name = "maxQueueSize", value = "10"),
              @HystrixProperty(name = "keepAliveTimeMinutes", value = "2"),
              @HystrixProperty(name = "queueSizeRejectionThreshold", value = "15"),
              @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "12"),
              @HystrixProperty(name = "metrics.rollingStats.time", value = "1440")*/
          }
      )
      public String isolationByThread() {
        System.out.println("Service Thread -> " + Thread.currentThread().getName());
        return "this is a tagged method by Hystrix";
      }
    
      @HystrixCommand(
          fallbackMethod = "isolationFallback", // 降级方法
          commandKey = "isolationBySemaphore",
          groupKey = "isolationBySemaphoreGroup",
          commandProperties = {
              @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE"), // 线程池模式, 默认值是 THREAD
              @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
              /*@HystrixProperty(name = "execution.timeout.enable", value = "true"), // HystrixCommand.run()的执行是否启用超时时间,默认为 true。
              @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" ), // hystrix 超时时间
              @HystrixProperty(name = "circuitBreaker.enabled", value = "true" ), //开启熔断器
              @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3" ), // 窗口时间内多少个请求失败了,会打开断路器*/
          }
      )
      public String isolationBySemaphore() {
        System.out.println("Service Thread -> " + Thread.currentThread().getName());
        return "this is a tagged method by Hystrix";
      }
    
      public String isolationFallback() {
        System.out.println("Service Thread fallback -> " + Thread.currentThread().getName());
        return "function isolationConsume is isolated";
      }
    }

    该类中有两个方法,一个是业务方法,标记了 HystrixCommand;另外一个是降级方法

  2. 创建测试类 ..IsolationServiceImplTest

    @RunWith(SpringJUnit4ClassRunner.class)
    @SpringBootTest(classes = StoreHystrix.class)
    @WebAppConfiguration
    public class IsolationServiceImplTest {
    
      @Autowired
      IsolationServiceImpl isolationService;
    
      private int count = 11;
    
      private CountDownLatch cdl = new CountDownLatch(count);
    
      @Test
      public void testHystrix() {
    
        for (int i = 0; i < count; i++) {
          new Thread(() -> {
              try {
                cdl.await();
              } catch (InterruptedException e) {
                e.printStackTrace();
              }
            System.out.println(Thread.currentThread().getName() + "==>" + isolationService.isolationConsume());
            }
          , "TestThread-" + i).start();
          cdl.countDown();
        }
    
        try {
          Thread.currentThread().join();
          TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
    
      }
    
      @After
      public void after() {
    
      }
    
    }

    业务方法用线程池模式隔离,同时行该方法的线程最多为 10 个。在测试类中使用闩锁发起 11 个线程并发访问业务方法。

  3. 测试结果
    测试结果

使用信号量隔离

大同小异,把隔离策略改成 信号量,降级方法可以使用同一个。

  1. 业务方法

      @HystrixCommand(
          fallbackMethod = "isolationFallback", // 降级方法
          commandKey = "isolationByThread",
          groupKey = "isolationByThreadGroup",
          commandProperties = {
              @HystrixProperty(name = "execution.isolation.strategy", value = "SEMAPHORE"), // 线程池模式, 默认值是 THREAD
              @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
              /*@HystrixProperty(name = "execution.timeout.enable", value = "true"), // HystrixCommand.run()的执行是否启用超时时间,默认为 true。
              @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1000" ), // hystrix 超时时间
              @HystrixProperty(name = "circuitBreaker.enabled", value = "true" ), //开启熔断器
              @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "3" ), // 窗口时间内多少个请求失败了,会打开断路器*/
          }
      )
      public String isolationBySemaphore() {
        System.out.println("Service Thread -> " + Thread.currentThread().getName());
        return "this is a tagged method by Hystrix";
      }
  2. 测试方法

      @Test
      public void testHystrixSemaphore() {
    
        for (int i = 0; i < count; i++) {
          new Thread(() -> {
            try {
              cdl.await();
            } catch (InterruptedException e) {
              e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " ==> " + isolationService.isolationBySemaphore());
          }
              , "Test Thread-" + i).start();
          cdl.countDown();
        }
    
        try {
          Thread.currentThread().join();
          TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
    
      }
  3. 测试结果
    测试结果
可以发现两种隔离策略, Hystrix 的线程名称是有区别的。
降级方法也可以使用 Hystrix ,只是要考虑有没有必要这样做。

Hystrix 的设置

官方文档 ,有些参数在 annotation 中设置是不起作用的,需要在配置文件中指定。
application.yml

hystrix:
  command:
    default: # 全局配置
      execution:
        isolation:
          thread:
            interruptOnTimeout: false # 是否开启超时,默认false,不建议开启
            timeoutInMilliseconds: 1000 # hystrix超时时间,默认 1000 ms
    isolationByThread: # <commandKey> 局部配置
      circuitBreaker:
        sleepWindowInMilliseconds: 5000 # 熔断休眠时间,默认 5000 ms。设置在回路被打开后,拒绝请求到再次尝试请求的时间。

wowxhycoming
0 声望1 粉丝

引用和评论

0 条评论