一次 Redisson 升级排查

1695 字
8 分钟
一次 Redisson 升级排查

Redisson版本:4.3.0

起因是在配置 Redisson 的 Config 的设置重试延迟时间方法 setRetryDelay 时, 发现原本的 setRetryInterval(int retryInterval) 变成了 setRetryInterval(int retryInterval),

点进源码一看:

/**
* Use {@link #setRetryDelay(DelayStrategy)} instead.
*
* @param retryInterval - time in milliseconds
* @return config
*/
@Deprecated
public T setRetryInterval(int retryInterval)

原来是官方废弃了这个方法,让我用一个叫做 setRetryDelay(DelayStrategy) 的新方法, 那么我们直接跳转到这个新方法的定义,看看它的注释:

/**
* Defines the delay strategy for a new attempt to send a command.
* <p>
* Default is <code>EqualJitterDelay(Duration.ofSeconds(1), Duration.ofSeconds(2))</code>
*
* @see DecorrelatedJitterDelay
* @see EqualJitterDelay
* @see FullJitterDelay
* @see ConstantDelay
*
* @param retryDelay delay strategy implementation
* @return options instance
*/
public T setRetryDelay(DelayStrategy retryDelay)

看来是 setRetryDelay 不让我直接传 int 参数了,而是要我传 DelayStrategy 参数,而且这个参数还是个接口, 说明我得传一个实现类进去,注释里还说目前这个接口有以下四种实现类

  • ConstantDelay:固定延迟,每次重试都等同样长时间
  • EqualJitterDelay:等分抖动延迟,Redisson 当前默认策略。保留一半的指数退避值,再给另一半加随机抖动值
  • FullJitterDelay:完全随机抖动延迟,对指数退避结果做完全随机抖动化
  • DecorrelatedJitterDelay:相关抖动延迟,延迟会指数式增长,但抖动值会受前一次退避时长影响

上面的描述来自官方文档,除了 ConstantDelay 非常的简单易懂,其他三个都有点抽象, 不过我注意到,这三个重试策略中的前两个都提到了一个词:指数退避,那我高低得从这里切入。

指数退避#

指数退避,是一种失败后逐步拉长重试等待时间的机制。可以把它理解成:

第一次失败,等 1 秒

第二次失败,等 2 秒

第三次失败,等 4 秒

第四次失败,等 8 秒

也就是等待时间不是线性增加,而是按 2 的幂 这样越来越大,所以叫“指数”退避。

为什么要这样做呢?#

因为如果一个服务已经出问题了,客户端还用很高频率不断重试,就容易出现两个问题:

  1. 把服务压得更厉害:服务本来就在抖动、切换、恢复,客户端越频繁重试,它越难缓过来。
  2. 大量客户端同时重试:会形成“重试风暴”,一下子又把 Redis、数据库、接口打满。

指数退避的核心目的,就是失败越多,说明问题越可能不是服务器瞬时抖动,而是真故障,那客户端就应该等得更久一点

一个直观的例子#

假设程序访问 Redis 失败了。而你设置重试策略为固定每隔 1 秒重试一次,

如果有 100 台机器都这样干,就可能每秒一起冲 Redis 一次。

但如果用指数退避,这样重试频率会越来越低,系统就有恢复空间。

在代码中,这个指数退避值的计算是如何实现的?#

首先从 DelayStrategy 接口看起

public interface DelayStrategy {
/**
* Calculates the delay duration to wait before the next retry attempt.
*
* @param attempt the zero-based retry attempt number (0 = first retry)
* @return the duration to wait before the next retry attempt
*/
Duration calcDelay(int attempt);
}

可以看到,这个接口里只有一个 calcDelay 方法,接收一个 attempt 参数,表示重试的次数。 返回一个 Duration 类型,表示等待时间,另外三种策略都是通过实现这个方法来计算等待时间的。

calcDelay 方法中,首先需要显式指定延迟时间的最小值(baseMs/minDelay)和最大值(maxMs/maxDelay), 然后,定义一个 exponentialDelay 变量,用来存放计算好的指数退避值,计算方法如下:

