1. Hystrix

1. 概述

分布式系统面临的问题
复杂分布式体系结构中的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免地失败。

服务雪崩
多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”
image.png
对于高流量的应用来说,单一的后避依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

所以,通常当你发现一个模块下的某个实例失败后,这时候这个模块依然还会接收流量,然后这个有问题的模块还调用了其他的模块,这样就会发生级联故障,或者叫雪崩

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

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

Hystrix n. 豪猪属;猬草属;豪猪;豪猪亚属

2. Hystrix停更维护

能干嘛

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

官网资料
Hystrix官宣,停更进维

  • 被动修bugs
  • 不再接受合并请求
  • 不再发布新版本

image.png

3. Hystrix的服务降级/熔断/限流概念初讲

1. 服务降级

什么是服务降级?当服务器压力剧增的情况下,根据实际业务情况及流量,对一些服务和页面有策略的不处理或换种简单的方式处理,从而释放服务器资源以保证核心交易正常运作或高效运作。
举个例子:假如目前有很多人想要给我付钱,但我的服务器除了正在运行支付的服务之外,还有一些其它的服务在运行,比如搜索、定时任务和详情等等。然而这些不重要的服务就占用了JVM的不少内存与CPU资源,为了能把钱都收下来(钱才是目标),我设计了一个动态开关,把这些不重要的服务直接在最外层拒掉,这样处理后的后端处理收钱的服务就有更多的资源来收钱了(收钱速度更快了),这就是一个简单的服务降级的使用场景

  • 超时降级 —— 主要配置好超时时间和超时重试次数和机制,并使用异步机制探测恢复情况
  • 失败次数降级 —— 主要是一些不稳定的API,当失败调用次数达到一定阀值自动降级,同样要使用异步机制探测回复情况
  • 故障降级 —— 如要调用的远程服务挂掉了(网络故障、DNS故障、HTTP服务返回错误的状态码和RPC服务抛出异常),则可以直接降级
  • 限流降级 —— 当触发了限流超额时,可以使用暂时屏蔽的方式来进行短暂的屏蔽

2. 服务熔断

熔断这一概念来源于电子工程中的断路器(Circuit Breaker)
在互联网系统中,当下游服务因访问压力过大而响应变慢或失败,上游服务为了保护系统整体的可用性,可以暂时切断对下游服务的调用
类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

服务的降级 -> 进而熔断 -> 恢复调用链路

与服务降级的异同点

  • 相同点
    • 目的很一致,都是从可用性可靠性着想,为防止系统的整体缓慢甚至崩溃,采用的技术手段;
    • 最终表现类似,对于两者来说,最终让用户体验到的是某些功能暂时不可达或不可用;
    • 粒度一般都是服务级别,当然,业界也有不少更细粒度的做法,比如做到数据持久层(允许查询,不允许增删改);
    • 自治性要求很高,熔断模式一般都是服务基于策略的自动触发,降级虽说可人工干预,但在微服务架构下,完全靠人显然不可能,开关预置、配置中心都是必要手段;
  • 区别
    • 触发原因不太一样,服务熔断一般是某个服务(下游服务)故障引起,而服务降级一般是从整体负荷考虑;
    • 管理目标的层次不太一样, 熔断其实是一个框架级的处理,每个微服务都需要(无层级之分),而降级一般需要对业务有层级之分(比如降级一般是从最外围服务开始)
    • 实现方式不太一样;服务降级具有代码侵入性(由控制器完成/或自动降级),熔断一般称为自我熔断

3. 服务限流

有些场景并不能用熔断和降级来解决,比如稀缺资源(秒杀、抢购)、写服务(如评论、下单)、频繁的复杂查询(评论的最后几页),因此需有一种手段来限制这些场景的并发/请求量,即限流。

限流的目的是通过对并发访问/请求进行限速或者一个时间窗口内的的请求进行限速来保护系统,一旦达到限制速率则可以拒绝服务(定向到错误页或告知资源没有了)、排队或等待(比如秒杀、评论、下单)、降级(返回兜底数据或默认数据,如商品详情页库存默认有货)。

一般开发高并发系统常见的限流有:限制总并发数(比如数据库连接池、线程池)、限制瞬时并发数(如nginx的limit_conn模块,用来限制瞬时并发连接数)、限制时间窗口内的平均速率(如Guava的RateLimiter、nginx的limit_req模块,限制每秒的平均速率);其他还有如限制远程接口调用速率、限制MQ的消费速率。另外还可以根据网络连接数、网络流量、CPU或内存负载等来限流

