Hystrix

Hystrix简介

Hystrix 是一个用于处理分布式系统的 延迟容错 的开源库, 在分布式系统里, 许多依赖不可避兔的会调用失败, 比如超时、异常等, Hystrix 能够保证在一个依赖出问题的情况下, 不会导致整体服务失败, 避免级联故障, 以提高分布式系统的弹性。

官方地址:https://github.com/Netflix/Hystrix/wiki

"断路器"本身是一种开关装置, 当某个服务单元发生故障之后, 通过断路器的故障监控(类似熔断保险丝), 向调用方返回一个符合预期的、可处理的备选响应( Fallback), 而不是长时间的等待或者抛出调用方无法处理的异常, 这样就保证了服务调用方的线程不会被长时间、不必要地占用, 从而避免了故障在分布式系统中的蔓延, 乃至雪崩。

Hystrix作用

  • 服务降级 服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback 哪些情况会触发降级
    • 程序运行异常
    • 超时
    • 服务熔断触发服务降级
    • 线程池/信号量打满也会导致服务降级
  • 服务熔断
    • 「类比保险丝」达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示
    • 服务的降级 -> 进而熔断 -> 恢复调用链路
  • 服务限流
    • 秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟 N 个,有序进行
  • 接近实时的监控

Hystrix概念介绍

服务隔离:线程池隔离、信号量隔离

服务熔断:假如某服务一直错,把所有服务全熔断。还有恢复机制,等服务恢复时,让整个服务恢复。

服务降级:当服务发生异常或调用超时,返回默认数据。

Hystrix使用场景

当一切正常时,请求看起来是这样的:

img

当其中有一个系统有延迟时,它可能阻塞整个用户请求:

img

在高流量的情况下,一个后端依赖项的延迟可能导致所有服务器上的所有资源在数秒内饱和(PS:意味着后续再有请求将无法立即提供服务)

img

Hystrix设计原则

  • 防止任何单个依赖项耗尽所有容器(如Tomcat)用户线程。
  • 甩掉包袱,快速失败而不是排队。
  • 在任何可行的地方提供回退,以保护用户不受失败的影响。
  • 使用隔离技术(如隔离板、泳道和断路器模式)来限制任何一个依赖项的影响。
  • 通过近实时的度量、监视和警报来优化发现时间。
  • 通过配置的低延迟传播来优化恢复时间。
  • 支持对Hystrix的大多数方面的动态属性更改,允许使用低延迟反馈循环进行实时操作修改。
  • 避免在整个依赖客户端执行中出现故障,而不仅仅是在网络流量中。

Hystrix实操

创建Hystrix 支付服务模块

创建maven

项目名称 loud-provider-payment-hystrix-8001

配置pom
<dependencies>
    <!-- 其它部分省略,主要要引入hystrix依赖 -->
    <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>
</dependencies>
创建application.yml
# 核心配置如下
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
创建主启动类
@SpringBootApplication
@EnableDiscoveryClient
public class CloudProviderPaymentHystrixApp {

    public static void main(String[] args) {
        SpringApplication.run(CloudProviderPaymentHystrixApp.class,args);
    }
}
业务类

我们在controller中分别创建一个能立刻响应的方法及耗时3秒响应的方法。

