实现RPC调用保护
在Spring Cloud的微服务架构下,RPC保护可以通过Hystrix开源组件实现,Spring Cloud集成了Hystrix组件,使用起来非常方便。

海斯特里克斯翻译成豪猪。因为豪猪身上长满了刺,可以保护自己免受天敌的侵害,代表一种防御机制。Hystrix开源框架是网飞开源的延迟和容错组件,主要用于在远程提供者服务异常时,保护消费端的RPC。关于Hystrix的详细信息,请参考其官网。本文仅介绍其基本原理和用途。
在使用Hystrix之前,您需要在Maven的pom文件中添加以下Spring CloudHystrix集成模块的依赖关系:
org . spring framework . Cloud Spring-Cloud-starter-网飞-Hystrix在Spring云架构中,Hystrix与Feign结合使用,所以需要在应用的属性配置文件中开启Feign对Hystrix的支持:
Feign: Hystrix: enabled: true #打开Hystrix对Feign的支持,并在startup类上添加@EnableHystrix或@ enableclircuitbreaker。注意@EnableHystrix包含@EnableCircuitBreaker。作为示例,下面是演示提供者启动类的一些代码:
包com . crazy maker . spring cloud . demo . start;..@ EnableHystrixpublic类demo cloud application { public static void main { spring application . run;...}}Spring Cloud Hystrix的}} RPC保护功能包括回切、熔断、重试、隔板隔离等。接下来了解一下Hystrix的回切和熔断功能。
云hystrix无法回滚。
什么是回退?当目标提供者实例失败时,RPC的失败回滚将生效,并返回备份结果。失败的回退演示如图2-16所示。提供者A、B、C和D有四个实例,A提供者和B提供者向D提供者发起RPC远程调用,但是D提供者失败。当A和B接收到失败的回退保护时,它们将最终获得由失败的回退提供的备份结果。
图2-16 RPC远程调用失败回滚示意图
如何设置RPC调用的回退逻辑?有两种方法:
并使用回退定义回退处理类。
并定义使用FallbackFactory进行回退处理工厂类。让我们先来看第一种方式:定义并使用一个回退处理类。
第一种方法的具体实现可以分为两步:第一步是实现Feign client远程调用接口,编写一个fallback处理类,在fallback处理类对应的实现方法中编写RPC失败后的Fallback逻辑;第二步是在FeignClient接口的key annotation @FeignClient上配置故障处理类。具体来说,该注释的Fallback属性的值被配置为上一步中定义的回退处理类。
下面是演示如何定义和使用回退处理类的具体示例。在crazy-springcloud脚手架的uaa-client模块中,有一个Feign client远程调用接口UserClient,用于对uaa-provider进行RPC调用。其目的是获取用户信息。
第一步是用下面的代码为UserClient接口定义一个简单的回退处理实现类:
package com . crazy maker . spring cloud . user . info . remote . fallback;//import@ Component公共类UserClientFallback在RPC获取用户信息失败后实现用户客户端{@ override Public restout detail { return restout . error;}}}第二步是将Fallback属性的值配置为回退处理类UserClientFallback,该类是上一步在UserClient客户端接口的@FeignClient批注中定义的。代码如下:
包com . crazy maker . spring cloud . user . info . remote . client;//省略import@ FeignClientPublic接口用户客户端{ @ Request Mapping Restout Detail Long用户ID);}回退处理类的实现已经完成。如何验证?仍然使用前面定义的demoprovider的REST接口/api/call/uaa/user/detail/v2,通过UserClient对uaa-provider进行远程调用。具体的演示模式是:
停止所有uaa-provider服务,然后在demo-provider的swagger-ui界面中访问其REST接口/api/call/uaa/user/detail/v2。这个接口的内部代码会通过UserClient远程调用Feign interface发起对目标uaa-provider的REST接口/api/user/detail/v1的FeignRPC远程调用,而uaa-provider的所有服务都是down的,所以Feign会触发Hystrix回滚。执行回退处理类UserClientFallback的回退实现方法,返回回退处理内容。输出内容如图2-17所示。
图2-17 UserClientFallback生效后的回退处理类示意图
接下来,看看第二种方式,定义并使用一个回退处理工厂类。
第二种方法的具体实现也可以分为两步:第一步是创建一个FallbackFactory类,需要实现Hystrix的Fallback factory接口及其抽象的create创建方法。在该方法的实现代码中,需要返回一个Feign client接口的实现类,方法中的具体实现是fallback处理实例。匿名类可以创建一个新的回退处理类,在第二步是在FeignClient接口的key annotation @FeignClient上配置失败处理工厂类,将fallbackFactory属性的值配置为上一步定义的FallbackFactory回退处理工厂类。
以下是演示如何定义和使用FallbackFactory回退处理工厂类的具体示例。这里以uaa-client模块中的RPC调用接口UserClient为例进行演示。第一步是用以下代码定义一个简单的FallbackFactory回退处理工厂类:
package com . crazy maker . spring cloud . user . info . remote . fallback;//省略import的回退处理工厂类@ slf4j @ component公共类userclientfallbackfactory实现fallbackfactory {@ override公共用户客户端create { log.errorreturn new user client {@ override public reset detail { return reset . error;} };}}第二步,在FeignClient接口UserClient的@FeignClient注释上,将fallbackfactory属性的值配置为上一步定义的fallbackFactory回退处理工厂类。代码如下:
包com . crazy maker . spring cloud . user . info . remote . client;//省略import@ FeignClientPublic接口用户客户端{ @ Request Mapping Restout Detail Long用户ID);}第二种方式的工厂类的具体验证过程与第一种方式的工厂类相同:
停止所有uaa-provider服务,然后在demo-provider的swagger-ui界面中访问其REST接口/api/call/uaa/user/detail/v2。这个REST接口的内部代码会通过UserClient远程调用Feign接口,对目标uaa-provider的REST接口/api/user/detail/v1进行Feign RPC远程调用,而uaa-provider的所有服务都是down的,所以Feign会触发Hystrix回滚。执行回退工厂类UserClientFallbackFactory的create方法,创建回退处理类的实例,执行回退处理类实例中的回退处理逻辑,返回回退处理的结果。
回退失败时,使用第一种方法的回退类和使用第二种方法的回退工厂类有什么区别?
答:第一种方式使用fallback类时,远程调用RPC过程中引起的异常已经被fallback逻辑完全屏蔽了。应用不方便介入,也看不到RPC过程中的具体异常,虽然这些异常对故障排除很有帮助。当以第二种方式使用fallback factory类时,应用程序可以通过Java代码拦截和处理RPC异常,包括日志输出。
分布式系统面临的雪崩问题
在分布式系统中,一个服务可能依赖于许多其他服务,这些服务将不可避免地失败。如果一个应用运行Provider的30个实例,每个实例99.99%的时间都在正常服务,即使故障率只有0.01%,每个月还是会有几个小时不可用。此外,还有一个很大的问题:当流量高峰到来时,该服务可能会依赖于其他服务。如果这个提供者实例有延迟响应,将导致其他提供者更多的级联失败,从而使这个分布式系统不可用。
举个简单的例子,在一个spike系统中,商品、订单、spike三个提供者会通过RPC远程调用用户账号和认证的相关接口来查询用户的相关信息,如图2-18所示。
图2-18 spike系统中商品、订单、spike和用户四个提供者之间依赖关系示意图
如果流量高峰来临时uaa-Provider响应慢,那么商品、订单、秒杀三个提供者的等待时间都会超时,导致响应慢。随着越来越多的请求排队,单个请求的时间变得很长,各个服务节点的系统资源很快就会被耗尽,最后会进入系统雪崩状态,如图2-19所示。
图2-19当洪峰到来时,整体雪崩是由uaa-provider的缓慢响应造成的

