重试利器之Guava Retrying

╰半夏微凉° 2023-05-29 09:16 136阅读 0赞
  • 重试的使用场景
  • 如何优雅地设计重试实现
  • guava-retrying基础用法
  • guava-retrying实现原理
  • guava-retrying高级用法
  • 使用中遇到的问题

    • Guava版本冲突
    • 动态调节重试策略

重试的使用场景

在很多业务场景中,为了排除系统中的各种不稳定因素,以及逻辑上的错误,并最大概率保证获得预期的结果,重试机制都是必不可少的。

尤其是调用远程服务,在高并发场景下,很可能因为服务器响应延迟或者网络原因,造成我们得不到想要的结果,或者根本得不到响应。这个时候,一个优雅的重试调用机制,可以让我们更大概率保证得到预期的响应。

format_png

通常情况下,我们会通过定时任务进行重试。例如某次操作失败,则记录下来,当定时任务再次启动,则将数据放到定时任务的方法中,重新跑一遍。最终直至得到想要的结果为止。

无论是基于定时任务的重试机制,还是我们自己写的简单的重试器,缺点都是重试的机制太单一,而且实现起来不优雅。

如何优雅地设计重试实现

一个完备的重试实现,要很好地解决如下问题:

  1. 什么条件下重试
  2. 什么条件下停止
  3. 如何停止重试
  4. 停止重试等待多久
  5. 如何等待
  6. 请求时间限制
  7. 如何结束
  8. 如何监听整个重试过程

并且,为了更好地封装性,重试的实现一般分为两步:

  1. 使用工厂模式构造重试器
  2. 执行重试方法并得到结果

一个完整的重试流程可以简单示意为:

format_png 1

guava-retrying基础用法

guava-retrying是基于谷歌的核心类库guava的重试机制实现,可以说是一个重试利器。

下面就快速看一下它的用法。

1.Maven配置

  1. <!-- https://mvnrepository.com/artifact/com.github.rholder/guava-retrying -->
  2. <dependency>
  3. <groupId>com.github.rholder</groupId>
  4. <artifactId>guava-retrying</artifactId>
  5. <version>2.0.0</version>
  6. </dependency>
  7. 复制代码

需要注意的是,此版本依赖的是27.0.1版本的guava。如果你项目中的guava低几个版本没问题,但是低太多就不兼容了。这个时候你需要升级你项目的guava版本,或者直接去掉你自己的guava依赖,使用guava-retrying传递过来的guava依赖。

2.实现Callable

  1. Callable<Boolean> callable = new Callable<Boolean>() {
  2. public Boolean call() throws Exception {
  3. return true; // do something useful here
  4. }
  5. };
  6. 复制代码

Callable的call方法中是你自己实际的业务调用。

  1. 通过RetryerBuilder构造Retryer

    Retryer retryer = RetryerBuilder.newBuilder()

    1. .retryIfResult(Predicates.<Boolean>isNull())
    2. .retryIfExceptionOfType(IOException.class)
    3. .retryIfRuntimeException()
    4. .withStopStrategy(StopStrategies.stopAfterAttempt(3))
    5. .build();

    复制代码

  2. 使用重试器执行你的业务

    retryer.call(callable);
    复制代码

下面是完整的参考实现。

  1. public Boolean test() throws Exception {
  2. //定义重试机制
  3. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  4. //retryIf 重试条件
  5. .retryIfException()
  6. .retryIfRuntimeException()
  7. .retryIfExceptionOfType(Exception.class)
  8. .retryIfException(Predicates.equalTo(new Exception()))
  9. .retryIfResult(Predicates.equalTo(false))
  10. //等待策略:每次请求间隔1s
  11. .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
  12. //停止策略 : 尝试请求6次
  13. .withStopStrategy(StopStrategies.stopAfterAttempt(6))
  14. //时间限制 : 某次请求不得超过2s , 类似: TimeLimiter timeLimiter = new SimpleTimeLimiter();
  15. .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS))
  16. .build();
  17. //定义请求实现
  18. Callable<Boolean> callable = new Callable<Boolean>() {
  19. int times = 1;
  20. @Override
  21. public Boolean call() throws Exception {
  22. log.info("call times={}", times);
  23. times++;
  24. if (times == 2) {
  25. throw new NullPointerException();
  26. } else if (times == 3) {
  27. throw new Exception();
  28. } else if (times == 4) {
  29. throw new RuntimeException();
  30. } else if (times == 5) {
  31. return false;
  32. } else {
  33. return true;
  34. }
  35. }
  36. };
  37. //利用重试器调用请求
  38. return retryer.call(callable);
  39. }
  40. 复制代码

