递增退避与指数退避算法介绍
前言
在分布式系统、微服务、网络请求、消息队列以及爬虫系统中,重试(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 | /** |
程序运行结果:
1 | Retry 1 -> 10000 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 | /** |
程序运行结果:
1 | Retry 1 -> 10000 ms |
两种算法对比
算法对比
| 对比项 | 递增退避算法 | 指数退避算法 |
|---|---|---|
| 增长方式 | 每次固定递增 | 每次翻倍 |
| 数学公式 | Base × Retry | Base × 2^(Retry - 1) |
| 增长速度 | 较慢、平缓 | 较快、激进 |
| 最大等待时间 | 增长较慢 | 增长很快 |
| 对服务器压力 | 恢复速度快 | 更有利于服务恢复 |
| 实现复杂度 | 简单 | 简单 |
下面可以直观看出二者的区别:
| 重试次数 | 递增退避算法 | 指数退避算法 |
|---|---|---|
| 第 1 次 | 10 秒 | 10 秒 |
| 第 2 次 | 20 秒 | 20 秒 |
| 第 3 次 | 30 秒 | 40 秒 |
| 第 4 次 | 40 秒 | 80 秒 |
| 第 5 次 | 50 秒 | 160 秒 |
| 第 6 次 | 60 秒 | 320 秒 |
增长趋势:
1 | 递增退避算法: 10 → 20 → 30 → 40 → 50 → 60 |
前两次等待时间几乎一样,但从第三次开始,指数退避算法的增长速度远高于递增退避算法。
适用场景
递增退避算法
- 优点:
- 等待时间增长平缓
- 用户体验较好
- 重试恢复速度快
- 缺点:
- 服务端压力下降较慢
- 大规模客户端同时重试时效果一般
- 适用场景:
- 短暂网络抖动
- 数据库偶发超时
- 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 | 23 秒 |
这样可以有效打散客户端重试时间,降低瞬时流量峰值,提高系统整体稳定性。
总结
递增退避和指数退避都是失败重试中的经典策略,它们的共同目标都是降低失败请求对系统造成的压力,提高系统恢复能力。二者最大的区别在于等待时间的增长速度:
- 递增退避算法:等待时间按固定步长增加,增长平缓,适用于短暂故障和对响应时间较敏感的业务。
- 指数退避算法:等待时间按指数级增长,更适合服务端过载、限流或网络拥塞等场景,也是云计算和分布式系统中最常见的退避策略。
最佳实践
在生产环境中,推荐使用指数退避算法 + 最大等待时间 + 随机抖动的组合方案,这也是 AWS、Google Cloud、Kubernetes、gRPC 等主流平台和框架广泛采用的最佳实践。
