发送http请求出现异常,怎样设置重试? 例如出现异常后 间隔30秒 3分钟 3小时 重试三次.

九冷
  • 1
新手上路,请多包涵

问题描述

java编程相关: 发送http请求出现异常,怎样设置重试机制?

问题出现的环境背景及自己尝试过哪些方法

1.使用for循环,通过try catch , 但是这样会一直占用一个线程的资源.

你期待的结果是什么?实际看到的错误信息又是什么?

现在有个想法是 把请求放入消息队列中,然后设置延时等级. 如果第二次重试再次失败,就把延时等级提高 ~ 不知道是否可行,或者有没有更好的解决方法.

回复
阅读 1.6k
4 个回答

消息队列确实是ok的,毕竟题主的需求来看无碍乎就是一个循环的重试,重试间隔需要指定,所以用延迟消息队列绝对能够解决问题,消息队列中间件选择很多

BaLaLaLs提到的rabbitmq确实需要一个插件来做,也可以选择RocketMQ,天生支持延迟消息,当然用redis去模拟一种延迟效果也是可以的

不过以上方案的选型,感觉需要额外多部署一些东西,假如系统之前并没有这方面的中间件,那可能为了做一个重试方案,显得有点大材小用了,毕竟确实真要实现,简单的循环,加上一些if判断就搞定了(至于占用线程资源倒是小事了),只是说这样的代码比较难看,而且也无法复用

我记得在Java轮子百宝屋Spring中,有一个工程:Spring Batch,这里面有一个重试的注解@Retryable,去看了一下,这一套API现在已经独立出来,名叫:Spring-Retry

简单的@Retryable注解配置就可以让一个方法直接具有重试效果(即使我不解释注解属性的含义,也应该能简单看懂重试规则)
image.png

执行该方法的效果
image.png

可以看到很简单就配置好了一套按照倍数增长间隔时间的重试方案

当然可以玩出很多效果,题主可以自己探索,但是为了解决题主的问题,我还是提前玩了一下,先说结论:
我自己反正试过已经有的API还不能支持题主30秒,3分钟,3小时的配置(或许我眼瞎没看到。。。)。毕竟他们之间的倍数没有特定的递增关系,可以看作是直接配置死了几次重试的间隔而已

由于注解@Retryable的实现重试的方式是按照已有的Spring-Retry配置的策略,所以我们得使用Spring-Retry提供的另一种方式去定制一个重试模版

先简单介绍一下,@Retryable的实现方式其实是基于一个拦截器的AnnotationAwareRetryOperationsInterceptor,而拦截器里面会去为被注解的方法创建一个RetryTemplate,这个RetryTemplate中包含了注解中的规则以及被注解的方法

所以定制重试模版的方式其实就是我们自己定义一个RetryTemplate,然后自己定义规则

先说规则,因为我们只是为了把一列间隔时间直接取出来然后重试,所以我们的规则就很简单,实现类CustomBackOffPolicy(以下截图并不是完整代码)
image.png

用一个Queue<Long> intervalQueue的方式简单粗暴的存储间隔时间,在需要取间隔时间直接调用poll方法,取队列首位并删除

然后我们就可以自定义RetryTemplate,间隔时间分别为1秒,5秒,10秒,其中SimpleRetryPolicySpring-Retry自带的,表示重试策略,参数就是重试次数,这里取的间隔数组长度+1(包括第一次失败,所以这里是4不是3)
image.png

然后我们就可以去在需要重试的地方使用RetryTemplate
image.png

最后效果
image.png

当然当然,说实话哈,Spring-Retry也算是造了一个轮子,但是其实哈仔细去查看一下Spring-Retry的实现模式,在题主的这个需求上,还是while循环然后用Sleep去停,哈哈哈哈,所以我最开始才说担心什么线程的资源占用,这都是小事,毕竟定时器或者什么定时任务这玩意儿,底层不都还是while循环嘛,所以我觉得我都可以给你整一个简要版,弄一个CustomRetryTemplate

image.png

然后调用的话也很简单
image.png

最终效果也是一样的
image.png

所有代码我都放在了github上,稍微有点多,就不在这里发了,可以自取哈

就酱,( ^_^ )/~~拜拜

最后再提一嘴,真要使用Spring-Retry,记得把spring-aspects的配置要加上哈,我的pom里是有的
image.png

消息队列不太好实现这种逻辑,一般都是用来解耦两个系统的。

用redis实现更简单点,可以使用redis的set或list数据结构。比如对应1分钟后,10分钟后,一小时后创建三个list。

用一个job轮训重试,成功了就删除,失败了就放到下一个list中。

可以用消息队列中间件比如rabbitmq,也可以用redis,看自己的需求使用消息队列各方面的可靠性要好些,看自己选择。rabbitmq有一个插件 rabbitmq_delayed_message_exchange 是专门来干这种事情的。

1.使用for循环,通过try catch , 但是这样会一直占用一个线程的资源.
你的意思是对每一个请求进行for循环?出现异常就保留这个线程?那这样的确是不合理的。

思路肯定还是要用到生产者-消费者模型。 出现异常时写入生产队列。至于实现,消息中间件当然最好,不过不想额外部署的话,关系型数据库或者JAVA内置的队列啥的都可以。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
宣传栏