Hystrix断路器
一、概述
分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候不可避免地失败。
服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其他的微服务,这就是所谓的 “扇出效应” 如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓“雪崩效应”
Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性。
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的,可处理的备选响应(Fallback).而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用发的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩
1. 作用
服务降级
服务熔断
接近实时的监控
2. 现状:停更进维
官网:https://github.com/Netflix/Hystrix/wiki/How-To-Use
Hystrix官宣,停更进维
Hystrix is no longer in active development, and is currently in maintenance mode.
二、Hystrix重要概念
1. 服务降级 FallBack
        向调用方返回一个复合预期的可处理的备选响应,如 “服务器忙,请稍后再试!”不让客户等待并立刻返回一个友好提示。FallBack
哪些情况会触发降级
- 程序运行异常
 - 超时
 - 服务熔断触发服务降级
 - 线程池/信号量打满也会导致服务降级
 
2. 服务熔断 Break
        类似保险丝达到最大服务访问后,直接拒绝访问,然后用服务降级的方法返回友好提示。
        就是保险丝->服务降级-进而熔断-恢复调用链路
3. 服务限流 Flowlimit
秒杀高并发等操作,严禁拥挤,排队1秒N个,有序进行
三、Hystrix 案例
1.构建
1.1 新建module
cloud-provider-hystrix-payment8001
1.2 pom
需引入 spring-cloud-starter-netflix-hystrix 和 Eureka
    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</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>
        <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>
1.3 yml
server:
  port: 8001
spring:
  application:
    name: cloud-provider-hystrix-payment
eureka:
  client:
    register-with-eureka: true
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
1.4 main
@SpringBootApplication
@EnableEurekaClient
public class HystrixPaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixPaymentMain8001.class,args);
    }
}
1.5 service
@Service
public class PaymentService {
    /*正常访问OK的*/
    public String paymentInfo_OK(Integer id){
        return  "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,ID: "+id;
    }
    public String paymentInfo_Timeout(Integer id) {
        try{
            TimeUnit.SECONDS.sleep(3);}
        catch (    InterruptedException e){
            e.printStackTrace();
        }
        return  "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id;
    }
}
1.6 controller
@RestController
@Slf4j
public class PaymentController {
    @Resource
    private PaymentService paymentService;
    @Value("${server.port}")
    private String serverPort;
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id")Integer id){
        String result=paymentService.paymentInfo_OK(id);
        log.info("****result:"+result);
        return  result;
    }
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
        String result=paymentService.paymentInfo_Timeout(id);
        log.info("****result:"+result);
        return  result;
    }
}
1.7 启动本服务 和 Eureka服务

1.8 查看正常访问耗时
3000ms

不到10ms

2.Jmeter高并发测试8001
2.1 新建线程组



2.2 开启压测后 两个接口耗时明显增多
开启压测后查看耗时
ok:1700ms

timeout: 4200ms

tomcat的默认工作线程数被打满了,没有多余的线程来分解压力和处理。
2.3 结论
上面是服务提供者自测,如果消费者访问,只能干等,最终导致消费端80不满意,服务端8001直接被拖死。
3.新建消费者80 使用消费者测试接口
3.1. 新建module cloud-consumer-feign-hystrix-order80
3.2 pom
    <dependencies>
        <!--open feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--eureka-client-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.zhl.springcloud</groupId>
            <artifactId>cloud-api-commons</artifactId>
            <version>${project.version}</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>
            <scope>test</scope>
        </dependency>
    </dependencies>
3.3. yml
server:
  port: 80
spring:
  application:
    name: cloud-consumer-feign-hystrix-order80
eureka:
  client:
    register-with-eureka: true
    fetchRegistry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
3.4 main
@SpringBootApplication
@EnableFeignClients
public class FeignHystixOrderMainF80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignHystixOrderMainF80.class,args);
    }
}
3.5 Service
com.zhl.springcloud.service.PaymentHystrixService
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT")
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id")Integer id);
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id);
}
3.6 Controller
com.zhl.springcloud.controller.OrderHystrixController
@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    PaymentHystrixService paymentHystrixService;
    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id")Integer id){
        return paymentHystrixService.paymentInfo_OK(id);
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }
}
3.7 调用测试
测试OK接口 耗时8ms


开启JMeter进行压测后,再访问