4. Hystrix支付微服务构建

前言:将cloud-eureka-server7001改配置成单机版

1. 新建cloud-provider-hystrix-payment8001

2. pom.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  5. <parent>
  6. <artifactId>SpringCloud</artifactId>
  7. <groupId>org.gmf</groupId>
  8. <version>1.0-SNAPSHOT</version>
  9. </parent>
  10. <modelVersion>4.0.0</modelVersion>
  11. <artifactId>cloud-provider-hystrix-payment8001</artifactId>
  12. <properties>
  13. <maven.compiler.source>8</maven.compiler.source>
  14. <maven.compiler.target>8</maven.compiler.target>
  15. </properties>
  16. <dependencies>
  17. <!--hystrix-->
  18. <dependency>
  19. <groupId>org.springframework.cloud</groupId>
  20. <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
  21. </dependency>
  22. <!--eureka client-->
  23. <dependency>
  24. <groupId>org.springframework.cloud</groupId>
  25. <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
  26. </dependency>
  27. <!--web-->
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-starter-web</artifactId>
  31. </dependency>
  32. <dependency>
  33. <groupId>org.springframework.boot</groupId>
  34. <artifactId>spring-boot-starter-actuator</artifactId>
  35. </dependency>
  36. <dependency><!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
  37. <groupId>org.gmf</groupId>
  38. <artifactId>cloud-api-commons</artifactId>
  39. <version>1.0-SNAPSHOT</version>
  40. </dependency>
  41. <dependency>
  42. <groupId>org.springframework.boot</groupId>
  43. <artifactId>spring-boot-devtools</artifactId>
  44. <scope>runtime</scope>
  45. <optional>true</optional>
  46. </dependency>
  47. <dependency>
  48. <groupId>org.projectlombok</groupId>
  49. <artifactId>lombok</artifactId>
  50. <optional>true</optional>
  51. </dependency>
  52. <dependency>
  53. <groupId>org.springframework.boot</groupId>
  54. <artifactId>spring-boot-starter-test</artifactId>
  55. <scope>test</scope>
  56. </dependency>
  57. </dependencies>
  58. </project>

3. application.yaml

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

4. 主启动类

@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
    }
}

5. 业务类Service

@Slf4j
@Service
public class PaymentService {
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"paymentId:"+id;
    }

    public String paymentInfo_Timeout(Integer id){
        Integer timeout = 3;
        try {
            TimeUnit.SECONDS.sleep(timeout);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:"+Thread.currentThread().getName()+"paymentId:"+id+"耗时:"+timeout+"秒";
    }
}

6. Controller

@Slf4j
@RestController
@RequestMapping("/payment/hystrix")
public class PaymentController {
    @Value("${server.port}")
    private String serverPort;

    @Autowired
    private PaymentService paymentService;

    @GetMapping("/ok/{id}")
    public String payment_OK(@PathVariable("id")Integer id){
        String result = paymentService.paymentInfo_OK(id);
        log.info("result = "+result);
        return result;
    }

    @GetMapping("/timeout/{id}")
    public String payment_Timeout(@PathVariable("id")Integer id){
        String result = paymentService.paymentInfo_Timeout(id);
        log.info("result = "+result);
        return result;
    }
}

7. 测试

运行eureka7001、cloud-provider-hystrix-payment8001
浏览器访问未超时方法:http://localhost:8001/payment/hystrix/ok/1
浏览器访问超时方法,每次耗时3+秒:http://localhost:8001/payment/hystrix/timeout/1
以上测试全部成功,以上述为根基平台,从正确 -> 错误 -> 降级熔断 -> 恢复

5. JMeter高并发压测

上述在非高并发情形下,还能勉强满足,接下来使用Jmeter高并发测试
JMeter官网
JMeter下载使用教程

The Apache JMeter™ application is open source software, a 100% pure Java application designed to load test functional behavior and measure performance. It was originally designed for testing Web Applications but has since expanded to other test functions.

创建线程组,并设置并发量

image.png
image.png

创建HTTP请求,并设置请求路径

image.png
image.png

开启Jmeter,来20000个并发压死8001,20000个请求都去访问paymentInfo_TimeOut服务

image.png
开启测试,控制台不断打印测试结果,当浏览器再去请求http://localhost:8001/payment/hystrix/ok/1时发现未超时的请求速度也被拖慢

Jmeter压测结论

