java 重试_Java实现几种简单的重试机制

java 重试_Java实现几种简单的重试机制背景当业务执行失败之后 进行重试是一个非常常见的场景 那么如何在业务代码中优雅的实现重试机制呢 设计我们的目标是实现一个优雅的重试机制 那么先来看下怎么样才算是优雅无侵入 这个好理解 不改动当前的业务逻辑 对于需要重试的地方 可以很简单的实现可配置 包括重试次数 重试的间隔时间 是否使用异步方式等通用性 最好是无改动 或者很小改动 的支持绝大部分的场景 拿过来直接可用针对上面的几点 分别看下右什么

背景

当业务执行失败之后,进行重试是一个非常常见的场景,那么如何在业务代码中优雅的实现重试机制呢?

设计

我们的目标是实现一个优雅的重试机制,那么先来看下怎么样才算是优雅

无侵入:这个好理解,不改动当前的业务逻辑,对于需要重试的地方,可以很简单的实现

可配置:包括重试次数,重试的间隔时间,是否使用异步方式等

通用性:最好是无改动(或者很小改动)的支持绝大部分的场景,拿过来直接可用

针对上面的几点,分别看下右什么好的解决方案

几种解决思路

要想做到无侵入或者很小的改动,一般来将比较好的方式就是切面或者消息总线模式;可配置和通用性则比较清晰了,基本上开始做就表示这两点都是基础要求了,唯一的要求就是不要硬编码,不要写死,基本上就能达到这个基础要求,当然要优秀的话,要做的事情并不少

切面方式

这个思路比较清晰,在需要添加重试的方法上添加一个用于重试的自定义注解,然后在切面中实现重试的逻辑,主要的配置参数则根据注解中的选项来初始化

优点:

真正的无侵入

缺点:

某些方法无法被切面拦截的场景无法覆盖(如spring-aop无法切私有方法,final方法)

直接使用aspecj则有些小复杂;如果用spring-aop,则只能切被spring容器管理的bean

消息总线方式

这个也比较容易理解,在需要重试的方法中,发送一个消息,并将业务逻辑作为回调方法传入;由一个订阅了重试消息的consumer来执行重试的业务逻辑

优点:

重试机制不受任何限制,即在任何地方你都可以使用

利用EventBus框架,可以非常容易把框架搭起来

缺点:

业务侵入,需要在重试的业务处,主动发起一条重试消息

调试理解复杂(消息总线方式的最大优点和缺点,就是过于灵活了,你可能都不知道什么地方处理这个消息,特别是新的童鞋来维护这段代码时)

如果要获取返回结果,不太好处理, 上下文参数不好处理

模板方式

把这个单独捞出来,主要是某些时候我就一两个地方要用到重试,简单的实现下就好了,也没有必用用到上面这么重的方式;而且我希望可以针对代码快进行重试

这个的设计还是非常简单的,基本上代码都可以直接贴出来,一目了然:

public abstract class RetryTemplate {

private static final int DEFAULT_RETRY_TIME = 1;

private int retryTime = DEFAULT_RETRY_TIME;

// 重试的睡眠时间

private int sleepTime = 0;

public int getSleepTime() {

return sleepTime;

}

public RetryTemplate setSleepTime(int sleepTime) {

if(sleepTime < 0) {

throw new IllegalArgumentException(“sleepTime should equal or bigger than 0”);

}

this.sleepTime = sleepTime;

return this;

}

public int getRetryTime() {

return retryTime;

}

public RetryTemplate setRetryTime(int retryTime) {

if (retryTime <= 0) {

throw new IllegalArgumentException(“retryTime should bigger than 0”);

}

this.retryTime = retryTime;

return this;

}

/

* 重试的业务执行代码

* 失败时请抛出一个异常

*

* todo 确定返回的封装类,根据返回结果的状态来判定是否需要重试

*

* @return

*/

protected abstract Object doBiz() throws Exception;

public Object execute() throws InterruptedException {

for (int i = 0; i < retryTime; i++) {

try {

return doBiz();

} catch (Exception e) {

log.error(“业务执行出现异常,e: {}”, e);

Thread.sleep(sleepTime);

}

}

return null;

}

public Object submit(ExecutorService executorService) {

if (executorService == null) {

throw new IllegalArgumentException(“please choose executorService!”);

}

return executorService.submit((Callable) () -> execute());

}

}