guava-retrying实现原理

guava-retrying的核心是Attempt类、Retryer类以及一些Strategy(策略)相关的类。

  1. Attempt

Attempt既是一次重试请求(call),也是请求的结果,并记录了当前请求的次数、是否包含异常和请求的返回值。

  1. /**
  2. * An attempt of a call, which resulted either in a result returned by the call,
  3. * or in a Throwable thrown by the call.
  4. *
  5. * @param <V> The type returned by the wrapped callable.
  6. * @author JB
  7. */
  8. public interface Attempt<V>
  9. 复制代码
  1. Retryer

Retryer通过RetryerBuilder这个工厂类进行构造。RetryerBuilder负责将定义的重试策略赋值到Retryer对象中。

在Retryer执行call方法的时候,会将这些重试策略一一使用。

下面就看一下Retryer的call方法的具体实现。

  1. /**
  2. * Executes the given callable. If the rejection predicate
  3. * accepts the attempt, the stop strategy is used to decide if a new attempt
  4. * must be made. Then the wait strategy is used to decide how much time to sleep
  5. * and a new attempt is made.
  6. *
  7. * @param callable the callable task to be executed
  8. * @return the computed result of the given callable
  9. * @throws ExecutionException if the given callable throws an exception, and the
  10. * rejection predicate considers the attempt as successful. The original exception
  11. * is wrapped into an ExecutionException.
  12. * @throws RetryException if all the attempts failed before the stop strategy decided
  13. * to abort, or the thread was interrupted. Note that if the thread is interrupted,
  14. * this exception is thrown and the thread's interrupt status is set.
  15. */
  16. public V call(Callable<V> callable) throws ExecutionException, RetryException {
  17. long startTime = System.nanoTime();
  18. //说明: 根据attemptNumber进行循环——也就是重试多少次
  19. for (int attemptNumber = 1; ; attemptNumber++) {
  20. //说明:进入方法不等待,立即执行一次
  21. Attempt<V> attempt;
  22. try {
  23. //说明:执行callable中的具体业务
  24. //attemptTimeLimiter限制了每次尝试等待的时常
  25. V result = attemptTimeLimiter.call(callable);
  26. //利用调用结果构造新的attempt
  27. attempt = new ResultAttempt<V>(result, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
  28. } catch (Throwable t) {
  29. attempt = new ExceptionAttempt<V>(t, attemptNumber, TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime));
  30. }
  31. //说明:遍历自定义的监听器
  32. for (RetryListener listener : listeners) {
  33. listener.onRetry(attempt);
  34. }
  35. //说明:判断是否满足重试条件,来决定是否继续等待并进行重试
  36. if (!rejectionPredicate.apply(attempt)) {
  37. return attempt.get();
  38. }
  39. //说明:此时满足停止策略,因为还没有得到想要的结果,因此抛出异常
  40. if (stopStrategy.shouldStop(attempt)) {
  41. throw new RetryException(attemptNumber, attempt);
  42. } else {
  43. //说明:执行默认的停止策略——线程休眠
  44. long sleepTime = waitStrategy.computeSleepTime(attempt);
  45. try {
  46. //说明:也可以执行定义的停止策略
  47. blockStrategy.block(sleepTime);
  48. } catch (InterruptedException e) {
  49. Thread.currentThread().interrupt();
  50. throw new RetryException(attemptNumber, attempt);
  51. }
  52. }
  53. }
  54. }
  55. 复制代码

