Spring Cloud源码 - Hystrix原理分析

Hystrix原理分析

一:熔断,降级,限流,隔离

面对高并发的流量,我们通常会使用四种方式(熔断&降级&限流&隔离)来防止瞬时大流量对系统的冲击

1:何为熔断

关键字:断路保护

比如 A 服务调用 B 服务,由于网络问题或 B 服务宕机了或 B 服务的处理时间长,导致请求的时间超长,如果在一定时间内多次出现这种情况,就可以直接将 B 断路了(A 不再请求B)。

而调用 B 服务的请求直接返回降级数据,不必等待 B 服务的执行。因此 B 服务的问题,不会级联影响到 A 服务

在这里插入图片描述

2:何为降级

关键字:返回降级数据

网站处于流量高峰期,服务器压力剧增,根据当前业务情况及流量,对一些服务和页面进行有策略的降级(停止服务,所有的调用直接返回降级数据)。

以此缓解服务器资源的压力,保证核心业务的正常运行,保持了客户和大部分客户得到正确的响应。

降级数据可以简单理解为快速返回了一个 false,前端页面告诉用户“服务器当前正忙,请稍后再试。”

在这里插入图片描述

熔断和降级的相同点?

  • 熔断和限流都是为了保证集群大部分服务的可用性和可靠性。防止核心服务崩溃。
  • 给终端用户的感受就是某个功能不可用。

熔断和降级的不同点?

  • 熔断是被调用方出现了故障,主动触发的操作。
  • 降级是基于全局考虑,停止某些正常服务,释放资源。

3:何为限流

对请求的流量进行控制, 只放行部分请求,使服务能够承担不超过自己能力的流量压力

在这里插入图片描述

二:Hystrix

Hystrix:高可用性保障的一个框架。由 Netflix 出品(Netflix可以理解为国内的爱奇艺等视频网站)

  • 2011 年,其中的 API 团队为了提升系统的可用性和稳定性,发展出了 Hystrix 框架。
  • 2012 年,Hystrix 区域比较成熟稳定。其他团队也开始使用 Hystrix。
  • 2018 年 11 月,Hystrix在其 Github 主页宣布,不再开放新功能,推荐开发者使用其他仍然活跃的开源项目。

Hystrix设计理念如下:

  • 阻止服务的雪崩效应。
  • 快速失败和快速恢复。
  • 优雅降级。
  • 使用资源隔离技术,如 bulkhead(舱壁隔离技术)、swimlane(泳道技术)、circuit breaker(断路技术)。
  • 近实时的监控、报警及运维操作。

1:线程池隔离技术

使用线程池隔离,比如说有 3 个服务 A、B、C,每个服务的线程池分配 10,20,30个线程,当 A 服务线程池中的 10 个线程都拿出来使用后,如果调用服务 A 的请求量增加,还想再增加线程是不行的,因为 A 服务分配的线程已经用完了,不会拿其他的服务的线程,这样就不会影响其他服务了。

Hystrix 默认使用线程池隔离模式。

在这里插入图片描述

线程池隔离技术优点

  • 即使自己的此案成池满了,也不会影响任何其他其他的服务调用
  • 线程池的健康状态会上报,可以近实时修改依赖服务的调用配置。
  • 线程池具有异步特性,可以构建一层异步调用层。
  • 具有超时检测的机制,尤其在服务间调用特别有用。

线程池隔离技术缺点

  • 线程池本身就会带来一些问题,比如线程切换,线程管理,无疑增加了 CPU 的开销
  • 如果线程池中的线程利用率很低,则无疑是一种浪费。

2:信号量隔离技术

简单来说就是一个池子里面放着一定数量的信号量

服务 A 每次调用服务 B 之前,需要从池子里面申请信号量,申请到了,才能去调用 B 服务

在这里插入图片描述

线程池隔离和信号量的场景对比

  • 线程池隔离技术,适合大部分场景,但需要设置服务的超时时间。
  • 信号量隔离技术,适合内部比较复杂的业务,不涉及网络请求问题。

3:断路器工作原理

断路器时间窗内的请求数是否超过了请求数断路器生效阈值circuitBreaker.requestVolumeThreshold

如果超过了阈值,则将会触发断路,断路状态为开启

如果当前阈值设置的是20,则当时间窗内统计的请求数共计19个,即使19个全部失败了,都不会触发断路器。

并且请求错误率超过了请求错误率阈值errorThresholdPercentage

如果两个都满足,则将断路器由关闭迁移到开启