预留一个doBiz方法由业务方来实现,在其中书写需要重试的业务代码,然后执行即可

使用case也比较简单

public void retryDemo() throws InterruptedException {

Object ans = new RetryTemplate() {

@Override

protected Object doBiz() throws Exception {

int temp = (int) (Math.random() * 10);

System.out.println(temp);

if (temp > 3) {

throw new Exception(“generate value bigger then 3! need retry”);

}

return temp;

}

}.setRetryTime(10).setSleepTime(10).execute();

System.out.println(ans);

}

优点:

简单(依赖简单:引入一个类就可以了; 使用简单:实现抽象类,讲业务逻辑填充即可;)

灵活(这个是真正的灵活了,你想怎么干都可以,完全由你控制)

缺点:

强侵入

代码臃肿

实现

上面的模板方式基本上就那样了,接下来谈到的实现,毫无疑问将是切面和消息总线的方式

1. 切面方式

实现依然是基于前面的模板方式做的,简单来看就是添加一个切面,内部实现模版类即可

注解定义如下

@Documented

@Target(ElementType.METHOD)

@Retention(RetentionPolicy.RUNTIME)

public @interface RetryDot {

/

* 重试次数

* @return

*/

int count() default 0;

/

* 重试的间隔时间

* @return

*/

int sleep() default 0;

/

* 是否支持异步重试方式

* @return

*/

boolean asyn() default false;

}

切面逻辑如下

@Aspect

@Component

@Slf4j

public class RetryAspect {

ExecutorService executorService = new ThreadPoolExecutor(3, 5,

1, TimeUnit.MINUTES,

new LinkedBlockingQueue());

@Around(value = “@annotation(retryDot)”)

public Object execute(ProceedingJoinPoint joinPoint, RetryDot retryDot) throws Exception {

RetryTemplate retryTemplate = new RetryTemplate() {

@Override

protected Object doBiz() throws Throwable {

return joinPoint.proceed();

}

};

retryTemplate.setRetryCount(retryDot.count())

.setSleepTime(retryDot.sleep());

if (retryDot.asyn()) {

return retryTemplate.submit(executorService);

} else {

return retryTemplate.execute();

}

}

}

2. 消息方式

依然是在EventBus的基础上进行开发,结果写到一半,发现这种方式局限性还蛮大,基本上不太适合实际使用,下面依然给出实现逻辑

定义的重试事件RetryEvent

@Data

public class RetryEvent {

/

* 重试间隔时间, ms为单位

*/

private int sleep;

/

* 重试次数

*/

private int count;

/

* 是否异步重试

*/

private boolean asyn;

/

* 回调方法

*/

private Supplier callback;

}

消息处理类

@Component

public class RetryProcess {

ExecutorService executorService = new ThreadPoolExecutor(3, 5,

1, TimeUnit.MINUTES,

new LinkedBlockingQueue());

private static EventBus eventBus = new EventBus(“retry”);

public static void post(RetryEvent event) {

eventBus.post(event);

}

public static void register(Object handler) {

eventBus.register(handler);

}

public static void unregister(Object handler) {

eventBus.unregister(handler);

}

@PostConstruct

public void init() {

register(this);

}

@Subscribe

public void process(RetryEvent event) throws InterruptedException {

RetryTemplate retryTemplate = new RetryTemplate() {

@Override

protected Object doBiz() throws Throwable {

return event.getCallback().get();

}

};

retryTemplate.setSleepTime(event.getSleep())

.setRetryCount(event.getCount());

if(event.isAsyn()) {

retryTemplate.submit(executorService);

} else {

retryTemplate.execute();

}

}

}

问题比较明显,返回值以及输入参数的传入,比较不好处理

测试

测试下上面两种使用方式, 定义一个实例Service,分别采用注解和消息两种方式

@Service

public class RetryDemoService {

private int genNum() {

return (int) (Math.random() * 10);

}

@RetryDot(count = 5, sleep = 10)

public int genBigNum() throws Exception {

int a = genNum();

System.out.println(“genBigNum ” + a);

if (a < 3) {

throw new Exception(“num less than 3”);

}

return a;

}

public void genSmallNum() throws Exception {

RetryEvent retryEvent = new RetryEvent();

retryEvent.setSleep(10);

retryEvent.setCount(5);

retryEvent.setAsyn(false);

retryEvent.setCallback(() -> {

int a = genNum();

System.out.println(“now num: ” + a);

if (a > 3) {

throw new RuntimeException(“num bigger than 3”);

}

return a;

});

RetryProcess.post(retryEvent);

}

}

