作者:🍧dabing(王甜甜)

视频:尚硅谷 2020 周阳 SpringCloud(H 版 & alibaba)

官方文档:https://cloud.spring.io/spring-cloud-static/Hoxton.SR1/reference/htmlsingle/#features

github 上代码地址:https://github.com/dabing85/springcloud2020

gitee 上代码地址:https://gitee.com/hedabing/springcloud2020

# 十、 Hystrix 断路器

# 1 - 概述

# 1.1. 分布式系统面临的问题

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

img

服务雪崩

多个微服务之间调用的时候,假如微服务 A 调用微服务 B 和微服务 C,微服务 B 和微服务 C 又调用其他的微服务,这就是所谓的 " 扇出 "。

如果扇出的链路上某个微服务的调用响应的时间过长或者不可用,对微服 A 的调用就会占用越来越多的系统资源,进而引起系统崩溃,所谓的 " 雪崩效应 "。

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

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

# 1.2. 是什么

Hystrix 是一个用于处理分布式系统的 延迟容错 的开源库,在分布式系统里,许多依赖不可避免的会调用失败,比如超时、异常等,

Hystrix 能够保证在一个依赖出问题的情况下,不会导致整体服务失败,避免级联故障,以提高分布式系统的弹性

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

# 1.3. 能干嘛

🌸 服务降级

🌸 服务熔断

🌸 接近实时的监控

🌸 。。。

# 1.4. Hystrix 官宣,停更进维

https://github.com/Netflix/Hystrix

img

# 2 - Hystrix 重要概念

# 2.1. 服务降级 Fallback

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

    说的高级,其实就是主方法不行了就调用兜底的方法。

  • 哪些情况会触发降级

    • 程序运行异常

    • 超时自动降级

    • 服务熔断触发服务降级

    • 线程池 / 信号量打满也会导致服务降级

    • 人工降级

# 2.2. 服务熔断 Breaker

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

  • 就是保险丝

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

# 2.3. 服务限流 Flowlimit

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

# 3 - hystrix 案例

# 3.1. 构建

先把 7001 的注册中心服务端改成单机了便于测试不用起太多服务。(本来就是单机的请忽略)

# 1. 新建 Module:cloud-provider-hystrix-payment8001

# 2. 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>Cloud2020</artifactId>
        <groupId>com.dabing</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-provider-hystrix-payment8001</artifactId>
    <description>hystrix服务提供端</description>
    <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</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.dabing</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>
</project>

# 3. YML

server:
  port: 8001
spring:
  application:
    name: cloud-hystrix-payment-service
eureka:
  client:
    register-with-eureka: true
    fetch-registry: true
    service-url:
      defaultZone: http://localhost:7001/eureka/

# 4. 主启动

package com.dabing.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
@SpringBootApplication
@EnableEurekaClient
public class PaymentHystrixMain8001 {
    public static void main(String[] args) {
        SpringApplication.run(PaymentHystrixMain8001.class,args);
    }
}

# 5. 业务类

# Service/ServiceImpl
package com.dabing.springcloud.service;
public interface PaymentService {
    public String paymentInfo_OK(Integer id);
    public String payment_Timeout(Integer id);
}
package com.dabing.springcloud.service.impl;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentServiceImpl implements PaymentService {
    // 成功
    public String paymentInfo_OK(Integer id){
        return "线程池:"+Thread.currentThread().getName()+"   paymentInfo_OK,id:  "+id+"\t"+"哈哈哈"  ;
    }
    // 失败
    public String payment_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
package com.dabing.springcloud.controller;
import com.dabing.springcloud.service.PaymentService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@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.payment_Timeout(id);
        log.info("*******result:"+result);
        return result;
    }
}

# 6. 正常测试

  • 启动 eureka7001

  • 启动 cloud-provider-hystrix-payment8001

  • 访问

访问: http://localhost:8001/payment/hystrix/ok/31

每次调用耗费 3 秒钟: http://localhost:8001/payment/hystrix/timeout/31

  • 上述 module 均 OK

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

# 3.2. 高并发测试

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

# 2. Jmeter 压测测试

下载地址:https://archive.apache.org/dist/jmeter/binaries/

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

测试计划 右键 新建一个 线程组

image-20221025115504666

线程组 下右键新建一个 Sampler –> HTTP请求 ,路径添加上

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

image-20221025115649669

  • 压测的过程中再来访问一下微服务

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

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

  • 演示结果

    • 两个都在自己转圈圈
    • 为什么会被卡死

image-20221025115749010

tomcat 的默认的工作线程数被打满了,没有多余的线程来分解压力和处理。

