SpringCloud之Hystrix快速入门介绍+案例分析(服务降级、服务熔断)

文章目录

Hystrix(豪猪哥)

Hystrix是什么

Hystrix是一个用于处理分布式系统的延迟和容错的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,Hystrix能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性
“断路器”本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。

服务降级(fallback)

服务压力剧增的时候根据当前的业务情况及流量对一些服务和页面有策略的降级,以此环节服务器的压力,以保证核心任务的进行。

同时保证部分甚至大部分任务客户能得到正确的相应。也就是当前的请求处理不了了或者出错了,给一个默认的返回。

服务熔断(break)

服务熔断:在股票市场,熔断这个词大家都不陌生,是指当股指波幅达到某个点后,交易所为控制风险采取的暂停交易措施。相应的,服务熔断一般是指软件系统中,由于某些原因使得服务出现了过载现象,为防止造成整个系统故障,从而采用的一种保护措施,所以很多地方把熔断亦称为过载保护。

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

服务限流(flowlimit)

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

服务雪崩

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

hystrix服务端测试用例

新建两个方法来测试在没有使用服务降级等情况下,系统在高并发环境下的情况

1、建Module

cloud-provider-hystrix-payment8001

2、改pom

<dependencies>
    <!--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>

    <!--删除掉entities后,将公用代码打包放到公共模块中,引入模块-->
    <dependency>
        <groupId>com.lejia.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>
        <version>1.18.16</version>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-devtools</artifactId>
        <scope>runtime</scope>
        <optional>true</optional>
    </dependency>

</dependencies>

3、写yml

server:
  port: 8001
  
spring:
  application:
    name: cloud-payment-hystrix-payment
   
eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true
    register-with-eureka: true
    #是否从EurekaServe抓取已有的注册信息,默认为true
    # 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
        defaultZone: http://localhost:7001/eureka
      ## 集群版
#      defaultZone: http://eureka7001.com:7001/eurekaka,http://eureka7003.com:7003/eureka

4、主启动

@SpringBootApplication
@EnableDiscoveryClient
public class PaymentHystrixMain8001 {

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

5、业务类

服务层

package com.lejia.springcloud.service;

新建PaymentService.java

新建两个方法,一个是延时3秒后返回数据,一个是立即返回数据,测试在高并发的情况下系统的性能

public class PaymentService {
    public String paymentInto_OK(Integer id){
        return "线程池\t"+Thread.currentThread().getName()+"\tpaymentInto_OK\t" + id + "\tO(∩_∩)O哈哈~";
    }

    public String paymentInto_Timeout(Integer id){
        int time = 3;
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "线程池\t"+Thread.currentThread().getName()+"\tpaymentInto_Timeout\t" + id + "\t┭┮﹏┭┮,超时,超时"+time+"s";
    }
}

控制层

package com.lejia.springcloud.controller;

PaymentController.java

@RestController
@Slf4j
@RequestMapping("/payment/hystrix")
public class PaymentController {

    @Resource
    private PaymentService paymentService;

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

