RPC面临高并发场景,提供服务的每个节点就都可能由于访问量过大而异常,如业务处理耗时过长、CPU飘高、频繁Full GC以及服务进程直接宕机。要保证服务稳定性和高可用性,就要业务自我保护。
使用RPC时,业务如何自我保护?
最常见的限流。
RPC调用包括服务端和调用端,调用端向服务端发起调用。
服务端与调用端分别是如何进行自我保护的。
1 服务端自保
要发布一个RPC服务,作为服务端接收调用端发过来的请求,这时服务端某节点负载压力过高,如何保护这节点?
那就不让它再接收太多请求,等接收和处理的请求数量下来后,该节点负载自然下来。即限流,RPC调用中,服务端自保策略就是限流。
- RPC框架中集成限流功能,让使用方自己配置限流阈值
- 在服务端添加限流逻辑,当调用端发请求过来时,服务端在执行业务逻辑之前先执行限流逻辑,若发现访问量过大且超限流阈值,就让服务端直接抛给调用端限流异常,否则就执行正常业务
服务端限流逻辑实现
最简单的计数器,还有可平滑限流的滑动窗口、漏斗算法及令牌桶算法等,令牌桶算法最常用。
假如我发布了一个服务,提供给多个应用的调用方去调用,这时有一个应用的调用方发过来的请求流量比其它应用大很多,就对该应用下的调用端发来的请求流量限流。因此,限流要考虑应用级别维度,甚至IP维度:
- 不仅可让我们对一个应用下的调用端发送过来的请求限流
- 还可对一个IP发送过来的请求限流
使用方如何配置应用维度及IP限流?RPC强在治理功能,而治理功能大都需要依赖一个注册中心或配置中心,可通过RPC治理的管理端进行配置,再通过注册中心或配置中心将限流阈值的配置下发到服务提供方的每个节点,实现动态配置。
在服务端实现限流,配置的限流阈值是作用在每个服务节点。如我配置的阈值是每秒1000次请求,指一台机器每秒处理1000次请求;若服务集群10个节点,那我提供的服务限流阈值在最理想的情况下就是每秒10000次。
案例
我提供了一个服务,而这服务业务逻辑依赖MySQL,就要对其保护。假如MySQL处理业务逻辑中,SQL能力10000次/s,那提供的服务处理的访问量就不能超过10000次/s,而我们的服务有10个节点,配置限流阈值就是1000次/s。那如果之后因为某种需求我们对这个服务扩容了呢?扩容到20个节点,就要把限流阈值调整到500次/s?这样操作每次都要自己去计算,重新配置,太麻烦!
配置优化
让RPC框架自己计算,当注册中心或配置中心将限流阈值配置下发时,将总服务节点数也下发给服务节点,之后由服务节点自己计算限流阈值。
但还有问题,实际一个服务节点所接收到的访问量并非绝对均匀,如20个节点,而每个节点限流500,其中有的节点访问量已达阈值,但有的节点可能在这一s内访问量450,这时调用端发送过来的总调用量还没达到10000次,但可能也会被限流,这是不是就不精确了?有比较精确的限流方式吗?
这种限流不精确,是因为限流逻辑是服务集群下的每个节点独立执行,是一种单机限流,而且每个服务节点所接收到的流量并不是绝对均匀。
我们可以提供一个专门的限流服务,让每个节点都依赖一个限流服务,当请求流量打过来时,服务节点触发限流逻辑,调用这个限流服务来判断是否到达了限流阈值。我们甚至可以将限流逻辑放在调用端,调用端在发出请求时先触发限流逻辑,调用限流服务,如果请求量已经到达了限流阈值,请求都不需要发出去,直接返回给动态代理一个限流异常即可。
这种限流方式可以让整个服务集群的限流变得更加精确,但也由于依赖了一个限流服务,它在性能和耗时上与单机的限流方式相比是有很大劣势的。至于要选择哪种限流方式,就要结合具体的应用场景进行选择了。
调用端的自我保护
刚才我讲解了服务端如何进行自我保护,最简单有效的方式就是限流。那么调用端呢?调用端是否需要自我保护呢?
举个例子,假如我要发布一个服务B,而服务B又依赖服务C,当一个服务A来调用服务B时,服务B的业务逻辑调用服务C,而这时服务C响应超时了,由于服务B依赖服务C,C超时直接导致B的业务逻辑一直等待,而这个时候服务A在频繁地调用服务B,服务B就可能会因为堆积大量的请求而导致服务宕机。
由此可见,服务B调用服务C,服务C执行业务逻辑出现异常时,会影响到服务B,甚至可能会引起服务B宕机。这还只是A->B->C的情况,试想一下A->B->C->D->……呢?在整个调用链中,只要中间有一个服务出现问题,都可能会引起上游的所有服务出现一系列的问题,甚至会引起整个调用链的服务都宕机,这是非常恐怖的。
所以说,在一个服务作为调用端调用另外一个服务时,为了防止被调用的服务出现问题而影响到作为调用端的这个服务,这个服务也需要进行自我保护。而最有效的自我保护方式就是熔断。
我们可以先了解下熔断机制。
熔断器的工作机制主要是关闭、打开和半打开这三个状态之间的切换。在正常情况下,熔断器是关闭的;当调用端调用下游服务出现异常时,熔断器会收集异常指标信息进行计算,当达到熔断条件时熔断器打开,这时调用端再发起请求是会直接被熔断器拦截,并快速地执行失败逻辑;当熔断器打开一段时间后,会转为半打开状态,这时熔断器允许调用端发送一个请求给服务端,如果这次请求能够正常地得到服务端的响应,则将状态置为关闭状态,否则设置为打开。
了解完熔断机制,你就会发现,在业务逻辑中加入熔断器其实是不够优雅的。那么在RPC框架中,我们该如何整合熔断器呢?
熔断机制主要是保护调用端,调用端在发出请求的时候会先经过熔断器。我们可以回想下RPC的调用流程:
你看图的话,有没有想到在哪个步骤整合熔断器会比较合适呢?
我的建议是动态代理,因为在RPC调用的流程中,动态代理是RPC调用的第一个关口。在发出请求时先经过熔断器,如果状态是闭合则正常发出请求,如果状态是打开则执行熔断器的失败策略。
总结
今天我们主要讲解了RPC框架是如何实现业务的自我保护。
服务端主要是通过限流来进行自我保护,我们在实现限流时要考虑到应用和IP级别,方便我们在服务治理的时候,对部分访问量特别大的应用进行合理的限流;服务端的限流阈值配置都是作用于单机的,而在有些场景下,例如对整个服务设置限流阈值,服务进行扩容时,限流的配置并不方便,我们可以在注册中心或配置中心下发限流阈值配置的时候,将总服务节点数也下发给服务节点,让RPC框架自己去计算限流阈值;我们还可以让RPC框架的限流模块依赖一个专门的限流服务,对服务设置限流阈值进行精准地控制,但是这种方式依赖了限流服务,相比单机的限流方式,在性能和耗时上有劣势。
调用端可以通过熔断机制进行自我保护,防止调用下游服务出现异常,或者耗时过长影响调用端的业务逻辑,RPC框架可以在动态代理的逻辑中去整合熔断器,实现RPC框架的熔断功能。
熔断是调用方为了避免在调用过程中,服务提供方出现问题的时候,自身资源被耗尽的一种保护行为;而限流则是服务提供方为防止自己被突发流量打垮的一种保护行为。虽然这两种手段作用的对象不同,但出发点都是为了实现自我保护,所以一旦发生这种行为,业务都是有损的。
FAQ
在使用RPC的过程中业务要实现自我保护,有其它解决方案吗?
服务保护一般就是限流、熔断、降级。 限流的落地方式有:Guava RateLimiter、lua+Redis、Sentinel等; 熔断:Hystrix、Resilience4j; 降级:服务降级,就是对不怎么重要的服务进行低优先级的处理。说白了,就是尽可能的把系统资源让给优先级高的服务。资源有限,而请求是无限的。
对,取舍很重要。
服务的自我保护: 1:压测——进行性能优化及容量规划 2:限流——防止服务端被流量高峰压垮 3:降级——优先保证核心服务高可用 4:熔断——防止当前服务被下游慢服务拖垮 5:自动扩缩容——可以扛住流量洪峰,需要资源冗余 原理OK,具体咋弄呢?哈哈😄,解刨一下某个知名RPC
熔断策略如何能避免频繁重复在三个状态切换呢?除了增加机器扩容,以及业务和性能优化,熔断策略还能做些什么吗?
熔断可以避免进一步恶化,比如某个节点性能不行,可以通过熔断的手段,避免调用发整体TP性能下降
若设置一个专门的限流服务,那么压力都会集中到这个服务,对这个服务的性能要求会提高,当业务再变大的时候,这限流服务会成为一个瓶颈,要换解决方案了?
不一定要采用集中式限流。
除了文中提到的服务熔断、降级、限流保护手段,还可以使用异步RPC 提升性能吧
异步RPC可以提升吞吐量。
这几节的解决方案都有通过注册中心下发,注册中心比如consul怎么下发数据给各个节点啊 没有找到相关的api,是要自己做二次开发吗?
利用consul推送能力。
调用方限流的时候,应该是发起调用时请求限流器去判断阈值,调用完成后,还需要通知一下限流器吧。这样是不是才能达到接近饱和的性能吧。
是的。