JavaEE 企业级分布式高级架构师(十三)微服务框架 SpringCloud (H 版)(3)

Hystrix熔断机制与服务降级

前置概念

服务熔断简介

雪崩效应
  • 如果说服务流控是为了在高并发场景下不至于将系统压垮,那么服务熔断则是为了在外部环境不通畅场景下,不至于将系统拖垮,也就是为了防止服务雪崩的发生。

在这里插入图片描述

  • 在复杂的系统中,经常会出现 A 依赖于 B、B 依赖于 C、C 依赖于 D、…这种依赖将会产生很长的调用链路,这种复杂的调用链路称为 1到 N 的扇出。
  • 如果在 A 的调用链路上某一个或几个被调用的子服务不可用或延迟较高,则会导致调用 A 服务的请求被堵住。堵住的 A 请求会消耗占用系统的线程、IO 等资源,当对 A 服务的请求越来越多,占用的计算机资源越来越多的时候,会导致系统瓶颈出现,造成其他的请求同样不可用,最终导致业务系统崩溃,这种现象称为雪崩效应。
服务雪崩
  • 以下图片都来自于 Hystrix 官网的 wiki 中 https://github.com/netflix/hystrix
  • 下图是用户请求的多个服务(A, H, I, P)均能正常访问并返回的情况。

在这里插入图片描述

  • 下图为请求服务 I 出现问题时,一个用户请求被阻塞的情况。

在这里插入图片描述

  • 下图为大量用户请求服务 I 出现异常全部陷入阻塞的的情况,即服务发生雪崩的情况。

在这里插入图片描述

熔断机制
  • 为了防止服务雪崩的发生,在发现了对某些资源请求的响应缓慢或调用异常较多时,直接将对这些资源的请求掐断一段时间。而在这段时间内的请求将不再等待超时,而是直接返回事先设定好的降级结果。这些请求将不占用系统资源,从而避免了服务雪崩的发生。这就是服务熔断。
  • 熔断机制是服务雪崩的一种有效解决方案。当指定时间窗内的请求失败率达到设定阈值时,系统将通过断路器直接将此请求链路断开。常见的熔断有两种:
    • 预熔断:也叫提前熔断,提前设定阈值,例如:主动断电。
    • 即时熔断:不可预知的,例如:使用大功率用电器时,保险丝自己烧断了。

服务降级简介

  • 服务降级是请求发生问题时的一种增强用户体验的方式。
  • 现代系统中,发生服务熔断,一定会发生服务降级;但发生服务降级,并不一定会发生服务熔断。

Hystrix简介

  • Spring Cloud 是通过 Hystrix 来实现服务熔断与降级的。

官网wiki

  • 地址:https://github.com/netflix/hystrix/wiki
    在这里插入图片描述
  • 原文】In a distributed environment, inevitably(不可避免地) some of the many service dependencies will fail. Hystrix is a library that helps you control the interactions(交互) between these distributed services by adding latency tolerance(延迟容忍) and fault tolerance logic(容错逻辑). Hystrix does this by isolating points of access between the services, stopping cascading failures(级联错误) across them(跨服务), and providing fallback options(回退选项), all of which improve your system’s overall resiliency(弹性).
  • 翻译】在分布式环境中,许多服务依赖中的一些服务发生失败是不可避免的。Hystrix 是一个库,通过添加延迟容忍和容错逻辑,帮助你控制这些分布式服务之间的交互。Hystrix 通过隔离服务之间的访问点停止跨服务的级联故障以及提供回退选项来实现这一点,所有这些都可以提高系统的整体弹性。

综合说明

  • 当 Hystrix 监控到某个服务发生故障后,其不会让该服务的消费者阻塞,或向消费者抛出异常,而是向消费者返回一个符合预期的、可处理的备选响应(FallBack),这样就避免了服务雪崩的发生。

Hystrix 服务降级方式

  • Hystrix 对于服务降级的实现方式有两种:fallbackMethod 服务降级,与 fallbackFactory 服务降级

创建消费者工程 04-consumer-hystrix-8080

  • 复制 02-consumer-8080 工程,并重命名为 04-consumer-hystrix-8080。
  • 添加hystrix依赖:
<!-- hystrix依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
</dependency>

fallbackMethod服务降级

  • 修改 Controller 层接口:

在这里插入图片描述

  • 启动类修改:开启熔断器

在这里插入图片描述

  • 把所有提供者都停掉,启动该消费者工作,访问:http://localhost:8080/consumer/depart/get/1。

在这里插入图片描述

