SpringCloud系列之服务降级(Hystrix断路器)

1、概述

①、分布式系统面临的问题

往往复杂分布式体系架构的应用程序有数十个依赖关系,每个依赖关系在某些时候将不可避免的失败

image-20210210170312935

就像图中所看到的那样,一个用户请求需要调用A服务,但是A服务也需要调用P服务,然后PHHI等等······如果一切顺利,则没有啥问题,但是如果I服务出现了超时,会出现什么问题呢?

②、服务雪崩

多个微服务之间调用的时候,假设微服务A调用微服务B和微服务C,微服务B和微服务C又调用其它的微服务,这就是所谓的“扇出”。如果扇出的链路上某个微服务的调用响应时间过长或者不可用,对微服务A的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的“雪崩效应”

对于高流量的应用来说,单一的后端依赖可能会导致所有服务器上的所有资源都在几秒钟内饱和。比失败更糟糕的是,这些应用程序还可能导致服务之间的延迟增加,备份队列,线程和其他系统资源紧张,导致整个系统发生更多的级联故障。这些都表示需要对故障和延迟进行隔离和管理,以便单个依赖关系的失败,不能取消整个应用程序或系统。

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

③、什么是Hystrix

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

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

能干嘛?

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

④、官网

点击访问

很遗憾,这款优秀的开源库,也停更了,SpringCloud社区里面神仙打架,我们程序员遭殃

image-20210211145506995

官网推荐使用resilience4j,但是国内用的很少,貌似今日头条就在用,另外还有一部分就是自研的,剩下的都是用阿里的Sentinel,关于Sentinel后面会介绍,所谓天下作业一通抄,它也是借鉴了Hystrix,所以这里我们先了解Hystrix的优秀设计思想,之后再看Sentinel就会很轻松。

2、三个重要概念

①、服务降级Fallback

我相信大家肯定写过这样的代码

if(){

}else if(){

}else if(){

}else {
    //对方系统不可用的,你需要给我一个兜底的解决方法
}

大家也遇到过这样的情况,打10086的时候,一阵悦耳动听的音乐结束后,电话那头响起:坐席忙,继续等待请按一,不愿等待,请挂机。可一看到,当服务器忙的时候,为了不让客户端长时间等待,应该立刻返回一个友好提示。fallback

哪些情况会出发降级?

  • 程序运行异常
  • 超时
  • 服务熔断出发服务降级
  • 线程池/信号量已满

②、服务熔断Break

类似保险丝,达到一些限制条件时,直接跳闸不允许请求,过程就是:服务的降级,进而熔断,再是恢复链路

③、服务限流Flowlimit

秒杀高并发等操作,严禁一窝蜂过来拥挤,等待排队

3、问题体现

①、构建支付微服务

Ⅰ、建module

image-20210211160830301

Ⅱ、POM

<?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>SpringCloudDemo</artifactId>
        <groupId>com.phz.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>HystrixProviderPayment8009</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.phz.springcloud</groupId>
            <artifactId>CloudAPI</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>
        <!-- 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>
        <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>

Ⅲ、YML

server:
  port: 8009

spring:
  application:
    name: hystrix-payment-service

eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
  #血和泪的教训,一定要配!
  instance:
    prefer-ip-address: true
    instance-id: payment8009

Ⅳ、主启动

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/11 16:14
 * @projectName SpringCloudDemo
 * @className PaymentMain8009.java
 * @description TODO
 */
@SpringBootApplication
@EnableEurekaClient
public class PaymentMain8009 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8009.class, args);
    }
}

Ⅴ、业务类

Service

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/11 16:15
 * @projectName SpringCloudDemo
 * @className PaymentService.java
 * @description TODO
 */
@Service
public class PaymentService {

    public String paymentInfo_OK(Integer id) {
        return "线程池: " + Thread.currentThread().getName()
                + "   paymentInfo_OK,id:" + id + " 正常访问!";
    }

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

Controller

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/11 16:16
 * @projectName SpringCloudDemo
 * @className PaymentController.java
 * @description TODO
 */
@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;
    }
}

Ⅵ、测试

到这里一个简易的支付微服务搭建完毕,简单测试一下

image-20210211162613792

②、Jmeter开始搞事

上面这个服务,在非高并发的情形下还能维持实用,但是高并发就不一定了哦

Ⅰ、Jmeter安装

下载地址

解压缩后打开bin目录,运行jmeter.bat

image-20210211163926656

Ⅱ、创建测试线程组

image-20210211164642383

不要开太多,性能不好没准就炸了哈

image-20210211164604981

Ⅲ、创建请求,并配置参数

image-20210211170225182

