Skip to main content

指数退避策略

介绍

指数退避策略(Exponential Back-off Algorithm)是一种在网络通信和分布式系统中广泛使用的重试机制。当一个操作(例如网络请求或消息发送)失败时,该策略会使用一种算法来计算下一次重试之前需要等待的时间。

指数退避算法的基本思想是:每次重试之间等待的时间间隔逐渐增加,通常是通过乘以一个常数因子(通常为 2)来实现的。这种设计有两个主要目标:

  1. 减少拥塞: 当多个节点同时尝试重新访问资源时,可能会导致系统再次过载。指数退避可以帮助分散这些重试请求,从而减轻系统的压力。
  2. 避免无休止的重试: 如果在每次失败后立即重试,可能会陷入无限循环,特别是在问题没有得到解决的情况下。指数退避通过引入越来越长的等待时间来降低这种情况的可能性。

指数退避的具体实现可能包括一些变体,如二进制指数退避、随机化退避等。其中,二进制指数退避算法通常用于像 CSMA/CD 这样的网络协议中,它涉及到冲突窗口大小的概念,并根据冲突次数调整退避时间。

在实践中,指数退避策略经常被应用到网络编程、API 调用、数据库连接等领域,作为一种优雅且高效的错误恢复方法。许多现代的软件库和中间件,如 Envoy 和 AWS 的服务,都实现了指数退避作为其内部重试机制的一部分。

示例代码

package main

import (
"context"
"fmt"
"log"
"net/http"
"time"
)

func main() {
// 演示“指数退避策略”
//if err := WaitForServer("localhost"); err != nil {
// log.Fatal(err)
//}

// 演示“指数退避策略”
url := "localhost"
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)

go func() {
time.Sleep(30 * time.Second)
cancel()
}()

err := WaitForCallbackWithContext(ctx, func() error {
_, err := http.Head(url)
return err
})

if err != nil {
log.Fatal(err)
}
}

// WaitForCallbackWithContext 尝试执行cb函数,直到cb函数返回nil或者超时
func WaitForCallbackWithContext(ctx context.Context, cb func() error) error {
tries := 0

for {
select {
case <-ctx.Done():
return fmt.Errorf("timed out after %s", ctx.Err())
case <-time.After(time.Second << uint(tries)):
if err := cb(); err != nil {
log.Printf("server not responding (%s); retrying....\n", err)
tries++
} else {
return nil
}
}
}
}

// WaitForServer 尝试链接url对应的服务器
// 在一分钟之内,使用指数退避策略重试连接
// 所有的尝试失败后,返回错误
func WaitForServer(url string) error {
const timeout = 1 * time.Minute
deadline := time.Now().Add(timeout)

for tries := 0; time.Now().Before(deadline); tries++ {
_, err := http.Head(url)

if err == nil {
return nil
}

log.Printf("server not responding (%s); retrying....\n", err)
time.Sleep(time.Second << uint(tries))
}

return fmt.Errorf("server %s failed to respond after %s", url, timeout)
}