# 3. Jmeter 压测结论

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

# 4. 看热闹不嫌弃事大,80 新建加入:cloud-consumer-feign-hystrix-order80

# 1) 新建:cloud-consumer-feign-hystrix-order80
# 2) 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>Cloud2020</artifactId>
        <groupId>com.dabing</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-consumer-feign-hystrix-order80</artifactId>
    <description>hystrix服务消费端</description>
    <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</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>com.dabing</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>
</project>
# 3) YML
server:
  port: 80
spring:
  application:
    name: cloud-consumer-hystrix-order
eureka:
  client:
    register-with-eureka: true    #表示不向注册中心注册自己
    fetch-registry: true   #表示自己就是注册中心,职责是维护服务实例,并不需要去检索服务
    service-url:
      defaultZone: http://localhost:7001/eureka/
# 4) 主启动
package com.dabing.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@EnableEurekaClient
@EnableFeignClients
@SpringBootApplication
public class OrderHystrixMain80 {
    public static void main(String[] args) {
        SpringApplication.run(OrderHystrixMain80.class,args);
    }
}
# 5) 业务类

PaymentHystrixService

package com.dabing.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Component
@FeignClient("CLOUD-HYSTRIX-PAYMENT-SERVICE")
public interface PaymentHystrixService {
    @GetMapping("/payment/hystrix/ok/{id}")
    public String paymentInfo_OK(@PathVariable("id") Integer id);
    @GetMapping("/payment/hystrix/timeout/{id}")
    public String payment_Timeout(@PathVariable("id") Integer id);
}

OrderHystrixController

package com.dabing.springcloud.controller;
import com.dabing.springcloud.service.PaymentHystrixService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
public class OrderHystrixController {
    @Resource
    private PaymentHystrixService paymentHystrixService;
    @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.payment_Timeout(id);
        log.info("*******result:"+result);
        return result;
    }
}
# 6) 正常测试

http://localhost/consumer/payment/hystrix/ok/32

# 7) 高并发测试
  • 2W 个线程压 8001

  • 消费端 80 微服务再去访问正常的 OK 微服务 8001 地址

  • http://localhost/consumer/payment/hystrix/timeout/32

  • 消费者 80,呜呜呜

    • 要么转圈圈等待

    • 要么消费端报超时错误

img

# 3.3. 故障现象和导致原因

  • 8001 同一层次的其他接口服务被困死,因为 tomcat 线程里面的工作线程已经被挤占完毕

  • 80 此时调用 8001,客户端访问响应缓慢,转圈圈

# 3.4. 上诉结论

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

# 3.5. 如何解决?解决的要求

image-20221025120119242

# 3.6. 服务降级

# 1. 降低配置

@HystrixCommand

# 2. 8001 先从自身找问题

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

# 3. 8001fallback

业务类启用阶级处理:使用 @HystrixCommand 注解来干活。

package com.dabing.springcloud.service.impl;
import com.dabing.springcloud.service.PaymentService;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class PaymentServiceImpl implements PaymentService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "线程池:"+Thread.currentThread().getName()+" paymentInfo_OK,id="+id +" \t O(∩_∩)O哈哈~";
    }
    // 超时降级演示
    @HystrixCommand(fallbackMethod = "payment_TimeoutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="5000") //5 秒钟以内就是正常的业务逻辑
    })
    @Override
    public String payment_Timeout(Integer id) {
        //int timeNumber = 3; // 小于等于 3 秒算是正常情况
        int timeNumber = 15; // 模拟非正常情况
        //int i = 1/0 ; // 模拟非正常情况
        try {
            TimeUnit.SECONDS.sleep(timeNumber);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "线程池:"+Thread.currentThread().getName()+" payment_Timeout,id="+id+" \t o(╥﹏╥)o 耗时:"+timeNumber;
    }
    // 兜底方法,上面方法出问题,我来处理,返回一个出错信息
    public String payment_TimeoutHandler(Integer id) {
        return "线程池:"+Thread.currentThread().getName()+" payment_TimeoutHandler,系统繁忙,请稍后再试\t o(╥﹏╥)o ";
    }
}

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

主启动类激活

@EnableCircuitBreaker

测试超时和算数异常,都会走兜底方法 —— 服务降级

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

img

# 4. 80fallback

  1. 80 订单微服务,也可以更好的保护自己,自己也依样画葫芦进行客户端降级保护。注意:服务降级可以在服务提供者侧,也可以在服务消费者侧。更多是在服务消费者侧。

  2. 题外话,切记

我们自己配置过的热部署方式对 java 代码的改动明显,但对 @HystrixCommand 内属性的修改建议重启微服务

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