exponentialDelayMs=min(baseMs×attempt,maxMs);exponentialDelayMs = min(baseMs \times attempt, maxMs);

随机抖动#

真实系统里,往往不是只做指数退避,还会再加一个 jitter(随机抖动值)。

原因在于,如果所有客户端都严格按 1、2、4、8 秒去重试,它们还是可能在同一时刻一起打过去。

所以更常见的是:

  • 大方向按指数增长
  • 具体每次等待再加一点随机性

当按照上面计算出指数退避值后,接下来就是对指数退避值进行抖动处理,这里 Redisson 提出了两种策略:

  1. EqualJitterDelay:等分抖动延迟,Redisson 当前默认策略。保留一半的指数退避值,再给另一半加随机抖动值
  2. FullJitterDelay:完全随机抖动延迟,对指数退避结果做完全随机抖动化

首先来看下 EqualJitterDelay,它的核心逻辑如下:

// 保留一半的指数退避值
long halfDelay = exponentialDelayMs / 2;
// 在 [0, halfDelay] 之间生成一个随机抖动值
long randomComponent = random(0, halfDelay + 1);
// 将保留的一半指数退避值和随机抖动值相加,得到最终的等待时间
return halfDelay + randomComponent;

另一种策略 FullJitterDelay 的实现如下:

// 在 [0, exponentialDelayMs] 之间生成一个随机抖动值
long jitteredDelayMs = random(0, exponentialDelayMs + 1);
// 直接返回这个随机抖动值
return jitteredDelayMs;

最终,EqualJitterDelay 的等待时间范围为[halfDelay,exponentialDelay][halfDelay, exponentialDelay], 而 FullJitterDelay 的等待时间范围为[0,exponentialDelayMs][0, exponentialDelayMs]

这两种策略分别适用于什么场景呢?

  • 如果你想追求稳定性,可以选择 EqualJitterDelay 策略,因为它会保留一半的指数退避值,再给另一半加随机抖动值,所以等待时间会比较稳定。
  • 如果你想追求随机性,可以选择 FullJitterDelay 策略,因为它会对指数退避结果做完全随机抖动化,所以等待时间会比较随机。

基于前一次结果的随机退避#

现在来看最后一个策略:DecorrelatedJitterDelay

这个策略和前两个策略最大的不同在于,它的退避时间不跟重试次数 attempt 挂钩了,而是只跟上一次重试时间 previousDelay 挂钩,

前两个策略计算退避时间时,使用的是 min(baseMs×attempt,maxMs)min(baseMs \times attempt, maxMs),而该策略计算退避时间,使用的是如下核心逻辑:

// 1. 第一次调用时,因为 previousDelay 还是 0,系统会先把 prev 取成 minDelay;
// 之后调用时,将prev设置为上一次重试时间
long prev = (previousDelay == 0 ? minDelay : previousDelay)
// 2. 在 [0, 3倍上次重试时间] 之间生成一个随机值
long randomComponent = random(0, prev * 3)
// 3. 在随机值上加一个最小重试时间,然后和最大重试时间取最小值
long newDelay = min(minDelay + randomComponent, maxDelay)
// 4. 更新上次重试时间
previousDelay = newDelay
return newDelay

按照如此逻辑执行,第一次重试时间会落在 [minDelay,4×minDelay)[minDelay, 4 \times minDelay) 这个区间内; 后面每次都会在 0和上一次结果的 3 倍 随机滚动。

支持与分享

如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!

赞助
一次 Redisson 升级排查
https://blog.xoxxo18.icu/posts/redisson-setretrydelay/
作者
Kyson
发布于
2026-04-03
许可协议
CC BY-NC-SA 4.0

评论区

Profile Image of the Author
Kyson
君子终日乾乾,夕惕若厉,无咎。
公告
欢迎来到我的博客!
音乐
封面

音乐

暂未播放

0:00 0:00
暂无歌词
分类
标签
站点统计
文章
3
分类
3
标签
3
总字数
3,176
运行时长
0
最后活动
0 天前

目录