Java重试

ChatGPT:

在Java中,有一些重试框架可以帮助处理在执行某些操作时可能发生的错误,并尝试重新执行这些操作。以下是一些常用的Java重试框架:

  1. Spring Retry:

    • 特点: Spring Retry是Spring框架的一部分,提供了在方法执行失败时进行重试的机制。它通过注解或者编程方式来配置重试策略。
  2. Guava Retryer:

    • 特点: Google的Guava库提供了一个Retryer类,允许你指定重试的条件和策略。它具有灵活的配置选项。
  3. Resilience4j:

    • 特点: Resilience4j是一个轻量级且模块化的容错库,提供了重试、断路器、限流等功能。它与函数式编程兼容。
  4. Failsafe:

    • 特点: Failsafe是一个功能强大的库,支持失败重试、断路器、超时等。它具有简单的API和丰富的配置选项。

 

Guava-Retry

和 Spring Retry 相比,Guava Retry 具有更强的灵活性,并且能够根据 返回值 来判断是否需要重试,比如返回false我们就重试

  <!-- https://mvnrepository.com/artifact/com.github.rholder/guava-retrying -->
    <dependency>
        <groupId>com.github.rholder</groupId>
        <artifactId>guava-retrying</artifactId>
        <version>2.0.0</version>
    </dependency>

demo:

// RetryerBuilder 构建重试实例 retryer,可以设置重试源且可以支持多个重试源,可以配置重试次数或重试超时时间,以及可以配置等待时间间隔
    Retryer<Boolean> retryer = RetryerBuilder.<Boolean> newBuilder()
            .retryIfExceptionOfType(RemoteAccessException.class)//设置异常重试源
            .retryIfResult(res-> res==false)  //设置根据结果重试
            .withWaitStrategy(WaitStrategies.fixedWait(3, TimeUnit.SECONDS)) //设置等待间隔时间
            .withStopStrategy(StopStrategies.stopAfterAttempt(3)) //设置最大重试次数
            .build();

    try {
      retryer.call(() -> RetryDemoTask.retryTask("abc"));
    } catch (Exception e) {
      e.printStackTrace();
    }

Retryer 是线程安全的:

  Guava Retryer 是线程安全的,可以在多线程环境中使用。

  Retryer 实例是不可变的(immutable),这意味着一旦创建,它的配置就不能被修改。因此,你可以在多个线程中共享相同的 Retryer 实例,而不必担心线程安全问题。

  如果你需要在多个线程中同时使用 Retryer,建议创建一个全局共享的 Retryer 实例,而不是每个线程都创建一个新的实例。这有助于提高性能,因为不必反复创建对象,而且由于 Retryer 是不可变的,多个线程之间共享它是安全的。

  以下是一个简单的示例,演示如何创建一个全局共享的 Retryer 实例:

import com.github.rholder.retry.Retryer;
import com.github.rholder.retry.RetryerBuilder;

public class GlobalRetryer {

    private static final Retryer<Object> globalRetryer = RetryerBuilder.newBuilder()
            // 配置重试策略
            .retryIfException()
            .retryIfRuntimeException()
            .withMaxAttempts(3)
            .build();

    public static Retryer<Object> getGlobalRetryer() {
        return globalRetryer;
    }
}

在多个线程中,你可以共享 GlobalRetryer.getGlobalRetryer() 实例来执行需要重试的操作

 

 

方式1、sisyphus

  github:https://github.com/houbb/sisyphus

 

 一、工具类方式使用

  1、POM

        <!-- retry -->
        <dependency>
            <groupId>com.github.houbb</groupId>
            <artifactId>sisyphus-core</artifactId>
            <version>0.1.0</version>
        </dependency>
        <dependency>
            <groupId>com.github.houbb</groupId>
            <artifactId>sisyphus-annotation</artifactId>
            <version>0.1.0</version>
        </dependency>

 

  2、SisyphusUtil:

package com.xxx.mitv.common.util;

import com.github.houbb.sisyphus.core.core.RetryWaiter;
import com.github.houbb.sisyphus.core.core.Retryer;
import com.github.houbb.sisyphus.core.support.condition.RetryConditions;
import com.github.houbb.sisyphus.core.support.listen.RetryListens;
import com.github.houbb.sisyphus.core.support.recover.Recovers;
import com.github.houbb.sisyphus.core.support.wait.ExponentialRetryWait;
import com.github.houbb.sisyphus.core.support.wait.NoRetryWait;

