天天看点

扒一扒隔离熔断之Hystrix VS Sentinel

作者:闪念基因

引言

为什么需要在项目中引入Hystrix等熔断隔离机制,其可以应用在什么场景中?在分布式系统中,单个应用通常会有多个不同类型的外部依赖服务,内部通常依赖于各种RPC服务,外部则依赖于各种HTTP服务。这些依赖服务不可避免的会出现调用失败,比如超时、异常等情况,如何在外部依赖出问题的情况,仍然保证自身应用的稳定,就是Hystrix这类服务保障框架的工作了。常见的服务依赖如下图所示,应用X依赖于服务A、B和C,A和B正常提供服务,C服务出错,这是如何避免C服务对A、B服务产生影响,也引出了一个隔离的概念。

扒一扒隔离熔断之Hystrix VS Sentinel

Hystrix

Hystrix [hɪst’rɪks],中文含义是豪猪,因其背上长满棘刺,从而拥有了自我保护的能力。本文所说的Hystrix是Netflix开源的一款容错框架,同样具有自我保护能力。

Hystrix设计目标

•对来自依赖的延迟和故障进行防护和控制——这些依赖通常都是通过网络访问的•阻止故障的连锁反应•快速失败并迅速恢复•回退并优雅降级•提供近实时的监控与告警

Hystrix遵循的设计原则

•防止任何单独的依赖耗尽资源(线程)•过载立即切断并快速失败,防止排队•尽可能提供回退以保护用户免受故障•使用隔离技术(例如隔板,泳道和断路器模式)来限制任何一个依赖的影响•通过近实时的指标,监控和告警,确保故障被及时发现•通过动态修改配置属性,确保故障及时恢复•防止整个依赖客户端执行失败,而不仅仅是网络通信

主要流程

•使用命令模式将所有对外部服务(或依赖关系)的调用包装在HystrixCommand或HystrixObservableCommand对象中,并将该对象放在单独的线程中执行;•每个依赖都维护着一个线程池(或信号量),线程池被耗尽则拒绝请求(而不是让请求排队)。•记录请求成功,失败,超时和线程拒绝。•服务错误百分比超过了阈值,熔断器开关自动打开,一段时间内停止对该服务的所有请求。•请求失败,被拒绝,超时或熔断时执行降级逻辑。•近实时地监控指标和配置的修改。

扒一扒隔离熔断之Hystrix VS Sentinel

Command对象封装请求

类结构:

扒一扒隔离熔断之Hystrix VS Sentinel

执行命令方式:

有4种方式可以执行一个Hystrix命令。

execute()和queue() 适用于HystrixCommand对象,而observe()和toObservable()适用于HystrixObservableCommand对象。

•execute()—该方法是阻塞的,从依赖请求中接收到单个响应(或者出错时抛出异常)。•queue()—从依赖请求中返回一个包含单个响应的Future对象。•observe()—订阅一个从依赖请求中返回的代表响应的Observable对象。•toObservable()—返回一个Observable对象,只有当你订阅它时,它才会执行Hystrix命令并发射响应。

扒一扒隔离熔断之Hystrix VS Sentinel

核心代码AbstractCommand:

public Observable<R> toObservable() {
 ...
 final Func0<Observable<R>> applyHystrixSemantics = new Func0<Observable<R>>() {
 @Override
 public Observable<R> call() {
 if (commandState.get().equals(CommandState.UNSUBSCRIBED)) {
 return Observable.never();
 }
 return applyHystrixSemantics(_cmd);//1.关键步骤,命令处理
 }
 };
 ...

private Observable<R> applyHystrixSemantics(final AbstractCommand<R> _cmd) {
 ...
 if (circuitBreaker.attemptExecution()) {//1.【断路器相关处理】,之后HystrixCircuitBreaker中展示
 ..
 if (executionSemaphore.tryAcquire()) {//2.获取信号量,如果是THREAD线程池策略,【直接返回true】,这里需要注意,不然流程将进行不下去
 try {
 executionResult = executionResult.setInvocationStartTime(System.currentTimeMillis());
 return executeCommandAndObserve(_cmd)//3.核心执行方法
 .doOnError(markExceptionThrown)
 .doOnTerminate(singleSemaphoreRelease)
 .doOnUnsubscribe(singleSemaphoreRelease);
 } ...
}           

断路器实现逻辑

下面的图展示了HystrixCommand和HystrixObservableCommand如何与HystrixCircuitBroker进行交互。

扒一扒隔离熔断之Hystrix VS Sentinel

回路器打开和关闭有如下几种情况:

•假设回路中的请求满足了一定的阈值(HystrixCommandProperties.circuitBreakerRequestVolumeThreshold())•假设错误发生的百分比超过了设定的错误发生的阈值HystrixCommandProperties.circuitBreakerErrorThresholdPercentage()•回路器状态由CLOSE变换成OPEN•如果回路器打开,所有的请求都会被回路器所熔断。•一定时间之后HystrixCommandProperties.circuitBreakerSleepWindowInMilliseconds(),下一个的请求会被通过(处于半打开状态),如果该请求执行失败,回路器会在睡眠窗口期间返回OPEN,如果请求成功,回路器会被置为关闭状态,重新开启1步骤的逻辑。

Hystrix 断路器状态:

熔断器有三个状态 CLOSED、 OPEN、HALF_OPEN 熔断器默认关闭状态,当触发熔断后状态变更为 OPEN,在等待到指定的时间,Hystrix会放请求检测服务是否开启,这期间熔断器会变为HALF_OPEN 半开启状态,熔断探测服务可用则继续变更为 CLOSED关闭熔断器。

扒一扒隔离熔断之Hystrix VS Sentinel

断路器实现类:

扒一扒隔离熔断之Hystrix VS Sentinel

核心代码:

public boolean allowRequest() {

if (properties.circuitBreakerForceOpen().get()) {

// properties have asked us to force the circuit open so we will allow NO requests

return false;

}

if (properties.circuitBreakerForceClosed().get()) {

// we still want to allow isOpen() to perform it's calculations so we simulate normal behavior

isOpen();

// properties have asked us to ignore errors so we will ignore the results of isOpen and just allow all traffic through

return true;

}

return !isOpen() || allowSingleTest();

}

这里代码判断逻辑

1.判断是否强制开启熔断器,是则return false,command不能执行 2.判断是否强制关闭熔断器,是则return true, command可执行 3.判断熔断器是否开启 circuitOpened.get() == -1表示没有开启,则return true,command可执行。4.到这步证明已经开启了熔断器,那么判断是否可尝试请求,如果可以同时会把熔断器的状态改为HALF_OPEN

熔断参数:

扒一扒隔离熔断之Hystrix VS Sentinel

隔离:

Hystrix采用舱壁模式来隔离相互之间的依赖关系,并限制对其中任何一个的并发访问。

扒一扒隔离熔断之Hystrix VS Sentinel

隔离方式:

•线程池隔离 请求并发量大,并且耗时长(一般是计算量大或者读数据库):采用线程池隔离,这样的话,可以保证大量的容器线程可用,不会由于服务原因,一直处于阻塞或者等待状态,快速失败返回。•信号量隔离请求并发量大,并且耗时短(一般是计算量小,或读缓存):采用信号量隔离:因为这类服务的返回往往非常快,不会占用容器线程太长时间,并且减少了线程切换的一些开销,提高了缓存服务的效率

线程池信号量线程请求线程和调用provider线程不是同一条线程请求线程和调用provider线程是同一条线程开销排队、调度、上下文切换等无线程切换,开销低异步支持不支持并发支持支持:最大线程池大小支持:最大信号量上限传递Header不支持支持支持超时支持不支持

扒一扒隔离熔断之Hystrix VS Sentinel
扒一扒隔离熔断之Hystrix VS Sentinel

超时器实现

HystrixCommand里有个 TimedOutStatus 超时状态

扒一扒隔离熔断之Hystrix VS Sentinel

实现流程:

有两个线程,一个是hystrixCommand任务执行线程,一个是等着给hystrixCommand判定超时的线程,现在两个线程看谁能先把hystrixCommand的状态置换,只要任何一个线程对hystrixCommand打上标就意味着超时判定结束。

扒一扒隔离熔断之Hystrix VS Sentinel

超时器实现类

扒一扒隔离熔断之Hystrix VS Sentinel

HystrixObservableTimeoutOperator.call(),TimerListener的实现

TimerListener listener = new TimerListener() {

 @Override
 public void tick() {

 if (originalCommand.isCommandTimedOut.compareAndSet(TimedOutStatus.NOT_EXECUTED, TimedOutStatus.TIMED_OUT)) {
 // 标记事件,可以认为是开的hook,这里暂忽略
 originalCommand.eventNotifier.markEvent(HystrixEventType.TIMEOUT, originalCommand.commandKey);

 //取消原Obserable的订阅
 s.unsubscribe();

 final HystrixContextRunnable timeoutRunnable = new HystrixContextRunnable(originalCommand.concurrencyStrategy, hystrixRequestContext, new Runnable() {

 @Override
 public void run() {
 child.onError(new HystrixTimeoutException());
 }
 });
 timeoutRunnable.run();
 }
 }

 //获取配置的超时时间配置
 @Override
 public int getIntervalTimeInMilliseconds() {
 return originalCommand.properties.executionTimeoutInMilliseconds().get();
 }
 };           

应用监控

监控指标:

扒一扒隔离熔断之Hystrix VS Sentinel

实心圆:包含两个含义,颜色表示实例的健康程度,健康程度从绿色、黄色、橙色、红色递减;大小则根据请求流量的大小发生变化,流量越大则实心圆越大,反之则越小。

曲线:统计了2分钟内的请求流量的变化,通过该曲线可以对流量进行上升和下降的趋势分析。

实现逻辑:

订阅了执行的完成事件后会把执行结果汇总到HystrixThreadEventStream。顾名思义就是一个事件流。

接下来的操作也比较容易猜到,我们需要一个订阅者来订阅这个事件来进行汇总。最终会把处理的结果写入HystrixThreadPoolCompletionStream和HystrixThreadPoolCompletionStream这两个流里面。

扒一扒隔离熔断之Hystrix VS Sentinel

统计实现:HealthCountsStream(订阅者)

处理的结果会写到HystrixThreadPoolCompletionStream和HystrixThreadPoolCompletionStream。最核心的统计实现逻辑HealthCountsStream。

扒一扒隔离熔断之Hystrix VS Sentinel

滑行窗口:

扒一扒隔离熔断之Hystrix VS Sentinel

类图:

扒一扒隔离熔断之Hystrix VS Sentinel

核心代码:

protected BucketedRollingCounterStream(HystrixEventStream<Event> stream, final int numBuckets, int bucketSizeInMs,
 final Func2<Bucket, Event, Bucket> appendRawEventToBucket,
 final Func2<Output, Bucket, Output> reduceBucket) {
 super(stream, numBuckets, bucketSizeInMs, appendRawEventToBucket);
 Func1<Observable<Bucket>, Observable<Output>> reduceWindowToSummary = new Func1<Observable<Bucket>, Observable<Output>>() {
 @Override
 public Observable<Output> call(Observable<Bucket> window) {
 return window.scan(getEmptyOutputValue(), reduceBucket).skip(numBuckets);
 }
 };
 this.sourceStream = bucketedStream //stream broken up into buckets
 .window(numBuckets, 1) //emit overlapping windows of buckets
 .flatMap(reduceWindowToSummary) //convert a window of bucket-summaries into a single summary
 .doOnSubscribe(new Action0() {
 @Override
 public void call() {
 isSourceCurrentlySubscribed.set(true);
 }
 })
 .doOnUnsubscribe(new Action0() {
 @Override
 public void call() {
 isSourceCurrentlySubscribed.set(false);
 }
 })
 .share() //multiple subscribers should get same data
 .onBackpressureDrop(); //if there are slow consumers, data should not buffer
 }           

环形数组数据结构:

扒一扒隔离熔断之Hystrix VS Sentinel

数据结构类:

class ListState {

/*

* 这里的data之所以用AtomicReferenceArray而不是普通数组,是因为data需要

* 在不同的ListState对象中跨线程来引用,需要可见性和并发性的保证。

*/

private final AtomicReferenceArray<Bucket> data;

private final int size;

private final int tail;

private final int head;

private ListState(AtomicReferenceArray<Bucket> data, int head, int tail) {

this.head = head;

this.tail = tail;

if (head == 0 && tail == 0) {

size = 0;

} else {

this.size = (tail + dataLength - head) % dataLength;

}

this.data = data;

}

}

Sentinel

Sentinel(哨兵) 是阿里中间件团队开源的,面向分布式服务架构的轻量级高可用流量控制组件,主要以流量为切入点,从流量控制、熔断降级、系统负载保护等多个维度来帮助用户保护服务的稳定性。

扒一扒隔离熔断之Hystrix VS Sentinel

Hystrix vs Sentinel

Hystrix 的关注点在于以隔离和熔断为主的容错机制,超时或被熔断的调用将会快速失败,并可以提供 fallback 机制。

Sentinel 的侧重点在于:

•多样化的流量控制•熔断降级•系统负载保护•实时监控和控制台

两者解决的问题还是有比较大的不同的。

扒一扒隔离熔断之Hystrix VS Sentinel

资源模型和执行模型上的对比

Sentinel 提供多样化的规则配置方式。除了直接通过 loadRules API 将规则注册到内存态之外,用户还可以注册各种外部数据源来提供动态的规则。用户可以根据系统当前的实时情况去动态地变更规则配置,数据源会将变更推送至 Sentinel 并即时生效

扒一扒隔离熔断之Hystrix VS Sentinel

隔离设计上的对比

线程池隔离会让机器资源碎片化。

线程池模式比较彻底的隔离性使得 Hystrix 可以针对不同资源线程池的排队、超时情况分别进行处理,但这其实是超时熔断和流量控制要解决的问题,如果组件具备了超时熔断和流量控制的能力,线程池隔离就显得没有那么必要了。

Hystrix 的信号量隔离overhead 比较小,但是效果不错。但缺点是无法对慢调用自动进行降级,只能等待客户端自己超时,因此仍然可能会出现级联阻塞的情况。

Sentinel 可以通过并发线程数模式的流量控制来提供信号量隔离的功能。并且结合基于响应时间的熔断降级模式,可以在不稳定资源的平均响应时间比较高的时候自动降级,防止过多的慢调用占满并发数,影响整个系统。

扒一扒隔离熔断之Hystrix VS Sentinel

熔断降级的对比

Sentinel 与 Hystrix 都支持基于失败比率(异常比率)的熔断降级

Sentinel 还支持基于平均响应时间的熔断降级,可以在服务响应时间持续飙高的时候自动熔断,拒绝掉更多的请求,直到一段时间后才恢复。这样可以防止调用非常慢造成级联阻塞的情况。

•降级判断标准•平均响应时间•异常比例•异常数•系统保护规则 (SystemRule):系统负载保护:Sentinel 对系统的维度提供保护,负载保护算法借鉴了 TCP BBR 的思想,让系统的入口流量和系统的负载达到一个平衡,保证系统在能力范围之内处理最多的请求。

扒一扒隔离熔断之Hystrix VS Sentinel

Sentinel控制台界面:

扒一扒隔离熔断之Hystrix VS Sentinel

Sentinel之流量控制

Sentinel 的「设计理念」是让编码人员自由选择控制流量的角度,并进行灵活组合,从而达到想要的效果。

我们可以通过以下的几个角度实现流量控制:

•资源的调用关系:根据调用方限流 根据调用链路入口限流-链路限流 具有关系的资源流量控制-关联流量控制•运行指标:例如 QPS、线程池、系统负载等;•控制的效果:例如直接限流、冷启动、排队等。

Sentinel之流量整形

Sentinel 支持多样化的流量整形策略,

在 QPS 过高的时候可以自动将流量调整成合适的形状。常用的有:

•直接拒绝模式:即超出的请求直接拒绝。•慢启动预热模式:当流量激增的时候,控制流量通过的速率,让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。•匀速器模式:利用 Leaky Bucket 算法实现的匀速模式,严格控制了请求通过的时间间隔,同时堆积的请求将会排队,超过超时时长的请求直接被拒绝。Sentinel 还支持基于调用关系的限流,包括基于调用方限流、基于调用链入口限流、关联流量限流等,依托于 Sentinel 强大的调用链路统计信息,可以提供精准的不同维度的限流。

扒一扒隔离熔断之Hystrix VS Sentinel

实时指标统计实现的对比

Hystrix 1.5 之前的版本是通过环形数组实现的滑动窗口,通过锁配合 CAS 的操作对每个桶的统计信息进行更新。

Hystrix 1.5 开始对实时指标统计的实现进行了重构,将指标统计数据结构抽象成了响应式流(reactive stream)的形式,方便消费者去利用指标信息。同时底层改造成了基于 RxJava 的事件驱动模式,在服务调用成功/失败/超时的时候发布相应的事件,通过一系列的变换和聚合最终得到实时的指标统计数据流,可以被熔断器或 Dashboard 消费。

Sentinel 目前抽象出了 Metric 指标统计接口,底层可以有不同的实现,目前默认的实现是基于 LeapArray 的滑动窗口,后续根据需要可能会引入 reactive stream 等实现。

扒一扒隔离熔断之Hystrix VS Sentinel
扒一扒隔离熔断之Hystrix VS Sentinel
扒一扒隔离熔断之Hystrix VS Sentinel

比对汇总

比较项 Sentinel Hystrix 说明
隔离策略 信号量隔离(并发线程数限流)(模拟信号量) 线程池隔离/信号量隔离 Sentinel不创建线程依赖tomcat或jetty容器的线程池,存在的问题就是运行容器的线程数量限制了sentinel设置值的上限可能设置不准。比如tomcat线程池为10,sentinel设置100是没有意义的,同时隔离性不好 hystrix使用自己创建的线程池,隔离性会更好
熔断降级策略 基于响应时间、异常比率、异常数 基于异常比率 快速失败的本质功能
实时统计实现 滑动窗口(LeapArray) 滑动窗口(基于 RxJava)
动态规则配置 支持多种数据源 支持多种数据源
扩展性 多个扩展点 插件的形式
注解 支持 支持
限流 基于 QPS,支持基于调用关系的限流 有限的支持(并发线程数或信号量大小) 快速失败的本质功能
流量整形 支持预热模式、匀速器模式、预热排队模式 不支持(排队)
系统自适应保护 支持(仅对linux/unix生效) 不支持 设置一个服务器最大允许处理量的阈值
控制台 提供开箱即用的控制台,可配置规则、查看秒级监控、机器发现等 简单的监控查看 接近实时数据 控制台是非常有竞争力的功能,因为能集中配置限 制数据更方便,但是展示数据和实时性没有hystrix 直观。
配置持久化 ZooKeeper, Apollo, Nacos、本地文件 Git/svn/本地文件 Sentinel客户端采用直接链接持久化存储,应用客户 端引用了更多的依赖,同样的存储链接可能有多个 配置
动态配置 支持 支持
黑白名单 支持 不支持
springcloud集成 非常高 Spring boot使用hystrix集成度更高
整体优势 集中配置设置及监控+更细的控制规则 漂亮的界面+接近实时的统计结果 docker容器化部署之后sentinel可能更会发挥作用

作者:李彩云

来源-微信公众号:到家交易平台技术

出处:https://mp.weixin.qq.com/s/TiuplYZBjV5u7h17G7fqhw

继续阅读