    @GetMapping("/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id){
        String result = paymentService.paymentInto_OK(id);
        log.info("************ result: "+result);
        return result;
    }
    @GetMapping("/timeout/{id}")
    public String paymentInfo_Timeout(@PathVariable("id") Integer id){
        String result = paymentService.paymentInto_Timeout(id);
        log.info("************ result: "+result);
        return result;
    }
}

6、测试

http://localhost:8001/payment/hystrix/ok/222

立刻显示在页面中

image-20201204105103191

http://localhost:8001/payment/hystrix/timeout/111

超时3s后显示在页面中

image-20201204105128369

Jmeter使用教程

为了进行高并发测试,我们下载Jmeter软件来进行一下测试

进入官网

https://jmeter.apache.org/

image-20201204101956126

windows选择zip包

image-20201204102041568

下载解压

进入apache-jmeter-5.3文件夹下的bin目录

找到 jmeter.bat,双击打开即可

image-20201204102237697

桌面会立刻打开一个命令行窗口,里面运行的是jmeter的后台程序,稍等片刻会弹出jmeter应用窗口

image-20201204102410952

初始界面是英语的,我们可以选择一下语言

image-20201204102459218

http压力测试

右击测试计划 -> 添加 -> 线程 - > 线程组

输入线程组名称 选择

image-20201204103249162

CTRL + S 保存

选择刚才新建的线程组

右击 线程组2w测试 -> 添加 -> 取样器 -> HTTP 请求

image-20201204103456517

image-20201204103544603

CTRL +S 保存 我选择保存在了 jmx中

image-20201204103822014

点击启动开启压力测试

image-20201204103913458

然后进入程序后台可以看到压力测试正在进行中

image-20201204104003781

点击 × 取消按钮即可取消当前测试

测试上一个案例

当20000个请求发给http://localhost:8001/payment/hystrix/timeout/1时,我们能够看到,tomcat将线程池的资源全部用来处理timeout的这个方法的请求。

此时我们访问无延时的请求http://localhost:8001/payment/hystrix/ok/111,系统也会出现 短暂的响应后才返回数据

Hystrix服务降级(fallback)

相信你看到现在可能会有一点疑惑,上述的测试并没有什么明显的效果啊,我们在实际生产环境中,在大量并发的请求以及带宽的影响下,服务端的响应速度急剧上升,此时我们就需要进行一些优化,处理。此时就用到了Hystrix的服务降级功能。我们将在下述案例中进行测试

1、什么是服务降级

服务器忙,请稍后再试,不让客户端等待并立刻返回一个友好提示,Fallback

2、哪些情况会发出服务降级

  • 程序运行异常
  • 超时
  • 服务熔断触发服务降级
  • 线程池/信号量打满也会导致服务降级

3、服务降级可以用在哪里

Hystrix一般用在消费者(客户端),但是在服务端也是可以用的

4、服务降级案例分析

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

5、Hystrix配置说明

@HystrixCommand(fallbackMethod = “” , commandProperties = {})

fallbackMethod 服务降级切换的方法名称

commandProperties 配置详解

当请求服务失败并抛出了错误信息后,会自动调用@HystrixCommand标注好的fallbackMethod调用指定方法

当前服务只要不可用了就会做服务降级,降到兜底服务

服务端服务降级配置

我们继续使用8001来做我们的测试服务

1、建Module

2、改pom

3、写yml

4、主启动

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker	//开启断路器
public class PaymentHystrixMain8001 {

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

5、业务类

package com.lejia.springcloud.service;

我们已经创建了两个测试方法

一个方法正常返回测试字符串

另一个方法等待n秒后返回字符串

现在我们新建一个服务降级方法,当我们提供的服务出现异常或者超时时,hystrix会自动将当前线程切换到我们设置的降级服务中

@HystrixCommand说明

fallbackMethod 服务降级切换的方法名称

commandProperties 配置详解 https://www.jianshu.com/p/39763a0bd9b8

@Service
public class PaymentService {

    public String paymentInto_OK(Integer id){
        return "线程池\t"+Thread.currentThread().getName()+"\tpaymentInto_OK\t" + id + "\tO(∩_∩)O哈哈~";
    }


    /**
     *  @HystrixCommand说明
     *      fallbackMethod 服务降级切换的方法名称
     *      commandProperties 配置详解
     *      https://www.jianshu.com/p/39763a0bd9b8
     */
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
            commandProperties = @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5000"))
    public String paymentInto_Timeout(Integer id){
        int time = 3;
        try {
            TimeUnit.SECONDS.sleep(time);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return "线程池\t"+Thread.currentThread().getName()+"\tpaymentInto_Timeout\t" + id + "┭┮﹏┭┮,超时"+time+"s";
    }

    public String paymentInfo_TimeOutHandler(Integer id){
        return "线程池\t"+Thread.currentThread().getName()+"\tpaymentInto_Timeout\t" + id + "\t8001系统服务提供出错,或者请求超时了,此方法为降级方法";
    }
}

6、测试

fallback设置等待时间为5s,系统响应等待3s

测试

http://localhost:8001/payment/hystrix/timeout/3

结果:

当我们的等待时间设置小于我们熔断器所设置的最大等待时间时,我们真实所需的业务被正常的响应到客户端

image-20201204155712781

fallback设置等待时间为5s,系统响应等待10s

image-20201204155931620

再次测试

http://localhost:8001/payment/hystrix/timeout/3

结果

测试发现,当页面响应5s后,就立刻停止了响应,转而返回给客户端降级后的服务

image-20201204160001602


我们再观察一下输出日志

可以看到,我们的等待被hystrix给打断了,所以能够在等待之前切换到降级服务中

image-20201204160131279

客户端服务降级配置

前面已经介绍过了服务端的服务降级配置。而客户端与服务端的配置也是大同小异,我们在项目开发时,一般都将服务降级设置在客户端来进行调控。

1、建Module

cloud-consumer-feign-hystrix-order80

2、改pom

我们使用open feign来进行远程服务的调用

<dependencies>
    <!--hystrix-->
    <dependency>
        <groupId>org.springframework.cloud</groupId>
        <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
    </dependency>