@GetMapping("/hystrix/timeout/{id}")
@ResponseBody
public CommonResult paymentTimeout(@PathVariable("id") Long id){
  try {
    TimeUnit.SECONDS.sleep(3);
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  Payment payment = paymentService.getById(id);
  CommonResult cr = null;
  if(payment!=null){
    cr = new CommonResult(200,"[线程:"+Thread.currentThread().getName()+"]-TIMEOUT-查询成功(服务端口:"+port+")",payment);
  }else{
    cr = new CommonResult(400,"无对应记录(服务端口:"+port+")",null);
  }
  return cr;
}

@GetMapping("/hystrix/success/{id}")
@ResponseBody
public CommonResult paymentSuccess(@PathVariable("id") Long id){
  Payment payment = paymentService.getById(id);
  CommonResult cr = null;
  if(payment!=null){
    cr = new CommonResult(200,"[线程:"+Thread.currentThread().getName()+"]-SUC-查询成功(服务端口:"+port+")",payment);
  }else{
    cr = new CommonResult(400,"无对应记录(服务端口:"+port+")",null);
  }
  return cr;
}

功能测试

访问:http://130.30.3.224:8001/payment/hystrix/success/1 立刻返回

访问:http://130.30.3.224:8001/payment/hystrix/timeout/1 等待3秒返回

高并发测试

使用Apifox进行压力测试。

现在Apifox中配置两个接口或者测试用例,然后创建一个测试用例,配置循环次数100次;每次200个线程,然后开始测试。

image-20210302160958556

系统已经明显的出现了长时间的响应延迟中。如下图所示:

![image-20210302154036810](/Users/huzd/Library/Application Support/typora-user-images/image-20210302154036810.png)

生产环境高并发模拟

通过编写服务消费者;并且结合Jmeter来模拟高并发的情况;开发者不同的电脑扛住高并发的量不同;大家可以逐步网上加。并发量从100、500、1000、5000、10000 逐步加;我的电脑是在10000并发并且循环100次时出现明显的服务响应超时问题。这就模拟了实际的生产情况。

具体模拟场景如下:

  • 开发一个延迟5秒才返回的接口;
  • 开发一个立刻返回的接口;
  • 给延迟返回的接口添加10000 * 100 的高并发访问;
  • 发现立刻返回的接口也出现了访问超时错误。

创建Hystrix 支付消费模块

创建Maven

创建名为:cloud-consumer-feign-hystrix-order-80 的maven项目

配置pom
<!-- 核心依赖 -->
    <dependencies>
        <dependency>
            <groupId>info.huzd.springcloud</groupId>
            <artifactId>cloud-api-common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </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>
        <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>
        </dependency>
        <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.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
    </dependencies>
创建application.yml
server:
  port: 80

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka,http://eureka7002.com:7002/eureka
    healthcheck:
      enabled: true
spring:
  application:
    name: cloud-consumer-feign-hystrix-order-80
创建主启动类
@SpringBootApplication
@EnableFeignClients //开启Feign
public class CloudConsumerFeignHystrixOrder80App {
    public static void main(String[] args) {
        SpringApplication.run(CloudConsumerFeignHystrixOrder80App.class,args);
    }
}
编写业务类

编写Feign支持的服务类PaymentHystrixService

@Component
@FeignClient(value = "CLOUD-PROVIDER-PAYMENT")
public interface PaymentHystrixService {

    @GetMapping("/payment/hystrix/timeout/{id}")
    public CommonResult paymentTimeout(@PathVariable("id") Long id);

    @GetMapping("/payment/hystrix/success/{id}")
    public CommonResult paymentSuccess(@PathVariable("id") Long id);
}

编写Controller类

@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {

    @Resource
    PaymentHystrixService paymentHystrixService;

    @GetMapping("/payment/hystrix/timeout/{id}")
    public CommonResult paymentTimeout(@PathVariable("id") Long id){
        return paymentHystrixService.paymentTimeout(id);
    }

    @GetMapping("/payment/hystrix/success/{id}")
    public CommonResult paymentSuccess(@PathVariable("id") Long id){
        return paymentHystrixService.paymentSuccess(id);
    }
}
高并发测试
  1. 访问:http://localhost:8001/payment/hystrix/success/4 正常快速返回;

  2. 访问:http://localhost:8001/payment/hystrix/timeout/2 延迟三秒返回;

  3. 访问:http://localhost/consumer/payment/hystrix/success/4 正常返回;

使用JMeter来进行10000并发100次测试时非超时接口会报错。已经影响到超时接口以外的接口访问。

image-20210302192959397

如果应对高并发?

  • 超时导致服务变慢 -> 超时不再等待
  • 出错(宕机或程序出错) -> 出错要有兜底

例如:

  1. 服务提供方超时,调用方不能一直等待卡死;
  2. 服务提供方宕机,调用方不能一直卡死等待,必须有服务降级
  3. 服务提供方正常,调用方自己出故障或有超时要求,必须有服务降级。

服务降级实操

改造服务提供者项目
开启服务降级
@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker  //开启服务降级功能
public class CloudProviderPaymentHystrixApp {

    public static void main(String[] args) {
        SpringApplication.run(CloudProviderPaymentHystrixApp.class,args);
    }
}

配置服务降级
    public CommonResult paymentTimeoutHandler(Long id){
        return new CommonResult(300,"服务不可用,请稍后再试",Thread.currentThread().getName()+"-"+port+"-服务降级");
    }

    @GetMapping("/hystrix/timeout/{id}")
    @ResponseBody
    @HystrixCommand(fallbackMethod = "paymentTimeoutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
		/**
    Hystrix 使用@HystrixCommand来进行服务降级配置;其中:
    fallbackMethod 来指定当触发服务降级时执行的方法!注意传参要和注解的方法一致;
    commandProperties 用来指定触发服务降级成立的条件。
		**/
    public CommonResult paymentTimeout(@PathVariable("id") Long id){
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 代码省略;和之前的demo一样
        return cr;
    }
测试服务降级

image-20210303094954388

改造服务消费者项目
修改yml配置
feign:
  hystrix:
    enabled: true # 开启服务消费者端的Hystrix功能
修改启动类开启Hystrix
@SpringBootApplication
@EnableFeignClients
@EnableHystrix  //开启Hystrix
public class CloudConsumerFeignHystrixOrder80App {
    public static void main(String[] args) {
        SpringApplication.run(CloudConsumerFeignHystrixOrder80App.class,args);
    }
}

修改Controller
@RestController
@Slf4j
@RequestMapping("/consumer")
public class OrderController {

    @Resource
    PaymentHystrixService paymentHystrixService;

		//添加服务用降级调用方法
    public CommonResult paymentTimeoutHandler(Long id){
        log.info("PaymentHystrixService - paymentTimeoutHandler");
        return new CommonResult(300,"服务不可用(PaymentHystrixService - paymentTimeoutHandler)",null);
    }
		//添加消费者端服务降级配置
    @GetMapping("/payment/hystrix/timeout/{id}")
     @HystrixCommand(fallbackMethod = "paymentTimeoutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")}
    )
    public CommonResult paymentTimeout(@PathVariable("id") Long id){
        log.info("/consumer/payment/hystrix/timeout/");
        return paymentHystrixService.paymentTimeout(id);
    }

    @GetMapping("/payment/hystrix/success/{id}")
    public CommonResult paymentSuccess(@PathVariable("id") Long id){
        log.info("/consumer/payment/hystrix/success/");
        return paymentHystrixService.paymentSuccess(id);
    }

}
功能测试

