在本教程中,咱们将探讨如何使用两种不一样的策略改进客户端重试:指数后退和抖动。git
在分布式系统中,多个组件之间的网络通讯随时可能发生故障。github
客户端应用程序经过实现重试来处理这些失败。算法
设想咱们有一个调用远程服务的客户端应用程序—— PingPongService 。网络
interface PingPongService {
String call(String ping) throws PingPongServiceException;
}复制代码
若是 PingPongService 返回一个 PingPongServiceException ,则客户端应用程序必须重试。在如下选项当中,咱们将考虑实现客户端重试的方法。app
在咱们的例子中,咱们将使用 Resilience4j 库,特别是它的 retry 模块。咱们须要将添加 resilience4j-retry 模块到 pom.xml :dom
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-retry</artifactId>
</dependency>复制代码
关于重试的复习,不要忘记查看咱们的 Resilience4j 指南。分布式
客户端应用程序必须负责地实现重试。当客户在没有等待的状况下重试失败的调用时,他们可能会使系统不堪重负,并致使已经处于困境的服务进一步降级。函数
指数回退是处理失败网络调用重试的经常使用策略。简单地说,客户端在连续重试之间等待的时间间隔愈来愈长:ui
wait_interval = base * multiplier^n复制代码
其中,spa
经过这种方法,咱们为系统提供了喘息的空间,以便从间歇性故障或更严重的问题中恢复过来。
咱们能够在 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);复制代码
让咱们看看 NUMCONCURRENTCLIENTS = 4 的远程调用日志:
[thread-1] At 00:37:42.756
[thread-2] At 00:37:42.756
[thread-3] At 00:37:42.756
[thread-4] At 00:37:42.756
[thread-2] At 00:37:43.802
[thread-4] At 00:37:43.802
[thread-1] At 00:37:43.802
[thread-3] At 00:37:43.802
[thread-2] At 00:37:45.803
[thread-1] At 00:37:45.803
[thread-4] At 00:37:45.803
[thread-3] At 00:37:45.803
[thread-2] At 00:37:49.808
[thread-3] At 00:37:49.808
[thread-4] At 00:37:49.808
[thread-1] At 00:37:49.808复制代码
咱们能够在这里看到一个清晰的模式——客户机等待指数级增加的间隔,可是在每次重试(冲突)时,它们都在同一时间调用远程服务。

咱们只解决了问题的一部分 - 咱们再也不从新启动远程服务,可是,取而代之的是随着时间的推移分散工做量,咱们在工做时间间隔更多,空闲时间更长。此行为相似于惊群问题。
在咱们前面的方法中,客户机等待时间逐渐变长,但仍然是同步的。添加抖动提供了一种方法来中断跨客户机的同步,从而避免冲突。在这种方法中,咱们给等待间隔增长了随机性。
wait_interval = (base * 2^n) +/- (random_interval)复制代码
其中,random_interval 被添加(或减去)以打破客户端之间的同步。
咱们不会深刻研究随机区间的计算机制,可是随机化必须将峰值空间分离到更平滑的客户端调用分布。
咱们能够经过配置一个指数随机回退 IntervalFunction,它也接受一个 randomizationFactor,从而在 Resilience4j 重试中使用带有抖动的指数回退:
IntervalFunction intervalFn =
IntervalFunction.ofExponentialRandomBackoff(INITIAL_INTERVAL, MULTIPLIER, RANDOMIZATION_FACTOR);复制代码
让咱们回到咱们的真实场景,并查看带抖动的远程调用日志:
[thread-2] At 39:21.297
[thread-4] At 39:21.297
[thread-3] At 39:21.297
[thread-1] At 39:21.297
[thread-2] At 39:21.918
[thread-3] At 39:21.868
[thread-4] At 39:22.011
[thread-1] At 39:22.184
[thread-1] At 39:23.086
[thread-5] At 39:23.939
[thread-3] At 39:24.152
[thread-4] At 39:24.977
[thread-3] At 39:26.861
[thread-1] At 39:28.617
[thread-4] At 39:28.942
[thread-2] At 39:31.039复制代码
如今咱们有了更好的传播。咱们已经消除了冲突和空闲时间,并以几乎恒定的客户端调用率结束,除非出现最初的激增。

注意:咱们夸大了插图的间隔时间,在实际状况中,咱们会有较小的差距。
在本教程中,咱们探讨了如何经过使用抖动增长指数回退来改进客户端应用程序重试失败调用的方法。本教程中使用的示例的源代码能够在 GitHub 上找到。
原文:https://www.baeldung.com/resilience4j-backoff-jitter
译者:Queena
------
9月福利,关注公众号后台回复:004,领取8月翻译集锦!往期福利回复:001,002, 003便可领取!