递增退避与指数退避算法介绍

前言

在分布式系统、微服务、网络请求、消息队列以及爬虫系统中,重试(Retry)机制是一种非常常见的容错手段。但如果失败后立即再次重试,可能会导致:

  • 服务端压力进一步增大
  • 多个客户端同时重试,引发「惊群效应」
  • 网络拥塞进一步恶化
  • 限流接口持续返回失败

因此,大多数系统都会采用 “退避(Backoff)算法”,即每次重试之前等待一段时间,并且随着重试次数增加,等待时间越来越长。目前最常见的两种退避算法分别是:

  • 递增退避(Linear Backoff)
  • 指数退避(Exponential Backoff)

提示

有些书籍或者网络资料,会将 "退避(Backoff)算法" 称为 "回避算法",这两个叫法都是对的,但 "退避算法" 是更标准、更专业的译名。

递增退避算法

概念说明

递增退避(Linear Backoff)算法指的是:每次重试等待时间按照固定步长递增,即等待时间与重试次数成正比。增长速度是线性的

计算公式

1
等待时间 = 基础间隔 × 重试次数

例如:

  • 基础等待时间:10 秒
  • 第一次重试等待:10 秒
  • 第二次重试等待:20 秒
  • 第三次重试等待:30 秒
  • 第四次重试等待:40 秒
  • 第五次重试等待:50 秒
重试次数等待时间
第 1 次 10 秒(10 x 1)
第 2 次 20 秒(10 x 2)
第 3 次 30 秒(10 x 3)
第 4 次 40 秒(10 x 4)
第 5 次 50 秒(10 x 5)

增长曲线如下:

1
10 → 20 → 30 → 40 → 50 ...

可以看到,每次都固定增加 10 秒

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Java 实现:递增退避算法
*/
public class LinearBackoff {

/**
* 计算递增退避时间
*
* @param baseIntervalMs 基础等待时间(毫秒)
* @param retryAttempt 第几次重试(从 1 开始)
* @return 等待时间(毫秒)
*/
public static long calculateInterval(long baseIntervalMs, int retryAttempt) {
if (baseIntervalMs <= 0 || retryAttempt <= 0) {
return 0;
}

return baseIntervalMs * retryAttempt;
}

public static void main(String[] args) {
long base = 10000;

for (int i = 1; i <= 5; i++) {
System.out.printf("Retry %d -> %d ms%n", i, calculateInterval(base, i));
}
}
}

程序运行结果:

1
2
3
4
5
Retry 1 -> 10000 ms
Retry 2 -> 20000 ms
Retry 3 -> 30000 ms
Retry 4 -> 40000 ms
Retry 5 -> 50000 ms

指数退避算法

概念说明

  • 指数退避(Exponential Backoff)是一种更加常见的重试算法。
  • 特点是每次等待时间按指数级增长,通常每次翻倍。增长速度明显快于递增退避。

计算公式

1
等待时间 = 基础间隔 × 2^(重试次数 - 1)

例如:

  • 基础等待时间:10 秒
  • 第一次:10 秒
  • 第二次:20 秒
  • 第三次:40 秒
  • 第四次:80 秒
  • 第五次:160 秒
重试次数等待时间
第 1 次 10 秒(10 × 2⁰)
第 2 次 20 秒(10 × 2¹)
第 3 次 40 秒(10 × 2²)
第 4 次 80 秒(10 × 2³)
第 5 次 160 秒(10 × 2⁴)

增长曲线:

1
10 → 20 → 40 → 80 → 160 ...

可以看到,等待时间呈指数增长(翻倍)

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Java 实现:指数退避算法
*/
public class ExponentialBackoff {

/**
* 计算指数退避时间
*
* @param baseIntervalMs 基础等待时间(毫秒)
* @param retryAttempt 第几次重试(从 1 开始)
* @return 等待时间(毫秒)
*/
public static long calculateInterval(long baseIntervalMs, int retryAttempt) {
if (baseIntervalMs <= 0 || retryAttempt <= 0) {
return 0;
}

return baseIntervalMs * (1L << (retryAttempt - 1));
}

public static void main(String[] args) {
long base = 10000;

for (int i = 1; i <= 5; i++) {
System.out.printf("Retry %d -> %d ms%n", i, calculateInterval(base, i));
}
}
}