一般来说,在微服务架构中,服务按照业务分为提供商微服务,但由于网络原因或自身原因,服务无法保证100%可用。为了确保服务提供者的高可用性,单个提供者服务通常部署在多个主体中。由于提供者之间的依赖性,故障或不可用将沿着请求-调用链向上传递,导致整个系统瘫痪的灾难性结果,这就是故障的雪崩效应。
雪崩效应的原因很多,以下是常见的几种:
硬件故障:如服务器宕机、机房停电、光纤被切断等。
流量激增:如流量异常、巨大请求瞬间涌入等。
缓存穿透:一般在系统重启,所有缓存失效,或者短时间内大量缓存失效时,前端大量请求无法命中缓存,直接冲击后端服务和数据库,导致服务提供者和数据库过载,造成整体瘫痪。
BUG:程序逻辑BUG导致内存泄露等原因造成的整体瘫痪。
JVM stuck:JVM的FullGC时间长,极端情况下长达几十秒。在此期间,JVM不能提供任何服务。
为了解决雪崩效应,业界提出了熔断模型。通过fuse,当一些非核心服务响应慢或宕机时,会降级提供有损服务,保证服务的灵活性和可用性,避免雪崩效应。
云状保险丝
在物理学中,保险丝本身就是一个开关器件,用在电路中,保护电路不至于过载。当电路发生短路时,熔断器能及时切断故障,防止过载、发热甚至火灾等严重后果。分布式架构中的保险丝主要用在RPC接口上,在接口上安装“保险丝”是为了防止RPC接口拥塞时系统压力过大导致系统瘫痪。当RPC接口流量过大或目标提供方异常时,保险丝可以及时切断故障,保护自身。
为什么说fuse很重要?如果没有过载保护,在分布式系统中,当被调用的远程服务无法使用时,所请求的资源将在远程服务器上被阻塞并耗尽。在许多情况下,在开始时,可能只有局部的小规模故障。然而,由于各种原因,故障的范围越来越大,最终导致全面的后果。
熔丝通常被称为fuse,其具体工作机制是:统计最近RPC调用的错误次数,然后根据统计中的失败率等信息决定是允许后续RPC调用继续还是快速回切。
保险丝的三种状态如下:
关:保险丝关断,这也是保险丝的初始状态。在这种状态下,RPC调用通常被释放。
On:故障率达到一定阈值后,熔丝会进入on状态,在这种状态下RPC会快速失效,然后执行故障回滚逻辑。
半开:打开一定时间后,保险丝进入半开状态,小流量尝试进行RPC调用并释放。如果尝试成功,熔丝会关闭,RPC调用正常;如果尝试失败,保险丝将被打开,RPC调用将很快失败。
保险丝状态之间的相互转换关系如图2-20所示。
图2-20保险丝状态之间的相互转换关系
下面重点介绍熔断器的半开状态。在半开状态下,允许一个RPC调用。如果实际调用成功,保险丝将重置为闭合状态,并返回到正常模式。但是,如果这个RPC调用失败,熔丝将返回到打开状态,并等待直到下一个半开状态。
默认情况下,Cloud Hystrix中的保险丝是打开的,但可以通过配置保险丝的参数进行自定义。以下是demo-provider微服务中的熔丝配置示例:
Hystrix:...命令:默认值:...断路器:#熔丝相关配置启用:true #是否使用熔丝,默认为true RequestVolumeThreshold:20 #窗口时间内的最小请求数,SleepWindowMillions: 5000 #断开后的休眠时间,默认配置为5秒error threshold percentage:50 #窗口时间内熔丝断开的误差比。默认配置是50度量:滚动统计:时间毫秒:10000 #滑动窗口时间数量桶:10 #滑动窗口时间桶。上面使用的Hystrix熔丝相关参数可以分为两类:熔丝相关参数和滑动窗口相关参数。示例中使用的保险丝的相关参数一般介绍如下:
hy strix . command . default . circuit breaker . enabled:此配置用于确定保险丝是否用于跟踪RPC请求的运行状态,或者配置保险丝是否启用。默认值为true。
hy strix . command . default . circuit breaker . requestvolumethreshold:
该配置用于设置保险丝触发请求的最小数量。如果设置为20,当在滑动窗口内接收到19个请求时,即使所有19个请求都失败,保险丝也不会打开而变成开路。默认值为20。
海斯特里克斯。命令。默认。断路器。ErrorThresholdPercentage:此配置用于设置错误率阈值。在滑动窗口时间内,当错误率超过该值时,保险丝将进入断开状态,所有请求将触发故障回退。错误率阈值百分比的默认值是50。
海斯特里克斯。命令。默认。断路器。Sleepwindowin毫秒:该配置用于设置保险丝的睡眠窗口。具体来说,它指的是确定在保险丝打开后尝试请求需要多长时间。默认值为5 000毫秒,这意味着当保险丝打开时,所有请求将在5 000毫秒内被拒绝,然后保险丝将在5 000毫秒后进入半开状态。
海斯特里克斯。命令。默认。断路器。ForceOpen:如果配置为true,熔丝将被强制打开,所有请求将触发失败回退。默认值为false。
保险丝的状态转换与Hystrix滑动窗口的健康统计有关。接下来,本例中使用的与Hystrix健康统计相关的配置简要介绍如下:
海斯特里克斯。命令。默认。度量标准。滚动统计。Timeinmilliseconds:设置统计滑动窗口的持续时间,默认值为10 000毫秒。保险丝的开度将根据滑动窗口的统计值来计算。如果滑动窗口时间内的错误率超过阈值,保险丝将进入断开状态。滑动窗口将被进一步细分成时间段。滑动窗口的统计值等于窗口内所有时段的统计信息的累加,每个时段的统计信息包括成功、失败、超时和拒绝的请求数。
Hystrix.com mand . default . metrics . rolling stats . numbuckets:设置滑动窗口划分的时间段数,默认值为10。如果滑动窗口的持续时间是10 000毫秒,并且滑动窗口被分成10个时间段,则一个时间段的时间是1秒。numBuckets和timeInMilliseconds的设定值有一定的关系,必须符合timeInMilliseconds % number buckets = = 0的规则,否则会抛出异常。比如70 000%700==0是可以的,但是70000%600==400会抛出异常。
以上关于Hystrix fuse的配置选项使用了前缀hystrix.command.default这些默认配置项将对项目中的所有FeignRPC接口生效,除非单独配置一个Feign RPC接口。如果佯RPC调用需要特殊配置,配置项前缀具有以下格式:
hy strix . command . Class Name # Method Name让我们看一个特殊配置单个接口的例子,这样UserClient类

