1. 什么是重试机制
重试模式,可以拆成重试+模式。
重试: 在工作流执行过程中,当执行到一个活动实例出现异常时,可以重新执行这个活动实例,直到成功,或者规定重试次数的最大值,当重试次数达到最大值时失败。
模式: 事物的标准样式,理论和实践之间的中介环节,具有一般性、简单性、重复性、结构性、稳定性、可操作性的特征。
重试模式: 透明地重试某些涉及与外部资源通信的失败的操作,尤其是通过网络,从而将调用代码与重试实现细节隔离开来的一种标准的解决方案。
2.为什么要用重试模式
为了解决什么样的问题?
客户端A连接到服务B的时候,服务B突发临时故障,并且该故障在短时间后将会恢复了正常。那么此刻客户端A要怎么做?
重试模式可以透明的有限的重试请求,提高客户端的请求的成功率和稳定性。
不用重试模式会怎么样?
如果失败不重试,那么此次操作就会返回失败,只能等待下次客户端重新发起请求。相对于有失败重试的机制的方案稳定性略差一些。
3.怎么用重试模式
什么场景适用
错误预计只会短时存在,并且通过后续尝试重复执行之前失败的请求可能会成功。
- 组件和服务瞬间断开网络连接
- 服务暂时不可用
- 当服务繁忙时出现超时
什么场景不适用
- 当错误可能会持续很长时间,因为此模式可能会影响应用程序的响应能力,
- 不是由于短暂性错误而导致的故障,重新执行的情况下也不可能成功,例如业务异常:登陆的时候用户名密码错误,不能重试
重试模式在Java开源项目的使用
- RocketMQ 消息发送重试
- RocketQq 消息消费重试策略:
消息重发延时级别为: 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
如果消费失败,那么1S后再次消费,如果失败,那么5S后,再次消费,……直至2H后如果消费还失败,那么该条消息就会终止发送给消费者了!
在实际中也许我们并不需要这么多重试,比如重试3次,可以通过message.getResumeTimes来判断重试次数,超过一定次数可以把消息存储起来并采用另一种方式处理。
使用不当有什么后果
- 大量客户端过多的重试请求而导致某个服务持续过载,甚至耗尽服务的资源
如何正确使用重试模式
流程图
关键步骤
如何判断是否临时故障
- 拦截故障,根据故障相关属性区分是否是临时故障还是永久故障,或者是无需重试的业务本身的异常
是否是关键业务必须重试
- 因为对于某些非关键操作,最好是快速失败而不是重试多次反而影响应用程序的吞吐量
判断操作是否是幂等的
- 例如,某个服务已经收到并成功处理该请求,但无法发送响应。此时如果重试操作不是幂等将会导致意外的副作用。
- 最大重试次数设置多少
- 是延迟重试还是立即重试
如果延迟重试,重试尝试之间的间隔时间设置多少
- 在尝试之间采用递增方式或指数方式增大延迟时间的方式
其他
目标服务如果已经处于繁忙的状态,避免采用延迟时间间隔最小的,且尝试次数较多的积极重试策略,这会进一步降低目标服务的性能
- 如何判断服务出于繁忙的状态?
- 如果某个请求在进行大量的重试后仍然失败,则此刻考虑使用断路器模式
- 请确保针对各种故障状况充分测试重试代码,重试不能严重影响应用程序的性能或可靠性、不能导致服务和资源过载,不能导致争用状况或瓶颈
- 只有充分了解失败操作的完整上下文后才能实现重试逻辑
记录并调查服务或者资源可能的错误
- 记录导致重试的所有连接故障,以便可以查明应用程序、服务或资源的底层问题。
- 提前调查服务或资源最有可能发生的错误,针对该错误制定相应的应对策略,是重试还是快速失败
有没有用来实现重试模式的开源框架
4. 延伸思考
有没有功能类似的模式?
有没有功能相反的模式?
- 断路器模式
- Fail fast 快速失效模式