编程技术文章分享与教程

网站首页 > 技术文章 正文

Java中使用指数退避和抖动实现重试

hmc789 2024-11-09 13:06:06 技术文章 2 ℃

问题

  • 您正在设计一个需要与外部 API 通信的服务,该服务偶尔会因暂时的网络问题而失败。请描述您将如何实施重试机制来处理这些故障。
  • 接下来,解释一下何时使用断路器而不是重试机制,并讨论同时实现两者的场景。

使用指数退避和抖动进行重试

  • 当与外部 API 通信以处理瞬时网络问题时,我们应该实现重试机制来自动重试失败的请求。
  • 重试机制会尝试重新发送请求有限次,然后放弃。为了实现这一点,我们将使用指数退避和抖动来确定重试之间的等待时间。此策略会随着每次重试而呈指数增加退避时间,而抖动(随机延迟)有助于分散重试请求,从而降低同时重试导致外部服务不堪重负的风险。



public class RetryWithExponentialBackoff {
    private static final int MAX_ATTEMPTS = 5;
    private static final long INITIAL_BACKOFF_MILLIS = 1000; // 1 second
    private static final long MAX_BACKOFF_MILLIS = 10000; // 10 seconds
    private static final Random RANDOM = new Random();

    public static void main(String[] args) {
        try {
            retryTask();
            System.out.println("Task completed successfully.");
        } catch (Exception e) {
            System.err.println("Task failed after retries: " + e.getMessage());
        }
    }

    private static void retryTask() throws Exception {
        int attempts = 0;
        while(attempts < MAX_ATTEMPTS){
            try {
                performTask();
                return;
            } catch (Exception e){
                attempts++;
                if(attempts >= MAX_ATTEMPTS){
                    throw new Exception("Max retry reached.", e);
                }

                long backOffTime = calculateBackOffWithJitter(attempts);
                System.err.printf("Attempt %d failed. Retrying in %d ms...%n", attempts, backOffTime);
                try {
                    Thread.sleep(backOffTime);
                } catch (InterruptedException ie) {
                    Thread.currentThread().interrupt();
                    throw new RuntimeException("Thread was interrupted during retry delay", ie);
                }
            }
        }
    }

    //计算指数后退并增加抖动
    private static long calculateBackOffWithJitter(int attempts) {
        double exponentialBackOff = Math.min(INITIAL_BACKOFF_MILLIS * Math.pow(2, attempts - 1), MAX_BACKOFF_MILLIS);
        return (long) (exponentialBackOff * RANDOM.nextDouble());
    }

    // 随机故障的任务,描述瞬时故障
    private static void performTask(){
        if(Math.random() > 0.7){
            System.out.println("Task succeeded.");
        }else{
            throw new RuntimeException("Task failed.");
        }
    }
}



常量:

  • MAX_ATTEMPTS:重试的最大次数(在本例中为 5 次)。
  • INITIAL_BACKOFF_MILLIS:初始退避时间,以毫秒(1 秒)为单位。
  • MAX_BACKOFF_MILLIS:最大退避时间,以毫秒为单位(10 秒)。
  • RANDOM:Random用于产生抖动的对象。


retryTask方法:

  • 尝试执行该performTask()方法,直至达到最大重试次数(MAX_ATTEMPTS)。
  • 如果任务失败,重试机制将等待使用抖动指数退避算法计算的退避时间。
  • 每次重试后退避时间都会增加,并且会添加抖动以防止同时重试导致服务器不堪重负。


performTask方法:

  • 此方法模拟了可能随机失败的不可靠任务。
  • 如果任务成功(Math.random() > 0.7),则成功返回;否则,抛出异常。


calculateBackoffWithJitter方法:

  • 指数退避计算:等待时间使用 计算INITIAL_BACKOFF_MILLIS * 2^(attempt-1)。退避上限为 ,MAX_BACKOFF_MILLIS以防止等待时间过长。
  • 抖动添加:退避时间随后乘以 0 到 1 之间的随机因子 ( RANDOM.nextDouble())。此随机延迟会分散重试请求,并降低同时重试导致外部服务不堪重负的风险。


何时使用断路器

  • 当我们通信的服务持续出现故障时,我们应该使用断路器。在这种情况下,重试请求只会增加不必要的负载并延迟恢复。
  • 断路器的工作原理是在预定义的连续故障次数后断开连接。然后等待指定的时间,然后允许有限数量的请求来检查外部服务是否已恢复。


具有重试机制的断路器

  • 使用断路器和重试机制是提高与外部服务交互的应用程序的弹性的常见模式。当服务持续失败时,断路器会阻止发出请求,而重试机制会使用再次尝试请求的策略来处理瞬时故障。
  • 当与外部 API 通信时,我们应该首先尝试使用重试机制发出请求。如果发生故障,则延迟重试(使用指数退避和抖动等策略)以克服瞬态问题。
  • 现在,我们可以将整个重试逻辑包装在断路器中。断路器会监控重试过程中的故障,如果超过指定的故障阈值,它会打开并在一段时间内阻止后续请求。
  • 一旦断路器打开,它会阻止在给定时间内尝试重试,从而避免使外部服务不堪重负。

Java中使用指数退避和抖动实现重试 - 极道

Tags:

标签列表
最新留言