06.SpringCloud Hystrix (断路器)


1.概述

分布式系统面临的问题


是什么


能干嘛

  • 服务降级
  • 服务熔断
  • 接近实时的监控

官网资料

https://github.com/Netflix/Hystrix/wiki/How-To-Use

Hystrix官宣,停更进维

https://github.com/Netflix/Hystrix
  • 被动修复bugs
  • 不再接受合并请求
  • 不再发布新版本

2.Hystrix重要概念

服务降级

服务器忙,请稍候再试,不让客户端等待并立刻返回一个友好提示,fallback哪些情况会触发降级
  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满也会导致服务降级

服务熔断

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示就是保险丝步骤如下:
  • 服务的降级->进而熔断->恢复调用链路(这个重点)

服务限流

秒杀高并发等操作,严禁一窝蜂的过来拥挤,大家排队,一秒钟N个,有序进行

3.hystrix案例

演示工程构建

新建cloud-provider-hystrix-payment8001pom
<?xml version="1.0" encoding="UTF-8"?>

    
        springcloud2020
        com.chl.springcloud
        1.0-SNAPSHOT
    
    4.0.0

    cloud-provider-hystrix-payment8001

    
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        


        
            com.chl.springcloud
            cloud-api-commons
            1.0-SNAPSHOT
        


        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-actuator
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        

        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    


yml
server:
  port: 8001

spring:
  application:
    name: cloud-provider-hystrix-payment

eureka:
  client:
    register-with-eureka: true    #表识不向注册中心注册自己
    fetch-registry: true   #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
主启动
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}
业务类service
@Service
public class PaymentService {

     //成功
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_OK,id:  "+id+"\t"+"哈哈哈"  ;
    }

    //失败
    public String paymentInfo_TimeOut(Integer id){
        int timeNumber = 3;
        try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"呜呜呜"+" 耗时(秒)"+timeNumber;
    }

}
controller
@RestController
@Slf4j
public class PaymentController {

    @Autowired
    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;
    }
}
正常测试/* 7001启动cloud-provider-hystrix-payment8001访问
  • http://localhost:8001/payment/hystrix/ok/1
  • http://localhost:8001/payment/hystrix/timeout/31   每次调用耗费5秒钟

上述module均OK以上述为根基平台,从正确->错误->降级熔断->恢复 

高并发测试

上述在非高并发情形下,还能勉强满足   but.....

Jmeter压测测试

开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务再来一个访问
  • http://localhost:8001/payment/hystrix/ok/31
  • http://localhost:8001/payment/hystrix/timeout/31
看演示结果两个都在自己转圈圈为什么会被卡死tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

Jmeter压测结论

上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖死tomcat的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

看热闹不嫌弃事大,80新建加入

新建立cloud-consumer-feign-hystrix-order80pom
<?xml version="1.0" encoding="UTF-8"?>

    
        springcloud2020
        com.chl.springcloud
        1.0-SNAPSHOT
    
    4.0.0

    cloud-consumer-feign-hystrix-order80

    
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-eureka-client
        
        
            com.chl.springcloud
            cloud-api-commons
            1.0-SNAPSHOT
        
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-actuator
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        

        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    

yml
server:
  port: 80

spring:
  application:
    name: cloud-provider-hystrix-order

eureka:
  client:
    register-with-eureka: true    #表识不向注册中心注册自己
    fetch-registry: true   #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/ #设置与eureka server交互的地址查询服务和注册服务都需要依赖这个地址
#配置ribbon的超时 避免服务提供方业务处理太久,ribbon报错,默认ribbon只等待1秒
ribbon:
  ReadTimeout:  5000 #请求连接的超时时间 5秒
  ConnectTimeout: 5000 #请求处理的超时时间 5秒

feign:
  hystrix:
    enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
主启动
@SpringBootApplication
@EnableFeignClients
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}
业务类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);
}
OrderHystrixController
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
//全局的hystrix 服务降级  要用全局服务降级的,就在方法上面加一个 @HystrixCommand  没有@HystrixCommand,有问题,不会走hystrix
public class OrderHystrixController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_OK(id);
        log.info("*******result:"+result);
        return result;
    }
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        log.info("*******result:"+result);
        return result;
    }
}
正常测试http://localhost/consumer/payment/hystrix/ok/1 高并发测试2W个线程压8001消费端80微服务再去访问正常的OK微服务8001地址http://localhost/consumer/payment/hystrix/timeout/31 消费者80,呜呜呜
  • 要么转圈圈等待
  • 要么消费端报超时错误

故障现象和导致原因

8001同一层次的其他接口服务被困死,因为tomcat线程里面的工作线程已经被挤占完毕80此时调用8001,客户端访问响应缓慢,转圈圈

上诉结论

正因为有上述故障或不佳表现,才有我们的降级/容错/限流等技术诞生

如何解决?解决的要求

超时导致服务器变慢(转圈)   
  • 超时不再等待
出错(宕机或程序运行出错)
  • 出错要有兜底
解决
  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级
  • 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级
  • 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

4.Hystrix的降级,熔断,限流说明

服务降级

降低配置  主要注解 @HystrixCommand

服务提供者的fallback处理

8001先从自身找问题设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处理,作服务降级fallback

业务类启用

@HystrixCommand报异常后如何处理
package com.chl.springcloud.service;

import cn.hutool.core.util.IdUtil;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PathVariable;

import java.util.concurrent.TimeUnit;

@Service
public class PaymentService {

    //成功
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_OK,id:  "+id+"\t"+"哈哈哈"  ;
    }
    //失败
    /**
     * 服务提供方自己也要有自己的熔断处理
     * @param id
     * @return
     */
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",commandProperties = {
            //3秒钟以内就是正常的业务逻辑
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
    })
    public String paymentInfo_TimeOut(Integer id){

        int age = 10/0; //测试异常的降级
        int timeNumber = 3; //测试超时的降级  业务逻辑处理3秒

        try { TimeUnit.SECONDS.sleep(timeNumber); }catch (Exception e) {e.printStackTrace();}
    
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_TimeOut,id:  "+id+"\t"+"成功"+" 耗时(秒)";
    }

    //兜底方法
    public String paymentInfo_TimeOutHandler(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   系统繁忙, 请稍候再试  ,id:  "+id+"\t"+"哭了哇呜";
    }
}
一旦调用服务方法失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用类中的指定方法