上面还是服务提供者8001自己测试,假如此时外部的消费者80也来访问,那消费者只能干等,最终导致消费端80不满意,服务端8001直接被拖慢。

6. 订单微服务调用支付微服务出现卡顿

加入80端口的订单微服务消费端进行测试

1. 新建 - cloud-consumer-feign-hystrix-order80

2. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloud</artifactId>
        <groupId>org.gmf</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-feign-hystrix-order80</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--openfeign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--hystrix-->
        <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>
        <!-- 引入自己定义的api通用包,可以使用Payment支付Entity -->
        <dependency>
            <groupId>org.gmf</groupId>
            <artifactId>cloud-api-commons</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-starter-test</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>
    </dependencies>
</project>

3. application.yaml

server:
  port: 80

eureka:
  client:
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/

#设置OpenFeign超时控制,防止调用远程服务实例出现Timeout异常
ribbon:
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ReadTimeout: 5000
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间
  ConnectTimeout: 5000

4. 主启动类

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

5. OpenFeign业务类Service

@FeignClient("CLOUD-PROVIDER-HYSTRIX-PAYMENT")
@RequestMapping("/payment/hystrix")
public interface PaymentHystrixService {
    @GetMapping("/ok/{id}")
    public String payment_OK(@PathVariable("id")Integer id);

    @GetMapping("/timeout/{id}")
    public String payment_Timeout(@PathVariable("id")Integer id);
}

6. Controller

@RestController
@RequestMapping("/payment/hystrix")
public class OrderHystrixController {
    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/ok/{id}")
    public String payment_OK(@PathVariable("id")Integer id){
        return paymentHystrixService.payment_OK(id);
    }

    @GetMapping("/timeout/{id}")
    public String payment_Timeout(@PathVariable("id")Integer id){
        return paymentHystrixService.payment_Timeout(id);
    }
}

7. 正常测试

启动cloud-consumer-feign-hystrix-order80
浏览器访问:http://localhost/payment/hystrix/ok/1(响应极快)
浏览器访问:http://localhost/payment/hystrix/timeout/1(等待3秒)

8. 高并发测试

开启20000线程并发测试
消费端80微服务再去访问正常的ok微服务8001地址http://localhost/payment/hystrix/ok/1(消费者80被拖慢)
消费端80微服务再去访问超时的timeout微服务8001地址http://localhost/payment/hystrix/ok/1(速度变慢甚至出现超时异常的情况)

原因:8001同一层次的其它接口服务被困死,因为tomcat线程池里面的工作线程已经被挤占完毕
正因为有上述故障或不佳表现才有我们的降级/容错/限流等技术诞生

7. 降级容错解决的维度要求

超时导致服务器变慢(转圈) - 超时不再等待
出错(宕机或程序运行出错) - 出错要有兜底
解决:

  • 对方服务(8001)超时了,调用者(80)不能一直卡死等待,必须有服务降级。
  • 对方服务(8001)down机了,调用者(80)不能一直卡死等待,必须有服务降级。
  • 对方服务(8001)OK,调用者(80)自己出故障或有自我要求(自己的等待时间小于服务提供者),自己处理降级

8. Hystrix之服务降级支付侧fallback

@HystrixCommand —— 降级配置注解
设置自身调用超时时间的峰值,峰值内可以正常运行,超过了需要有兜底的方法处埋,作服务降级fallback

1. 修改支付端8001的业务类

@Slf4j
@Service
public class PaymentService {
    public String paymentInfo_OK(Integer id) {
        return "线程池:" + Thread.currentThread().getName() + "paymentId:" + id;
    }

    //服务降级处理
    @HystrixCommand(
            //指定善后方法名
            fallbackMethod = "paymentInfo_TimeOutHandler",
            commandProperties = {
                    //设置条件执行时间不能超过3秒,超过则调用善后方法
                    @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3000")
            })
    public String paymentInfo_Timeout(Integer id) {
        Integer timeout = 5;
        int num = 10 / 0;
        try {
            TimeUnit.SECONDS.sleep(timeout);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:" + Thread.currentThread().getName() + "paymentId:" + id + "\tO(∩_∩)O" + "耗时:" + timeout + "秒";
    }

    //用作服务降级超时善后方法
    public String paymentInfo_TimeOutHandler(Integer id) {
        return "线程池:  " + Thread.currentThread().getName() + "  8001系统繁忙或者运行报错,请稍后再试,id:  " + id + "\to(╥﹏╥)o";
    }
}

上面故意制造两种异常(注释掉一处,单独测试另一处):