image-20210211170430819

Ⅳ、开始压力测试

200个线程开启完毕

image-20210211170454021

重新访问OK请求,发现以前的秒回应到现在还要转圈圈一会后才收到回应

image-20210211170553104

观察控制台后发现,20000个请求打在timeout的延时方法上,所以它还在继续运行,从而把Tomcat的默认工作线程给打满了,没有多余的线程来分摊压力,难怪把本身很快的OK给拖慢了

image-20210211170748816

③、情况继续恶化,消费者的加入

Ⅰ、建module

image-20210211172002035

Ⅱ、POM

<?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>SpringCloudDemo</artifactId>
        <groupId>com.phz.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>HystrixAndFeignConsumerOrder8010</artifactId>
    <dependencies>
        <dependency>
            <groupId>com.phz.springcloud</groupId>
            <artifactId>CloudAPI</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- openfeign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <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-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

Ⅲ、YML

由于服务端延时3S,而Ribbon默认接收延时1S,这里需要修改,不然就会报Read timed out

server:
  port: 8010

#这里只把feign做客户端用,不注册进eureka
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true
    register-with-eureka: false
    service-url:
      defaultZone: http://eureka7001.com:7001/eureka/
      
#设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
  #指的是建立连接后从服务器读取到可用资源所用的时间
  ReadTimeout: 5000
  #指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的实际
  ConnectTimeout: 5000

Ⅳ、主启动

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/11 17:22
 * @projectName SpringCloudDemo
 * @className OrderMain8010.java
 * @description TODO
 */
@SpringBootApplication
@EnableFeignClients
public class OrderMain8010 {
    public static void main(String[] args) {
        SpringApplication.run(OrderMain8010.class, args);
    }
}

Ⅴ、Service

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/11 17:24
 * @projectName SpringCloudDemo
 * @className PaymentHystrixService.java
 * @description TODO
 */
@Component
@FeignClient(value = "hystrix-payment-service")
public interface PaymentHystrixService {

    @GetMapping("/payment/hystrix/ok/{id}")
    String paymentInfo_OK(@PathVariable("id") Integer id);

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

}

Ⅵ、Controller

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/11 17:27
 * @projectName SpringCloudDemo
 * @className OrderController.java
 * @description TODO
 */
@RestController
@Slf4j
public class OrderController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

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

    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_Timeout(@PathVariable("id") Integer id) {
        String result = paymentHystrixService.paymentInfo_Timeout(id);
        return result;
    }
}

Ⅶ、正常访问测试

都通过

image-20210211174628428

Ⅷ、再次使用Jmeter压测

毫无疑问会影响到OK服务的正常运行,更严重的情况可能会显示ErrorPage

image-20210211174843708

Ⅹ、故障原因

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

4、如何解决

正是因为有了上述问题的出现,才有了这里的降级/容错/限流技术

①、解决哪些问题

  • 超时导致服务器变慢(转圈)
  • 出错(宕机或程序出错)

换句话说

  • 8009超时,调用者8010不能一直卡死等待,需要有服务降级
  • 8009宕机,调用者8010不能一直卡死等待,需要有服务降级
  • 8010自身故障,要求等待时间小于几秒,需要有服务降级

②、服务降级

首先我们从自身寻找原因,现在约定,timeout方法执行时间在3S内为正常,输出正常业务逻辑,超过3S出错,应该返回错误的信息

Ⅰ、服务方

这时候就需要在timeout方法上面添加@HystrixCommand注解,并配置兜底方法和参数

@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {
    	//超过3S执行paymentInfo_TimeoutHandler方法,实测,value中的时间还是大于3000才好,因为除了线程sleep有3S,代码语句执行也会有时间,只写3000就只能执行兜底方法了
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "3020")
})
public String paymentInfo_Timeout(Integer id) {
    int timeNumber = 3;
    try {
        TimeUnit.SECONDS.sleep(timeNumber);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return "线程池: " + Thread.currentThread().getName() + "   paymentInfo_OK,id:" + id + " 耗时(秒):" + timeNumber;
}

public String paymentInfo_TimeoutHandler(Integer id) {
    return "/(ToT)/调用支付接口超时或异常、\t" + "\t当前线程池名字" + Thread.currentThread().getName();
}

添加了@HystrixCommand注解当然就应该去主启动类上添加好激活该注解的注解@EnableCircuitBreaker

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/11 16:14
 * @projectName SpringCloudDemo
 * @className PaymentMain8009.java
 * @description TODO
 */
@SpringBootApplication
@EnableEurekaClient
@EnableCircuitBreaker
public class PaymentMain8009 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentMain8009.class, args);
    }
}