Retryer执行过程如下。

format_png 2

guava-retrying高级用法

基于guava-retrying的实现原理,我们可以根据实际业务来确定自己的重试策略。

下面以数据同步这种常规系统业务为例,自定义重试策略。

如下实现基于Spring Boot 2.1.2.RELEASE版本。

并使用Lombok简化Bean。

  1. <dependency>
  2. <groupId>org.projectlombok</groupId>
  3. <artifactId>lombok</artifactId>
  4. <optional>true</optional>
  5. </dependency>
  6. 复制代码

业务描述

当商品创建以后,需要另外设置商品的价格。由于两个操作是有两个人进行的,因此会出现如下问题,即商品没有创建,但是价格数据却已经建好了。遇到这种情况,价格数据需要等待商品正常创建以后,继续完成同步。

我们通过一个http请求进行商品的创建,同时通过一个定时器来修改商品的价格。

当商品不存在,或者商品的数量小于1的时候,商品的价格不能设置。需要等商品成功创建且数量大于0的时候,才能将商品的价格设置成功。

实现过程

  1. 自定义重试阻塞策略

默认的阻塞策略是线程休眠,这里使用自旋锁实现,不阻塞线程。

  1. package net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.strategy;
  2. import com.github.rholder.retry.BlockStrategy;
  3. import lombok.NoArgsConstructor;
  4. import lombok.extern.slf4j.Slf4j;
  5. import java.time.Duration;
  6. import java.time.LocalDateTime;
  7. /**
  8. * 自旋锁的实现, 不响应线程中断
  9. */
  10. @Slf4j
  11. @NoArgsConstructor
  12. public class SpinBlockStrategy implements BlockStrategy {
  13. @Override
  14. public void block(long sleepTime) throws InterruptedException {
  15. LocalDateTime startTime = LocalDateTime.now();
  16. long start = System.currentTimeMillis();
  17. long end = start;
  18. log.info("[SpinBlockStrategy]...begin wait.");
  19. while (end - start <= sleepTime) {
  20. end = System.currentTimeMillis();
  21. }
  22. //使用Java8新增的Duration计算时间间隔
  23. Duration duration = Duration.between(startTime, LocalDateTime.now());
  24. log.info("[SpinBlockStrategy]...end wait.duration={}", duration.toMillis());
  25. }
  26. }
  27. 复制代码
  1. 自定义重试监听器

RetryListener可以监控多次重试过程,并可以使用attempt做一些额外的事情。

  1. package net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.listener;
  2. import com.github.rholder.retry.Attempt;
  3. import com.github.rholder.retry.RetryListener;
  4. import lombok.extern.slf4j.Slf4j;
  5. @Slf4j
  6. public class RetryLogListener implements RetryListener {
  7. @Override
  8. public <V> void onRetry(Attempt<V> attempt) {
  9. // 第几次重试,(注意:第一次重试其实是第一次调用)
  10. log.info("retry time : [{}]", attempt.getAttemptNumber());
  11. // 距离第一次重试的延迟
  12. log.info("retry delay : [{}]", attempt.getDelaySinceFirstAttempt());
  13. // 重试结果: 是异常终止, 还是正常返回
  14. log.info("hasException={}", attempt.hasException());
  15. log.info("hasResult={}", attempt.hasResult());
  16. // 是什么原因导致异常
  17. if (attempt.hasException()) {
  18. log.info("causeBy={}" , attempt.getExceptionCause().toString());
  19. } else {
  20. // 正常返回时的结果
  21. log.info("result={}" , attempt.getResult());
  22. }
  23. log.info("log listen over.");
  24. }
  25. }
  26. 复制代码
  1. 自定义Exception