主启动类激活

添加新注解@EnableCircuitBreaker
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启hystrix
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}

服务消费者的fallback处理

80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护切记我们自己配置过的热部署方式对java代码的改动明显,但对@HystrixCommand内属性的修改建议重启微服务

配置开启

feign:
  hystrix:
    enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。

启动类开启

@EnableHystrix
@SpringBootApplication
@EnableFeignClients
@EnableHystrix  //开启hystrix
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}

业务类处理

在OrderHystrixController上面加测试方法
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    //消费者端的降级处理   用这边的hystrix获取, 先注释掉服务提供方的HystrixCommand 一般hystrix,降级在消费端进行
    //也可这边的等待时间,小于那边的时间来测试
    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "1500")  //3秒钟以内就是正常的业务逻辑
    })
    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秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
    }

目前问题

  • 每个业务方法对应一个兜底的方法,代码膨胀
  • 统一和自定义的分开

解决问题

统一的全局处理

每个方法配置一个???膨胀,所以来个全局
  • @DefaultProperties(defaultFallback = "")
controller的配置
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
//全局的hystrix 服务降级  要用全局服务降级的,就在方法上面加一个 @HystrixCommand  没有@HystrixCommand,有问题,不会走hystrix
public class OrderHystrixController {

    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @Value("${server.port}")
    private String serverPort;

    @GetMapping("/consumer/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_OK(id);
        log.info("*******result:"+result);
        return result;
    }

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    @HystrixCommand  //用这个注解来使用全局的hystrix  使用了业务类解耦分离的降级方式,不用使用@HystrixCommand 在方法上了
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }

    //下面是全局fallback方法
    public String payment_Global_FallbackMethod(){
        return "Global异常处理信息,请稍后再试,(┬_┬)";
    }

}

和feign整合

和业务逻辑混一起???混乱
服务降级,客户端去调用服务端,碰上服务端宕机或关闭本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦
未来我们要面对的异常
  • 运行
  • 超时
  • 宕机

再看我们的业务类PaymentController
修改cloud-consumer-feign-hystrix-order80根据cloud-consumer-feign-hystrix-order80已经有的PaymentHystrixService接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理
/**
 * 实现feign的接口 来实现hystrix 降级和业务类分离 解耦
 */