Ⅱ、测试

image-20210212153329366

修改为5S,再次测试

image-20210212153409370

还原为3S,添加一条错误语句int a = 10 / 0;

image-20210212153604185

所以结论就是,当前服务不可用了,不管是报错还是超时,都会执行兜底的方法。

Ⅲ、客户方

正常业务中,Hystrix服务降级大多数是放在客户端,所谓从上游发现,及早处理,这里一葫芦画瓢,和服务方一样的配方,首先记得先把刚刚改的报错方法和时间修改到正常的状态

主启动添加注解

//这里的@EnableHystrix注解自带@EnableCircuitBreaker注解,所以功能基本一样
@EnableHystrix

timeout方法添加Hystrix注解

@HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler",commandProperties = {
        @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000")
})
@GetMapping("/consumer/payment/hystrix/timeout/{id}")
public String paymentInfo_Timeout(@PathVariable("id") Integer id) {
    return paymentHystrixService.paymentInfo_Timeout(id);
}

public String paymentInfo_TimeoutHandler(Integer id) {
    return "/(ToT)/我是消费者8010,调用8009支付系统繁忙,请10秒钟后重新尝试、\t";
}

修改YML配置文件,添加配置使Feign将使用断路器包装所有方法

feign:
  hystrix:
    enabled: true

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

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

Ⅳ、测试

image-20210212155838569

在方法中添加报错语句,再次测试

image-20210212155522467

5、初步解决后又出现新的问题

  • 每个方法如果都写一个兜底方法,那代码就太冗余了
  • 业务逻辑方法和处理异常的方法放在一块,不规范

①、解决冗余代码

我们可以让没有特别注明的方法统一使用一个Default方法来兜底,统一跳转到处理页面,而特别注明的就自定义方法兜底。这样就能达到通用和独享的各自分开,避免了代码膨胀

这时候引入新的注解,类名上添加@DefaultProperties(defaultFallback="")

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/11 17:27
 * @projectName SpringCloudDemo
 * @className OrderController.java
 * @description TODO
 */
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "paymentInfo_Global_FallbackMethod")
public class OrderController {

    @Resource
    private PaymentHystrixService paymentHystrixService;

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

//    @HystrixCommand(fallbackMethod = "paymentInfo_TimeoutHandler", commandProperties = {
//            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "5000")
//    })
    @HystrixCommand//不指定特定方法,执行default方法
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_Timeout(@PathVariable("id") Integer id) {
        int a = 10 / 0;
        return paymentHystrixService.paymentInfo_Timeout(id);
    }

    public String paymentInfo_TimeoutHandler(Integer id) {
        return "/(ToT)/我是消费者8010,调用8009支付系统繁忙,请10秒钟后重新尝试、\t";
    }

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

②、测试

image-20210212161555723

③、解决耦合代码

我们面临的的大部分异常都是运行时异常,超时异常,宕机,前两种我们已经演示了,现在我们再演示一个宕机,当服务突然不可用的时候,Feign和服务端对应的Service方法变得不可用,我们对期望Service方法能够被方法兜底,开整

Ⅰ、新建兜底方法类

改类应该实现FeignService接口

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/12 16:27
 * @projectName SpringCloudDemo
 * @className PaymentFallbackService.java
 * @description TODO
 */
@Component
public class PaymentFallbackService implements PaymentHystrixService {

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

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

Ⅱ、在Service接口上配置fallback

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/11 17:24
 * @projectName SpringCloudDemo
 * @className PaymentHystrixService.java
 * @description TODO
 */
@Component
@FeignClient(value = "hystrix-payment-service", fallback = PaymentFallbackService.class)
public interface PaymentHystrixService {

    @GetMapping("/payment/hystrix/ok/{id}")
    String paymentInfo_OK(@PathVariable("id") Integer id);

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

}

④、测试

正常访问

image-20210212172540057

当服务8009宕机(直接停掉)

image-20210212172540057

6、再次提出新的问题

类比保险丝达到最大服务访问后,直接拒绝访问,拉闸限电,然后调用服务降级的方法并返回友好提示

  • 过程即:

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

①、熔断机制

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

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

服务熔断机制的提出者个人博客