有些异常需要重试,有些不需要。

  1. package net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.exception;
  2. /**
  3. * 当抛出这个异常的时候,表示需要重试
  4. */
  5. public class NeedRetryException extends Exception {
  6. public NeedRetryException(String message) {
  7. super("NeedRetryException can retry."+message);
  8. }
  9. }
  10. 复制代码
  1. 实现具体重试业务与Callable接口

使用call方法调用自己的业务。

  1. package net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.model;
  2. import lombok.AllArgsConstructor;
  3. import lombok.Data;
  4. import java.math.BigDecimal;
  5. /**
  6. * 商品model
  7. */
  8. @Data
  9. @AllArgsConstructor
  10. public class Product {
  11. private Long id;
  12. private String name;
  13. private Integer count;
  14. private BigDecimal price;
  15. }
  16. 复制代码
  17. package net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.repository;
  18. import net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.model.Product;
  19. import org.springframework.stereotype.Repository;
  20. import java.math.BigDecimal;
  21. import java.util.ArrayList;
  22. import java.util.List;
  23. import java.util.concurrent.ConcurrentHashMap;
  24. import java.util.concurrent.atomic.AtomicLong;
  25. /**
  26. * 商品DAO
  27. */
  28. @Repository
  29. public class ProductRepository {
  30. private static ConcurrentHashMap<Long,Product> products=new ConcurrentHashMap();
  31. private static AtomicLong ids=new AtomicLong(0);
  32. public List<Product> findAll(){
  33. return new ArrayList<>(products.values());
  34. }
  35. public Product findById(Long id){
  36. return products.get(id);
  37. }
  38. public Product updatePrice(Long id, BigDecimal price){
  39. Product p=products.get(id);
  40. if (null==p){
  41. return p;
  42. }
  43. p.setPrice(price);
  44. return p;
  45. }
  46. public Product addProduct(Product product){
  47. Long id=ids.addAndGet(1);
  48. product.setId(id);
  49. products.put(id,product);
  50. return product;
  51. }
  52. }
  53. 复制代码
  54. package net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.service;
  55. import lombok.extern.slf4j.Slf4j;
  56. import net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.exception.NeedRetryException;
  57. import net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.model.Product;
  58. import net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.repository.ProductRepository;
  59. import org.springframework.beans.factory.annotation.Autowired;
  60. import org.springframework.stereotype.Component;
  61. import java.math.BigDecimal;
  62. import java.util.HashMap;
  63. import java.util.Map;
  64. import java.util.concurrent.Callable;
  65. /**
  66. * 业务方法实现
  67. */
  68. @Component
  69. @Slf4j
  70. public class ProductInformationHander implements Callable<Boolean> {
  71. @Autowired
  72. private ProductRepository pRepo;
  73. private static Map<Long, BigDecimal> prices = new HashMap<>();
  74. static {
  75. prices.put(1L, new BigDecimal(100));
  76. prices.put(2L, new BigDecimal(200));
  77. prices.put(3L, new BigDecimal(300));
  78. prices.put(4L, new BigDecimal(400));
  79. prices.put(8L, new BigDecimal(800));
  80. prices.put(9L, new BigDecimal(900));
  81. }
  82. @Override
  83. public Boolean call() throws Exception {
  84. log.info("sync price begin,prices size={}", prices.size());
  85. for (Long id : prices.keySet()) {
  86. Product product = pRepo.findById(id);
  87. if (null == product) {
  88. throw new NeedRetryException("can not find product by id=" + id);
  89. }
  90. if (null == product.getCount() || product.getCount() < 1) {
  91. throw new NeedRetryException("product count is less than 1, id=" + id);
  92. }
  93. Product updatedP = pRepo.updatePrice(id, prices.get(id));
  94. if (null == updatedP) {
  95. return false;
  96. }
  97. prices.remove(id);
  98. }
  99. log.info("sync price over,prices size={}", prices.size());
  100. return true;
  101. }
  102. }
  103. 复制代码
  1. 构造重试器Retryer