4.故障现象和导致原因
8001 访问繁忙,客户端相应缓慢。
5.结论
正因为有上述故障或不佳表现,才有 降级、容错、限流技术诞生。
6.如何解决?解决的需求
- 服务提供者超时、服务器变慢:消费者不能一直等待,必须有服务降级
 - 服务提供者出错(宕机或程序运行出错):消费者不能一直等待,必须有服务降级
 - 消费者等待时间小于服务提供者运行时间,消费者自己处理降级。
 
7. 服务降级
7.1.降级配置
@HystrixCommand
7.2 服务提供者分析
设置自身调用超时时间峰值,峰值内可以正常运行超过了需要有兜底的方法处理,作服务降级fallback
7.3 服务提供者8001服务降级代码优化
一旦调用服务方法失败并抛出了错误信息后会自动调用@HystrixCommand标注好的fallbackMethod调用类中指定的方法
超时 或 降级 都由fallbackMethod处理
使用@HystrixCommand 注解 标注超时处理方法,
7.4 服务提供者8001服务降级优化
7.4.1 Service:
com.zhl.springcloud.service.PaymentService
@Service
public class PaymentService {
    /*正常访问OK的*/
    public String paymentInfo_OK(Integer id){
        return  "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,ID: "+id;
    }
    /*超时 或 降级 都由fallbackMethod处理*/
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
            //正常超时时间3秒,超时执行fallbackMethod
            @HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public String paymentInfo_Timeout(Integer id) {
        int timenumner=5;
        try{
            TimeUnit.SECONDS.sleep(timenumner);}
        catch (    InterruptedException e){
            e.printStackTrace();
        }
        return  "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id+"\t"+"耗时:"+timenumner;
    }
    /*降级处理*/
    public   String paymentInfo_TimeoutHandler(Integer id){
        return  "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id+"\t"+"降级处理";
    }
}
7.4.2 主启动类激活 添加新注解 @EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class HystrixPaymentMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixPaymentMain8001.class,args);
    }
}
7.4.3 超时降级测试
服务需要5秒,3秒返回降级信息。

7.4.4 异常测试
修改业务类
    /*超时 或 降级 都由fallbackMethod处理*/
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
            //正常超时时间3秒,超时执行fallbackMethod
            @HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "3000")
    })
    public String paymentInfo_Timeout(Integer id) {
        int timenumner=5;
        /*测试报错*/
        int age=10/0;
        try{
            TimeUnit.SECONDS.sleep(timenumner);}
        catch (    InterruptedException e){
            e.printStackTrace();
        }
        return  "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id+"\t"+"耗时:"+timenumner;
    }
    /*降级处理*/
    public   String paymentInfo_TimeoutHandler(Integer id){
        return  "线程池:"+Thread.currentThread().getName()+" 系统繁忙或运行保存,请稍后再试,ID:"+id+"\t"+"降级处理";
    }
访问测试:

7.5 消费者服务80 服务降级
7.5.1 YML 配置OpenFeign 支持 Hystrix
server:
  port: 80
spring:
  application:
    name: cloud-consumer-feign-hystrix-order80
eureka:
  client:
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka
#OpenFeign开启Hystrix
feign:
  hystrix:
    enabled: true
7.5.2 Main 开启熔断器@EnableCircuitBreaker
@SpringBootApplication
@EnableFeignClients
@EnableCircuitBreaker
public class FeignHystixOrderMainF80 {
    public static void main(String[] args) {
        SpringApplication.run(FeignHystixOrderMainF80.class,args);
    }
}
7.5.3 Controller
com.zhl.springcloud.controller.OrderHystrixController
@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    PaymentHystrixService paymentHystrixService;
    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id")Integer id){
        return paymentHystrixService.paymentInfo_OK(id);
    }
    /*1.5秒超时*/
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
            @HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }
    /*降级处理*/
    public   String paymentInfo_TimeoutHandler(Integer id){
        return  "我是消费者80,对方支付系统繁忙,请10秒钟后再试或者自己运行出错请检查自己 (╥╯^╰╥)";
    }
}
7.5.4 服务提供者业务代码 5秒超时,3秒业务处理时间
@Service
public class PaymentService {
    /*正常访问OK的*/
    public String paymentInfo_OK(Integer id){
        return  "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,ID: "+id;
    }
    /*超时 或 降级 都由fallbackMethod处理*/
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
            //正常超时时间3秒,超时执行fallbackMethod
            @HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "5000")
    })
    public String paymentInfo_Timeout(Integer id) {
        int timenumner=3;
        /*测试报错*/
        try{
            TimeUnit.SECONDS.sleep(timenumner);}
        catch (    InterruptedException e){
            e.printStackTrace();
        }
        return  "线程池:"+Thread.currentThread().getName()+" paymentInfo_Timeout,ID:"+id+"\t"+"耗时:"+timenumner;
    }
    /*降级处理*/
    public   String paymentInfo_TimeoutHandler(Integer id){
        return  "线程池:"+Thread.currentThread().getName()+" 系统繁忙或运行保存,请稍后再试,ID:"+id+"\t"+"降级处理";
    }
}
7.5.5 超时测试效果 提供者3秒 消费者1.5秒