image-20210303111435893

重要知识讲解

何时触发fallbackMethod

FAILURE:执行失败,抛出异常。 TIMEOUT:执行超时。 SHORT_CIRCUITED:断路器打开。 THREAD_POOL_REJECTED:线程池拒绝。 SEMAPHORE_REJECTED:信号量拒绝。

注意:并不是所有的异常都会触发fallbackMethod,下面的这些异常或其子类会直接抛出

  1. @HystrixCommand中定义的被忽略的异常ignoreExceptions,会被封装在HystrixBadRequestException中抛出HystrixBadRequestException

  2. 继承了ExceptionNotWrappedByHystrix的异常ExceptionNotWrappedByHystrix

  3. 无法恢复的系统异常 StackOverflowError VirtualMachineError ThreadDeath LinkageError

全局服务降级实践

解决代码膨胀问题

  1. 添加注解

    @DefaultProperties(defaultFallback = "defaultFallbackMethod")

  2. 添加默认处理方法

          //全局异常处理方法不能有入参!
          public CommonResult defaultFallbackMethod(){
              log.info("OrderController - defaultFallbackMethod");
              return new CommonResult(300,"服务不可用(OrderController - defaultFallbackMethod)",null);
          }
    
  3. 启用默认配置

         @GetMapping("/payment/hystrix/timeout/{id}")
    // 如果指定了fallbackMethod 那么会走指定的服务降级处理方法。
    //    @HystrixCommand(fallbackMethod = "paymentTimeoutHandler",commandProperties = {
    //            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")}
    //    )
        @HystrixCommand //如果没有指定fallbackMethod 那么会执行默认的defaultFallbackMethod
        public CommonResult paymentTimeout(@PathVariable("id") Long id){
            int a = 10 / 0;
            log.info("/consumer/payment/hystrix/timeout/");
            return paymentHystrixService.paymentTimeout(id);
        }  
    

解决代码耦合度高问题

为Feign客户端定义,一个默认的服务降级处理类。

为Feign接口创建一个fallback实现类

@Component
public class PaymentFallbackServiceImpl implements PaymentHystrixService {
    @Override
    public CommonResult paymentTimeout(Long id) {
        return new CommonResult(300,"服务不可用,PaymentFallbackServiceImpl默认处理-paymentTimeout",null);
    }

    @Override
    public CommonResult paymentSuccess(Long id) {
        return new CommonResult(300,"服务不可用,PaymentFallbackServiceImpl默认处理-paymentSuccess",null);
    }
}

修改Feign接口定义

@Component
@FeignClient(value = "CLOUD-PROVIDER-PAYMENT",fallback = PaymentFallbackServiceImpl.class)
public interface PaymentHystrixService {

    @GetMapping("/payment/hystrix/timeout/{id}")
    public CommonResult paymentTimeout(@PathVariable("id") Long id);

    @GetMapping("/payment/hystrix/success/{id}")
    public CommonResult paymentSuccess(@PathVariable("id") Long id);
}

测试实例

访问:localhost/consumer/payment/hystrix/success/1 结果如下图:

![image-20210303150435199](/Users/huzd/Library/Application Support/typora-user-images/image-20210303150435199.png)

==停止服务提供8001,==再次访问localhost/consumer/payment/hystrix/success/1 结果如下图:

image-20210303150458961