以Feign RPC interface /detail/v1为例。该接口的功能是从用户提供商服务获取用户信息。在配置之前,请看看UserClient接口的代码,如下所示:
包com . crazy maker . spring cloud . user . info . remote . client;...@ FeignClientPublic接口用户客户端{@请求映射重置详细信息long userId);}在demo-provider中,如果要专门配置UserClient.detail接口的RPC调用的fuse参数,不使用默认前缀hystrix.command.default,而使用前缀hystrix.command.feignclient #方法格式。具体配置项目如下:
Hystrix:...命令:用户客户端#详细信息:#格式:类名#方法名...断路器:#熔丝相关配置启用:true #是否使用熔丝,默认为true requestVolumeThreshold: 20 #至少有20个请求,只有当熔丝达到熔丝触发次数的阈值时,SleepWindowInMilliseconds:5000 #断开后允许一次尝试的休眠时间,默认配置为5秒error threshold percentage:50 #窗口时间内熔丝断开的误差比。默认配置为50 metrics:rolling percent:timeinmilliseconds:60000 #滑动窗口时间numBuckets: 600 #滑动窗口时间bucketSize: 200 #除了与fuse circuitBreaker和metrics滑动窗口相关的参数之外,还可以为特定的Feign RPC接口专门配置许多其他的Hystrix命令参数,配置时仍然使用“类名#方法名”的格式。对于初学者来说,很难理解滑动窗口的概念和配置。
本文内容是springcloud的介绍:Feign+Hystrix实现RPC调用保护。
下一篇文章会给你讲解SpringCloudRPC远程调用的核心原理;