因为使用了切面,在spring的基础上进行开发的,所以需要加上对应的配置信息 aop.xml

Test代码

@RunWith(SpringJUnit4ClassRunner.class)

@ContextConfiguration({“classpath:aop.xml”})

public class AspectRetryTest {

@Autowired

private RetryDemoService retryDemoService;

@Test

public void testRetry() throws Exception {

for (int i = 0; i < 3; i++) {

int ans = retryDemoService.genBigNum();

System.out.println(“—-” + ans + “—-“);

retryDemoService.genSmallNum();

System.out.println(“——————“);

}

}

}

输出

genBigNum 9

—-9—-

now num: 1

——————

genBigNum 9

—-9—-

now num: 4

now num: 1

——————

genBigNum 5

—-5—-

now num: 6

now num: 6

now num: 0

——————

其他

guava-retrying和 spring-retry 实际上是更好的选择,设计与实现都非常优雅,实际的项目中完全可以直接使用

相关代码:

参考

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/232785.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • 微信小程序定位权限怎么打开_怎么用微信定位朋友的位置

    微信小程序定位权限怎么打开_怎么用微信定位朋友的位置最近有客户提了一个需求,要求登陆考试系统的测评者记录下当时的位置。web形式的虽然可以通过IP来定位,但是IP太容易作假了,所以为了比较高的准确性,最后决定用微信小程序,虽然也有作假的可能,但比web形式要好一些。一、准备工作既然要定位,那么肯定需要找到跟地图相关的功能API,查找微信开发文档,因为我们这里只是需要记录地位功能,不需要打开地图,所以只使用wx.getLocatio…

    2025年8月12日
    2
  • std::ostringstream的用法

    std::ostringstream的用法原文:ostringstream的用法使用stringstream对象简化类型转换为什么要学习进入stringstream你的编译器支持吗?string到int的转换重复利用stringstream对象在类型转换中使用模板结论一些实例例子一:基本数据类型转换例子int转string例子二:除了基本类型的转换,也支持char*的转换。例子三:再进行多次转换的时候,必须调用stringstre…

    2022年6月15日
    63
  • linux更改用户名字_linux修改用户id

    linux更改用户名字_linux修改用户idlinux更改用户名的方法是:1、以root身份登录,然后使用下列命令进行修改。“usermod-lNewUser-d/home/NewUser-mOldUser”、“usermod-lalao-d/home/alao-mtom”。2、修改用户名,修改登录后使用的路径,修改登录名称的同时将目录名称一同修改。…

    2022年9月18日
    3
  • php 扫描url死链接 \033[31m ANSI Linux终端输出带颜色

    php 扫描url死链接 \033[31m ANSI Linux终端输出带颜色

    2021年11月3日
    45
  • spss分析方法聚类分析_变量聚类分析

    spss分析方法聚类分析_变量聚类分析聚类分析是根据研究对象的特征,按照一定标准对研究对象进行分类的一种分析方法。下面我们主要从下面四个方面来解说:一、实际应用聚类分析的目标就是在相似的基础上收集数据来分类。聚类源于很多领域,包括数学,计算机科学,统计学,生物学和经济学。在不同的应用领域,很多聚类技术都得到了发展,这些技术方法被用作描述数据,衡量不同数据源间的相似性,以及把数据源分类到不同的簇中。商业上:聚类分析被用来发现不同的客户群,并且通过购买模式刻画不同的客户群的特征。聚类分析是细分市场的有效工具,同时也可用于研究消费者行为

    2022年10月18日
    3
  • 用python3实现粒子群优化算法(PSO)

    用python3实现粒子群优化算法(PSO)粒子群优化算法(ParticleSwarmOptimization,PSO)属于进化算法的一种,是通过模拟鸟群捕食行为设计的。从随机解出发,通过迭代寻找最优解,通过适应度来评价解的品质。设想这样一个场景:一群鸟在随机搜索食物。在这个区域里只有一块食物。所有的鸟都不知道食物在那里。但是他们知道当前的位置离食物还有多远。那么找到食物的最优策略是什么呢。最简单有效的就是搜寻目前离食物最近的鸟的周围区…

    2022年5月24日
    43

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号