  • int age = 10/0,计算异常
  • 我们能接受3秒钟,它运行5秒钟,超时异常

当前服务不可用了,做服务降级,兜底的方案都是paymentInfo_TimeOutHandler

controller中超时时间配置不生效原因:关键在于feign:hystrix:enabled: true的作用,官网解释“Feign将使用断路器包装所有方法”,也就是将@FeignClient标记的那个service接口下所有的方法进行了hystrix包装(类似于在这些方法上加了一个@HystrixCommand),这些方法会应用一个默认的超时时间为1s,所以你的service方法也有一个1s的超时时间,service1s就会报异常,controller立马进入备用方法,controller上那个3秒那超时时间就没有效果了
解决方法:改变这个默认超时时间方法,在application.yaml中添加如下配置

hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 5000

2. 主启动类激活服务降级功能

主启动类上添加注解 @EnableCircuitBreaker

@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker  //开启服务降级处理
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class, args);
    }
}

3. 测试

浏览器访问:http://localhost/payment/hystrix/timeout/1 显示如下结果
image.png

9. Hystrix之服务降级订单侧fallback

80订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护

1. application.yaml中添加如下

...

#开启feign与hystrix协调功能
feign:
  hystrix:
    enabled: true

2. 主启动类

@SpringBootApplication
@EnableFeignClients
@EnableHystrix  //开启服务降级处理
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class, args);
    }
}

查看@EnableHystrix的源码可以发现,它继承了@EnableCircuitBreaker,并对它进行了在封装

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@EnableCircuitBreaker
public @interface EnableHystrix {
}

这两个注解都是激活hystrix的功能,我们根据上面代码得出来结论,只需要在服务启动类加入@EnableHystrix注解即可,无须增加@EnableCircuitBreaker注解,本身@EnableHystrix注解已经涵盖了EnableCircuitBreaker的功能

3. 结合Feign实现Hystrix服务降级功能

在Controller调用FeignService时进程服务降级处理
@RestController
@RequestMapping("/payment/hystrix")
public class OrderHystrixController {
    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/ok/{id}")
    public String payment_OK(@PathVariable("id")Integer id){
        return paymentHystrixService.payment_OK(id);
    }

    @HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
    })
    @GetMapping("/timeout/{id}")
    public String payment_Timeout(@PathVariable("id")Integer id){
        //int num  = 10 / 0;
        return paymentHystrixService.payment_Timeout(id);
    }

    //善后方法
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
    }
}

测试:浏览器访问http://localhost/payment/hystrix/timeout/1
image.png
发现响应的时消费端的善后方法
结论:在消费端的服务降级处理条件如果先于服务实例端,则会优先使用消费端的服务降级处理

10. 全局服务降级DefaultProperties

目前问题1 每个业务方法对应一个兜底的方法,代码膨胀
解决方法

  • 1:1每个方法配置一个服务降级方法,技术上可以,但是不聪明
  • 1:N除了个别重要核心业务有专属,其它普通的可以通过@DefaultProperties(defaultFallback = "")统一跳转到统一处理结果页面

通用的和独享的各自分开,避免了代码膨胀,合理减少了代码量

@RestController
@RequestMapping("/payment/hystrix")
@DefaultProperties(defaultFallback = "paymentGlobalTimeOutFallbackMethod")  //标注该类全局使用的善后方法
public class OrderHystrixController {
    @Autowired
    private PaymentHystrixService paymentHystrixService;

    @GetMapping("/ok/{id}")
    public String payment_OK(@PathVariable("id")Integer id){
        return paymentHystrixService.payment_OK(id);
    }

    /*@HystrixCommand(fallbackMethod = "paymentTimeOutFallbackMethod",commandProperties = {
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "1500")
    })*/
    @HystrixCommand  //使用全局的服务降级
    @GetMapping("/timeout/{id}")
    public String payment_Timeout(@PathVariable("id")Integer id){
        int num  = 10 / 0;
        return paymentHystrixService.payment_Timeout(id);
    }

    //善后方法
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        return "我是消费者80,对方支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,o(╥﹏╥)o";
    }

    //全局的善后方法(注意全局的善后处理方法不能带有参数)
    public String paymentGlobalTimeOutFallbackMethod(){
        return "Global异常处理信息,请稍后再试,/(ㄒoㄒ)/";
    }
}