// 请求数 >= 生效阈值 并且 错误率 > 设置的错误率
if (requestCount >= circuitBreaker.requestVolumeThreshold && errorRate > errorThresholdPercentage) {
    flag = true; // 将断路器由关闭迁移到开启
    // 断路计时器开始计时
}

// 后续的请求直接断路掉

// 计时器计时长 > sleepWindowInMilliseconds -> 半开状态 -> 再次请求,允许调用底层服务
// 1:如果请求失败了,则保持断路器状态为开启状态,并更新沉睡时间窗
// 2:如果请求成功了,则将断路器状态改为关闭状态

如果断路器开启,则后续的所有相同请求将会被断路掉;

直到过了沉睡时间窗sleepWindowInMilliseconds后,再发起请求时,允许其通过(此时的状态为半开起状态)。如果请求失败了,则保持断路器状态为开启状态,并更新沉睡时间窗。如果请求成功了,则将断路器状态改为关闭状态;

4:Hystrix工作原理(三态)

Hystrix的工作流程上大概会有如下9个步骤

在这里插入图片描述

4.1:两大抽象类

在使用Hystrix的过程中,会对依赖服务的调用请求封装成命令对象

Hystrix 对 命令对象抽象了两个抽象类:HystrixCommandHystrixObservableCommand

HystrixCommand 表示的命令对象会返回一个唯一返回值

HystrixObservableCommand 表示的命令对象 会返回多个返回值

public class QueryOrderCommand extends HystrixCommand<Order> {
    private String orderId;
    public QueryOrderCommand(String orderId){
        super(Setter.withGroupKey(HystrixCommandGroupKey.Factory.asKey("hystrix-order-group"))
                .andThreadPoolKey(HystrixThreadPoolKey.Factory.asKey("hystrix-thread-order"))
                .andCommandKey(HystrixCommandKey.Factory.asKey("hystrix-pay-order"))
                .andCommandPropertiesDefaults(HystrixCommandProperties.defaultSetter())
                .andThreadPoolPropertiesDefaults(HystrixThreadPoolProperties.defaultSetter()
                        .withCoreSize(10)
                        .withQueueSizeRejectionThreshold(15)
                )
        );
        this.orderId = orderId;
    }
    @Override
    protected Order run() throws Exception {
        System.out.println("fetching order info via service call");
        return new Order();
    }
}

class Order{
    private String orderId;
    private String productId;
    private String status;
}
4.2:执行命令

Hystrix中共有4种方式执行命令,如下所示:

执行方式说明可用对象
execute()阻塞式同步执行,返回依赖服务的单一返回结果(或者抛出异常)HystrixCommand
queue()基于Future的异步方式执行,返回依赖服务的单一返回结果(或者抛出异常)HystrixCommand
observe()基于Rxjava的Observable方式,返回通过Observable表示的依赖服务返回结果
代调用代码先执行(Hot Obserable)
HystrixObservableCommand
toObservable()基于Rxjava的Observable方式,返回通过Observable表示的依赖服务返回结果
执行代码等到真正订阅的时候才会执行(cold observable)
HystrixObservableCommand

这四种命令中,exeucte()queue()observe()的表示也是通过toObservable()实现的,其转换关系如下图所示:

在这里插入图片描述

K value   = command.execute();
// 等价语句:
K value = command.execute().queue().get();


Future<K> fValue  = command.queue();
//等价语句:
Future<K> fValue = command.toObservable().toBlocking().toFuture();

Observable<K> ohValue = command.observe(); //hot observable,立刻订阅,命令立刻执行
//等价语句:
Observable<K> ohValue = command.toObservable().subscribe(subject);         

// 上述执行最终实现还是基于`toObservable()`
Observable<K> ocValue = command.toObservable(); // cold observable,延后订阅,订阅发生后,执行才真正执行
4.3:返回结果是否被缓存

如果当前命令对象配置了允许从结果缓存中取返回结果,并且在结果缓存中已经缓存了请求结果,则缓存的请求结果会立刻通过Observable的格式返回。

4.4:断路器是否打开

如果第3步没有缓存没有命中,则判断一下当前断路器的断路状态是否打开:

  • 如果断路器状态为打开状态,则Hystrix将不会执行此 Command 命令,直接执行步骤8 调用Fallback;
  • 如果断路器状态是关闭,则执行 步骤5 检查是否有足够的资源运行 Command 命令
4.5:资源是否已满

这里的资源指的是和Command命令 先关连的线程池 和队列(或者信号量)等资源。

  • 如果当前要执行的资源已经满了,Hystrix将不会运行 Command命令,直接执行 步骤8的Fallback降级处理;
  • 如果未满,表示有剩余的资源执行Command命令,则执行步骤6
