集群服务调用失败后,服务框架需要能够在底层自动容错。
1 集群容错场景
在分布式服务框架中,业务消费者不需要了解服务提供者的具体未知,它发起的服务调用请求也不包含服务提供者具体地址信息。因此,某个服务提供者是否可用对消费者而言无关紧要,最终的服务调用成功才是最重要的。
经过服务路由之后,选定某个服务提供者进行远程服务调用,但是服务调用可能会出错,下面进行故障场景进行分析。
1.1 通信链路故障
这里的链路指的是消费者和服务提供者之间的链路(通常为长连接),可能导致链路中断的原因有:
- 通信过程中,对方突然宕机导致链路中断。
- 通信过程中,对方因为解码失败等原因 Rest 掉连接,导致链路中断。
- 通信过程中,消费者 write SocketChannel 发生 IOException 导致链路中断。
- 通信过程中,消费者 read SocketChannel 发生 IOException 导致链路中断。
- 通信双方因为心跳超时,主动 close SocketChannel 导致链路中断。
- 通信过程中,网络发生闪断故障。
- 通信过程中,交换机异常导致链路中断。
- 通信过程中,消费者或者服务提供者因为长时间 Full GC 导致链路中断。
1.2 服务端超时
- 服务端的 I/O 线程没有及时从网络中读取客户端请求消息,导致该问题的原因通过是 I/O 线程被意外阻塞或者执行长周期操作。
- 服务端业务处理缓慢,或者被长时间阻塞,例如查询数据库,由于没有索引导致全表查询,耗时较长。
- 服务端发生长时间 Full GC,导致所有业务线程暂停运行,无法及时返回应答给客户端。
1.3 服务端调用失败
- 服务端解码失败,返回消息解码失败异常。
- 服务端发生动态流控,返回流控异常。
- 服务端消息队列积压率超过最大阈值,返回系统阻塞异常。
- 访问权限校验失败,返回权限相关异常。
- 违反 SLA 策略,返回 SLA 控制相关异常。
- 其它系统异常。
服务调用异常不包括业务层面的处理异常,例如数据库操作异常、用户记录不存在等异常。
2 容错策略
服务不同,容错策略往往也不同,下面是集群容错和服务路由的关系:
消费者根据配置的路由策略选择某个目标地址之后,发起远程服务调用,发起远程服务调用,在此期间如果发生了远程服务调用异常,则需要服务框架进行集群容错,重新进行选路和调用。集群容错是系统自动执行的,上层用户并不需要关心底层的服务调用过程。
2.1 失败自动切换(FailOver)
服务调用失败自动切换策略指的是当发生 RPC调用异常时,重新选路,查找下一个可用的服务提供者。
服务发布的时候,可以指定服务的集群容错策略。消费者可以覆盖服务提供者的通用配置,实现个性化的容错策略。
FailOver 策略的设计思路如下:消费者路由操作完成之后,获得目标地址,调用通信框架的消息发送接口发送请求,监听服务端应答。如果返回的结果是 RPC调用异常(超时、流控、解码失败等系统异常),根据消费者集群容错的策略进行容错路由,如果是 FailOver,则重新返回到路由 Handler 的入口,从路由节点继续执行。选录完成之后,对目标地址进行对比,防止重新路由到故障服务节点,过滤掉上次的故障服务提供者之后,调用通信框架的消息发送接口发送请求消息。
- 读操作,因为通常它是幂等的。
- 幂等性服务,保证调用1 次和 N 次效果相同。
注意:失败重试会增加服务调用时延,因此框架必须对失败重试的最大次数做限制,通常默认为 3,防止无限制重试导致服务调用时延不可控。
2.2 失败通知(Failback)
适用于非幂等性的服务调用,通过对失败错误码等异常信息的判断,决定后续的执行策略。
Failback 的设计方案如下:服务框架获取到服务提供者返回的 RPC 异常响应之后,根据策略进行容错。将 RPC异常通知给消费者,由消费者捕获异常进行后续处理。
2.3 失败缓存(Failcache)
Failcache 策略是失败自动恢复的一种,应用场景如下:
- 服务有状态路由,必须定点发送到指定的服务提供者。当发生链路中断、流控等服务暂时不可用时,服务框架将消息临时缓存起来,等待周期T,重新发送,直到服务提供者能够正常处理该消息。
- 对时延要求不敏感的服务。系统服务调用失败,通常是链路暂时不可用、服务流控、GC 挂住服务提供者进程等,这种失败不是永久性的失败,它的恢复是可预期的。如果消费者对服务调用时延不敏感,可以考虑采用自动恢复模式,即先缓存,再等待,最后重试。
- 通知类服务。例如通知粉丝积分增长、记录接口日志等,对服务调用的实时性要求不高,可以容忍自动恢复带来的时延增加。
为了保证可靠性,Failcache 策略在设计的时候需要考虑如下几个要素:
- 缓存时间、缓存对象上限数等需要做出限制,防止内存溢出。
- 缓存淘汰算法的选择,是否支持用户配置。
- 定时重试的周期T、重试的最大次数等需要做出限制并支持用户指定。
重试达到最大上限仍失败,需要丢弃消息,记录异常日志。
2.4 快速失败(Failfast)
在业务高峰期,对于一些非核心的服务,希望只调用一次,失败也不再重试,为重要的核心服务解约宝贵的运行资源。此时,快速失败是不错的选择。
原理在于,获取服务调用异常之后,直接忽略异常,记录异常日志。
2.5 容错策略扩展
无论默认支持多少种容错策略,在业务实际使用过程中都需要支持用户自定义扩展容错策略。
3 个人总结
集群容错虽然功能简单,设计也并不复杂(不复杂???),但是该特性却非常重要。