  • 重点在博客中的熔断机制流程图,我仿照画了一个,如下

image-20210213091314823

所谓Half Open状态指的是当前并发量没有那么多了,我尝试重新开启服务,放入部分请求,如果测试通过了,则可以恢复链路,如果测试没有通过,重新回到熔断打开的状态

②、熔断的实现

Ⅰ、添加熔断方法

8009服务的PaymentService添加一个新的熔断方法

// 服务熔断
@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"),  //错误率达到多少跳闸,百分比
})
public String paymentCircuitBreaker(@PathVariable("id") Integer id) {
    if(id < 0){
        throw  new RuntimeException("****id 不能为负数");
    }
    //咱们导入的Hutool工具包的方法,国人写的,点赞
    String serialNumber = IdUtil.simpleUUID();

    return  Thread.currentThread().getName() + "\t" + "调用成功,流水号:" + serialNumber;
}

public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
    return "id 不能为负数,请稍后再试, o(╥﹏╥)o id: " + id;
}

Ⅱ、断路器执行的流程

  • 请求次数有没有达到我们配置的requestVolumeThreshold数量
  • 错误百分比达到了多少
  • 当错误百分比超过了规定的阈值,将会由Closed状态变为Open状态
  • 当状态变为Open后,所有的请求都会无法正常使用
  • 当休眠时间完成以后,那么下一个请求我们尝试让它通过一下,如果还是不行,断路器还是会处于Open状态,如果成功,那么断路器就会关闭,重新回到第一步

Ⅲ、HystrixCommandProperties

此类中详细定义了所有@HystrixProperty注解中的所有能使用的参数,参考我的另一篇文章

Hystrix配置大全

Ⅳ、Controller添加方法

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

③、测试

启动70018009,正数请求

image-20210213095647799

负数请求

image-20210213095720200

狂发负数请求,之后再次访问正数请求

image-20210213095846946

10S后再次访问正数请求

image-20210213095912960

④、总结

这个简单的断路器避免了在电路断开时发出受保护的呼叫,但是当情况恢复正常时,将需要外部干预才能将其重置。对于建筑物中的电路断路器,这是一种合理的方法,但是对于软件断路器,我们可以让断路器本身检测基础调用是否再次正常工作。我们可以通过在适当的时间间隔后再次尝试受保护的调用来实现这种自我重置行为,并在成功后重置断路器。

Ⅰ、熔断类型

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

Ⅱ、短路器在什么时候起作用?

image-20210213100436716

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

Ⅲ、断路器打开或关闭的条件

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

Ⅳ、断路器打开之后

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

⑤、关于服务限流

关于Hystrix的服务限流,不介绍,直接过渡到AlibabaSentinel,它更适合,不仅仅是服务限流,服务降级,服务熔断都更加的适合

7、图形化Dashboard搭建

除了隔离依赖服务的调用以外,Hystrix还提供了准实时的调用监控(Hystrix Dashboard),Hystrix会持续地记录所有通过Hystrix发起的请求的执行信息,并以统计报表和图形的形式展示给用户,包括每秒执行多少请求多少成功,多少失败等。Netflix通过hystrix-metrics-event-stream项目实现了对以上指标的监控。Spring Cloud也提供了Hystrix Dashboard的整合,对监控内容转化成可视化界面。

①、建module

image-20210213113838910

②、POM

<?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>SpringCloudDemo</artifactId>
        <groupId>com.phz.springcloud</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>HystrixDashboard9001</artifactId>

    <dependencies>
        <dependency>
            <groupId>com.phz.springcloud</groupId>
            <artifactId>CloudAPI</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <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.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-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

③、YML

server:
  port: 9001

④、主启动

/**
 * @author PengHuAnZhi
 * @createTime 2021/2/13 11:42
 * @projectName SpringCloudDemo
 * @className HystrixDashboardMain9001.java
 * @description TODO
 */
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class, args);
    }
}

⑤、测试

看到这头豪猪就代表搭建成功了

image-20210213114517617

凡是被监控的服务,都需要导入依赖

<!--监控-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

且当前的H版还需要在被监控的服务主启动中配置一个bean

/**
 * 此配置是为了服务监控而配置,与服务容错本身无关,springcloud升级后的坑
 * ServletRegistrationBean因为SpringBoot的默认路径不是 “/hystrix.stream"
 * 只要在自己的项目里配置上下的servlet就可以了
 */
@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;
}

image-20210213114517617

⑥、注册被监控的服务

启动Eureka7001Dashboard9001

image-20210213115216305

注册被监控服务URL

localhost:8009/hystrix.stream

image-20210213115344457

然后开启8009,再点击上图的Monitor Stream按钮进入监控面板

image-20210213115515573

然后狂发之前的请求http://localhost:8009/payment/circuit/31,就会发现仪表盘有了数据的监控,且Circuitclosed状态

image-20210213115633873

再次狂发http://localhost:8009/payment/circuit/-31请求,就能看到仪表盘的变化了。

image-20210213115823898

图形面板如何看?

image-20210213120128818

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值