4.6:run command

当经过步骤5 判断,有足够的资源执行Command命令时,本步骤将调用Command命令运行方法

基于不同类型的Command,有如下两种两种运行方式:

运行方式说明
HystrixCommand.run()返回一个处理结果或者抛出一个异常
HystrixObservableCommand.construct()返回一个Observable表示的结果(可能多个),或者 基于onError的错误通知

如果运行方法的真实执行时间超过了Command设置的超时时间阈值, 则当前则执行线程(或者是独立的定时器线程)将会抛出TimeoutException。

抛出超时异常TimeoutException后,将执行步骤8的Fallback降级处理。

即使run()或者construct()执行没有被取消或中断,最终能够处理返回结果

但在降级处理逻辑中,将会抛弃run()或construct()方法的返回结果,而返回Fallback降级处理结果。

注意事项:

需要注意的是,Hystrix无法强制 将正在运行的线程停止掉–Hystrix能够做的最好的方式就是在JVM中抛出一个InterruptedException。

如果Hystrix包装的工作不抛出中断异常InterruptedException, 则在Hystrix线程池中的线程将会继续执行,尽管调用的客户端已经接收到了TimeoutException。这种方式会使Hystrix 的线程池处于饱和状态。

大部分的Java Http Client 开源库并不会解析 InterruptedException。所以确认HTTP client 相关的连接和读/写相关的超时时间设置。

如果Command命令没有抛出任何异常,并且有返回结果,则Hystrix将会在做完日志记录和统计之后会将结果返回。

  • 如果是通过run()方式运行,则返回一个Obserable对象,包含一个唯一值,并且发送一个onCompleted通知;

  • 如果是通过consturct()方式运行 ,则返回一个Observable对象。

4.7:计算断路器的健康状况

Hystrix 会统计Command命令执行执行过程中的成功数、失败数、拒绝数和超时数,将这些信息记录到断路器(Circuit Breaker)中。

断路器将上述统计按照时间窗的形式记录到一个定长数组中。

断路器根据时间窗内的统计数据去判定请求什么时候可以被熔断,熔断后,在接下来一段恢复周期内,相同的请求过来后会直接被熔断。

当再次校验,如果健康监测通过后,熔断开关将会被关闭。

4.8:获取Fallback(降级四大条件)

当以下场景出现后,Hystrix将会尝试触发Fallback:(触发服务降级的四个情况)

  • 步骤6 Command run时抛出了任何异常;
  • 步骤4 断路器已经被打开[已经是服务熔断状态]
  • 步骤5 执行命令的线程池、队列或者信号量资源已满
  • 命令执行的时间超过阈值
4.9:返回成功的结果

如果 Hystrix 命令对象执行成功,将会返回结果,或者以Observable形式包装的结果。

根据步骤2的command 调用方式,返回的Observable 会按照如下图说是的转换关系进行返回:

在这里插入图片描述

三:简单说一下Sentinel

下载地址:https://github.com/alibaba/Sentinel/releases

Sentinel:面向分布式服务架构的流量控制组件

主要以流量为切入点,从限流、流量整形、熔断降级、系统负载保护、热点防护等多个维度来帮助开发者保障微服务的稳定性

  • 2012 年,Sentinel 诞生,主要功能为入口流量控制。
  • 2013-2017 年,Sentinel 在阿里巴巴集团内部迅速发展,成为基础技术模块,覆盖了所有的核心场景。
  • 2018 年,Sentinel 开源,并持续演进。
  • 2019 年,Sentinel 朝着多语言扩展的方向不断探索,推出 C++ 原生版本,同时针对 Service Mesh 场景也推出了 Envoy 集群流量控制支持,以解决 Service Mesh 架构下多语言限流的问题。
  • 2020 年,推出 Sentinel Go 版本,继续朝着云原生方向演进

sentinel特征如下

  • 丰富的应用场景。 支撑阿里的双十一核心场景,如秒杀、消息削峰填谷、集群流量控制、实时熔断下游不可用。
  • 完备的实时监控。 可以看到接入应用的单台机器秒级数据,以及集群的汇总情况。
  • 广泛的开源生态。 Spring Cloud、Dubbo、gRPC 都可以接入 Sentinel。
  • 完善的SPI扩展点。 实现扩展接口来快速定制逻辑。

在这里插入图片描述

sentinel的构成

核心库不依赖任何框架/库,能在所有的 Java 运行时环境运行,且对 Spring Cloud、Dubbo 等框架有较好的支持。

