如何在业务逻辑当中优雅引入重试机制

为什么要引入重试机制

我们首先看看正常的业务系统交互流程,就像下面图中所示一样,我们自己开发的系统通过HTTP接口或者通过RPC去访问其他业务系统,其他系统在没出现任何问题的情况下会返回给我们需要的数据,状态为success。

图片

但大家在日常的开发工作当中应该碰到过不少这样的问题:自己应用因为业务需求需要调其他关联应用的接口或二方包,而其他应用的接口稳定性不敢过分恭维,老是出一些莫名奇妙的幺蛾子,比如由于接口暂时升级维护导致的短暂不可用,又或者网络抖动因素导致的单次接口请求失败。

图片

诸如此类的麻烦问题会因为业务强依赖致使我们自己维护的系统也跟着陷入一种不稳定的状态(当然这个强依赖是没有办法的事情,毕竟业务之间需要解耦独立开发维护)。

所以也就是说重试的使用场景大多是因为我们的系统依赖了其他的业务,或者是由于我们自己的业务需要通过网络请求去获取数据这样的场景。既然一次请求结果的状态非常不可控、不稳定,那么一个非常自然的想法就是多试几次,就能很好的避开网络抖动或其他关联应用暂时down机维护带来的系统不可用问题。

图片

当然,这里也有几个引入重试机制以后需要考虑的问题。

  • 我们应该重试几次?
  • 每次重试的间隔设置为多少合适?
  • 如果所有重试机会都用完了还是不成功怎么办?下面我们就这几个问题展开分析一下。

重试几次合适

通常来说我们单次重试所面临的情况就如上面我们分析的一样,有很大的不可确定性,那到底多少次是比较合理的次数呢?这个就要“具体业务具体分析”了,但一般来说3次重试就差不多可以满足大多数业务需求了,当然,这是需要结合后面要说的重试间隔一起讨论的。为什么说3次就基本够了呢,因为如果被请求系统实在处于长时间不可用状态。我们重试多次是没有什么意义的。

重试间隔设置为多少合适

如果重试间隔设置得太小,可能被调用系统还没来得及恢复过来我们就又发起调用,得到的结果肯定还是Fail;如果设置的太大,我们自己的系统就会牺牲掉不少数据时效性。所以,重试间隔也要根据被调用的系统平均恢复时间去正确估量,通常而言这个平均恢复时间很难统计到,所以一般的经验值是3至5分钟。

重试机会用完以后依旧Fail怎么办

这种情况也是需要认真考虑的,因为不排除被调用系统真的起不来的情况,这时候就需要采取一定的补偿措施了。首先要做的就是在我们自己的系统增加错误报警机制,这样我们才能即时感知到应用发生了不可自恢复的调用异常。其次就是在我们的代码逻辑中加入触发手动重试的开关,这样在发生异常情况以后我们就可以方便的修改触发开关然后手动重试。

在这里还有一个非常重要的问题需要考虑,那就是接口调用的幂等性问题,如果接口不是幂等的,那我们手动重试的时候就很容易发生数据错乱相关的问题。

Spring重试工具包

Spring为我们提供了原生的重试类库,我们可以方便地引入到工程当中,利用它提供的重试注解,没有太多的业务逻辑侵入性。如下,我们先引入依赖包。

<dependency>    <groupId>org.springframework.retry</groupId>    <artifactId>spring-retry</artifactId></dependency><dependency>    <groupId>org.aspectj</groupId>    <artifactId>aspectjweaver</artifactId></dependency>

然后在启动类或者配置类上添加@EnableRetry注解,并在需要重试的方法上添加@Retryable注解。

@Retryablepublic String hello(){    long times = helloTimes.incrementAndGet();    log.info("hello times:{}", times);    if (times % 4 != 0){        log.warn("发生异常,time:{}", LocalTime.now() );        thrownew HelloRetryException("发生Hello异常");    }    return"hello " + nameService.getName();}

更详细的用法和属性大家参阅Spring Retry的文档就好了,解释的非常清楚。这里要说的一点是,Spring的重试机制也还是存在一定的不足,只支持对异常进行捕获,而无法对返回值进行校验。

Guava Retry

相比Spring Retry,Guava Retry具有更强的灵活性,可以根据返回值校验来判断是否需要进行重试。我们依然需要先引入它的依赖包。

<dependency>    <groupId>com.github.rholder</groupId>    <artifactId>guava-retrying</artifactId>    <version>2.0.0</version></dependency>

在用的时候也很简单,先创建一个Retryer实例,然后使用这个实例对需要重试的方法进行调用,可以通过很多方法来设置重试机制,比如使用retryIfException来对所有异常进行重试,使用retryIfExceptionOfType方法来设置对指定异常进行重试,使用retryIfResult来对不符合预期的返回结果进行重试,使用retryIfRuntimeException方法来对所有RuntimeException进行重试。

@Testpublic void guavaRetry() {    Retryer<String> retryer = RetryerBuilder.<String>newBuilder()        .retryIfExceptionOfType(HelloRetryException.class)        .retryIfResult(StringUtils::isEmpty)        .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS))        .withStopStrategy(StopStrategies.stopAfterAttempt(3))        .build();    try {        retryer.call(() -> helloService.hello());    } catch (Exception e){        e.printStackTrace();    }}

相比Spring,Guava Retry提供了几个核心特性。

  • 可以设置任务单次执行的时间限制,如果超时则抛出异常。
  • 可以设置重试监听器,用来执行额外的处理工作。
  • 可以设置任务阻塞策略,即可以设置当前重试完成,下次重试开始前的这段时间做什么事情。
  • 可以通过停止重试策略和等待策略结合使用来设置更加灵活的策略,比如指数等待时长并最多10次调用,随机等待时长并永不停止等等。

小结

上面针对我们为什么要引入重试机制,引入重试机制需要思考的几个核心问题,以及为重试机制提供良好支持的工具类库都分别作了简单介绍,相信大家在今后的开发工作中遇到类似场景也能驾轻就熟地使用思考了。

我们日常工作中有很多“大”的业务场景需要我们集中精力去突破、去思考,但也有很多类似的“小”点需要我们去打穿、吃透,大家共勉。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342