1. 概述

在本教程中,我们将探讨如何使用两种不同的策略改进客户端重试:指数后退和抖动。

2. 重试

在分布式系统中,多个组件之间的网络通信随时可能发生故障。

客户端应用程序通过实现重试来处理这些失败。

设想我们有一个调用远程服务的客户端应用程序—— PingPongService 。

interface PingPongService {     String call(String ping) throws PingPongServiceException; }

如果 PingPongService 返回一个 PingPongServiceException ,则客户端应用程序必须重试。在以下选项当中,我们将考虑实现客户端重试的方法。

3. Resilience4j 重试

在我们的例子中,我们将使用 Resilience4j 库,特别是它的 retry 模块。我们需要将添加 resilience4j-retry 模块到 pom.xml :

<dependency>     <groupId>io.github.resilience4j</groupId>     <artifactId>resilience4j-retry</artifactId> </dependency>

关于重试的复习,不要忘记查看我们的 Resilience4j 指南

4. 指数后退

客户端应用程序必须负责地实现重试。当客户在没有等待的情况下重试失败的调用时,他们可能会使系统不堪重负,并导致已经处于困境的服务进一步降级。

指数回退是处理失败网络调用重试的常用策略。简单地说,客户端在连续重试之间等待的时间间隔越来越长:

wait_interval = base * multiplier^n

其中,

  • base 是初始间隔,即等待第一次重试
  • n 是已经发生的故障数量
  • multiplier 是一个任意的乘法器,可以用任何合适的值替换

通过这种方法,我们为系统提供了喘息的空间,以便从间歇性故障或更严重的问题中恢复过来。

我们可以在 Resilience4j 重试中使用指数回退算法,方法是配置它的 IntervalFunction ,该函数接受 initialInterval 和 multiplier

重试机制使用 IntervalFunction 作为睡眠函数:

IntervalFunction intervalFn =   IntervalFunction.ofExponentialBackoff(INITIAL_INTERVAL, MULTIPLIER);  RetryConfig retryConfig = RetryConfig.custom()   .maxAttempts(MAX_RETRIES)   .intervalFunction(intervalFn)   .build(); Retry retry = Retry.of("pingpong", retryConfig);  Function<String, String> pingPongFn = Retry     .decorateFunction(retry, ping -> service.call(ping)); pingPongFn.apply("Hello");

让我们模拟一个真实的场景,假设我们有几个客户端同时调用 PingPongService :

ExecutorService executors = newFixedThreadPool(NUM_CONCURRENT_CLIENTS); List<Callable> tasks = nCopies(NUM_CONCURRENT_CLIENTS, () -> pingPongFn.apply("Hello")); executors.invokeAll(tasks);

让我们看看 NUM_CONCURRENT_CLIENTS = 4 的远程调用日志:

[thread-1] At 00:37:42.756 [thread-2] At 00:37:42.756 [thread-3]