Spring Cloud中Hystrix、Ribbon及Feign的熔断关系
在Spring Cloud中Hystrix、Ribbon以及Feign它们三者之间在处理微服务调用超时从而触发熔断降级的关系是什么?
我们知道在Spring Cloud微服务体系下,微服务之间的互相调用可以通过Feign进行声明式调用,在这个服务调用过程中Feign会通过Ribbon从服务注册中心获取目标微服务的服务器地址列表,之后在网络请求的过程中Ribbon就会将请求以负载均衡的方式打到微服务的不同实例上,从而实现Spring Cloud微服务架构中最为关键的功能即服务发现及客户端负载均衡调用。
另一方面微服务在互相调用的过程中,为了防止某个微服务的故障消耗掉整个系统所有微服务的连接资源,所以在实施微服务调用的过程中我们会要求在调用方实施针对被调用微服务的熔断逻辑。而要实现这个逻辑场景在Spring Cloud微服务框架下我们是通过Hystrix这个框架来实现的。
调用方会针对被调用微服务设置调用超时时间,一旦超时就会进入熔断逻辑,而这个故障指标信息也会返回给Hystrix组件,Hystrix组件会根据熔断情况判断被调微服务的故障情况从而打开熔断器,之后所有针对该微服务的请求就会直接进入熔断逻辑,直到被调微服务故障恢复,Hystrix断路器关闭为止。
Hystrix、Feign及Ribbon的配置说明
接下来我们先来看看在Spring Cloud微服务系统中Hystrix、Feign及Ribbon的常用配置都有哪些以及它们的使用场景分别是什么?
Hystrix配置说明
在Spring Cloud微服务体系中Hystrix主要被用于实现实现微服务之间网络调用故障的熔断、过载保护及资源隔离等功能。而要正确使用Hystrix提供的这些功能就需要对Hystrix常用的配置有一定深入的了解,否则你会发现使用过程中认为一定会生效的配置常常不会起作用,接下来我们就按照参数配置的类型对Hystrix中的常见配置做一个梳理。
1)、线程隔离相关配置
Hystrix具备的重要关键特性之一就是它能够实现对第三方服务依赖的资源隔离,而隔离最常见的方式是通过线程池资源的隔离来实现的,Hystrix会为每个第三方服务依赖配置单独的线程池资源,从而避免对第三方服务依赖的请求占用应用主线程资源以免造成系统雪崩。Hystrix中关于线程隔离相关的配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | hystrix: command: #全局默认配置 default : #线程隔离相关 execution: timeout: #是否给方法执行设置超时时间,默认为 true 。一般我们不要改。 enabled: true isolation: #配置请求隔离的方式,这里是默认的线程池方式。还有一种信号量的方式semaphore,使用比较少。 strategy: threadPool thread: #方式执行的超时时间,默认为 1000 毫秒,在实际场景中需要根据情况设置 timeoutInMilliseconds: 1000 #发生超时时是否中断方法的执行,默认值为 true 。不要改。 interruptOnTimeout: true #是否在方法执行被取消时中断方法,默认值为 false 。没有实际意义,默认就好! interruptOnCancel: false |
2)、熔断器相关配置
熔断器是Hystrix最主要的功能,它开启和关闭的时机、灵敏度及准确性是Hystrix是否能够发挥重要的关键,而在Hystrix中与熔断器相关的几个配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | hystrix: command: #全局默认配置 default : #熔断器相关配置 circuitBreaker: #说明:是否启动熔断器,默认为 true 。我们使用Hystrix的目的就是为了熔断器,不要改,否则就不要引入Hystrix。 enabled: true #说明 1 :启用熔断器功能窗口时间内的最小请求数,假设我们设置的窗口时间为 10 秒, #说明 2 :那么如果此时默认值为 20 的话,那么即便 10 秒内有 19 个请求都失败也不会打开熔断器。 #说明 3 :此配置项需要根据接口的QPS进行计算,值太小会有误打开熔断器的可能,而如果值太大超出了时间窗口内的总请求数,则熔断永远也不会被触发 #说明 4 :建议设置一般为:QPS*窗口描述* 60 % requestVolumeThreshold: 20 #说明 1 :熔断器被打开后,所有的请求都会被快速失败掉,但是何时恢复服务是一个问题。熔断器打开后,Hystrix会在经过一段时间后就放行一条请求 #说明 2 :如果请求能够执行成功,则说明此时服务可能已经恢复了正常,那么熔断器会关闭;相反执行失败,则认为服务仍然不可用,熔断器保持打开。 #说明 3 :所以此配置的作用是指定熔断器打开后多长时间内允许一次请求尝试执行,官方默认配置为 5 秒。 sleepWindowInMilliseconds: 5000 #说明 1 :该配置是指在通过滑动窗口获取到当前时间段内Hystrix方法执行失败的几率后,根据此配置来判断是否需要打开熔断器 #说明 2 :这里官方的默认配置为 50 ,即窗口时间内超过 50 %的请求失败后就会打开熔断器将后续请求快速失败掉 errorThresholdPercentage: 50 #说明:是否强制启用熔断器,默认 false ,没有什么场景需要这么配置,忽略! forceOpen: false #说明:是否强制关闭熔断器,默认 false ,没有什么场景需要这么配置,忽略! forceClosed: false |
3)、Metrics(统计器)相关配置
Hystrix是否正常工作最主要的依赖就是根据捕获的调用指标信息来判断是否打开或者关闭熔断器,而影响Hystrix行为很重要的因素就是以下Hystrix关于Metrics的配置。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | hystrix: command: #全局默认配置 default : metrics: rollingStats: #说明:此配置用于设置Hystrix统计滑动窗口的时间,单位为毫秒,默认设置为 10000 毫秒,即一个滑动窗口默认统计的是 10 秒内的请求数据。 timeInMilliseconds: 10000 #说明 2 :此属性指定了滑动统计窗口划分的桶数。默认为 10 。 #说明 2 :需要注意的是,metrics.rollingStats.timeInMilliseconds % metrics.rollingStats.numBuckets == 0 必须成立,否则就会抛出异常 numBuckets: 10 rollingPercentile: #说明 1 :此属性配置统计方法是否响应时间百分比,默认为 true 。 #说明 2 :Hystrix会统计方法执行 1 %, 10 %, 50 %, 90 %, 99 %等比例请求的平均耗时用来生成统计图表。 #说明 3 :如果禁用该参数设置 false ,那么所有汇总统计信息(平均值、百分位数)将返回- 1 。 enabled: true #说明:统计响应时间百分比时的窗口大小,默认为 60000 毫秒,即 1 分钟 timeInMilliseconds: 60000 #说明 1 :此属性用于设置滑动百分比窗口要划分的桶数,默认为 6 。 #说明 2 :需要注意的是,metrics.rollingPercentile.timeInMilliseconds % metrics.rollingPercentile.numBuckets == 0 必须成立,否则会抛出异常 numBuckets: 6 #说明 1 :该属性表示统计响应时间百分比,每个滑动窗口的桶内要保存的请求数,默认为 100 。 #说明 2 :即默认 10 秒的桶内,如果执行了 500 次请求,那么只有最后 100 次请求执行的信息会被保存到桶内。 #说明 3 :增加这个值会增加内存消耗量,一般情况下无需更改。 bucketSize: 100 healthSnapshot: #说明 1 :该参数配置了健康数据统计器(会影响Hystrix熔断)中每个桶的大小,默认为 500 毫秒。 #说明 2 :在统计时Hystrix通过metrics.rollingStats.timeInMilliseconds / metrics.healthSnapshot.intervalInMilliseconds计算出桶数。 #说明 3 :在窗口滑动时,每滑过一个桶的时间就统计一次当前窗口内请求的失败率。 intervalInMilliseconds: 500 |
在上述关于Metrics的配置中出现了两个比较频繁的概念:滑动窗口、桶,Hystrix的统计器就是由滑动窗口来实现的。关于滑动窗口举一个非常形象的例子,假设你坐在一辆大巴车的上,车窗外是一排排笔直的大树,车辆在高速地行驶着,大树迅速地从车窗滑过,如果用每棵树来代表一个网络请求,用大巴车的行驶代表时间的流逝。那么大巴车的窗口就是一个非常典型的滑动窗口,而你通过这个车窗能够看到的大树就是Hystrix要统计的数据。
桶(bucket)是Hystrix统计滑动窗口数据时的最小单位。同样以上面的例子为例,在大巴车行驶非常快的情况下,如果每掠过一棵树就统计一次窗口内大树的数据的话,会造成比较大的开销,而如果我们将车窗分成10份,大巴车行驶时每掠过窗口的1/10就统计一次数据,那么开销就会小很多了。所以,Hystrix中的桶就是滑动窗口1/N的概念。
4)、线程池相关配置
在前面提到过Hystrix实现对第三方服务依赖资源隔离最主要的方式就是通过线程池,而Hystrix内线程的使用是基于Java内置线程池的简单封装,通过以下Hystrix线程池参数我们可以控制执行Hystrix命令的线程池的行为。具体如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | hystrix: command: default : #说明:核心线程池的大小,默认值是 10 coreSize: 10 #说明:是否允许线程池扩展到最大线程池数量,默认为 false 。 allowMaximumSizeToDivergeFromCoreSize: false #说明:线程池中线程的最大数量,默认值是 10 。此配置项单独配置时并不会生效,需要启用allowMaximumSizeToDivergeFromCoreSize maximumSize: 10 #说明 1 :作业队列的最大值,默认值为- 1 。表示队列会使用SynchronousQueue,此时值为 0 ,Hystrix不会向队列内存放作业。 #说明 2 :如果此值设置为一个正 int 型,队列会使用一个固定size的LinkedBlockingQueue,此时在核心线程池都忙碌的情况下,会将作业暂时存放在此队列内,但是超出此队列的请求依然会被拒绝 maxQueueSize: - 1 #设置队列拒绝请求的阀值,默认为 5 。 queueSizeRejectionThreshold: 5 #控制线程在释放前未使用的时间,默认为 1 分钟。 keepAliveTimeMinutes: 1 |
Ribbon配置说明
Ribbon在Spring Cloud中对于支持微服之间的通信发挥着非常关键的作用,其主要功能包括客户端负载均衡器及用于中间层通信的客户端。在基于Feign的微服务通信中无论是否开启Hystrix,Ribbon都是必不可少的,Ribbon的配置参数主要如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | ribbon: #说明:同一台实例的最大自动重试次数,默认为 1 次,不包括首次 MaxAutoRetries: 1 #说明:要重试的下一个实例的最大数量,默认为 1 ,不包括第一次被调用的实例 MaxAutoRetriesNextServer: 1 #说明:是否所有的操作都重试,默认为 true OkToRetryOnAllOperations: true #说明:从注册中心刷新服务器列表信息的时间间隔,默认为 2000 毫秒,即 2 秒 ServerListRefreshInterval: 2000 #说明:使用Apache HttpClient连接超时时间,单位为毫秒 ConnectTimeout: 3000 #说明:使用Apache HttpClient读取的超时时间,单位为毫秒 ReadTimeout: 3000 #说明:初始服务器列表,不需要手工配置,在运行时动态根据注册中心更新 listOfServers: www.microsoft.com: 80 ,www.yahoo.com: 80 ,www.google.com: 80 |
以上配置方式将对所有的微服务调用有效,如果想针对单独的微服务进行配置,使用“微服务名.ribbon”这样的配置方式即可,例如:
1 2 3 4 5 6 | bike: ribbon: ReadTimeout: 30000 operation: ribbon: ReadTimeout: 30000 |
Feign配置说明
Feign是一款Java语言编写的HttpClient绑定器,在Spring Cloud微服务中用于实现微服务之间的声明式调用,Feign自身可以支持多种HttpClient工具包,例如OkHttp及Apache HttpClient,针对不同的HttpClient其默认常见配置如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | feign: hystrix: enabled: true client: config: #JDK默认HttpURLConnection 实现的 Http Client配置 default : #连接超时时间 ConnectTimeout: 5000 #读取超时时间 ReadTimeout: 5000 #错误解码器 errorDecoder: com.wudimanong.client.common.FeignClientErrorDecoder #解码器 encoder: com.wudimanong.client.common.FeignClientEncoder #编码器 decoder: com.wudimanong.client.common.FeignClientDecoder |
Feign、Hystrix、Ribbon的超时配置关系
在前面的内容我们分别单独梳理了Feign、Hystrix及Ribbon三者常见的配置,针对各自的特性功能配置我们并没有异议,但是我们也看到它们都有针对微服务超时的配置,而在开启熔断器功能后,这些超时配置会影响到熔断器及服务降级逻辑的行为,那么它们之间超时的配置有什么关系呢?如下:
如上图所示,在Spring Cloud中使用Feign进行微服务调用分为两层:Ribbon的调用及Hystrix的调用。所以Feign的超时时间就是Ribbon和Hystrix超时时间的结合,而如果不启用Hystrix则Ribbon的超时时间就是Feign的超时时间配置,Feign自身的配置会被覆盖。
而如果开启了Hystrix,那么Ribbon的超时时间配置与Hystrix的超时时间配置则存在依赖关系,因为涉及到Ribbon的重试机制,所以一般情况下都是Ribbon的超时时间小于Hystrix的超时时间,否则会出现以下错误:
1 | 2019 - 07 - 12 11 : 10 : 20 , 238 397194 [http-nio- 8084 -exec- 2 ] WARN o.s.c.n.z.f.r.s.AbstractRibbonCommand - The Hystrix timeout of 40000ms for the command operation is set lower than the combination of the Ribbon read and connect timeout, 80000ms. |
那么Ribbon和Hystrix的超时时间配置的关系具体是什么呢?如下:
1 | Hystrix的超时时间=Ribbon的重试次数(包含首次)*(ribbon.ReadTimeout+ribbon.ConnectTimeout) |
而Ribbon的重试次数的计算方式为:
1 | Ribbon重试次数(包含首次)= 1 +ribbon.MaxAutoRetries+ribbon.MaxAutoRetriesNextServer+(ribbon.MaxAutoRetries*ribbon.MaxAutoRetriesNextServer) |
以上图中的Ribbon配置为例子,Ribbon的重试次数=1+(1+1+1)*(30000+10000),所以Hystrix的超时配置应该>=160000毫秒。在Ribbon超时但Hystrix没有超时的情况下,Ribbon便会采取重试机制;而重试期间如果时间超过了Hystrix的超时配置则会立即被熔断(fallback)。
如果不配置Ribbon的重试次数,则Ribbon默认会重试一次,加上第一次调用Ribbon的重试次数为2次,以上述配置为例Hystrix超时时间配置为2*40000=80000,由于很多情况下,大家一般不会主动配置Ribbon的重试次数,所以这里需要注意下!强调下,以上超时配置的值只是示范,超时配置有点大不太合适实际的线上场景,大家根据实际情况设置即可!