import java.util.concurrent.Callable;

/**
 * 重试工具类
*/ public class SisyphusUtil { /** * 默认配置 * * @param callable 待重试执行方法 */ public static void defaultAttempt(final Callable<String> callable) { Retryer.<String>newInstance() //重试触发的条件,可以指定多个条件,默认为抛出异常。 .condition(RetryConditions.hasExceptionCause()) //重试等待的策略,可以指定多个,默认为不做任何等待。 .retryWaitContext(RetryWaiter.<String>retryWait(NoRetryWait.class).context()) //指定最大重试次数,包括第一次执行,默认3次 .maxAttempt(3) //指定重试的监听实现,默认为不做监听。 .listen(RetryListens.noListen()) //当重试完成之后,依然满足重试条件,则可以指定恢复的策略。默认不做恢复。 .recover(Recovers.noRecover()) //待重试执行的方法。 .callable(callable) //触发重试执行。 .retryCall(); } /** * 根据返回值等于期望值定制重试规则 * * @param condition 触发条件 * @param maxAttempt 最大重试次数 * @param initValue 第一次重试时间间隔 单位:毫秒。初始建议设置60000 * @param factor 递增因子 小于1越来越快,大于1越来越慢,等于1保持不变。 大部分场景设置2.0可满足需求 * @param callable 待重试执行方法 */ public static void attemptForEqualsResult(final String condition, final int maxAttempt, final int initValue, final double factor, final Callable<String> callable) { Retryer.<String>newInstance() .condition(RetryConditions.isEqualsResult(condition)) //递增策略 .retryWaitContext(RetryWaiter.<String>retryWait(ExponentialRetryWait.class).value(initValue).factor(factor).context()) .maxAttempt(maxAttempt) .listen(RetryListens.noListen()) .recover(Recovers.noRecover()) .callable(callable) .retryCall(); } /** * 根据返回值不等于期望值定制重试规则 * * @param condition 触发条件 * @param maxAttempt 最大重试次数 * @param initValue 第一次重试时间间隔 单位:毫秒。初始建议设置60000 * @param factor 递增因子 小于1越来越快,大于1越来越慢,等于1保持不变。 大部分场景设置2.0可满足需求 * @param callable 待重试执行方法 */ public static void attemptForNotEqualsResult(final String condition, final int maxAttempt, final int initValue, final double factor, final Callable<String> callable) { Retryer.<String>newInstance() .condition(RetryConditions.isNotEqualsResult(condition)) //递增策略 .retryWaitContext(RetryWaiter.<String>retryWait(ExponentialRetryWait.class).value(initValue).factor(factor).context()) .maxAttempt(maxAttempt) .listen(RetryListens.noListen()) .recover(Recovers.noRecover()) .callable(callable) .retryCall(); } /** * 根据是否存在异常定制重试规则 * * @param maxAttempt 最大重试次数 * @param initValue 第一次重试时间间隔 单位:毫秒。初始建议设置60000 * @param factor 递增因子 小于1越来越快,大于1越来越慢,等于1保持不变。 大部分场景设置2.0可满足需求 * @param callable 待重试执行方法 */ public static void attemptForException(final int maxAttempt, final int initValue, final double factor, final Callable<String> callable) { Retryer.<String>newInstance() .condition(RetryConditions.hasExceptionCause()) .retryWaitContext(RetryWaiter.<String>retryWait(ExponentialRetryWait.class).value(initValue).factor(factor).context()) .maxAttempt(maxAttempt) .listen(RetryListens.noListen()) .recover(Recovers.noRecover()) .callable(callable) .retryCall(); } }

  3、应用

    工具类方式:

  //重试3次
    SisyphusUtil.attemptForNotEqualsResult(
         "true",
         3,
         60000,
         2.0,
         new Callable<String>() {
        @Override
        public String call() throws Exception {
            return autoRenewService.wechatAdvanceNotify(info) == true ? "true" : "false";
        }
    });

 

     注解方式:与Spring整合

    @Retry :

/**
 * 重试注解
 * 1. 实际需要,只允许放在方法上。
 * 2. 如果放在接口上,是否所有的子类都生效?为了简单明确,不提供这种实现。
 * 3. 保持注解和接口的一致性。{@link com.github.houbb.sisyphus.api.core.Retry} 接口
 * @since 0.0.3
 */
@Documented
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@RetryAble(DefaultRetryAbleHandler.class)
public @interface Retry {

    /**
     * 重试类实现
     * @return 重试
     * @since 0.0.5
     */
    Class<? extends com.github.houbb.sisyphus.api.core.Retry> retry() default DefaultRetry.class;

    /**
     * 最大尝试次数
     * 1. 包含方法第一次正常执行的次数
     * @return 次数
     */
    int maxAttempt() default 3;

    /**
     * 重试触发的场景
     * @return 重试触发的场景
     */
    Class<? extends RetryCondition> condition() default ExceptionCauseRetryCondition.class;

    /**
     * 监听器
     * 1. 默认不进行监听
     * @return 监听器
     */
    Class<? extends RetryListen> listen() default NoRetryListen.class;

    /**
     * 恢复操作
     * 1. 默认不进行任何恢复操作
     * @return 恢复操作对应的类
     */
    Class<? extends Recover> recover() default NoRecover.class;

    /**
     * 等待策略
     * 1. 支持指定多个,如果不指定,则不进行任何等待,
     * @return 等待策略
     */
    RetryWait[] waits() default {};

}

 

  二、与Spring整合:

    1、POM

        <dependency>
            <groupId>com.github.houbb</groupId>
            <artifactId>sisyphus-spring</artifactId>
            <version>0.1.0</version>
        </dependency>

    会默认引入 spring 以及 AOP 相关 jar

    2、开启重试

    @EnableRetry

/**
 * 启用重试注解
 * @since 0.0.4
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RetryAopConfig.class)
@EnableAspectJAutoProxy
public @interface EnableRetry {
}

    3、使用 @Retry 标识方法需要进行重试。

    如:

    @Retry
    @Override
    public void test() {
        LOGGER.info("test");
        int i=1/0;
    }

 

 

 

 

方式2、spring retry

 

https://github.com/spring-projects/spring-retry#javaConfigForRetryProxies

  翻译的中文文档:https://segmentfault.com/a/1190000019932970

  总结好文:https://albenw.github.io/posts/69a9647f/

  spring retry是通过 AOP 实现的

  1、依赖

       <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
            <version>1.2.4.RELEASE</version>
        </dependency>

  如果使用 @Retryable 注解还需额外添加 aop和aspectj的依赖

   <dependency>
         <groupId>org.springframework.boot</groupId>
         <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjweaver</artifactId>
    </dependency>

 

  2、在启动类或配置类加上 @EnableRetry 注解,表示启用重试机制

  3、举例:

@Configuration
@EnableRetry
public class Application {

    @Bean
    public Service service() {
        return new Service();
    }

}

@Service
class Service {
    @Retryable(RemoteAccessException.class)
    public void service() {
        // ... do something
    }
    @Recover
    public void recover(RemoteAccessException e) {
       // ... panic
    }
}

  在此例中,当调用service()方法出现 RemoteAccessException 异常时,默认重试 3次(包含第一次的失败),如果仍失败,则会调用 recover 方法。

  @Retryable 注解属性中有各种选项,用于包含和排除异常类型、限制重试次数和回退策略。

@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Retryable {

    /**
     * 为重试方法应用重试拦截器的bean名称。与其他属性互斥
     */
    String interceptor() default "";

    /**
     * 可以重试的异常类型。与includes属性同义。默认值为空(并且如果exclude也是空的话,所有的异常都会重试)
     */
    Class<? extends Throwable>[] value() default {};

    Class<? extends Throwable>[] include() default {};

    /**
     * 不重试的异常类型,默认为空(并且如果exclude也是空的话,所有的异常都会重试)
     */
    Class<? extends Throwable>[] exclude() default {};

    /**
     * A unique label for statistics reporting. If not provided the caller may choose to
     * ignore it, or provide a default.
     *
     * @return the label for the statistics
     */
    String label() default "";

    /**
     * 标识重试是有状态的。如果异常在重试的时候重新抛出,
     * Flag to say that the retry is stateful: i.e. exceptions are re-thrown, but the
     * retry policy is applied with the same policy to subsequent invocations with the
     * same arguments. If false then retryable exceptions are not re-thrown.
     * @return true if retry is stateful, default false
     */
    boolean stateful() default false;

    /**
     * 尝试的最大次数(包含第一次失败),默认为3
     */
    int maxAttempts() default 3;

    /**
     * 返回一个求尝试最大次数值的表达式(包含第一次失败),默认为3
     * 重写 {@link #maxAttempts()}。
     */
    String maxAttemptsExpression() default "";

    /**
     * 退避策略,指怎么去做下一次的重试,在这里其实就是两次重试之间的间隔
     */
    Backoff backoff() default @Backoff();

    /**
     * 在SimpleRetryPolicy.canRetry()返回true之后执行一个计算表达式,可用来有条件的取消重试
     * 只在抛出异常后调用,求值的root对象为上一次的异常,可以引用上下文中的其他beans
     */
    String exceptionExpression() default "";
}

  @Backoff

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Import(RetryConfiguration.class)
@Documented
public @interface Backoff {

    /**
     * 与 delay() 属性同义
     * 返回延迟多少毫秒后重试(默认为1000毫秒)
     */
    long value() default 1000;

    /**
     * 一个标准的再重试周期,默认1000毫秒
     * @return the initial or canonical backoff period in milliseconds (default 1000)
     */
    long delay() default 0;

    /**
     * 重试之间最大等待(毫秒)时间。如果小于 {@link #delay()} 则忽略。
     * @return the maximum delay between retries (default 0 = ignored)
     */
    long maxDelay() default 0;

    /**
     * 如果是正数,则用于生成下次再重试等待时间的乘数
     * 返回一个乘数用于计算下次再重试延迟(默认为0忽略)
     */
    double multiplier() default 0;

    /**
     * 标准再重试周期求值表达式。在指数情况下用作初始值,始终如一的情况下用作最小值。
     * Overrides {@link #delay()}.
     * @return the initial or canonical backoff period in milliseconds.
     * @since 1.2
     */
    String delayExpression() default "";

    /**
     * 在重试之间最大等待(毫秒)数的求值表达式。
     *  * 如果小于 {@link #delay()} 则忽略。
     * Overrides {@link #maxDelay()}
     * @return the maximum delay between retries (default 0 = ignored)
     * @since 1.2
     */
    String maxDelayExpression() default "";

    /**
     * 表达式求值作为生成下次再重试延迟的乘数
     * Overrides {@link #multiplier()}.
     * @since 1.2
     */
    String multiplierExpression() default "";

    /**
     * 在指数情况下 ({@link #multiplier()} > 0) 设置该值为true将使再重试延迟随机化,
     * 使最大延迟为先前延迟的乘数倍数,并使这两个延迟值之间分布均匀。
     * 默认为false
     */
    boolean random() default false;
}

 

  实例:

    /**
     * 第一次延迟1000ms,第二次延迟2*1000ms,第三次延迟2*2*1000ms,之后都是延迟5000ms
     */
    @Retryable(maxAttempts = 6, backoff = @Backoff(delay = 1000, maxDelay = 5000, multiplier = 2))
    @Override
    public void test() {
        LOGGER.error("spring retry:{}", DateUtil.formatDate(new Date()));
        throw new RuntimeException("my test");
    }

  输出:

2020-09-28 14:12:24.536|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=0
2020-09-28 14:12:24.539|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:24 GMT
2020-09-28 14:12:24.539|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 1000
2020-09-28 14:12:25.541|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=1
2020-09-28 14:12:25.541|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=1 2020-09-28 14:12:25.541|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:25 GMT 2020-09-28 14:12:25.541|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 2000 2020-09-28 14:12:27.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=2
2020-09-28 14:12:27.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=2 2020-09-28 14:12:27.542|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:27 GMT 2020-09-28 14:12:27.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 40002020-09-28 14:12:31.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=3
2020-09-28 14:12:31.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=3 2020-09-28 14:12:31.542|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:31 GMT 2020-09-28 14:12:31.542|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 50002020-09-28 14:12:36.543|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=4
2020-09-28 14:12:36.543|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=4 2020-09-28 14:12:36.543|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:36 GMT 2020-09-28 14:12:36.543|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|ExponentialBackOffPolicy#backOff:179|Sleeping for 50002020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=5
2020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:282|Retry: count=5 2020-09-28 14:12:41.544|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|TaskServiceImpl#test:198|spring retry:Mon, 28 Sep 2020 06:12:41 GMT 2020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:321|Checking for rethrow: count=6 2020-09-28 14:12:41.544|http-nio-8080-exec-1|DEBUG|80e4d19848464ae1ac00ee957ca88a39|RetryTemplate#doExecute:346|Retry failed last attempt: count=6 2020-09-28 14:12:41.546|http-nio-8080-exec-1|ERROR|80e4d19848464ae1ac00ee957ca88a39|LogAspect#around:59|test方法执行异常:my test java.lang.RuntimeException: my test

 

  Spring Retry缺点

    1、其回退策略,默认使用的是Thread.sleep方法,会导致当前的线程被阻塞,因此使用的时候要注意。

    2、只能在异常后重试,重试条件单一

 

 

 

方式3:ScheduledExecutorService

     ScheduledExecutorService源码:

public interface ScheduledExecutorService extends ExecutorService {

    /**
     * 创建并执行一个一次性操作,该操作在给定的延迟后调用*/
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);

    /**
     * 创建和执行一个ScheduleFuture,该future在给定的延迟之后调用*/
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);

    /**
     * 每次执行时间为上一次任务开始起向后推一个时间间隔*/
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);

    /**
     * 每次执行时间为上一次任务结束后向后推一个时间间隔*/
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

}

 

  Dubbo 2.6.x 及以下版本对ScheduleExecutorService的使用:

  

 

 

   

  addFailed:

/**
 * 当失败时,记录失败请求,并计划在一个常规的间隔重试。特别适用于通知服务
 */
public class FailbackClusterInvoker<T> extends AbstractClusterInvoker<T> {

    private static final Logger logger = LoggerFactory.getLogger(FailbackClusterInvoker.class);

    private static final long RETRY_FAILED_PERIOD = 5 * 1000; // 重试时间间隔

    /**
     * ScheduledExecutorService线程池
     */
    private final ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2,
            new NamedInternalThreadFactory("failback-cluster-timer", true));
  
   // 缓存失败的调用,如果失败太多,有内存溢出风险
    private final ConcurrentMap<Invocation, AbstractClusterInvoker<?>> failed = new ConcurrentHashMap<Invocation, AbstractClusterInvoker<?>>();
    private volatile ScheduledFuture<?> retryFuture;

    public FailbackClusterInvoker(Directory<T> directory) {
        super(directory);
    }

    private void addFailed(Invocation invocation, AbstractClusterInvoker<?> router) {
        if (retryFuture == null) {
            synchronized (this) {
                if (retryFuture == null) {
                    retryFuture = scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {

                        @Override
                        public void run() {
                            // collect retry statistics
                            try {
                                retryFailed(); // 遍历failed map,对失败的调用发起重试
                            } catch (Throwable t) { // Defensive fault tolerance
                                logger.error("Unexpected error occur at collect statistic", t);
                            }
                        }
                    }, RETRY_FAILED_PERIOD, RETRY_FAILED_PERIOD, TimeUnit.MILLISECONDS);
                }
            }
        }
     // 如果retryFuture不为null,则将失败的调用添加在map缓存中
        failed.put(invocation, router);
    }

    void retryFailed() {
        if (failed.size() == 0) {
            return;
        }
        for (Map.Entry<Invocation, AbstractClusterInvoker<?>> entry : new HashMap<Invocation, AbstractClusterInvoker<?>>(
                failed).entrySet()) {
            Invocation invocation = entry.getKey();
            Invoker<?> invoker = entry.getValue();
            try {
                invoker.invoke(invocation);
                failed.remove(invocation); // 成功之后将调用从缓存中移除
            } catch (Throwable e) {
                logger.error("Failed retry to invoke method " + invocation.getMethodName() + ", waiting again.", e);
            }
        }
    }

    @Override
    protected Result doInvoke(Invocation invocation, List<Invoker<T>> invokers, LoadBalance loadbalance) throws RpcException {
        try {
            checkInvokers(invokers, invocation);
            Invoker<T> invoker = select(loadbalance, invocation, invokers, null);
            return invoker.invoke(invocation);
        } catch (Throwable e) {
            logger.error("Failback to invoke method " + invocation.getMethodName() + ", wait for retry in background. Ignored exception: "
                    + e.getMessage() + ", ", e);
            addFailed(invocation, this);
            return new RpcResult(); // ignore
        }
    }

}

  但是这种方式,有个风险,就是如果失败的调用太多,缓存失败调用的ConcurrentHashMap存在内存溢出风险。

 

  总结:

    ①:失败之后,将失败的调用添加到ConcurrentMap中缓存

    ②:scheduledExecutorService.scheduleWithFixedDelay遍历需要重试的调用Map,依次发起重试,成功后从Map中移除

 

 

 

END.

posted @ 2020-09-17 17:11  杨岂  阅读(535)  评论(0编辑  收藏  举报