控制台(Dashboard)基于 Spring Boot 开发,打包后可以直接运行,不需要额外的 Tomcat 等应用容器

资源

Sentinel 中的资源是核心概念,可以是 Java 应用程序中的任何内容,可以是提供的服务,甚至是一段代码。

可以通过 Sentinel API 定义的代码,就是资源,能够被 Sentinel保护起来。可以如下几种方式来标识资源:

  • 方法签名。
  • URL。
  • 服务名称等

1:设计理念

Sentinel 作为一个流量控制器,可以根据需要把随机的请求调整成合适的形状,如下图所示:

在这里插入图片描述

流控功能

Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能

而且它还具备响应时间的熔断降级模式,防止过多的慢调用占满并发数而影响整个系统

熔断功能

Sentinel 和 Hystrix 都是基于熔断器模式。

都支持基于异常比率来进行熔断,但 Sentinel 更强大,可以基于响应时间、异常比率和异常数来进行熔断降级

实时统计

Sentinel 和 Hystrix 都是基于滑动窗口进行实时统计

Hystrix 是基于 RxJava 的事件驱动模型,在服务调用成功/失败/超时的时候发布响应的事件,通过一系列的变换和聚合最终得到实时的指标统计数据流,可以被熔断器或 Dashboard 消费。

而 Sentinel 是基于 LeapArray 的滑动窗口

2:突出特性

除了上面提到的三大对比外,Sentinel 还有一些 Hystrix 不具备的功能

2.1:流量控制

原理是监控应用流量的 QPS 或并发线程数等指标,当达到指定的阈值时对流量进行控制,以避免被瞬时的流量高峰冲垮,从而保障应用的高可用性。

Sentinel 可以基于QPS/并发数进行流量控制,也可以基于调用关系进行流量控制。

基于 QPS 进行流量控制有以下几种方式

  • 直接拒绝:当QPS 超过一定阈值时,直接拒绝。适用于对系统处理能力确切已知的情况。

  • 慢启动预热:当系统长期处于低水位的情况下,当流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮

  • 匀速排队:请求以均匀的速度通过,对应的是漏桶算法。

基于调用关系的流量控制:

  • 根据调用方限流。
  • 根据调用链路入口限流:链路限流。
  • 根据具有关系的资源流量限流:关联流量限流。
2.2:系统自适应限流

Sentinel 系统自适应限流从整体维度对应用入口流量进行控制,借助 TCP BBR 思想,结合应用的 Load、CPU 使用率、总体平均 RT、入口 QPS 和并发线程数等几个维度的监控指标,通过自适应的流控策略,让系统的入口流量和系统的负载达到一个平衡,让系统尽可能跑在最大吞吐量的同时保证系统整体的稳定性

在这里插入图片描述
我们把系统处理请求的过程想象为一个水管,到来的请求是往这个水管灌水

当系统处理顺畅的时候,请求不需要排队,直接从水管中穿过,这个请求的RT是最短的;

反之,当请求堆积的时候,那么处理请求的时间则会变为:排队时间 + 最短处理时间。

  • 推论一: 如果我们能够保证水管里的水量,能够让水顺畅的流动,则不会增加排队的请求;也就是说,这个时候的系统负载不会进一步恶化。
  • 推论二: 当保持入口的流量是水管出来的流量的最大的值的时候,可以最大利用水管的处理能力
2.3:实时监控和控制面板

Sentinel 提供一个轻量级的开源控制台,它提供机器发现以及健康情况管理、监控(单机和集群),规则管理和推送的功能

在这里插入图片描述

2.4:发展和生态

Sentinel 针对 Spring Cloud、Dubbo、gRPC 都进行了适配

引入依赖和简单的配置即可快速接入 Sentinel,相信 Sentinel 将是未来流量防控的一大利器。

四:对比总结

功能SentinelHystrix
隔离策略信号量隔离(并发线程数限制)线程池隔离(默)/信号量隔离
熔断降级策略基于响应时间,异常比例,异常数基于异常比例
实时统计实现滑动窗口(LeapArray)滑动窗口(Rxjava)
动态规则配置支持多种数据源支持多种数据源
扩展性多个扩展点插件的方式
基于注解的支持支持
@SentinelResource
支持
@HystrixCommand + @HystrixProperty
限流基于QPS,调用关系的限流有限的限流手段
流量整形支持预热模式,匀速器模式,预热排队模式不支持
系统自适应保护支持不支持
控制台可以配置规则,查看秒级的监控,机器发现简单的监控查看
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值