    <!--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>

    <!--删除掉entities后,将公用代码打包放到公共模块中,引入模块-->
    <dependency>
        <groupId>com.lejia.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、写yml

server:
  port: 80

eureka:
  client:
    #表示是否将自己注册进EurekaServer默认为true
    register-with-eureka: false
    #是否从EurekaServe抓取已有的注册信息,默认为true
    # 单节点无所谓,集群必须设置为true才能配合ribbon使用负载均衡
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka

# 设置feign客户端超时时间(OpenFeign默认支持ribbon)
ribbon:
  # 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  # 指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

4、主启动

@SpringBootApplication
@EnableFeignClients			//开启feign
@EnableDiscoveryClient		//开启服务发现
@EnableHystrix				//开启hystrix 继承了@EnableCircuitBreaker
public class OrderHystrixOrderMain80 {

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

5、业务类

因为我们使用的是feign来进行远程访问,所以我们新建一个接口来存放服务端提供的方法

package com.lejia.springcloud.service;

PaymentHystrixService是一个接口,里面存放的服务端暴露的接口,我们在这里需要进行远程访问所必备的接口

@Component
@FeignClient(value = "CLOUD-PAYMENT-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);

}

package com.lejia.springcloud.controller;

OrderHystrixController.java 业务类,我们真正暴露给用户的接口

hystrix我们一般都用在客户端,所以我们在controller中来设置一下我们的服务降级程序

@RestController
@Slf4j
public class OrderHystrixController {

    @Resource
    private 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}")
    @HystrixCommand(fallbackMethod = "paymentInfo_TimeOutHandler",
            commandProperties = @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000"))
    public String paymentInfo_Timeout(@PathVariable("id") Integer id){
        return paymentHystrixService.paymentInfo_Timeout(id);
    }

    public String paymentInfo_TimeOutHandler(Integer id){
        return "我是80的服务降级,系统响应的时间过久,已切换到我来执行";
    }

}

6、时间设置说明

现在我们的这个服务注册中心已经集成了一个服务提供者一个服务消费者,因为涉及到一些时间问题所以我们先在测试之前说清楚一下,然后再进行验证

80客户端

feign

客户端连接+未收到响应 10s后 ,客户端报超时错误