程序运行结果:

1
2
3
4
5
Retry 1 -> 10000 ms
Retry 2 -> 20000 ms
Retry 3 -> 40000 ms
Retry 4 -> 80000 ms
Retry 5 -> 160000 ms

两种算法对比

算法对比

对比项递增退避算法指数退避算法
增长方式每次固定递增每次翻倍
数学公式Base × RetryBase × 2^(Retry - 1)
增长速度较慢、平缓较快、激进
最大等待时间增长较慢增长很快
对服务器压力恢复速度快更有利于服务恢复
实现复杂度简单简单

下面可以直观看出二者的区别:

重试次数递增退避算法指数退避算法
第 1 次 10 秒 10 秒
第 2 次 20 秒 20 秒
第 3 次 30 秒 40 秒
第 4 次 40 秒 80 秒
第 5 次 50 秒 160 秒
第 6 次 60 秒 320 秒

增长趋势:

1
2
3
递增退避算法: 10 → 20 → 30 → 40 → 50 → 60

指数退避算法: 10 → 20 → 40 → 80 → 160 → 320

前两次等待时间几乎一样,但从第三次开始,指数退避算法的增长速度远高于递增退避算法。

适用场景

  • 递增退避算法

    • 优点:
      • 等待时间增长平缓
      • 用户体验较好
      • 重试恢复速度快
    • 缺点:
      • 服务端压力下降较慢
      • 大规模客户端同时重试时效果一般
    • 适用场景:
      • 短暂网络抖动
      • 数据库偶发超时
      • RPC 调用失败
      • 对实时性要求较高的业务
      • 希望尽快恢复请求
  • 指数退避算法

    • 优点:
      • 能快速降低请求压力
      • 更有利于服务器恢复
      • 云计算领域最常见的退避策略
    • 缺点:
      • 后期等待时间可能较长
      • 如果没有设置上限,等待时间可能无限增长
    • 适用场景:
      • 服务端过载
      • HTTP 429(Too Many Requests)
      • API 限流
      • 网络拥塞
      • 消息队列消费失败
      • 云平台 SDK
        • gRPC
        • Kafka Client
        • RabbitMQ Client
        • Google Cloud SDK
        • Kubernetes Client
      • 很多流行框架都默认采用指数退避算法

算法优化

指数退避算法的优化

在实际生产环境中,很少直接使用最基础的指数退避算法,而是会结合以下两种优化策略。

最大等待时间(Cap)优化策略

为了避免等待时间无限增长,通常会设置一个最大值,例如:

1
wait = min(base × 2^(retry - 1), maxInterval)

例如:

  • 基础等待时间:10 秒
  • 最大等待时间:60 秒
  • 第一次:10 秒
  • 第二次:20 秒
  • 第三次:40 秒
  • 第四次:60 秒
  • 第五次:60 秒
  • ……

随机抖动(Jitter)优化策略

如果所有客户端都严格按照相同时间重试,就可能出现大量请求在同一时刻再次发送,形成惊群效应(Thundering Herd)。因此,通常会在计算出的等待时间基础上增加一定随机偏移量,例如:

1
实际等待时间 = 随机值(0 ~ 指数退避时间)

例如,理论等待:

1
40 秒

实际等待:

1
2
3
4
5
23  秒
31 秒
37 秒
15 秒
39 秒

这样可以有效打散客户端重试时间,降低瞬时流量峰值,提高系统整体稳定性。

总结

递增退避和指数退避都是失败重试中的经典策略,它们的共同目标都是降低失败请求对系统造成的压力,提高系统恢复能力。二者最大的区别在于等待时间的增长速度:

  • 递增退避算法:等待时间按固定步长增加,增长平缓,适用于短暂故障和对响应时间较敏感的业务。
  • 指数退避算法:等待时间按指数级增长,更适合服务端过载、限流或网络拥塞等场景,也是云计算和分布式系统中最常见的退避策略。

最佳实践

在生产环境中,推荐使用指数退避算法 + 最大等待时间 + 随机抖动的组合方案,这也是 AWS、Google Cloud、Kubernetes、gRPC 等主流平台和框架广泛采用的最佳实践。