阅读 431

干货!了解下这个重试机制

关于本文

今天我们来了解下重试这个东西,我们在日常工作过程中应该接触很多,比如对接第三方,很难保证每次调用都能正常响应,我们都知道外部服务对于调用者来说一般都是不可靠的,尤其是在网络环境比较差的情况下,其实这个不单单是多方交互,本系统内也很正常,比如A服务调用B服务,B服务忽然荡掉,这很正常。但是我们要考虑到这正常的背后也会引发一系列未知的问题,比如我去第三方通知购买一个会员,结果用户付完钱后,通知失败。那么这种严重且具有可预见性的问题,我们要及时规避掉,通俗点讲还有一个词叫“补偿机制”

我认为重试,主要考虑又下两种场景:

第一:是发生错误,比如空指针、请求超时,http状态异常等情况

第二:业务错误,比如修改某个状态失败,设置某个数据没有达到理想的值。

框架天然支持

上面我们提到重试这种场景很普遍,那么肯定有些框架已经帮我们做好了,我们开箱即用。

  • Spring的retry

  • Guava-Retry

今天我们主要讲第二种Guava的重试机制,因为第二种比第一种要在某种场景上要灵活很多。具体为什么灵活,我们后面再写一篇对比。

Guava Retrying是一个灵活方便的重试组件,包含了多种的重试策略,而且扩展起来非常容易。 我们看看作者是怎么描述的:

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-retrying你可以自定义来执行重试同时也可以监控每次重试的结果和行为,最重要的基于 Guava 风格的重试方式真的很方便。

代码展示

1、pom文件引入

<dependency>       <groupId>com.github.rholder</groupId>       <artifactId>guava-retrying</artifactId>       <version>2.0.0</version> </dependency> 复制代码

2、定义Callable,用以让retry调用

   /**     * @desc 同步会员     * 我们用boolean变量判断     * @author yn     * @date 2021/10/09 15:17     */    private static Callable<Boolean> syncMember = new Callable<Boolean>() {            @Override        public Boolean call() throws Exception {                    //业务实现,调用第三方会员接口            String url = ConfigureUtil.get(Memberstants.MEMBER_URL);            String result = HttpMethod.post(url, new ArrayList<BasicNameValuePair>());            if(StringUtils.isEmpty(result)){               throw new RemoteException("同步会员失败,请重试");            }                        List<Member> member = JSON.parseArray(result, Member.class);            if(CollectionUtils.isNotEmpty(member)){                //同步成功                return true;            }            //同步失败            return false;        }    }; 复制代码

这里我多说一句,为什么上面要用callable,我们看下Retryer的底层。是因为这个call,去重试的方法,参数必须是callable类型的。

image.png

3、定义Retry对象并设置相关策略

Retryer<Boolean> retryer = RetryerBuilder                 .<Boolean>newBuilder()                 //抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。                 .retryIfException()                 //返回false也需要重试                 .retryIfResult(Predicates.equalTo(false))                 //重调策略                 .withWaitStrategy(WaitStrategies.fixedWait(10, TimeUnit.SECONDS))                 //尝试次数                 .withStopStrategy(StopStrategies.stopAfterAttempt(3))                 //监听机制                 .withRetryListener(new MyRetryListener<>())                 .build();           try {             retryer.call(updateReimAgentsCall);         } catch (ExecutionException e) {             logger.error("同步会员失败,需要发送提醒邮件");         } catch (RetryException e) {             logger.error("同步会员失败,需要发送提醒邮件");         } 复制代码

OK,简简单单的三步就搞定重试机制,并且可以应用于你的实战当中。

我们下面来了解下每一步的参数是什么意思。

  • RetryerBuilder是一个factory创建者,可以定制设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔,创建重试者Retryer实例。

  • RetryerBuilder的重试源支持Exception异常对象 和自定义断言对象,通过retryIfException 和retryIfResult设置,同时支持多个且能兼容。

  • retryIfException,抛出runtime异常、checked异常时都会重试,但是抛出error不会重试。

  • retryIfRuntimeException只会在抛runtime异常的时候才重试,checked异常和error都不重试。

  • retryIfExceptionOfType允许我们只在发生特定异常的时候才重试,比如NullPointerException和IllegalStateException 都属于runtime异常,

也可以自定义的error:

.retryIfExceptionOfType(Error.class)// 只在抛出error重试 复制代码

我们还可以指定Callable方法在特定的返回值进行重试,如

// 返回false重试  .retryIfResult(Predicates.equalTo(false))  //以_error结尾才重试  .retryIfResult(Predicates.containsPattern("_error$")) 复制代码

那么还有一个问题,当发生重试之后,假如我们需要做一些额外的处理动作,比如发个告警邮件啥的,那么可以使用RetryListener。每次重试之后,guava-retrying会自动回调我们注册的监听。可以注册多个RetryListener,会按照注册顺序依次调用。,漂亮,这些问题考虑的面面俱到:

 public class MyRetryListener<Boolean> implements RetryListener {          @Override       public <Boolean> void onRetry(Attempt<Boolean> attempt) {              // 第几次重试,(注意:第一次重试其实是第一次调用)           System.out.print("[retry]time=" + attempt.getAttemptNumber());              // 距离第一次重试的延迟           System.out.print(",delay=" + attempt.getDelaySinceFirstAttempt());              // 重试结果: 是异常终止, 还是正常返回           System.out.print(",hasException=" + attempt.hasException());           System.out.print(",hasResult=" + attempt.hasResult());              // 是什么原因导致异常           if (attempt.hasException()) {               System.out.print(",causeBy=" + attempt.getExceptionCause().toString());           } else {               // 正常返回时的结果               System.out.print(",result=" + attempt.getResult());           }              // bad practice: 增加了额外的异常处理代码           try {               Boolean result = attempt.get();               System.out.print(",rude get=" + result);           } catch (ExecutionException e) {               System.err.println("this attempt produce exception." + e.getCause().toString());           }              System.out.println();       }   }  复制代码

那么此监听事件就是在此方法中设置:

//装载 .withRetryListener(new MyRetryListener<>()) 复制代码

OK,今天的重试机制就讲到这里。

弦外之音

感谢你的阅读,如果你感觉学到了东西,麻烦您点赞,关注。也欢迎有问题我们下面评论交流

加油! 我们下期再见!

给大家分享几个我前面写的几篇骚操作

聊聊不一样的策略模式(值得收藏)

copy对象,这个操作有点骚!


作者:苏世_
链接:https://juejin.cn/post/7017044636753461279


文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