fallbackFactory服务降级

  • fallbackMethod 服务降级存在的问题:接口方法与降级方法写在一起了,这样会很乱,不利于后期维护。
  • fallbackMethod 服务降级和Feign是没有关系的,但fallbackFactory服务降级是是有关系的,需要引入 openfeign 依赖,使用 feign.hystrix.FallbackFactory。
  • 定义降级处理类或降级工厂处理类:该实现方案会直接将 fallbackMethod 的服务降级给屏蔽掉
@Component
@RequestMapping("/fallback/consumer/depart")
public class DepartFallback implements DepartService {
    @Override
    public boolean saveOne(Depart depart) {
        System.out.println("执行 saveOne(Depart depart) 的服务降级处理方法");
        return false;
    }

    @Override
    public boolean deleteById(int id) {
        System.out.println("执行 deleteById(int id) 的服务降级处理方法");
        return false;
    }

    @Override
    public boolean updateOne(Depart depart) {
        System.out.println("执行 updateOne(Depart depart) 的服务降级处理方法");
        return false;
    }

    @Override
    public Depart getDepartById(int id) {
        System.out.println("执行 getDepartById(int id) 的服务降级处理方法");
        return new Depart().setId(id).setName("【Hystrix】No this depart -- 类级别");
    }

    @Override
    public List<Depart> listDeparts() {
        System.out.println("执行 listDeparts() 的服务降级处理方法");
        return null;
    }
}
@Component
public class DepartFallbackFactory implements FallbackFactory<DepartService> {

    @Autowired
    private DepartFallback departFallback;

    @Override
    public DepartService create(Throwable throwable) {
        return departFallback;
    }
}
  • Feign接口设置降级工厂或降级类:

在这里插入图片描述

  • 修改 application.yml 配置文件:
feign:
  hystrix:
    # 开启Feign对Hystrix的支持
    enabled: true
  • 再次访问:http://localhost:8080/consumer/depart/get/1。

在这里插入图片描述

Hystrix高级属性配置

官网资料https://github.com/Netflix/Hystrix/wiki/Configuration

执行隔离策略

  • 对依赖的请求数量进行限制的这种机制,称为执行隔离。
  • 执行隔离策略有两大作用:防止服务熔断,防止服务雪崩。
类型

隔离请求的方式有两种类型:

  • 线程隔离thread:Hystrix 的默认隔离策略。系统会创建一个依赖线程池,为每个依赖请求分配一个独立的线程,而每个依赖所拥有的线程数量是有上限的。当对该依赖的调用请求数量达到上限后再有请求,则该请求阻塞。所以对某依赖的并发量取决于为该依赖所分配的线程数量
  • 信号量隔离:对依赖的调用所使用的线程仍为请求线程,即不会为依赖请求再创建新的线程。但系统会为每种依赖分配一定数量的信号量,而每个依赖请求分配一个信号。当对该依赖的调用请求数量达到上限后再有请求,则该请求阻塞。所以对某依赖的并发量取决于为该依赖所分配的信号数量

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

  • 线程隔离与信号量隔离对比:
    • 线程是进程的一个执行体,其具有独立运行的特性,而信号量却不是,其仅仅是线程执行的条件。
    • 线程隔离中请求线程与提供者调用线程不是同一个线程,而信号量隔离中请求线程与调用线程是同一个线程。
    • 线程隔离的执行效率要高于信号量隔离的,因为线程隔离的执行体数量是信号量的 2 倍。
    • 线程隔离使每台主机处理请求的数量是有限制的,因为主机线程数量是有上限的;而信号量隔离不同,其没有上限,因为所谓信号量就是一个计数器,是一个数值,不存在上限。
    • 在服务器少而请求并发量大的情况下,不建议使用线程隔离,否则可能会使系统队请求的并发能力下降。
    • 线程隔离便于控制反馈给客户端的降级时间。
修改策略
  • 若是在配置文件中,则可以通过以下设置修改:
hystrix.command.default.execution.isolation.strategy=thread
hystrix.command.default.execution.isolation.strategy=semaphore
  • 若是在代码中,则可通过以下语句修改:只能在方法级别的注解中
HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.THREAD);
HystrixCommandProperties.Setter().withExecutionIsolationStrategy(ExecutionIsolationStrategy.SEMAPHORE);
@HystrixCommand(fallbackMethod = "getHystrixHandle",
            commandProperties = {@HystrixProperty(name = "hystrix.command.default.execution.isolation.strategy", value = "semaphore")})
@GetMapping("/get/{id}")
public Depart getHandle(@PathVariable("id") int id) {
    return departService.getDepartById(id);
}
默认值
  • 在 HystrixCommandProperties 类的构造器中设置有这些高级属性的默认值。
