什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!
Posted Java学习部落
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!相关的知识,希望对你有一定的参考价值。
往期推荐
在分布式架构中,很常见的一个情形就是某一个请求需要调用多个服务。
如客户端访问 user 服务,而 user 服务需要调用 order 服务,order 服务需要调用 goods 服务,由于网络原因或者自身的原因,如果 order 服务或者 goods 服务不能及时响应,user 服务将处于阻塞状态,直到 order 服务 goods 服务响应。
此时若有大量的请求涌入,容器的线程资源会被消耗完毕,导致服务瘫痪。
服务与服务之间的依赖性,故障会传播,造成连锁反应,会对整个微服务系统造成灾难性的严重后果,这就是服务故障的“雪崩”效应。
1.如图所示,此时的系统正在愉快的运行中。
2.突然这个时候,goods服务节点的网络发生了故障。goods服务节点瘫痪,goods服务不可用。
3.由于good服务瘫痪导致order服务向goods服务发送的请求得不到返回,一直处于阻塞,此时user服务仍然一直 向order服务发送请求,最终导致order服务节点的资源耗尽,order服务节点瘫痪,order服务不可用。
4.由于good服务瘫痪导致order服务向goods服务发送的请求得不到返回,一直处于阻塞,此时user服务仍然一直 向order服务发送请求,最终导致order服务节点的资源耗尽,也瘫痪掉。此时user服务向order服务发送的请求同样也得不到返回,而客户端依然源源不断的向user服务节点发送请求,最终user服务节点和order服务节点一样,由于资源耗尽导致服务器瘫痪,user服务也不可用。
如上所述,一个服务节点的瘫痪,导致整条链路的服务节点都瘫痪的情形,我们称之为服务雪崩。
流量激增:比如异常流量、用户重试导致系统负载升高;
缓存穿透:假设A为client端,B为Server端,假设A系统请求都流向B系统,请求超出了B系统的承载能力,就会造成B系统崩溃;
程序有Bug:代码循环调用的逻辑问题,资源未释放引起的内存泄漏等问题;
硬件故障:比如宕机,机房断电,光纤被挖断等。
数据库严重瓶颈,比如:长事务、慢sql等。
线程同步等待:系统间经常采用同步服务调用模式,核心服务和非核心服务共用一个线程池和消息队列。如果一个核心业务线程调用非核心线程,这个非核心线程交由第三方系统完成,当第三方系统本身出现问题,导致核心线程阻塞,一直处于等待状态,而进程间的调用是有超时限制的,最终这条线程将断掉,也可能引发雪崩。
当发生突发流量激增的情况下,我们可以使用自动扩容,或者是在负载均衡器中添加服务限流功能
对于缓存穿透造成的服务雪崩问题,可以通过缓存预加载、缓存异步加载等方式来解决。
对于程序bug,emmm...,只能改bug了。
对于数据库查询时间过长导致的服务雪崩可以进行sql优化,硬件升级等。
对于硬件故障造成的服务雪崩, ,跨机房路由,异地多活等方式。
对于不同的造成服务雪崩的场景,有着很多不同的解决方案,但是没有一个通用的解决方案可以解决所有的问题。
在通过大量的实践证明,线程同步等待是最常见引发的雪崩效应的场景,此刻,本章节的主人公Hystrix将粉墨登场,我们将详细介绍如何使用Hystrix做故障隔离,熔断器机制等可以解决依赖服务不可用的问题。
Hystrix是一个用于处理微服务架构中的服务之间调用故障和容错的开源库。
在微服务架构里,各个服务之间的调用都可能会失败,比如超时、异常等。
Hystrix能够保证在一个依赖出问题的情况下,不会导致整个服务链路全线崩溃,提高微服务架构的可用性。
Hystrix,我们又称“断路器”,其本身是一种开关装置,当某个服务单元发生故障之后,通过断路器的故障监控(类似熔断保险丝),向调用方返回一个符合预期的、可处理的备选响应(FallBack),而不是长时间的等待或者抛出调用方无法处理的异常,这样就保证了服务调用方的线程不会被长时间、不必要地占用,从而避免了故障在分布式系统中的蔓延,乃至雪崩。
设计目标
(1)对依赖服务调用时出现的调用延迟和调用失败进行控制和容错保护。
(2)阻止某一个依赖服务的故障在整个系统中蔓延,服务A->服务B->服务C,服务C故障了,服务B也故障了,服务A故障了,整个系统全部故障,整体宕机。
(3)提供fail-fast(快速失败)和快速恢复的支持。
(4)提供fallback优雅降级的支持。
(5)支持近实时的监控、报警以及运维操作。
实现方式
通过hystrixCommand或者HystrixObservableCommand来封装对外部依赖的访问请求,这个访问请求一般会运行在独立的线程中。
对于超出我们设定的阈(yu)值服务调用,直接进行超时返回,不允许它长时间的阻塞。
对每一个依赖服务进行资源隔离。通过线程池或者是semaphore这两种方式。
对依赖服务被调用的成功次数,失败次数,拒绝次数,超时次数进行统计。
如果对某一个依赖服务的调用失败次数超过了一点的阈值,Hystrix自动进行熔断,并在一段时间内对该服务的调用直接进行降级,一段时间后再自动尝试恢复
当对一个服务调用出现失败、被拒绝、超时、短路等异常情况时,自动调用fallback降级机制。
对属性和配置的修改提供近实时的支持
首先我们看一下管网的Hystrix工作流程图:
下面我们针对这张图详细解读下Hystrix的工作流程。
1.每次调用都会创建HystrixCommand或者HystrixObservableCommand对象
2.执行execute(observe)或queue(toObservable)做同步异步调用
3.检查请求结果是否被缓存,如果缓存直接返回
4.检查是否开启了断路器,如果开启直接跳到步骤8
5.检查线程池/信号量是否跑满,如果跑满进入步骤8
6.执行 HystrixObservableCommand.construct() or HystrixCommand.run(),如果执行异常或者调用超时直接跳到步骤8
7.计算断路器状态,所有的运行状态(成功, 失败, 拒绝,超时)上报给断路器,用于统计从而判断断路器状态
8.调用fallback降级机制,通过上述步骤会有(熔断器打开,线程池/信号量跑满,调用超时,调用失败)四种情况会进行降级处理
9.返回依赖请求的真正结果
工作流程详解
第一步,创建HystrixCommand或者HystrixObservableCommand对象
HytrixCommand和HystrixObservableCommand包装了对外部依赖访问的逻辑。
整个流程的第一个步骤就是实例化HystrixCommand或者HystrixObservableCommand对象。
在构造这两个Command对象时,可以通过构造方法传递任何执行过程中需要的参数。
如果对外部依赖调用只返回一个结果值,那么可以实例化一个HystrixCommand对象。
HystrixCommand command = newHystrixCommand(arg1, arg2);
如果在调用外部依赖时需要返回多个结果值时,可以实例化一个HystrixObservableCommand对象
HystrixObservableCommand command = newHystrixObservableCommand(arg1, arg2);
第二步,执行execute(observe)或queue(toObservable)做同步异步调用
HystrixCommand主要是使用以下两个命令
execute():同步执行,从依赖的服务返回一个单一的结果对象,或者是在发生错误的时候抛出异常。
queue():异步执行,直接返回一个Future对象,其中包含了服务执行结束时要返回的单一结果对象。
HystrixObservableCommand使用以下两个命令
observe():返回Observable对象,返回 Observable 对象,立即发出请求,在依赖服务响应(或者抛出异常/超时)时,通过注册的 Subscriber 得到返回结果,它是一个Hot Observable。
toObservable():返回Observable对象,但只有在订阅该对象时,才会发出请求,然后在依赖服务响应(或者抛出异常/超时)时,通过注册的 Subscriber 得到返回结果,它是一个Cold Observable。
第三步,检查请求结果是否被缓存,如果缓存直接返回
若当前命令的请求缓存功能是被启用的,并且该命令缓存命中,那么缓存的结果会立即以Observable对象的形式返回。
这个结果缓存的好处为:
1、在同一个请求上下文中,可以减少使用相同参数请求原始服务的开销。
2、请求缓存在步骤5执行之前生效,所以可以有效减少不必要的线程开销。
第四步,检查是否开启了断路器
在缓存没有被命中时,Hystrix会在执行步骤5之前先检查断路器是否被打开。如果打开了,Hystrix不会执行任何命令执行跳转到步骤8
断路器开关控制条件:
1.对外部依赖调用的次数满足配置的阈值
2.对外部依赖调用发生错误的比率满足配置的阈值
在满足以上两个条件时,断路器打开熔断开关,之后所有对外部依赖调用都将被直接断开。
在开关打开时长超过试探窗口期后,断路器将尝试放行部分外部依赖的调用,
根据试探的结果决定重新开启或者关闭熔断开关。
第五步,检查线程池/信号量是否跑满
我们知道,Hystrix引入了线程池和信号量两种方式实现资源隔离机制。如果此时命令对应的线程池或队列或信号量已经满了,直接跳转到步骤8。
第六步,执行 HystrixObservableCommand.construct() or HystrixCommand.run()
Hystrix会根据我们编写的方法来决定采取什么方式去请求依赖服务。
1.HystrixCommand.run()——返回单个响应或抛出异常。
2.HystrixObservableCommand.construct()——返回 Observable 对象来发射多个结果,或通过onError发送错误通知。
如果run()或construct()方法执行时长超过了命令的超时阀值,其线程将抛出一个TimeoutException(或者在一个单独的线程抛出,如果命令没有运行在它自己的线程)。
这种情况下 Hystrix转接到fallback处理逻辑(第8步)。
并且如果该命令没有取消或中断,它将放弃run()或construct()方法最终的返回值。
如果命令没有抛出异常并且返回了响应,Hystrix 将会在执行一些日志记录和度量报告之后返回结果给调用者。
如果是通过run()运行,Hystrix 将返回 Observable 发射单个结果,然后发送一个onCompleted的通知;如果是通过construct()运行,Hystrix 直接返回该方法产生的Observable对象。
第七步,计算断路器状态
Hystrix会将每一个依赖服务的调用成功,失败,拒绝,超时,等事件,都会发送给circuit breaker断路器。
HystrixCircuitBreaker通过维护一系列的counter记录外部依赖请求的执行情况。
断路器根据维护的这些信息,在符合触发条件下开启断路功能,在条件合适的时候关闭断路开关。
如果打开了断路器,那么在一段时间内就会直接短路,然后如果在之后第一次检查发现调用成功了,就关闭断路器。
第八步,调用fallback降级机制
通过对上述步骤的详细解读,我们发现有以下几种情况是会调用fallback降级机制的。1.断路器打开 2.线程池或者信号量已经满了 3.command执行异常 4.执行超时
在服务降级逻辑中,需要实现一个通用的响应结果,并且该结果的处理逻辑应当是从缓存或是根据一些静态逻辑来获取,而不是依赖网络请求获取。
如果一定要在服务降级逻辑中包含网络请求,那么该请求也必须包装在HystrixCommand或HystrixObservableCommand中,从而形成级联的降级策略。
而最终的降级逻辑一定不是一个依赖网络请求的处理,而是一个能够稳定的返回结果的处理逻辑。
1.在 HystrixCommand 中,在 HystrixCommand.getFallback()方法中提供自定义的回调逻辑,方法返回单个回调值。
2.在 HystrixObservableCommand 中,在HystrixObservableCommand.resumeWithFallback() 方法中提供自定义的回调逻辑,方法返回一个Observable对象来发射一个或多个降级结果
如果fallback返回了结果,那么Hystrix就会返回这个结果。
对于HystrixCommand,会返回一个Observable对象,其中会发返回对应的结果;
对于HystrixObservableCommand,会返回一个原始的Observable对象。
如果没有实现fallback,或者是fallback抛出了异常,Hystrix会返回一个Observable,但是不会返回任何数据。
不同的command执行方式,其fallback为空或者异常时的返回结果不同
1.对于execute(),直接抛出异常
2.对于queue(),返回一个Future,调用get()时抛出异常
3.对于observe(),返回一个Observable对象,但是调用subscribe()方法订阅它时,抛出调用者的onError方法
4.对于toObservable(),返回一个Observable对象,但是调用subscribe()方法订阅它时,抛出调用者的onError方法
第九步,返回依赖请求的真正结果
如果Hystrix命令执行成功,它将以Observable形式返回响应给调用者。根据你在步骤2的调用方式不同,在返回Observablez之前可能会做一些转换。
execute():通过调用queue()来得到一个Future对象,然后调用get()方法来获取Future中包含的值。
queue():将Observable转换成BlockingObservable,在将BlockingObservable转换成一个Future。
observe():订阅返回的Observable,并且立即开始执行命令的逻辑。
toObservable():返回一个没有改变的Observable,你必须订阅它,它才能够开始执行命令的逻辑。
上述就是整个Hystrix的工作流程,当然没有很深入的讲解,但是还是建议多看几遍,我面试的时候碰到好几次让我简述Hystrix工作流程,多看几遍,记在心里,面试不慌。
当然了,Hystrix也能和Feign 和 Zuul 的集成使用,这些在这里就不赘述了,后续介绍Feign和Zuul的文章中会详细说明。
本文主要介绍HystrixCommand 注解方式的使用。
首先我们搭建一个HystrixClient项目。
添加配置文件application.properties
新建RestConfiguration类,用来全局配置RestTemplate。
新建HystrixController类
然后在启动类的上添加@EnableHystrix注解。
改造上一篇中提供的OrderService,让代码休眠5秒后在返回。
在HystrixController中的三个方法中分别配置了2000ms,10000ms,10000ms如果没有返回结果,那么将直接回调用我们指定的fallback。
OrderService
上述三步,基本的Hystrix使用框架就搭建完成了,然后我们启动上一篇中提到的Eureka-Server,并按照上篇文章的启动方式,分别启动OrderServeice的7777,8888,9999三个端口,此时我们打开 http://localhost:8761/ 页面,我们发现已经有三个服务名为order-service,端口号分别7777,8888,9999的服务注册了进来。
最后我们启动HystrixClient启动类,然后我们先访问设置超时时间为10000ms的 localhost:8088/test2 ,因为我们在OrderService中设置的休眠时间为3000ms所以能在超时时间内返回请求,所以不用调用fallback。
Execution相关的属性的配置
hystrix.command.default.execution.isolation.strategy
隔离策略,默认是Thread, 可选Thread,Semaphore
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds
命令执行超时时间,默认1000ms。
hystrix.command.default.execution.timeout.enabled
执行是否启用超时,默认启用true。
hystrix.command.default.execution.isolation.thread.interruptOnTimeout
发生超时是是否中断,默认true。
hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests
理论上选择semaphore size的原则和选择thread size一致,但选用semaphore时每次执行的单元要比较小且执行速度快(ms级别),否则的话应该用thread。semaphore应该占整个容器(tomcat)的线程池的一小部分。
Fallback相关配置
hystrix.command.default.fallback.isolation.semaphore.maxConcurrentRequests
如果并发数达到该设置值,请求会被拒绝和抛出异常并且fallback不会被调用。默认10。
hystrix.command.default.fallback.enabled
当执行失败或者请求被拒绝,是否会尝试调用hystrixCommand.getFallback() 。默认true。
Metrics相关属性配置
hystrix.command.default.metrics.rollingStats.timeInMilliseconds
设置统计的时间窗口值的,毫秒值,circuit break 的打开会根据1个rolling window的统计来计算。若rolling window被设为10000毫秒,则rolling window会被分成n个buckets,每个bucket包含success,failure,timeout,rejection的次数的统计信息。默认10000。
hystrix.command.default.metrics.rollingStats.numBuckets
设置一个rolling window被划分的数量,若numBuckets=10,rolling window=10000,那么一个bucket的时间即1秒。必须符合rolling window % numberBuckets == 0。默认10。
hystrix.command.default.metrics.rollingPercentile.enabled
执行时是否enable指标的计算和跟踪,默认true。
hystrix.command.default.metrics.rollingPercentile.timeInMilliseconds
设置rolling percentile window的时间,默认60000。
hystrix.command.default.metrics.rollingPercentile.numBuckets
设置rolling percentile window的numberBuckets。逻辑同上。默认6。
hystrix.command.default.metrics.rollingPercentile.bucketSize
如果bucket size=100,window=10s,若这10s里有500次执行,只有最后100次执行会被统计到bucket里去。增加该值会增加内存开销以及排序的开销。默认100。
hystrix.command.default.metrics.healthSnapshot.intervalInMilliseconds
记录health 快照(用来统计成功和错误绿)的间隔,默认500ms。
ThreadPool 相关属性配置
hystrix.threadpool.default.coreSize
并发执行的最大线程数,默认10。
hystrix.threadpool.default.maxQueueSize
BlockingQueue的最大队列数,当设为-1,会使用SynchronousQueue,值为正时使用LinkedBlcokingQueue。该设置只会在初始化时有效,之后不能修改threadpool的queue size,除非reinitialising thread executor。默认-1。
hystrix.threadpool.default.queueSizeRejectionThreshold
即使maxQueueSize没有达到,达到queueSizeRejectionThreshold该值后,请求也会被拒绝。因为maxQueueSize不能被动态修改,这个参数将允许我们动态设置该值。if maxQueueSize == -1,该字段将不起作用。
hystrix.threadpool.default.keepAliveTimeMinutes
如果corePoolSize和maxPoolSize设成一样(默认实现)该设置无效。
hystrix.threadpool.default.metrics.rollingStats.timeInMilliseconds
线程池统计指标的时间,默认10000。
hystrix.threadpool.default.metrics.rollingStats.numBuckets
将rolling window划分为n个buckets,默认10。
1.什么是降级?
降级,通常指事务高峰期,为了保证核心服务正常运行,需要停掉一些不太重要的业务,或者某些服务不可用时,执行备用逻辑从故障服务中快速失败或快速返回,以保障主体业务不受影响。
Hystrix提供的降级主要是为了容错,保证当前服务不受依赖服务故障的影响,从而提高服务的健壮性。
要支持回退或降级处理,可以重写HystrixCommand的getFallBack方法或HystrixObservableCommand的resumeWithFallback方法。
2.什么情况下才会走降级?
从Hystrix的工作流程图中我们可以看到以下情况会走降级逻辑。
1.断路器打开
2.线程池或者信号量已经满了
3.command执行异常
4.执行超时
3.回退降级有哪些处理方式?
快速失败:发生故障后直接抛出,不做处理。
无声失败:发生故障后,返回无意义内容,如null,空Map等,故障会被屏蔽。
静态失败:这种配置下,发生故障会返回静态的默认值,如返回值是boolean,则结果为默认true。
Stubbed:这种配置适用于返回值是一个复合对象的情形,发生故障时,会手动创建一个复合对象的实例,实例中往往包含了一些默认值或错误信息。
依赖缓存:这种情况下,当下层服务故障时,会从缓存中取得之前的旧数据供使用。
主次模式:这是回退降级的一种特殊使用方法。
主次模式解释:
有时候,我们可能会遇到这样的场景。针对某个业务,可能会有两种处理方案,A方案高效,但是没有经过规模化测试,不敢保证可靠性。B方案保守,虽然效率较低,但是不会出现。这时候,我们就可以尝试采用主次模式。主流程基于A方案运行,fallback基于B方案运行。在运行过程中,如不出错,则一直使用A方案,一时出错,可通过回退降级,迅速切换为B方案,以避免问题的不受控扩散。
什么是Hystrix Dashboard?
Hystrix提供了准实时的调用监控(Hystrix DashBoard),Hystrix会持续的记录通过Hystrix发起的请求的执行信息,以统计报表和图形的形式展示给客户,包括每秒执行多少,请求多少成功,请求失败多少等。
Netflix通过Hystrix-metics-event-stream项目实现了对以上指标的监控,SpringCloud也提供了Hystrix DashBoard的整合,对监控内容转化成可视化的界面,以便于用户能够直接的看到服务和集群的状态,在实际使用中,我们往往还要结合Turbine来使用。
开始搭建Hystrix Dashboard
Hystrix Dashboard的搭建其实很简单,分为三步:
创建监控Hystrix Dashboard项目模块
配置application.yml
配置启动类
首先我们创建一个HystrixDashboard项目,配置pom.xml文件如下
配置端口为9001
server.port = 9001
配置启动类,添加@EnableHystrixDashboard注解
启动后访问http://localhost:9001/hystrix
只要我们能看到一只豪猪就说明启动成功了。
本文从介绍服务雪崩引入Hystrix,首先带领大家对Hystrix进行了一个整体认知,并且介绍了Hystrix的设计目标以及实现方式。
然后详细分九步了Hystrix的整个工作流程,并且带领大家实战搭建了Hystrix框架和Hystrix监控系统。
最后详细介绍了Hystrix的核心配置,以及Hystrix的重中之重之回退降级。
Hystrix断路器同样是SpringCloud生态系统中不可缺少的一环,同样也是面试中经常会出现的高频面试题。
原创不易,如果大家喜欢,赏个分享点赞在看三连吧。和大家一起成为这世界上最优秀的人。
以上是关于什么是Hystrix,阿里技术最终面,遗憾的倒在Hystrix面前!的主要内容,如果未能解决你的问题,请参考以下文章
面试半年!三面阿里,四面蚂蚁金服,居然倒在了一个Java集合之Map上?
面经分享工作两年多,面试Bigo(Java岗)居然挂在三轮技术面...