Retry Jitter Exponential Backoff
Retry transient failures with capped exponential backoff plus jitter, classifying errors by retryability and giving up with context.
Code
package main
import (
"context"
"errors"
"fmt"
"math/rand"
"time"
)
var (
errTransient = errors.New("transient upstream failure")
errPermanent = errors.New("invalid argument")
)
func classify(err error) bool {
if errors.Is(err, errPermanent) {
return false // 永久错误不值得重试
}
return errors.Is(err, errTransient) || isNetworkErr(err)
}
func isNetworkErr(err error) bool {
var netErr interface{ Timeout() bool }
return errors.As(err, &netErr)
}
func backoff(attempt int, base, cap time.Duration) time.Duration {
exp := base << attempt // base * 2^attempt
if exp > cap || exp <= 0 {
exp = cap
}
jitter := time.Duration(rand.Int63n(int64(exp) / 2))
return exp - jitter // 全抖动:避免多客户端同步重试
}
func withRetry(ctx context.Context, fn func() error, maxAttempts int) error {
var lastErr error
for attempt := 0; attempt < maxAttempts; attempt++ {
if err := ctx.Err(); err != nil {
return fmt.Errorf("context done: %w", err)
}
if err := fn(); err == nil {
return nil
} else {
lastErr = err
if !classify(err) {
return fmt.Errorf("permanent failure: %w", err)
}
}
if attempt < maxAttempts-1 {
wait := backoff(attempt, 100*time.Millisecond, 5*time.Second)
select {
case <-time.After(wait):
case <-ctx.Done():
return ctx.Err()
}
}
}
return fmt.Errorf("gave up after %d attempts: %w", maxAttempts, lastErr)
}
func main() {
calls := 0
err := withRetry(context.Background(), func() error {
calls++
if calls < 3 {
return errTransient
}
return nil
}, 5)
fmt.Println(err, "calls:", calls)
}Report a problem with this exampleOpens a pre-filled issue on GitHub.