注意:全局的善后处理方法不能带有参数,否则将会抛出异常
image.png

11. 通配服务降级FeignFallback

目前问题2 统一和自定义的分开,代码混乱
服务降级,客户端去调用服务端,碰上服务端宕机或关闭
本次案例服务降级处理是在客户端80实现完成的,与服务端8001没有关系,只需要为Feign客户端定义的接口添加一个服务降级处理的实现类即可实现解耦

未来我们要面对的异常

  • 运行
  • 超时
  • 宕机

修改cloud-consumer-feign-hystrix-order80
创建PaymentHystrixService业务接口的实现类,为其中所有方法进行善后处理

/**
 * @className: PaymentHystrixServiceFallback
 * @description 定义Feign业务接口实现类,为其中每一个方法都进行善后处理
 * @author GMF
 * @date 2021/3/31
 * @time 23:03
*/
@Component
public class PaymentHystrixServiceFallback implements PaymentHystrixService {
    @Override
    public String payment_OK(Integer id) {
        return "-----PaymentHystrixServiceFallback fall back-paymentInfo_OK ,o(╥﹏╥)o";
    }

    @Override
    public String payment_Timeout(Integer id) {
        return "-----PaymentHystrixServiceFallback fall back-paymentInfo_Timeout ,o(╥﹏╥)o";
    }
}

PaymentHystrixService业务接口中注册实现类为其断容器(善后处理)

@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",fallback = PaymentHystrixServiceFallback.class)  //使用fallback定义默认返回方法(断容器)
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    String payment_OK(@PathVariable("id")Integer id);

    @GetMapping("/payment/hystrix/timeout/{id}")
    String payment_Timeout(@PathVariable("id")Integer id);
}

源码中对于fallback方法的解释
image.png

注意application.yaml和启动类中开启OpenFeign功能以及与Hystrix的协调功能

#开启feign与hystrix协调功能
feign:
  hystrix:
    enabled: true
@SpringBootApplication
@EnableFeignClients
@EnableHystrix  //开启服务降级处理
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class, args);
    }
}

测试
关闭8001服务,再次使用浏览器访问:http://localhost/payment/hystrix/timeout/1
等待Ribbon超时时间,页面显示
image.png
客户端自己调用提示 - 此时服务端provider已经down了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

12. 服务熔断理论

断路器,相当于保险丝。

1. 熔断机制概述

熔断机制是应对雪崩效应的一种微服务链路保护机制。当扇出链路的某个微服务出错不可用或者响应时间太长时,会进行服务的降级,进而熔断该节点微服务的调用,快速返回错误的响应信息。当检测到该节点微服务调用响应正常后,恢复调用链路。

在Spring Cloud框架里,熔断机制通过Hystrix实现。Hystrix会监控微服务间调用的状况,当失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制。熔断机制的注解是@HystrixCommand

Martin Fowler的相关论文
五、服务降级 - 图13

2. 修改cloud-provider-hystrix-payment8001

Hutool国产工具类

PaymentService.java
@Slf4j
@Service
public class PaymentService {
    ...

    //=====服务熔断
    //详细的配置项可查看HystrixPropertiesManager类
    @HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
            @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),// 是否开启断路器
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),// 请求次数
            @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"), // 时间窗口期(经过多久后恢复一次尝试)
            @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"),// 失败率达到多少后跳闸 60%
    })
    public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
        if(id < 0) {
            throw new RuntimeException("******id 不能负数");
        }
        //使用HuTool API获取一个简易不带-的UUID
        String serialNumber = IdUtil.simpleUUID();

        return Thread.currentThread().getName()+"\t"+"调用成功,流水号: " + serialNumber;
    }
    public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id) {
        return "id 不能负数,请稍后再试,/(ㄒoㄒ)/~~   id: " +id;
    }
}

大量配置项可通过HystrixPropertiesManager类查看
image.png

The precise way that the circuit opening and closing occurs is as follows:

  • Assuming the volume across a circuit meets a certain threshold :

HystrixCommandProperties.circuitBreakerRequestVolumeThreshold()

  • And assuming that the error percentage, as defined above exceeds the error percentage defined in :

HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()

  • Then the circuit-breaker transitions from CLOSED to OPEN.
  • While it is open, it short-circuits all requests made against that circuit-breaker.
  • After some amount of time (HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds()), the next request is let through. If it fails, the command stays OPEN for the sleep window. If it succeeds, it transitions to CLOSED and the logic in 1) takes over again