将上面的实现作为参数,构造Retryer。

  1. package net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.service;
  2. import com.github.rholder.retry.*;
  3. import net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.exception.NeedRetryException;
  4. import net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.listener.RetryLogListener;
  5. import net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.strategy.SpinBlockStrategy;
  6. import org.springframework.stereotype.Component;
  7. import java.util.concurrent.TimeUnit;
  8. /**
  9. * 构造重试器
  10. */
  11. @Component
  12. public class ProductRetryerBuilder {
  13. public Retryer build() {
  14. //定义重试机制
  15. Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
  16. //retryIf 重试条件
  17. //.retryIfException()
  18. //.retryIfRuntimeException()
  19. //.retryIfExceptionOfType(Exception.class)
  20. //.retryIfException(Predicates.equalTo(new Exception()))
  21. //.retryIfResult(Predicates.equalTo(false))
  22. .retryIfExceptionOfType(NeedRetryException.class)
  23. //等待策略:每次请求间隔1s
  24. .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS))
  25. //停止策略 : 尝试请求3次
  26. .withStopStrategy(StopStrategies.stopAfterAttempt(3))
  27. //时间限制 : 某次请求不得超过2s , 类似: TimeLimiter timeLimiter = new SimpleTimeLimiter();
  28. .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS))
  29. //默认的阻塞策略:线程睡眠
  30. //.withBlockStrategy(BlockStrategies.threadSleepStrategy())
  31. //自定义阻塞策略:自旋锁
  32. .withBlockStrategy(new SpinBlockStrategy())
  33. //自定义重试监听器
  34. .withRetryListener(new RetryLogListener())
  35. .build();
  36. return retryer;
  37. }
  38. }
  39. 复制代码
  1. 与定时任务结合执行Retryer

定时任务只需要跑一次,但是实际上实现了所有的重试策略。这样大大简化了定时器的设计。

首先使用@EnableScheduling声明项目支持定时器注解。

  1. @SpringBootApplication
  2. @EnableScheduling
  3. public class DemoRetryerApplication {
  4. public static void main(String[] args) {
  5. SpringApplication.run(DemoRetryerApplication.class, args);
  6. }
  7. }
  8. 复制代码
  9. package net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.task;
  10. import com.github.rholder.retry.Retryer;
  11. import net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.service.ProductInformationHander;
  12. import net.ijiangtao.tech.framework.spring.ispringboot.demo.retryer.guava.service.ProductRetryerBuilder;
  13. import org.springframework.beans.factory.annotation.Autowired;
  14. import org.springframework.scheduling.annotation.Scheduled;
  15. import org.springframework.stereotype.Component;
  16. /**
  17. * 商品信息定时器
  18. */
  19. @Component
  20. public class ProductScheduledTasks {
  21. @Autowired
  22. private ProductRetryerBuilder builder;
  23. @Autowired
  24. private ProductInformationHander hander;
  25. /**
  26. * 同步商品价格定时任务
  27. * @Scheduled(fixedDelay = 30000) :上一次执行完毕时间点之后30秒再执行
  28. */
  29. @Scheduled(fixedDelay = 30*1000)
  30. public void syncPrice() throws Exception{
  31. Retryer retryer=builder.build();
  32. retryer.call(hander);
  33. }
  34. }
  35. 复制代码

执行结果:由于并没有商品,因此重试以后,抛出异常。

  1. 2019-二月-28 14:37:52.667 INFO [scheduling-1] n.i.t.f.s.i.d.r.g.l.RetryLogListener - log listen over.
  2. 2019-二月-28 14:37:52.672 ERROR [scheduling-1] o.s.s.s.TaskUtils$LoggingErrorHandler - Unexpected error occurred in scheduled task.
  3. com.github.rholder.retry.RetryException: Retrying failed to complete successfully after 3 attempts.
  4. at com.github.rholder.retry.Retryer.call(Retryer.java:174)
  5. 复制代码

你也可以增加一些商品数据,看一下重试成功的效果。

完整示例代码在这里。

使用中遇到的问题

Guava版本冲突

