前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >聊聊重试:Guava Retrying

聊聊重试:Guava Retrying

作者头像
用户8554325
发布2023-03-10 20:02:31
1.1K0
发布2023-03-10 20:02:31
举报
文章被收录于专栏:热度技术

聊聊重试:Guava Retrying

重试的一些知识点及应用场景

最近在做某小程序电商项目支付功能时,微信支付某个接口可能偶尔抽风,需要重试,这种还不能离线重试(XXL-JOB),只能在发送异常的时刻,进行一定次数的重试,这种情况,只能考虑在内存做重试。看图,借用知乎(西召)的图展示如何重试

这时候,搜刮自己脑瓜+搜索大法,可以总结出一些重试的知识点及重试的库(Spring-Retry、Guava Retring)。所以,借着此等机会,就认真好好梳理一下这块的知识点,以便自己或需要的开发人员做参考。

如果是你碰到这种情况,会如何处理呢?当然,还用问,直接啪啪啪写个工具类,比如以下的版本(v1):

是不是很快,一个for循环+重试次数,再做个异常处理,就能完成该需求。然后,自己思考一下,总觉得有哪些地方不合理呢?对着屏幕,苦思冥想,突然一个妹子路过,灵光一闪,对啊,要是请求的时候频率过快,3次的重试也很快就消耗没了,这样的代码重复执行也没啥好效果,然后脑海想起了Thread.sleep方法,就继续噼噼啪啪按着键盘,改成以下的版本(v2):

这次就完美了,考虑到请求频率。

随着业务的扩大,发现其他的场景也需要这种,这个时候,很多地方都是这种结构的代码了,于是乎,你就要思考,是不是要重构以下,抽取一个通用的工具类出来,有了思路,想到了jdk的并发库的Callable接口,跟着就埋头干起来...经过一段时间之后....,完美写出V3版本:

看着这个版本,是不是可以满足大部分的需求了,将需要重试的方法,封装到Callable接口里,让其在try/catch中执行,如果有结果返回直接返回,异常之类的情况则重试,并且能根据业务的需求,设置睡眠时间。

当你完成以上杰作的时候,是不是感觉美滋滋的,达到了“人生巅峰”,不断傻笑欣赏着自己的杰作,然而直到你看到了神器“Guava Retrying”的登场,顿时泄气了,觉得自己以前写的什么玩意。除了实用,啥高大上的概念都没用上哈。然后你不服,只能埋头去研究guava retrying的实现。

在看GuavaRetrying的源码前,也得先恶补一下重试的一些知识点。

try-catch-redo简单重试模式

在包装正常上传逻辑基础上,通过判断返回结果或监听异常决定是否重试,同时为了解决立即重试的无效执行(假设异常是有外部执行不稳定导致的:网络抖动),休眠一定延迟时间后重新执行功能逻辑。

try-catch-redo-retry strategy策略重试模式

上述方案还是有可能重试无效,解决这个问题尝试增加重试次数retrycount以及重试间隔周期interval,达到增加重试有效的可能性。

方案一和方案二存在一个问题:正常逻辑和重试逻辑强耦合,重试逻辑非常依赖正常逻辑的执行结果,对正常逻辑预期结果被动重试触发,对于重试根源往往由于逻辑复杂被淹没,可能导致后续运维对于重试逻辑要解决什么问题产生不一致理解。重试正确性难保证而且不利于运维,原因是重试设计依赖正常逻辑异常或重试根源的臆测。

应用命令设计模式解耦正常和重试逻辑

就是利用jdk的callable之类的接口

一个完备的重试实现,要很好地解决如下问题:

l什么条件下重试

l什么条件下停止

l如何停止重试

l停止重试等待多久

l如何等待

l请求时间限制

l如何结束

l如何监听整个重试过程

并且,为了更好地封装性,重试的实现一般分为两步:

l使用工厂模式构造重试器

l执行重试方法并得到结果

一个完整的重试流程可以简单示意为:

好,带着这些问题,我们来开始我们的guava Retrying 库的源码分析之路

Guava Retrying 库的介绍

Guava Retrying是一个灵活方便的重试组件,包含了多种的重试策略,而且扩展起来非常容易。使用Guava-retrying你可以自定义来执行重试,同时也可以监控每次重试的结果和行为,最重要的基于 Guava 风格的重试方式真的很方便。

作者原话:

This is a small extension to Google’s Guava library to allow for the creation of configurable retrying strategies for an arbitrary function call, such as something that talks to a remote service with flaky uptime.

Guava Retring的使用方式示例

引入Guava Retring库(maven)

定义实现Callable接口的方法,让Guava的Retryer类能够调用

定义Retry对象及设置相关策略

如此,通过以上三个简单步骤就能使用Guava Retrying实现重试。

那么接下来介绍一下API的简要说明

主要接口介绍

lAttempt:一次执行任务;

¡AttemptTimeLimiter:单次任务执行时间限制(如果单次任务执行超时,则终止执行当前任务);

¡ExceptionAttempt:执行异常