  # 指的是建立连接所用的时间,适用于网络状态正常的情况下,两端连接所用的时间
  ReadTimeout: 5000
  # 指的是建立连接后从服务器读取到可用资源所用的时间
  ConnectTimeout: 5000

熔断器hystrix

客户端超时等待2秒后未收到响应,hystrix打断sleep进行服务降级

@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "2000")

8001服务端

服务端延迟等待3s后再返回数据给客户端

public String paymentInto_Timeout(Integer id){
    int time = 3;
    try {
        TimeUnit.SECONDS.sleep(time);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

熔断器hystrix

服务端响应超时等待5秒后服务降级

commandProperties = @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds",value = "5001"))

7、测试

测试一:

客户端hystrix 2s feign 5s 服务端 sleep 3s hystrix5s

结果:

此时因为客户端的响应等待时间 2S < 服务端 睡眠等待的3s,所以一定返回客户端的服务降级方法

页面等待2s后显示

image-20201204162129280




测试二:

客户端hystrix 4s feign5+5s 服务端 sleep 3s hystrix5s

结果:

页面等待3s后显示

image-20201204162348335




测试三:

客户端hystrix 4s feign 5+5s 服务端 sleep 3s hystrix2s

结果:

页面等待2s后显示

image-20201204162607640




测试四:

客户端hystrix 4s feign1+1 s 服务端 sleep 3s hystrix2s

结果:

页面等待2s后显示

image-20201204163114615

客户端全局服务降级

我们在客户端使用服务降级时,需要配置降级方法,但是不可能每一个方法都需要手动的设置一个降级方法,这样就极大的耽误了开发的进度,并且造成了代码膨胀,所以我们使用hystrix提供的默认降级方法来进行统一配置。

配置方法

1、新建一个方法作为全局降级服务

  /**
     * 全局fallback方法
     *  不能有参数
     * @return
     */
    public String payment_Global_FallbackMethod(){
        return "Global异常信息处理,请稍后再试 (キ`゚Д゚´)!!";
    }

2、在控制层上添加一个注解@DefaultProperties,其中参数指定为全局降级服务方法名称

@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")

3、在需要降级的方法上添加注解@HystrixCommand

@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;

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

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

    public String paymentInfo_TimeOutHandler(Integer id){
        return "我是80的服务降级,系统响应的时间过久,已切换到我来执行";
    }

    /**
     * 全局fallback方法
     *  不能有参数
     * @return
     */
    public String payment_Global_FallbackMethod(){
        return "Global异常信息处理,请稍后再试 (キ`゚Д゚´)!!";
    }
}

4、测试

正常情况下测试

测试一:

请求ok http://localhost/consumer/payment/hystrix/ok/111

结果:

正确响应到了服务端的数据

image-20201209005910573

测试二:

请求timeout http://localhost/consumer/payment/hystrix/timeout/111

结果 : 因为在timeout上设置了服务降级的方法,根据就近原则会使用最近的服务降级方法

image-20201209010121700

异常情况下测试:

服务端宕机后(关闭8081服务)

测试一:

请求ok http://localhost/consumer/payment/hystrix/ok/111

结果:

客户端响应1s后 (@HystrixCommand 默认设置的超时时间为1s),跳转到服务降级全局处理方法中

image-20201209010348427


测试二

请求timeout http://localhost/consumer/payment/hystrix/timeout/111

结果:

客户端响应2s后 (@HystrixCommand自定义设置的超时时间为2s),跳转到服务降级指定处理方法中

image-20201209010627226

客户端统配服务降级

当我们使用feign远程调用服务时,我们需要创建了一个服务接口。

接口中其中存放着我们所调用的所有服务端的方法。所以我们可以新建一个类来继承这个接口,然后使用feign提供的注解来配置接口统配降级服务。

即接口中的每个方法都默认实现了降级服务。

配置

在yml中添加注解

feign:
  hystrix:
    enabled: true
#如果为真,OpenFeign客户端将被Hystrix断路器包裹。

image-20201204173635488

新建方法

在客户端80的package com.lejia.springcloud.service;

新建PaymentFallbackService.java

@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 ┭┮﹏┭┮ ";

    }
}

更改feign接口注解配置

在PaymentHystrixService接口中添加@FeignClient中的属性

@FeignClient(value = "CLOUD-PAYMENT-HYSTRIX-PAYMENT",fallback = PaymentFallbackService.class)

测试

异常情况下测试:

服务端宕机后(关闭8081服务)

测试一:

请求ok http://localhost/consumer/payment/hystrix/ok/111

结果:

客户端响应1s后 (@HystrixCommand 默认设置的超时时间为1s),跳转到服务降级统配处理方法中

image-20201209012501527


测试二

请求timeout http://localhost/consumer/payment/hystrix/timeout/111

结果:

客户端响应2s后 (@HystrixCommand自定义设置的超时时间为2s),跳转到服务降级指定处理方法中

image-20201209012620652

结论

服务降级方法的顺序

自定义fallback -> 继承接口统一配置服务降级方法 -> 默认全局fallback

Hystrix服务熔断(circuit breaker)

熔断机制概述

熔断机制是应对雪崩效应的一种微服务链路保护机制。

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

在Spring Cloud框架里,熔断机制通过Hystrix实现。

Hystrix会监控微服务间调用的状况,但失败的调用到一定阈值,缺省是5秒内20次调用失败,就会启动熔断机制,熔断机制的注解是@HystrixCommand。

熔断原理概述

image-20201209013531553

https://martinfowler.com/bliki/CircuitBreaker.html

熔断类型

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

服务端服务熔断配置

我们在8001服务提供者模块中进行配置

1、建Module

2、改pom

3、写yml

4、主启动

在主启动上添加注解

@EnableCircuitBreaker //开启断路器即服务熔断

5、业务类

在service中添加两个新的测试方法

//服务熔断
@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(Integer id) {
    if (id < 0) {
        throw new RuntimeException("##########id不能为负数###########");
    }
    String serialNumber = IdUtil.simpleUUID();
    return Thread.currentThread().getName() + "\t" + "调用成功,流水号为 :" + serialNumber;
}

//服务降级方法
public String paymentCircuitBreaker_fallback(Integer id){
    return "id不能为负数,请稍后再试  ┭┮﹏┭┮  id :" + id;
}

在controller中添加一个新的方法

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

6、测试

正常测试

http://localhost:8001/payment/hystrix/circuit/1

刷新n次都不会出现熔断现象

image-20201205000104532

异常测试

http://localhost:8001/payment/hystrix/circuit/-10

image-20201205000212042

刷新大于6次后

再次输入无异常的测试

http://localhost:8001/payment/hystrix/circuit/2

image-20201205000505360

我们可以看到,hystrix熔断功能有了效果。

此时我们需要等待窗口期时间的度过,在窗口期结束时,hystrix会先进行半熔断状态,自我恢复,测试请求是否正常,如果正常,则恢复

再次测试发现服务已经渐渐恢复了

image-20201209015106703

服务熔断之断路器

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

image-20201209015410391

断路器的三个重要参数

断路器的三个重要参数:快照时间窗、请求总数阈值、错误百分比阈值

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

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

1.当满足一定的阈值的时候 (默认10内超过20个请求次数)

2.当失败率达到一定的时候 (默认10秒内超过50%的请求失败)

3.到达以上阈值,断路器将会开启

4.当开启的时候,所有请求都不会进行转发

5.一段时间之后(默认是5秒),这个时候断路器是半开状态,会让其中一个请求进行转发。如果成功,断路器会关闭,若失败,继续开启,重复4和5

断路器打开之后如何恢复

再有请求调用的时候,将不会调用主逻辑,而是直接调用降级fallback。通过断路器,实现了自动地发现错误并将降级逻辑切换为主逻辑,减少响应延迟的效果。

原来的主逻辑要如何恢复呢?
对于这一问题,Hystrix也为我们实现了自动恢复功能。
当断路器打开,对主逻辑进行熔断之后,Hystrix会启动一个休眠时间窗,在这个时间窗内,降级逻辑是临时的成为主逻辑,当休眠时间窗到期,断路器将进入半打开状态,释放一次请求到原来的主逻辑上,如果此次请求正常返回,那么断路器将继续闭合,主逻辑恢复,如果此次请求依然有问题,断路器将继续进入打开状态,休眠时间窗重新计时。

@HystrixCommand属性commandProperties 详细说明

配置类 HystrixCommandProperties

image-20201209015907100

常用的配置说明

@HystrixCommand(fallbackMethod = "str_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 = "true"),
        //断路器强制关闭
              @HystrixProperty(name = "circuitBreaker.forceClosed", value = "true"),
        //滚动时间窗设置,该时间用于断路器判断健康度时需要收集信息的持续时间
              @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"),
    }
)
 

hystrix工作流程

工作流程图

image-20201205004018314

步骤说明

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

Hystrix Dashboard(服务监控)

什么是Hystrix Dashboard

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

Hystrix Dashboard(服务监控)配置

1、建Module

cloud-consumer-hystrix-dashboard9001

2、改pom

<dependencies>

    <!--hystrix dashboard-->
    <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-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、写yml

server:
  port: 9001

4、主启动

@SpringBootApplication
@EnableHystrixDashboard		//开启仪表盘功能
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.class, args);
    }
}

5、在需要被监控的服务中添加dashboard配置

8001中

#添加dashboard监控配置
management:
  endpoints:
    web:
      exposure:
        include: hystrix.stream

6、测试

输入 http://localhost:9001/hystrix ,进入仪表盘界面

image-20201209021009219

http://localhost:8001/actuator/hystrix.stream

输入8001服务的地址+hystrix路径

hystrix只能监控经过@HystrixCommand注解的方法

image-20201209124611295

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值