@EnableHystrix

  1. 业务类:OrderHystrixController
// 超时降级演示
    @HystrixCommand(fallbackMethod = "payment_TimeoutHandler",commandProperties = {
            @HystrixProperty(name="execution.isolation.thread.timeoutInMilliseconds",value="1500")// 超过 1.5 秒就降级自己,其实这里这个的 1.5 秒不管用,因为上一节的 feign 有一个超时控制,只允许 1 秒就会超时,要测试可以按照上节的配置将超时的时间改大
    })
    @GetMapping("/consumer/payment/hystrix/timeout/{id}")
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        //int age= 1/0;
        String result = paymentHystrixService.payment_Timeout(id);
        log.info("*******result:"+result);
        return result;
    }
    // 兜底方法,上面方法出问题,我来处理,返回一个出错信息
    public String payment_TimeoutHandler(Integer id) {
        return "我是消费者80,对方支付系统繁忙请10秒后再试。或自己运行出错,请检查自己。";
    }
  1. 测试超时

http://localhost/consumer/payment/hystrix/timeout/1

img

8001 有兜底方法,80 也有兜底的方法,那会优先使用谁的兜底方法?

可以测试一下,8001 有 1/0 这种异常时,即 8001 提供方已经出错时,会执行 8001 的兜底方法。

测试:8001 睡眠 2 秒,8001 允许 5 秒,不会进行兜底,但是 80 只允许 1 秒,会走 80 的兜底方法。

# 5. 目前问题

每个业务方法对应一个兜底的方法,代码膨胀,代码耦合

统一通用处理和自定义独立处理的分开

# 6. 解决问题

# 1) 每个方法配置一个???膨胀

使用注解 @DefaultProperties(defaultFallback = "") 指定全局的 fallback 方法。

需要降级的方法上留 @HystrixCommand 注解即可

img

说明

@DefaultProperties(defaultFallback = “”)

1:1 每个方法配置一个服务降级方法,技术上可以,但实际上傻 X

1:N 除了个别重要核心业务有专属,其它普通的可以通过 @DefaultProperties (defaultFallback = “”) 统一跳转到统一处理结果页面

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

controller 配置

package com.dabing.springcloud.controller;
import com.dabing.springcloud.service.PaymentHystrixService;
import com.netflix.hystrix.contrib.javanica.annotation.DefaultProperties;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@Slf4j
@DefaultProperties(defaultFallback = "payment_Global_FallbackMethod")  // 全局的
public class OrderHystrixController {
    @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
    public String paymentInfo_TimeOut(@PathVariable("id") Integer id){
        int age = 10/0;
        String result = paymentHystrixService.paymentInfo_TimeOut(id);
        return result;
    }
    // 兜底方法
    public String paymentTimeOutFallbackMethod(@PathVariable("id") Integer id){
        return "我是消费者80,对付支付系统繁忙请10秒钟后再试或者自己运行出错请检查自己,(┬_┬)";
    }
    // 下面是全局 fallback 方法
    public String payment_Global_FallbackMethod(){
        return "Global异常处理信息,请稍后再试,(┬_┬)";
    }
}

测试结果

img

# 2) 和业务逻辑混一起???混乱

服务降级,客户端去调用服务端,碰上服务端宕机或关闭

本次案例服务降级处理是在客户端 80 实现完成的,与服务端 8001 没有关系,只需要为 Feign 客户端定义的接口添加一个服务降级处理的实现类即可实现解耦

未来我们要面对的异常

  • 运行

  • 超时

  • 宕机

再看我们的业务类 PaymentController

修改 cloud-consumer-feign-hystrix-order80

根据 cloud-consumer-feign-hystrix-order80 已经有的 PaymentHystrixService 接口,重新新建一个类(PaymentFallbackService)实现该接口,统一为接口里面的方法进行异常处理

就是要把兜底的方法提取出来解耦不要跟业务代码放在一块混乱。

  1. 创建一个新的类实现 PaymentFeignClientService 接口

  2. 注解多加一个 fallback 的属性:@FeignClient (value = “CLOUD-PROVIDER-HYSTRIX-PAYMENT”,fallback = PaymentFallbackService.class)

PaymentFallbackService 类实现 PaymentFeignClientService 接口

package com.dabing.springcloud.service;
import org.springframework.stereotype.Component;
@Component
public class PaymentFallbackService implements PaymentHystrixService {
    @Override
    public String paymentInfo_OK(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_OK , (┬_┬)";
    }
    @Override
    public String payment_Timeout(Integer id) {
        return "-----PaymentFallbackService fall back-paymentInfo_TimeOut , (┬_┬)";
    }
}