Controller添加Service调用
@Slf4j
@RestController
@RequestMapping("/payment/hystrix")
public class PaymentController {
    ...

    @GetMapping("/circuit/{id}")
    public String paymentCircuitBreaker(@PathVariable("id") Integer id)
    {
        String result = paymentService.paymentCircuitBreaker(id);
        log.info("****result: "+result);
        return result;
    }
}

3. 服务熔断测试

启动cloud-provider-hystrix-payment8001

当id输入为正数http://localhost:8001/payment/hystrix/circuit/1
image.png
当id输入为负数http://localhost:8001/payment/hystrix/circuit/-1
image.png
而当我们多次输入负数,错误率达到60%及以上,Hystrix判定进入服务熔断状态,所有请求都会进行服务降级,此时再次输入负数结果任然是
image.png
直到时间窗口期计时结束,进入Half Open状态,此时输入正数,如果执行成功将会结束服务熔断Open状态
image.png

13. 熔断总结

熔断类型

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

官网断路器流程图

五、服务降级 - 图19

官网步骤

image.png

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

image.png

涉及到断路器的三个重要参数:

  • 快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的10秒。
  • 请求总数阀值:在快照时间窗内,必须满足请求总数阀值才有资格熔断。默认为20,意味着在10秒内,如果该hystrix命令的调用次数不足20次7,即使所有的请求都超时或其他原因失败,断路器都不会打开。
  • 错误百分比阀值:当请求总数在快照时间窗内超过了阀值,比如发生了30次调用,如果在这30次调用中,有15次发生了超时异常,也就是超过50%的错误百分比,在默认设定50%阀值情况下,这时候就会将断路器打开。

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

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

断路器打开之后

  • 再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。
  • 原来的主逻辑要如何恢复呢?
    • hystrix为我们实现了自动恢复功能,当断路器打开,对主逻辑进行熔断之后,hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果这次请求依然有问题,断路器继续进入打开状态,休眠时间窗重新计时

所有配置

