15 | 熔断限流:业务如何实现自我保护?
为什么我们的服务需要自我保护?
RPC是解决分布式系统通信问题的一大利器,它会面临高并发的场景,这意味着我们提供服务的每个服务节点都有可能由于访问量过大而引起一系列的问题,例如业务处理耗时过长、CPU利用率过高、频繁Full GC以及服务进程直接宕机等,在生产环境中,我们要保证服务的稳定性和高可用性,这就需要业务进行自我保护,从而在高访问量、高并发的场景下,应用系统依然稳定,服务依然高可用。
使用RPC时,业务如何实现自我保护?
可以在服务提供方做限流操作,在服务调用方做熔断操作。
熔断是调用方为了避免在调用过程中,服务提供方出现问题的时候,自身资源被耗尽的一种保护行为,而限流则是服务提供方为防止自己被突发流量打垮的一种保护行为。
熔断和限流有什么区别?
熔断主要在服务调用方进行设置,限流主要在服务提供方进行设置。
服务端如何实现限流逻辑?
方式有很多,包括计数器、滑动窗口、漏斗算法和令牌桶算法等,其中令牌桶算法最常用。
我们发布服务后,提供给多个应用的调用方去调用,这时有一个应用的调用方发送过来的请求流量要比其他的应用大很多,这时我们就应该对这个应用下的调用端发送过来的请求流量进行限流。所以我们在限流时,需要考虑应用级别的维度,甚至是IP级别的维度,这样做不仅可以让我们对一个应用下的调用端发送过来的请求流量做限流,还可以对一个IP发送过来的请求流量做限流。
在服务端实现限流,配置的限流阈值是作用在每个服务节点上的。
我们还可以提供一个专门的限流服务,让每个节点都依赖一个限流服务,当请求流量打过来时,服务节点触发限流逻辑,调用这个限流服务来判断是否到达了限流阈值。我们甚至可以将限流逻辑放在调用端,调用端在发出请求时先触发限流逻辑,调用限流服务,如果请求量已经到达了限流阈值,请求都不需要发出去,直接返回给动态代理一个限流异常即可。
在一个服务作为调用端去调用另外一个服务时,为了防止被调用的服务出现问题而影响到座位调用端的这个服务,那么服务调用端也需要进行自我保护,而最有效的自我保护方式就是熔断。
熔断器的工作原理是怎样的?
熔断器的工作机制主要是关闭、打开和半打开这三个状态之间的切换。在正常情况下,熔断器是关闭的,当调用端调用下游服务出现异常时,熔断器会收集异常指标信息进行计算,当达到熔断条件时熔断器打开,这时调用端再发起请求是会直接被熔断器拦截,并快速地执行失败逻辑,当熔断器打开一段时间后,会转为半打开状态,这时熔断器允许调用端发送一个请求给服务端,如果这次请求能够正常地得到服务端的响应,则将状态置为关闭状态,否则设置为打开。
熔断器放在哪里比较合适?
建议放在调用方的动态代理模块,因为这时RPC调用的第一个关口,在发出请求时先经过熔断器,如果状态是闭合则正常发出请求,如果状态是打开则执行熔断器的失败策略。
16 | 业务分组:如何隔离流量?
关于为什么要对请求流量进行分组,作者举了一个非常合适的例子:
在没有汽车的年代,我们的道路很简单,就一条,行人、洋车都在上边走。随着汽车的普及以及猛增,我们的道路越来越宽,慢慢地有了高速、辅路、人行道等。很显然,交通网的建设和完善不仅提高了我们的出行效率,而且还更好的保障了我们行人的安全。
对服务进行分组,并没有一个明确的可衡量的标准,但是一般建议非核心应用不要跟核心应用分在同一个组,核心应用之间应该做好隔离,一个重要的原则就是保障核心应用不受影响。
通过分组的方式隔离调用方的流量,从而避免因为一个调用方出现流量激增而影响其他调用方的可用率。
服务分组隔离后,单个调用方在发RPC请求时可以选择的服务节点数相比之前是减少了,那么对于单个调用方来说,出错的概率就增加了。
要解决这个问题,我们还需要把配置的分组区分主次分组,只有在主分组上的节点都不可用的情况下才去选择次分组节点,只要主分组里面的节点恢复正常,我们就必须把流量都切换到主节点上。整个切换过程对于应用层完全透明,从而在一定程度上保证了服务调用方应用高可用。
我们不仅可以通过分组把服务提供方划分成不同规模的小集群,我们还可以利用分组完成一个接口多种实现的功能。正常情况下,为了方便我们自己管理服务,一般会建议每个接口完成的功能尽量保证唯一。但在有些特殊场景下,两个接口也会完全一样,只是具体实现上有所差别,那么我们就可以在服务提供方应用里面同时暴露两个相同接口,但是接口分组不一样。
在实际工作中,测试人员和开发人员的工作一般都是并行的,这就导致一个问题经常出现:开发人员在开发过程中可能需要启动自身的应用,而测试人员为了能验证功能,会在测试环境中部署同样的应用。如果开发人员和测试人员用的接口分组名刚好一样,在这种情况下,就可能会干扰其它正在联调的调用方进行功能验证,进而影响整体的工作效率。有什么解决办法?
解决这个问题,有几种思路:
- 不同团队使用不同的服务注册中心来管理服务节点。
- 可以采取类似于K8S中的命名空间的方法来隔离服务节点。
- 可以尝试流量染色,具体可以参考有关于流量染色的一些实践。