YML - 已经写过了

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

PaymentFeignClientService 接口

package com.dabing.springcloud.service;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@Component
@FeignClient(value = "CLOUD-PROVIDER-HYSTRIX-PAYMENT",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);
}
# 3) 测试

单个 eureka 先启动 7001

PaymentHystrixMain8001 启动

正常访问测试:http://localhost/consumer/payment/hystrix/ok/31

故意关闭微服务 8001

客户端自己调用提升

此时服务端 provider 已经 down 了,但是我们做了服务降级处理,让客户端在服务端不可用时也会获得提示信息而不会挂起耗死服务器

如果有全局有自有的降级方法,会优先使用自带的降级方法

image-20221025131702391

# 3.7. 服务熔断

# 1. 断路器

一句话就是家里保险丝

# 2. 熔断是什么

熔断机制概述

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

当检测到该节点微服务调用响应正常后,恢复调用链路。

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

# 3. 大神论文:

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

img

论文的大概意思就是熔断有三个状态,打开,半开,关闭状态。但服务失败的次数达到阈值,例如默认 10 秒 20 个请求有 50% 的请求都失败了就打开熔断状态,等待一定的时间间隔,默认是 5s,重试处于半开状态,如果能成功则关闭熔断,正常使用服务。

# 4. 实操

# 1) 修改 cloud-provider-hystrix-payment8001
# 2) PaymentServiceImpl

com.netflix.hystrix.HystrixCommandProperties

// 服务熔断
@HystrixCommand(fallbackMethod = "paymentCircuitBreaker_fallback",commandProperties = {
        @HystrixProperty(name = "circuitBreaker.enabled",value = "true"),  // 是否开启断路器
        @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold",value = "10"),   // 当在配置时间窗口内达到此数量,打开断路,默认 20 个
        @HystrixProperty(name = "circuitBreaker.sleepWindowInMilliseconds",value = "10000"),  // 断路多久以后开始尝试是否恢复,默认 5s
        @HystrixProperty(name = "circuitBreaker.errorThresholdPercentage",value = "60"), // 出错百分比阈值,当达到此阈值后,开始短路。默认 50%
})
public String paymentCircuitBreaker(Integer id){
    if (id < 0){
        throw new RuntimeException("*****id 不能负数");
    }
    String serialNumber = IdUtil.simpleUUID();//hutool.cn 工具包
    return Thread.currentThread().getName()+"\t"+"调用成功,流水号:"+serialNumber;
}
public String paymentCircuitBreaker_fallback(@PathVariable("id") Integer id){
    return "id 不能负数,请稍候再试,(┬_┬)/~~     id: " +id;
}
# 3) PaymentController
//=== 服务熔断
@GetMapping("/payment/circuit/{id}")
public String paymentCircuitBreaker(@PathVariable("id") Integer id){
    String result = paymentService.paymentCircuitBreaker(id);
    log.info("*******result:"+result);
    return result;
}
# 4) 测试

自测 cloud-provider-hystrix-payment8001

正确: http://localhost:8001/payment/circuit/31

错误: http://localhost:8001/payment/circuit/-31

一次正确一次错误 trytry

重点测试

因为我们配置的是开启断路器,10 秒内 10 次请求有 60% 的失败率,那么就会打开断路器,间隔 10 秒后重试,如果成功,则断路器关闭,否则继续打开状态。当断路器打开时,尽管条件正确也不能正确访问地址,此时访问的时降级方法。

测试:狂点 4 次正确的链接,狂点 6 次错误链接,错误率达到 60%,此时再点击正确的链接也一样不能正常访问会直接执行降级方法。等待 10 秒后再执行正确的链接可以正常访问。

image-20221025212254926

# 5. 原理(小总结)

# 1) 大神结论

img

# 2) 熔断类型

熔断打开

请求不再进行调用当前服务,内部设置时钟一般为 MTTR (平均故障处理时间),当打开时长达到所设时钟则进入熔断状态

熔断关闭

熔断关闭不会对服务进行熔断

熔断半开

部分请求根据规则调用当前服务,如果请求成功且符合规则则认为当前服务恢复正常,关闭熔断