@HystrixCommand(fallbackMethod = "fallbackMethod", 
                groupKey = "strGroupCommand", 
                commandKey = "strCommand", 
                threadPoolKey = "strThreadPool",

                commandProperties = {
                    // 设置隔离策略,THREAD 表示线程池 SEMAPHORE:信号池隔离
                    @HystrixProperty(name = "execution.isolation.strategy", value = "THREAD"),
                    // 当隔离策略选择信号池隔离的时候,用来设置信号池的大小(最大并发数)
                    @HystrixProperty(name = "execution.isolation.semaphore.maxConcurrentRequests", value = "10"),
                    // 配置命令执行的超时时间
                    @HystrixProperty(name = "execution.isolation.thread.timeoutinMilliseconds", value = "10"),
                    // 是否启用超时时间
                    @HystrixProperty(name = "execution.timeout.enabled", value = "true"),
                    // 执行超时的时候是否中断
                    @HystrixProperty(name = "execution.isolation.thread.interruptOnTimeout", value = "true"),

                    // 执行被取消的时候是否中断
                    @HystrixProperty(name = "execution.isolation.thread.interruptOnCancel", value = "true"),
                    // 允许回调方法执行的最大并发数
                    @HystrixProperty(name = "fallback.isolation.semaphore.maxConcurrentRequests", value = "10"),
                    // 服务降级是否启用,是否执行回调函数
                    @HystrixProperty(name = "fallback.enabled", value = "true"),
                    // 是否启用断路器
                    @HystrixProperty(name = "circuitBreaker.enabled", value = "true"),
                    // 该属性用来设置在滚动时间窗中,断路器熔断的最小请求数。例如,默认该值为 20 的时候,如果滚动时间窗(默认10秒)内仅收到了19个请求, 即使这19个请求都失败了,断路器也不会打开。
                    @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "20"),

                    // 该属性用来设置在滚动时间窗中,表示在滚动时间窗中,在请求数量超过 circuitBreaker.requestVolumeThreshold 的情况下,如果错误请求数的百分比超过50, 就把断路器设置为 "打开" 状态,否则就设置为 "关闭" 状态。
                    @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage", value = "50"),
                    // 该属性用来设置当断路器打开之后的休眠时间窗。 休眠时间窗结束之后,会将断路器置为 "半开" 状态,尝试熔断的请求命令,如果依然失败就将断路器继续设置为 "打开" 状态,如果成功就设置为 "关闭" 状态。
                    @HystrixProperty(name = "circuitBreaker.sleepWindowinMilliseconds", value = "5000"),
                    // 断路器强制打开
                    @HystrixProperty(name = "circuitBreaker.forceOpen", value = "false"),
                    // 断路器强制关闭
                    @HystrixProperty(name = "circuitBreaker.forceClosed", value = "false"),
                    // 滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
                    @HystrixProperty(name = "metrics.rollingStats.timeinMilliseconds", value = "10000"),

                    // 该属性用来设置滚动时间窗统计指标信息时划分"桶"的数量,断路器在收集指标信息的时候会根据设置的时间窗长度拆分成多个 "桶" 来累计各度量值,每个"桶"记录了一段时间内的采集指标。
                    // 比如 10 秒内拆分成 10 个"桶"收集这样,所以 timeinMilliseconds 必须能被 numBuckets 整除。否则会抛异常
                    @HystrixProperty(name = "metrics.rollingStats.numBuckets", value = "10"),
                    // 该属性用来设置对命令执行的延迟是否使用百分位数来跟踪和计算。如果设置为 false, 那么所有的概要统计都将返回 -1。
                    @HystrixProperty(name = "metrics.rollingPercentile.enabled", value = "false"),
                    // 该属性用来设置百分位统计的滚动窗口的持续时间,单位为毫秒。
                    @HystrixProperty(name = "metrics.rollingPercentile.timeInMilliseconds", value = "60000"),
                    // 该属性用来设置百分位统计滚动窗口中使用 “ 桶 ”的数量。
                    @HystrixProperty(name = "metrics.rollingPercentile.numBuckets", value = "60000"),
                    // 该属性用来设置在执行过程中每个 “桶” 中保留的最大执行次数。如果在滚动时间窗内发生超过该设定值的执行次数,
                    // 就从最初的位置开始重写。例如,将该值设置为100, 滚动窗口为10秒,若在10秒内一个 “桶 ”中发生了500次执行,
                    // 那么该 “桶” 中只保留 最后的100次执行的统计。另外,增加该值的大小将会增加内存量的消耗,并增加排序百分位数所需的计算时间。
                    @HystrixProperty(name = "metrics.rollingPercentile.bucketSize", value = "100"),

                    // 该属性用来设置采集影响断路器状态的健康快照(请求的成功、 错误百分比)的间隔等待时间。
                    @HystrixProperty(name = "metrics.healthSnapshot.intervalinMilliseconds", value = "500"),
                    // 是否开启请求缓存
                    @HystrixProperty(name = "requestCache.enabled", value = "true"),
                    // HystrixCommand的执行和事件是否打印日志到 HystrixRequestLog 中
                    @HystrixProperty(name = "requestLog.enabled", value = "true"),

                },
                threadPoolProperties = {
                    // 该参数用来设置执行命令线程池的核心线程数,该值也就是命令执行的最大并发量
                    @HystrixProperty(name = "coreSize", value = "10"),
                    // 该参数用来设置线程池的最大队列大小。当设置为 -1 时,线程池将使用 SynchronousQueue 实现的队列,否则将使用 LinkedBlockingQueue 实现的队列。
                    @HystrixProperty(name = "maxQueueSize", value = "-1"),
                    // 该参数用来为队列设置拒绝阈值。 通过该参数, 即使队列没有达到最大值也能拒绝请求。
                    // 该参数主要是对 LinkedBlockingQueue 队列的补充,因为 LinkedBlockingQueue 队列不能动态修改它的对象大小,而通过该属性就可以调整拒绝请求的队列大小了。
                    @HystrixProperty(name = "queueSizeRejectionThreshold", value = "5"),
                }
               )
public String doSomething() {
    ...
}

14. Hystrix工作流程最后总结