由于项目中依赖的guava版本过低,启动项目时出现了如下异常。

  1. java.lang.NoSuchMethodError: com.google.common.util.concurrent.MoreExecutors.sameThreadExecutor()Lcom/google/common/util/concurrent/ListeningExecutorService;
  2. at org.apache.curator.framework.listen.ListenerContainer.addListener(ListenerContainer.java:41)
  3. at com.bzn.curator.ZkOperator.getZkClient(ZkOperator.java:207)
  4. at com.bzn.curator.ZkOperator.checkExists(ZkOperator.java:346)
  5. at com.bzn.curator.watcher.AbstractWatcher.initListen(AbstractWatcher.java:87)
  6. at com.bzn.web.listener.NebulaSystemInitListener.initZkWatcher(NebulaSystemInitListener.java:84)
  7. at com.bzn.web.listener.NebulaSystemInitListener.contextInitialized(NebulaSystemInitListener.java:33)
  8. at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:4939)
  9. at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5434)
  10. at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:150)
  11. at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1559)
  12. at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1549)
  13. at java.util.concurrent.FutureTask.run(FutureTask.java:266)
  14. at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
  15. at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
  16. at java.lang.Thread.run(Thread.java:748)
  17. 复制代码

因此,要排除项目中低版本的guava依赖。

  1. <exclusion>
  2. <groupId>com.google.guava</groupId>
  3. <artifactId>guava</artifactId>
  4. </exclusion>
  5. 复制代码

同时,由于Guava在新版本中移除了sameThreadExecutor方法,但目前项目中的ZK需要此方法,因此需要手动设置合适的guava版本。

果然,在19.0版本中MoreExecutors的此方法依然存在,只是标注为过期了。

  1. @Deprecated
  2. @GwtIncompatible("TODO")
  3. public static ListeningExecutorService sameThreadExecutor() {
  4. return new DirectExecutorService();
  5. }
  6. 复制代码

声明依赖的guava版本改为19.0即可。

  1. <!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
  2. <dependency>
  3. <groupId>com.google.guava</groupId>
  4. <artifactId>guava</artifactId>
  5. <version>19.0</version>
  6. </dependency>
  7. 复制代码

动态调节重试策略

在实际使用过程中,有时经常需要调整重试的次数、等待的时间等重试策略,因此,将重试策略的配置参数化保存,可以动态调节。

例如在秒杀、双十一购物节等时期增加等待的时间与重试次数,以保证错峰请求。在平时,可以适当减少等待时间和重试次数。

对于系统关键性业务,如果多次重试步成功,可以通过RetryListener进行监控与报警。

关于『动态调节重试策略 』下面提供一个参考实现:

  1. import com.github.rholder.retry.Attempt;
  2. import com.github.rholder.retry.WaitStrategy;
  3. /**
  4. * 自定义等待策略:根据重试次数动态调节等待时间,第一次请求间隔1s,第二次间隔10s,第三次及以后都是20s。
  5. *
  6. *
  7. * 在创建Retryer的时候通过withWaitStrategy将该等待策略生效即可。
  8. *
  9. * RetryerBuilder.<Boolean>newBuilder()
  10. * .withWaitStrategy(new AlipayWaitStrategy())
  11. *
  12. * 类似的效果也可以通过自定义 BlockStrategy 来实现,你可以写一下试试。
  13. *
  14. */
  15. public class AlipayWaitStrategy implements WaitStrategy {
  16. @Override
  17. public long computeSleepTime(Attempt failedAttempt) {
  18. long number = failedAttempt.getAttemptNumber();
  19. if (number==1){
  20. return 1*1000;
  21. }
  22. if (number==2){
  23. return 10*1000;
  24. }
  25. return 20*1000;
  26. }
  27. }

作者:西召
链接:https://juejin.im/post/5c77e3bcf265da2d914da410
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

发表评论

表情:
评论列表 (有 0 条评论,136人围观)

还没有评论,来说两句吧...

相关阅读