# 3) 官网断路器流程图
# 官网步骤
# 断路器在什么情况下开始起作用
// 服务熔断
@HystrixCommand(fallbackMethod = 
                "paymentCircuitBreaker_fallback",commandProperties = {
@HystrixProperty(name = 
                 "circuitBreaker.enabled",value = "true"),  // 是否开启断路器
@HystrixProperty(name = 
                 "circuitBreaker.requestVolumeThreshold",value = "20"),   // 当快照时间窗(默认 10 秒)内达到此数量才有资格打开断路,默认 20 个
@HystrixProperty(name = 
                 "circuitBreaker.sleepWindowInMilliseconds",value = "5000"),  // 断路多久以后开始尝试是否恢复,默认 5s
@HystrixProperty(name = 
                 "circuitBreaker.errorThresholdPercentage",value = "50"), // 出错百分比阈值,当达到此阈值后,开始短路。默认 50%
    })

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

配置属性参考:https://github.com/Netflix/Hystrix/wiki/Configuration

1、快照时间窗:断路器确定是否打开需要统计一些请求和错误数据,而统计的时间范围就是快照时间窗,默认为最近的 10 秒。

2、请求总数阈值:在快照时间窗内,必须满足请求总数阈值才有资格熔断。默认 20,意味着在 10 秒内,如果该 hystrix 命令的调用次数不足 20 次,即使所有的请求都超时或其他原因失败,断路器都不会打开。

3、错误百分比阈值:当请求总数在快照时间窗内超过了阈值,比如发生了 30 次调用,如果在这 30 次调用,有 15 次发生了超时异常,也就是超过 50% 的错误百分比,在默认设定 50% 阈值情况下,这时候就会将断路器打开。

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

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

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

到达以上阀值,断路器将会开启

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

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

# 断路器打开之后

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

2:原来的主逻辑要如何恢复呢?

对于这一个问题,hystrix 也为我们实现了自动恢复功能。

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

# All 配置

image-20221025212527038

image-20221025212542013

image-20221025212609356

image-20221025212625356

# 3.8. 服务限流

会在后面高级篇 alibaba 的 Sentinel 讲解

# 4 - Hystrix 工作流程

https://github.com/Netflix/Hystrix/wiki/How-it-Works

官网图例

image-20221025221254637

步骤说明

image-20221025221319106

# 5 - 服务监控 hystrixDashboard

# 5.1. 概述

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

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

# 5.2. 仪表盘 9001

# 1. 新建 Module:cloud-consumer-hystrix-dashboard9001

# 2. 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>Cloud2020</artifactId>
        <groupId>com.dabing</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>
    <artifactId>cloud-consumer-hystrix-dashboard9001</artifactId>
    <description>Hystrix服务监控hystrixDashboard</description>
    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>
    <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-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. YML

server:
  port: 9001
hystrix:
  dashboard:
    proxy-stream-allow-list: "*"

# 4. HystrixDashboardMain9001 + 新注解 @EnableHystrixDashboard

package com.dabing.springcloud;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.hystrix.dashboard.EnableHystrixDashboard;
@SpringBootApplication
@EnableHystrixDashboard
public class HystrixDashboardMain9001 {
    public static void main(String[] args) {
        SpringApplication.run(HystrixDashboardMain9001.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

img

# 5.3. 断路器演示

# 1. 修改 cloud-provider-hystrix-payment8001

注意:新版本 Hystrix 需要在主启动类 MainAppHystrix8001 中指定监控路径

/**
 * 此配置是为了服务监控而配置,与服务容错本身无关,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;
}

Unable to connect to Command Metric Stream

404

image-20221025213932003

# 2. 监控测试

# 1) 启动 1 个 eureka
# 2) 观察监控窗口

9001 监控 8001:

http://localhost:9001/hystrix

填写监控地址:

http://localhost:8001/hystrix.stream

img

测试地址

http://localhost:8001/payment/circuit/31

http://localhost:8001/payment/circuit/-31

上述测试通过

ok

先访问正确地址,再访问错误地址,再正确地址,会发现图示断路器都是慢慢放开的

监控结果,成功,断路器 closed

image-20221025215351624

监控结果,失败 60% 的请求,断路器打开。

image-20221025215527083 如何看

7 色

img

1 圈

实心圆:共有两种含义。它通过颜色的变化代表了实例的健康程度,它的健康度从绿色 < 黄色 < 橙色 < 红色递减。

该实心圆除了颜色的变化之外,它的大小也会根据实例的请求流量发生变化,流量越大该实心圆就越大。所以通过该实心圆的展示,就可以在大量的实体中快速的发现故障实例和高压力实例。

1 线

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

整图说明

image-20221025221522468

image-20221025221532147

整图说明 2

image-20221025221454166

搞懂一个才能看懂复杂的

image-20221025221548248

更新于 阅读次数

请我喝[茶]~( ̄▽ ̄)~*

Dabing-He 微信支付

微信支付