this.executionIsolationStrategy = getProperty(propertyPrefix, key, "execution.isolation.strategy", builder.getExecutionIsolationStrategy(), default_executionIsolationStrategy);
        
private static final ExecutionIsolationStrategy default_executionIsolationStrategy = ExecutionIsolationStrategy.THREAD;

执行隔离的其它属性

线程执行超时时限
  • 在默认的线程执行隔离策略中,关于线程的执行时间,可以为其设置超时时限。当然,首先通过下面的属性开启该超时时限,该属性默认是开启的,即默认值为 true。若要关闭,则可以在配置文件中设置该属性的值为 false。
hystrix.command.default.execution.timeout.enabled
this.executionTimeoutEnabled = getProperty(propertyPrefix, key, "execution.timeout.enabled", builder.getExecutionTimeoutEnabled(), default_executionTimeoutEnabled);
        
private static final Boolean default_executionTimeoutEnabled = true;
  • 在开启了执行线程超时时限后,可以通过以下属性设置时限长度,其默认值为 1000 毫秒。
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
this.executionTimeoutInMilliseconds = getProperty(propertyPrefix, key, "execution.isolation.thread.timeoutInMilliseconds", builder.getExecutionIsolationThreadTimeoutInMilliseconds(), default_executionTimeoutInMilliseconds);
        
private static final Integer default_executionTimeoutInMilliseconds = 1000;
超时中断
  • 当线程执行超时时是否中断线程的执行,默认为 true,即超时即中断。通过以下属性进行设置:
hystrix.command.default.execution.isolation.thread.interruptOnTimeout
取消中断
  • 在线程执行过程中,若请求取消了,当前执行线程是否结束呢?由下面属性进行设置,默认为 false,即取消后不中断。
hystrix.command.default.execution.isolation.thread.interruptOnCancel
信号量数量
  • 若采用信号量执行隔离策略,则可通过以下属性修改信号量的数量,即对某一依赖所允许的请求的最高并发量。默认值 10。
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests

服务降级属性

降级请求最大数量
  • 该属性仅限于信号量隔离。当信号量已用完后再有请求到达,并不是所有请求都会进行降级处理,而是在该属性设置值范围内的请求才会发生降级,其它请求将直接拒绝。默认值 10。
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests
服务降级开关
  • 无论是线程隔离还是信号量隔离,当请求数量到达其设置的上限后再有请求到达是否会对请求进行降级处理,取决于该属性值的设置。若该属性值设置为false,则不进行降级,而是直接拒绝请求。默认值 true。
hystrix.command.default.fallback.enabled
小结一下

在这里插入图片描述

服务熔断属性

熔断功能开关
  • 设置当前应用是否开启熔断器功能,默认值为 true。
hystrix.command.default.circuitBreaker.enabled
熔断器开启阈值
  • 当在时间窗内(10 秒)收到的请求数量超过该设置的数量后,将开启熔断器。默认值为 20。注意,开启熔断器是指将拒绝所有请求;关闭熔断器是指将使所有请求通过。
hystrix.command.default.circuitBreaker.requestVolumeThreshold
熔断时间窗
  • 当熔断器开启该属性设置的时长后,会尝试关闭熔断器,以恢复被熔断的服务。默认值为 5000 毫秒。
hystrix.command.default.circuitBreaker.sleepWindowInMilliseconds
熔断开启错误率
  • 当请求的错误率高于该百分比时,开启熔断器。默认值为 50,即 50%。
hystrix.command.default.circuitBreaker.errorThresholdPercentage
强制开启熔断器
  • 设置熔断器无需条件开启,拒绝所有请求。默认值为 false。
hystrix.command.default.circuitBreaker.forceOpen
强制关闭熔断器
  • 设置熔断器无需条件的关闭,通过所有请求。默认值为 false。
hystrix.command.default.circuitBreaker.forceClosed

其它属性设置

Dashboard监控仪表盘

  • Hystrix Dashboard 仪表盘用于以 GUI 的形式展示消费者的执行情况,包括其处理器方法与 Service 方法的调用执行情况,及熔断器 CircuitBreaker 的状态等。当然,这些显示出的数据都是在指定时间窗内的执行情况及状态信息。
  • Hystrix-dashboard 用于监控 Hystrix 服务降级情况,所以应添加在消费者工程中。

创建消费者工程 04-consumer-dashboard-8080

  • 复制 04-consumer-hystrix-8080 工程 并重命名为 04-consumer-dashboard-8080。
  • 依赖:
<!-- hystrix-dashboard依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<!-- actuator依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • 修改 application.yml 配置:在配置文件中添加如下内容,用于开启 actuator 的所有 web 终端,并调整 Hystrix 隔离线程执行的超时时限。
feign:
  hystrix:
    # 开启Feign对Hystrix的支持
    enabled: true
  client:
    config:
      default:
        # 指定Feign连接提供者的超时时限,决定于网络状况
        connectTimeout: 5000
        # 指定Feign从请求到获取提供者响应的超时时限,决定于提供者的处理时间
        readTimeout: 5000
  compression:
    request:
      # 开启对请求的压缩
      enabled: true
      # 指定对哪些MIME类型的文件进行压缩
      mime-types: ['text/xml', 'application/xml', 'application/json']
      min-request-size: 2048
    response:
      # 开启对客户端响应的压缩
      enabled: true
      
# 开启actuator的所有web终端
management:
  endpoints:
    web:
      exposure:
        include: "*"
        
# 设置服务熔断时限
hystrix:
  command:
    default:
      execution:
        isolation:
          thread:
            timeoutInMilliseconds: 3000
  • 修改启动类:使用 @EnableHystrixDashboard 注解开启 Hystrix 仪表盘功能。

在这里插入图片描述

  • 启动工程,并访问测试:http://localhost:8080/hystrix

在这里插入图片描述

创建提供者工程 04-provider-8090

  • 复制 03-provider-8090 工程,并重命名为 04-provider-8090,修改实现类中的 getDepartById()方法,增加 sleep() 休眠功能用来模拟耗时操作。

在这里插入图片描述

  • 为了演示效果,这里测试时 id 的取值为 2、4。当取 2 时,不会超时,但当取 4 时,隔离线程执行会超时。

仪表盘

  • 分别访问 http://localhost:8080/consumer/depart/get/2http://localhost:8080/consumer/depart/get/4,并查看 dashboard 界面

在这里插入图片描述

  • 使用postman批量访问100次:

在这里插入图片描述

  • 观察dashboard变化:

在这里插入图片描述

GUI介绍

在这里插入图片描述

关于Turbine
  • 我们在适用前面的方式通过 Dashboard 监控仪表盘来查看应用的运行状态是很方便的,但只能查看到某一个主机的情况。若该应用是一个集群,想查看整个集群的运行状态是无法实现的。
  • 此时 Turbine 就解决了这个问题,并可以使用一个 Dashboard 来查看你想要查看的一个或若干个集群的运行状态。

Turbine聚合监控——监控默认组集群

整体架构

在这里插入图片描述

总步骤

Turbine Client
  • 至少要有 actuator 与 netflix-hystrix 依赖
  • 在配置文件中必须开启 acturator 的 hystrix.stream 监控终端
Turbine Server
  • 至少需要如下依赖:
    • netflix-turbine
    • netflix-hystrix-dashboard
    • netflix-bystrix
    • actuator
    • eureka client
  • 在配置文件中配置 turbine:指定要监控的 group 及相应的微服务名称

创建消费者工程 04-consumer-turbine-8080

  • 复制 04-consumer-dashboard-8080 工程,并重命名为 04-consumer-turbine-8080,在此基础上修改。去掉 hystrix-dashboard 依赖。
  • 利用该工程在本地启动三个消费者,端口号分别是 8080、8081、8082,IDEA允许并行运行。

创建Turbine工程 00-hystrix-turbine-8888

  • 复制 00-eureka-server-8000 工程,重命名为 00-hystrix-turbine-8888,在此基础上修改,引入 Turbine Server 所需要的依赖。
  • application.yml 配置文件:
server:
  port: 8888

spring:
  application:
    name: hystrix-turbine
# eureka client配置
eureka:
  client:
    service-url:
      defaultZone: http://eureka8100.com:8100/eureka,http://eureka8200.com:8200/eureka,http://eureka8300.com:8300/eureka
# 配置turbine
turbine:
  # 指定要监控的微服务名称
  app-config: msc-consumer-depart
  aggregator:
    # 指定要监控的微服务组,default指定要监控默认组
    cluster-config: default
  # 指定要监控抽微服务组名称,默认组名称为'default'
  cluster-name-expression: "'default'"
  • 启动类开启 Turbine 和 HystrixDashboard:
@EnableTurbine
@EnableHystrixDashboard
@SpringBootApplication
public class HystrixTurbineApplication {
    public static void main(String[] args) {
        SpringApplication.run(HystrixTurbineApplication.class, args);
    }
}
  • 启动项目,并访问:http://localhost:8888/hystrix

在这里插入图片描述

  • 查看集群监控情况:

在这里插入图片描述

  • turbine 监控多个集群:

在这里插入图片描述

Turbine 聚合监控——监控多个组集群

  • 为了更加方便对集群的运行状态的监控,Turbine 将集群进行了分组。前面我们监控了两个集群,这两个集群默认被分为了一个组,是默认组。我们也可以将集群划分到多个组中使用同一个 Turbine 进行分别监控。

整体架构

在这里插入图片描述

分组监控原理

  • Turbine 对于集群分组进行监控的原理是,在集群之上再添加一种分类:组。为每个集群中的 Server 都添加一个 groupId,而 Turbine 是基于 groupId 进行监控的。
  • 这个 groupId 是基于自定义的 Eureka 元数据实现的。
  • Eureka 元数据是指,Eureka Client 向 Eureka Server 注册时的描述信息,注册数据。其有两种类型:
    • 标准元数据:Eureka 中已经定义好的客户端描述信息。
    • 自定义元数据:在客户端配置文件中自己指定的 Key-Value 数据

演示

  • 基于 04-consumer-turbine-8080 工程,启动4个Consumer:msc-consumer-depart8080、msc-consumer-depart8081、msc-consumer-depart8082、msc-consumer-depart8083;其中8080、8081为group1,8082、8083为group2,

在这里插入图片描述

  • 修改 00-hystrix-turbine-8888 工程中的 配置文件:

在这里插入图片描述

  • 启动 Turbine Server,并访问 http://localhost:8888/hystrix,监控 http://localhost:8888/turbine.stream?cluster=group1

在这里插入图片描述

  • 访问各个消费者工程,查看监控情况:

在这里插入图片描述

服务降级报警机制

  • 无论是由于个别原因导致某个或某些消费者出现问题,还是由于高并发出现消费问题而引发服务熔断,他们都将启用服务降级机制。无论哪种原因启用了服务降级,系统都应该向管理员发出警报通知管理员,例如向管理员发送短信。这种发生服务降级后向管理员发出警报的机制称为服务熔断报警机制
  • 在短信发送之前,需要先查看服务降级标识。该标识可以存放在:作为发生问题主机的全局变量、数据库、Redis等。

创建消费者工程 04-consumer-fallbackalarm-8080

  • 复制 04-consumer-hystrix-8080 工程,并重命名为 04-consumer-fallbackalarm-8080。添加 Spring Boot 与 Redis 整合依赖
<!-- Spring Boot与Redis整合依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
  • application.yml 配置文件中添加redis配置:
spring:
	# ...
	# 连接Redis服务器
	redis:
	  host: 192.168.254.128
	  post: 6379
	#  password:
  • 修改处理器,首先创建一个线程池,包含 5 个线程,然后再修改服务降级方法。
@RestController
@RequestMapping("/consumer/depart")
public class DepartController {
    @Autowired
    private DepartService departService;

    @HystrixCommand(fallbackMethod = "getHystrixHandle")
    @GetMapping("/get/{id}")
    public Depart getHandle(@PathVariable("id") int id, HttpServletRequest request) {
        return departService.getDepartById(id);
    }

    private Depart getHystrixHandle(@PathVariable("id") int id, HttpServletRequest request) {
        // 指定存放到Redis中的key为"ip + 发生降级的方法名"
        String ip = request.getLocalAddr();
        String key = ip + "_getDepartById";
        // 异常发生后的报警
        queryCache(key);

        return new Depart()
                .setId(id)
                .setName("【Hystrix】No this depart -- 方法级别");
    }

    @Autowired
    private StringRedisTemplate redisTemplate;

    // 创建一个线程池,包含5个线程
    private ForkJoinPool pool = new ForkJoinPool(5);

    /**
     * 异常发生后的报警
     */
    private void queryCache(String key) {
        // 获取Redis操作对象
        BoundValueOperations<String, String> ops = redisTemplate.boundValueOps(key);
        String value = ops.get();
        if(value == null) {
            synchronized (this) {
                value = ops.get();
                if(value == null) {
                    // 使用线程池实现异步短信发送
                    sendFallbackMsg(key);
//                    value = "短信已发送";
//                    ops.set(value, 10, TimeUnit.SECONDS);
                    ops.set("短信已发送", 10, TimeUnit.SECONDS);
                }
            }
        }
    }

    /**
     * 使用线程池实现异步短信发送
     */
    private void sendFallbackMsg(String key) {
        pool.submit(() -> System.out.println("发送服务异常报警短信:" + key));
    }
}
  • 测试访问 http://localhost:8080/consumer/depart/get/1,查看控制台输出

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

讲文明的喜羊羊拒绝pua

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值