RPC实战与核心原理笔记<四>
1、异常重试
1.1、为什么需要异常重试
当组装好数据,发起一次RPC调用的时候,由于网络抖动,导致RPC调用失败,如果此时距离超时时间还有一段距离,那么可以在可用的时间范围内,再重试一次,提高业务的可用率。
1.2、重试的一些注意事项
其实比起重试,最好还是不要重试,因为重试的局限性还是比较大。一方面需要保证业务的幂等性,也就是不论多少次调用,最终的结果都是一样的,如查询操作,更新操作,删除操作。另外重试的话,可能导致重试风暴,例如:
A调用B的时候,B需要调用C,假设B调用C失败,那么B会重试调用C,假设重试3次,那么假如3次都失败了,A重试调用B,再重试B,而B又要重试调用C三次,这样如果链路过长,必然造成重试风暴,因此这也是重试的业务需要注意的一个问题。
1.3、如何重试
利用do-while循环,或者递归,当抛出指定的可以重试的异常的时候,进行重试。重试的话,最好每次重新设置超时时间,如果时间已经到了,就直接抛出异常。
在重试的时候,一定要注意异常,可以设置一个重试异常的基类,然后其它可重试的异常均继承于该类,这样就可以根据异常判断是否可以重试,另外也可以设立一个异常的白名单,判断是否在报名单中,进而决定是否重试。
2、优雅关闭
当服务要重启或者关闭的时候,这个时候要保证已经接收的请求要处理完毕,新的请求不再接收,避免业务受损。那么如何实现优雅关闭呢?
2.1、优雅关闭流程
服务下线:进行反注册操作,注册中心会对相关conusmer下发配置,然后从健康列表中摘除这个节点。
但是仔细观察,这个这个过程其实涉及两次RPC调用,如果任意一次失败了(超时,其它原因),都会使得这个节点下线失败,最终流量还是会打在这个节点上。如果此时这个provider已经下线也就是关闭了端口,必然造成业务受损。其实大部分RPC框架都是这种机制,另外这个下线 / 反注册操作也应该无需手动操作,因为如果每个服务 / 实例都需要去服务治理平台上操作一下,还是够累的。
因此像大多数中间件一样,需要注册钩子函数,在JVM推出的时候,会发送一个信号,触发这个构造函数,在这个函数中完成相关的资源释放工作。
类似这么一段代码,然后在destory中完成反注册,关闭线程池,再最后关闭端口等操作。
2.2、一些额外的手段
刚才的下线逻辑,涉及两次RPC调用,降低了成功率,因为两次RPC任意一次失败,就会最终失败,那么能不能将两次RPC降为一次RPC调用呢?其实想法是这样的,当服务要下线的时候,再最后一次的调用中,服务端在响应报文中添加一些额外的数据,告知consumer,我这边要下线了,请不要再请求我了,这样的话,就没有注册中心什么事了。
但是这样的话,consuemr于provider之间又那么一点点耦合。其实业务调用就是相互耦合在一起的,这点也没有多大关系。
需要注意的是像kill -9这类操作是触发不了jvm的钩子函数的,需要注意。
其实对于非常重要的业务,还是先在治理平台上手动点击下线,然后观察日志有没有流量进来,确认没有流量进来后,再进行重启或者关闭,这是最可靠的。