服务限流 - 后面高级篇讲解alibaba的Sentinel说明
官方解释
官网工作流程图例
五、服务降级 - 图22
步骤说明

  1. 创建 HystrixCommand(用在依赖的服务返回单个操作结果的时候)或 HystrixObserableCommand(用在依赖的服务返回多个操作结果的时候)对象。
  2. 命令执行。
  3. 其中 HystrixCommand 实现了下面前两种执行方式
    1. execute():同步执行,从依赖的服务返回一个单一的结果对象或是在发生错误的时候抛出异常。
    2. queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。
  4. HystrixObservableCommand实现了后两种执行方式:
    1. obseve():返回Observable对象,它代表了操作的多个统果,它是一个Hot Observable (不论“事件源”是否有“订阅者”,都会在创建后对事件进行发布,所以对于Hot Observable的每一个“订阅者”都有可能是从“事件源”的中途开始的,并可能只是看到了整个操作的局部过程)。
    2. toObservable():同样会返回Observable对象,也代表了操作的多个结果,但它返回的是一个Cold Observable(没有“订间者”的时候并不会发布事件,而是进行等待,直到有“订阅者”之后才发布事件,所以对于Cold Observable 的订阅者,它可以保证从一开始看到整个操作的全部过程)。
  5. 若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。
  6. 检查断路器是否为打开状态。如果断路器是打开的,那么Hystrix不会执行命令,而是转接到fallback处理逻辑(第8步);如果断路器是关闭的,检查是否有可用资源来执行命令(第5步)。
  7. 线程池/请求队列信号量是否占满。如果命令依赖服务的专有线程地和请求队列,或者信号量(不使用线程的时候)已经被占满,那么Hystrix也不会执行命令,而是转接到fallback处理理辑(第8步)
  8. Hystrix会根据我们编写的方法来决定采取什么样的方式去请求依赖服务。
    1. HystrixCommand.run():返回一个单一的结果,或者抛出异常。
    2. HystrixObservableCommand.construct():返回一个Observable对象来发射多个结果,或通过onError发送错误通知。
  9. Hystix会将“成功”、“失败”、“拒绝”、“超时” 等信息报告给断路器,而断路器会维护一组计数器来统计这些数据。断路器会使用这些统计数据来决定是否要将断路器打开,来对某个依赖服务的请求进行”熔断/短路”。
  10. 当命令执行失败的时候,Hystix会进入fallback尝试回退处理,我们通常也称波操作为“服务降级”。而能够引起服务降级处理的情况有下面几种:
    1. 第4步:当前命令处于“熔断/短路”状态,断洛器是打开的时候。
    2. 第5步:当前命令的钱程池、请求队列或者信号量被占满的时候。
    3. 第6步:HystrixObsevableCommand.construct()或HytrixCommand.run()抛出异常的时候。
  11. 当Hystrix命令执行成功之后,它会将处理结果直接返回或是以Observable的形式返回

15. Hystrix图形化Dashboard搭建

概述

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。

Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面

仪表盘9001

1. 新建cloud-consumer-hystrix-dashboard9001

2. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>SpringCloud</artifactId>
        <groupId>org.gmf</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <!--Hystrix页面监控-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</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>
</project>

3. application.yaml
server:
  port: 9001

4. 主启动类
@SpringBootApplication
@EnableHystrixDashboard  //开启Hystrix页面监控功能
public class HystrixDashboard9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboard9001.class, args);
    }
}

5. 所有Provider微服务提供类(8001/8002/8003)都需要监控依赖配置
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

6. 启动cloud-consumer-hystrix-dashboard9001该微服务后续将监控微服务8001

浏览器访问:http://localhost:9001/hystrix
image.png

16. Hystrix图形化Dashboard监控实战

1. 图形化Dashboard监控相关配置

注意:新版本Hystrix需要在9001监控服务application中指定代理主机名列表,并且要开启服务端的endpoint端口暴露,否则在连接Dashboard时会出现该异常
image.png
9001监控服务添加代理主机配置

#添加如下配置
hystrix:
  dashboard:
    proxy-stream-allow-list: "localhost"

8001服务端endpoint端口暴露(SpringBoot Endpoint)

#开放hystrix监控端点
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

备注:可能会出现一直处于loading状态的情况,原因是监控端一直在等待负载均衡的提供方要去消费服务,即访问负载均衡服务器,去调用客户端,如果有数据响应则监控界面就会有图形数据展示:
image.png
当服务调用请求被监控端接收到后显示数据
image.png

2. 测试

浏览器访问:http://localhost:9001/hystrix
根据页面提示的规则填写要监控的服务
image.png
当尝试多次访问业务请求时,监控端也出现了变化
image.png
当多次访问失败使其服务熔断后,页面显示状态
image.png

3. 查看监控状态的方法

七种颜色代表不同状态

image.png

状态中心圆圈

实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色 < 黄色 < 橙色 < 红色递减。
该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实例中快速的发现故障实例和高压力实例

状态曲线

曲线:用来记录2分钟内流量的相对变化,可以通过它来观察到流量的上升和下降趋势
image.png image.png

整图说明

五、服务降级 - 图33
五、服务降级 - 图34