@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 fall back-paymentInfo_TimeOut , (┬_┬)";
    }
}
 
yml
feign:
  hystrix:
    enabled: true #如果处理自身的容错就开启。开启方式与生产端不一样。
feign的接口修改PaymentFeignClientService接口
@Component
@FeignClient(
        value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",
        //设置这个feigin类的 hystrix处理类  就不用在调用它的方法上面加 @HystrixCommand
        fallback = PaymentFallbackService.class)
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);
}
测试单个eureka先启动7001PaymentHystrixMain8001启动正常访问测试http://localhost/consumer/payment/hystrix/ok/1 故意关闭微服务8001客户端自己调用提升此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

服务熔断

断路器

一句话就是家里保险丝

熔断是什么

实操

修改cloud-provider-hystrix-payment8001PaymentService
   //服务熔断 的相关测试
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),  //是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),   //请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),  //时间范围就是默认10秒内,错误多少
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), //失败率达到多少后跳闸
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        if (id < 0){
            throw new RuntimeException("*****id 不能负数");
        }
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
    }
    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
        return "id 不能负数,请稍候再试,(┬_┬)/~~     id: " +id;
    }
why配置这些参数PaymentController新加测试方法
  //===测试服务熔断
    @GetMapping("/payment/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id){
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("*******result:"+result);
        return result;
    }

测试自测cloud-provider-hystrix-payment8001正确 http://localhost:8001/payment/circuit/1 错误http://localhost:8001/payment/circuit/-1
一次正确一次错误trytry多次错误,然后慢慢正确,发现刚开始不满足条件,就算是正确的访问地址也不能进行访问,需要慢慢的恢复链路

小总结

大神结论

熔断类型

  • 熔断打开
       请求不再进行调用当前服务,内部设置时钟一般为MTTR(平均故障处理时间),当打开时长达到所设时钟则进入熔断状态
  • 熔断关闭
      熔断关闭不会对服务进行熔断
  • 熔断半开   恢复链路
      部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

官网断路器流程图



断路器在什么情况下开始起作用


断路器开启或者关闭的条件

  • 当满足一定阀值的时候(默认10秒内超过20个请求次数)
  • 当失败率达到一定的时候(默认10秒内超过50%请求失败)
  • 到达以上阀值,断路器将会开启
  • 当开启的时候,所有请求都不会进行转发
  • 一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启。重复4和5

断路器打开之后

All配置


服务限流

后面高级篇讲解alibaba的Sentinel说明

5.hystrix工作流程

https://github.com/Netflix/Hystrix/wiki/How-it-Works
hystrix工作流程

6.服务监控hystrixDashboard监控仪表盘

概述

仪表盘9001

新建cloud-consumer-hystrix-dashboard9001pom
<?xml version="1.0" encoding="UTF-8"?>

    
        springcloud2020
        com.chl.springcloud
        1.0-SNAPSHOT
    
    4.0.0

    cloud-consumer-hystrix-dashboard9001

    
        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix-dashboard
        
        
            org.springframework.boot
            spring-boot-starter-actuator
        

        
            org.springframework.boot
            spring-boot-devtools
            runtime
            true
        

        
            org.projectlombok
            lombok
            true
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        
    
yml
server:
  port: 9001
启动类HystrixDashboardMain9001+新注解@EnableHystrixDashboard
@SpringBootApplication
@EnableHystrixDashboard //开启监控仪表盘 就是开启这个工程,去监控其他程序
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class,args);
    }
}
所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置
 
     org.springframework.boot
     spring-boot-starter-actuator
启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001http://localhost:9001/hystrix

断路器演示

修改cloud-provider-hystrix-payment8001注意:新版本Hystrix需要在主启动类MainAppHystrix8001中指定监控路径
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker //开启hystrix
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }

    //被监测的微服务要加这个 才能看到图形
    @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  404

监控测试(9001监控8001

启动1个eureka或者3个eureka集群均可观察监控窗口9001监控8001填写监控地址    http://localhost:8001/hystrix.stream
测试地址
  • http://localhost:8001/payment/circuit/31
  • http://localhost:8001/payment/circuit/-31
上述测试通过     先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的

如何看

  • 7色 
  • 1圈  
  • 1线

整图说明
搞懂一个才能看懂复杂的