lBlockStrategies:BlockStrategy的工厂类,任务阻塞策略确定重试器应如何在两次重试之间阻塞的策略(通俗的讲就是当前任务执行完,下次任务还没开始这段时间做什么),默认策略为:BlockStrategies.THREAD_SLEEP_STRATEGY 也就是调用 Thread.sleep(sleepTime),但是如果需要的话,实现可能会更加复杂。

lRetryException:重试异常;

lRetryListener:自定义重试监听器,可以用于异步记录错误日志;

lStopStrategy:停止重试策略,提供三种:

¡StopAfterDelayStrategy :设定一个最长允许的执行时间;比如设定最长执行10s,无论任务执行次数,只要重试的时候超出了最长时间,则任务终止,并返回重试异常RetryException;

¡NeverStopStrategy :不停止,用于需要一直轮训知道返回期望结果的情况;

¡StopAfterAttemptStrategy :设定最大重试次数,如果超出最大重试次数则停止重试,并返回重试异常;

lWaitStrategy:对应工厂类WaitStrategies,等待时长策略(控制时间间隔),返回结果为下次执行时长:

¡FixedWaitStrategy:固定等待时长策略;

¡RandomWaitStrategy:随机等待时长策略(可以提供一个最小和最大时长,等待时长为其区间随机值)

¡IncrementingWaitStrategy:递增等待时长策略(提供一个初始值和步长,等待时间随重试次数增加而增加)

¡ExponentialWaitStrategy:指数等待时长策略;

¡FibonacciWaitStrategy :Fibonacci 等待时长策略;

¡ExceptionWaitStrategy :异常时长等待策略;

¡CompositeWaitStrategy :复合时长等待策略;

Guava Retring 项目源码结构

(源码类,从这里基本可以看到它的实现非常简洁)

我们在Ideal利用【Diagram】工具,可以导出比较详细的源码依赖图,如下(可能比较大,需要下载观看)

下面我会根据该图中展示的关系,逐一解读。

你可以看到这个库的代码层次划分清晰,合理实用工厂、建造器和策略等设计模式,把重试的方方面面安排的明明白白。

Guava Retrying 源码:RetryerBuilder

重试器构造类,看如下代码截图

Guava Retrying 源码:Retryer

重试器,担当所有任务执行任务重试策略任务终止策略等流程执行。看改类一部分代码截图:

该类(Guava Retrying库)的核心方法,如下:

Guava Retrying 源码:WaitStrategies与WaitStrategy

WaitStrategy,等待策略接口,里面只要一个方法:

long computeSleepTime(Attempt failedAttempt); 返回下一次重试所需要等待的时间。传入的是一次执行失败的任务。

WaitStrategies,策略生成接口,依赖WaitStrategy,根据实际需求,生成所需要的等待策略

我们可以随便看一个具体策略实现,如下截图

Guava Retrying 源码:StopStrategy与AttemptTimeLimiters

StopStrategy:停止重试策略,StopStrategies是停止策略生成工厂。

StopStrategies:停止重试策略工厂类,里面包含了StopStrategy实现内部类;

AttemptTimeLimiters:将任何一个执行任务尝试包装在时间限制内的规则,如果超过该时间限制,则可能会被中断。

后记

Guava Retrying是基于内存、Callable、Thread.sleep等纯JDK的综合应用库,但实际的项目开发中,内存中重试往往也只是一种场景,更多需要离线、非实时这种重试,那么如果基于Guava Retrying你能有多少脑洞来做自己的重试机制,比如,结合Redis、开源的分布式定时调度框架(xxljob)、RabbitMQ队列服务(延迟重试)等等,想象一下,是不是突破了单机的限制。

感谢以下文章:

https://my.oschina.net/u/4278661/blog/4163080

https://juejin.im/post/6844903785031008263

https://juejin.im/post/6844903617183350798

总结

读代码是一种学习编码技巧和设计技巧的捷径,但读过了再写出属于自己对其中的理解就是一种升华。

通过这一次对Guava Retrying库的学习,可以感受到为了达到优雅,达到各种场景的使用所付出的努力,当然,在具体项目编码中,你可以简单使用一个工具类来做到类似的工作,但这不应该是一个库,想成为一个各种场景下都能无缝使用的库应该这样做的。所以需要运用库作者高超的编码技巧、深厚的OOP理论基础和对设计模式的灵活使用。

由于技术的发展,都说Java这一行容易“混”起来了,但“混”起来容易,有追求的开发应该要时刻要升华自己,多向前辈学习,不单单停留在CRUD的围城里。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-11-06,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 聊聊电商业务与技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 重试的一些知识点及应用场景
  • Guava Retrying 库的介绍
    • Guava Retring的使用方式示例
      • 主要接口介绍
        • Guava Retring 项目源码结构
          • Guava Retrying 源码:RetryerBuilder
            • Guava Retrying 源码:Retryer
              • Guava Retrying 源码:WaitStrategies与WaitStrategy
                • Guava Retrying 源码:StopStrategy与AttemptTimeLimiters
                • 后记
                • 总结
                相关产品与服务
                云数据库 Redis
                腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档