Spring-retry、guava的Retry都提供有重试工具,但二者均存在一个确缺点,即如果重试等待过程中会一直阻塞工作线程,这对于在生产环境使用是存在风险的,如果存在大量长时间等待的重试任务将会耗尽系统线程资源,下文基于线程池来完成一个简易的重试工具类。
核心思想
将任务封装为一个task,将任务的重试放入可调度的线程池中完成执行,避免在重试间隔中,线程陷入无意义的等待,同时将重试机制抽象为重试策略。
代码实现
重试工具类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
package com.huakai.springenv.retry.v2;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function;
@Slf4j public class RetryUtil {
public static ExecutorService EXECUTOR = Executors.newFixedThreadPool(1); private static final ScheduledExecutorService SCHEDULER_EXECUTOR = Executors.newScheduledThreadPool(20);
/** * 任务重试 * @param actualTaskFunction 执行的任务函数 * @param resultHandler 任务结果处理器 * @param maxRetry 最大重试次数 * @param retryStrategy 重试策略 */ public static void retryTask( Function<Integer, String> actualTaskFunction, Function<String, Boolean> resultHandler, int maxRetry, RetryStrategy retryStrategy // 使用策略模式 ) { Runnable runnable = new Runnable() { final AtomicInteger retryCount = new AtomicInteger(); // 当前重试次数 final AtomicInteger maxRetryCount = new AtomicInteger(maxRetry); // 最大重试次数
@Override public void run() { String taskResult = actualTaskFunction.apply(retryCount.get()); // 执行任务 Boolean taskSuccess = resultHandler.apply(taskResult); // 处理任务结果 if (taskSuccess) { if (retryCount.get() > 1) { log.info("任务重试成功,重试次数:{}", retryCount.get()); } return; // 任务成功,不需要再重试 }
if (retryCount.incrementAndGet() == maxRetryCount.get()) { log.warn("任务重试失败,重试次数:{}", retryCount.get()); return; // 达到最大重试次数,停止重试 }
// 获取重试间隔 long delay = retryStrategy.getDelay(retryCount.get()); TimeUnit timeUnit = retryStrategy.getTimeUnit(retryCount.get());
// 安排下次重试 SCHEDULER_EXECUTOR.schedule(this, delay, timeUnit); log.info("任务重试失败,等待 {} {} 后再次尝试,当前重试次数:{}", delay, timeUnit, retryCount.get()); } }; EXECUTOR.execute(runnable); // 执行任务 }
public static void main(String[] args) { // 使用指数退避重试策略 RetryStrategy retryStrategy = new ExponentialBackoffRetryStrategy(1, TimeUnit.SECONDS);
retryTask( retryCount -> "task result", taskResult -> Math.random() < 0.1, 5, retryStrategy ); } } |
重试策略
指数退避
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package com.huakai.springenv.retry.v2;
import java.util.concurrent.TimeUnit;
/** * 指数退避重试策略 */ public class ExponentialBackoffRetryStrategy implements RetryStrategy { private final long initialDelay; private final TimeUnit timeUnit;
public ExponentialBackoffRetryStrategy(long initialDelay, TimeUnit timeUnit) { this.initialDelay = initialDelay; this.timeUnit = timeUnit; }
@Override public long getDelay(int retryCount) { return (long) (initialDelay * Math.pow(2, retryCount - 1)); // 指数退避 }
@Override public TimeUnit getTimeUnit(int retryCount) { return timeUnit; } } |
自定义重试间隔时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
package com.huakai.springenv.retry.v2;
import java.util.List; import java.util.concurrent.TimeUnit;
/** * 自定义重试间隔时间的重试策略 */ public class CustomerIntervalRetryStrategy implements RetryStrategy { // 配置重试间隔和时间单位 List<RetryInterval> retryIntervals;
public CustomerIntervalRetryStrategy(List<RetryInterval> retryIntervals) { this.retryIntervals = retryIntervals; }
@Override public long getDelay(int retryCount) { return retryIntervals.get(retryCount).getDelay(); }
@Override public TimeUnit getTimeUnit(int retryCount){ return retryIntervals.get(retryCount).getTimeUnit(); } } |
固定间隔
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
package com.huakai.springenv.retry.v2;
import java.util.concurrent.TimeUnit;
/** * 固定间隔重试策略 */ public class FixedIntervalRetryStrategy implements RetryStrategy { private final long interval; private final TimeUnit timeUnit;
public FixedIntervalRetryStrategy(long interval, TimeUnit timeUnit) { this.interval = interval; this.timeUnit = timeUnit; }
@Override public long getDelay(int retryCount) { return interval; }
@Override public TimeUnit getTimeUnit(int retryCount) { return timeUnit; } } |