7.5.6 消费者自己出错测试
int age=10/0 测试出错
    /*1.5秒超时*/
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
            @HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
        int age=10/0;
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }
    /*降级处理*/
    public   String paymentInfo_TimeoutHandler(Integer id){
        return  "我是消费者80,对方支付系统繁忙,请10秒钟后再试或者自己运行出错请检查自己, (╥╯^╰╥)";
    }
7.6 服务降级全局配置
目前每个业务对应一个兜底的方法,代码膨胀,需要统一和自定义分开。
7.6.1 Controller级别配置
在Controller加@DefaultPropertie注解,指定控制器内全局处理方法 .
com.zhl.springcloud.controller.OrderHystrixController
在接口上只引用@HystrixCommand ,不用指定fallbackMethod.
定义一个Controller全局的处理方法,这里为ControllerGlobalFallBack
注意:
接口级别的Fallback Method需要参数与返回类型与接口一致。
类级别的 FallbackMethod 这里为ControllerGlobalFallBack 不能有参数,否则报错 fallback method wasn't found:
@RestController
@Slf4j
/*Controller级别默认处理方法*/
@DefaultProperties(defaultFallback = "ControllerGlobalFallBack")
public class OrderHystrixController {
    @Resource
    PaymentHystrixService paymentHystrixService;
    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id")Integer id){
        return paymentHystrixService.paymentInfo_OK(id);
    }
    /*1.5秒超时*/
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    /*自定义指定处理方法*/
    /*@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
            @HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })*/
    @HystrixCommand
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
        //int age=10/0;
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }
    /*降级处理*/
    public   String paymentInfo_TimeoutHandler(Integer id){
        return  "我是消费者80,对方支付系统繁忙,请10秒钟后再试或者自己运行出错请检查自己, (╥╯^╰╥)";
    }
    /*Controller全局FallBack处理*/
    public String ControllerGlobalFallBack(){
        return "ControllerGlobalFallBack";
    }
}
7.6.2 服务调用接口统一配置
新建一个类 实现FeignClient所在接口
@Component
public class HystrixPaymentFallBackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "---HystrixPaymentServiceImpl_paymentInfo_OK fallback ";
    }
    @Override
    public String paymentInfo_TimeOut(Integer id) {
        return "----HystrixPaymentServiceImpl_paymentInfo_TimeOut fallback";
    }
}
YML开启Feign
#OpenFeign开启Hystrix
feign:
  hystrix:
    enabled: true
controller 中取消 @HystrixCommand 的注解
@RestController
@Slf4j
/*Controller级别默认处理方法*/
//@DefaultProperties(defaultFallback = "ControllerGlobalFallBack")
public class OrderHystrixController {
    @Resource
    PaymentHystrixService paymentHystrixService;
    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id")Integer id){
        return paymentHystrixService.paymentInfo_OK(id);
    }
    /*1.5秒超时*/
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    /*自定义指定处理方法*/
    /*@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
            @HystrixProperty( name="execution.isolation.thread.timeoutInMilliseconds",value = "1500")
    })*/
    /* @HystrixCommand*/
    public String paymentInfo_TimeOut(@PathVariable("id")Integer id){
        //int age=10/0;
        return paymentHystrixService.paymentInfo_TimeOut(id);
    }
    /*降级处理*/
    public   String paymentInfo_TimeoutHandler(Integer id){
        return  "我是消费者80,对方支付系统繁忙,请10秒钟后再试或者自己运行出错请检查自己, (╥╯^╰╥)";
    }
    /*Controller全局FallBack处理*/
    public String ControllerGlobalFallBack(){
        return "ControllerGlobalFallBack";
    }
}
访问timeout接口测试


 
 
 
 
 
 