设计目标:
- 支持无状态函数重试
- 支持带返回数据的函数重试
- 支持指数退避 + 抖动
- 支持收集每次重试的error, 并返回
用户自定义配置Config, 默认重试次数3次, 间隔1s, 最大间隔10s,
package examples
import "time"
type Config struct {
attempts int
delay time.Duration
maxDelay time.Duration
}
func newDefaultConfig() *Config {
return &Config{
attempts: 3,
delay: time.Second,
maxDelay: time.Second * 10,
}
}
type Option func(*Config)
// 尝试次数
func Attempts(attempts int) Option {
return func(c *Config) {
c.attempts = attempts
}
}
// Dealy 间隔
func Delay(delay time.Duration) Option {
return func(c *Config) {
c.delay = delay
}
}
func MaxDelay(delay time.Duration) Option {
return func(c *Config) {
c.maxDelay = delay
}
}
自定义重试Error : 收集每次重试的error, 并统一返回
type Error []error
func (e Error) Error() string {
logWithNumber := make([]string, len(e))
for i, l := range e {
if l != nil {
logWithNumber[i] = fmt.Sprintf("#%d: %s", i+1, l.Error())
}
}
return fmt.Sprintf("All attempts fail:\n%s", strings.Join(logWithNumber, "\n"))
}
重试执行函数实现(默认开启抖动和指数退避, 不支持带返回数据的函数执行):
func Do(ctx context.Context, fn func() error, opts ...Option) error {
var errs Error
// default
c := newDefaultConfig()
// apply opts
for _, opt := range opts {
opt(c)
}
backoff := c.delay
for i := 0; i < c.attempts; i++ {
err := fn()
if err == nil {
return nil
}
errs = append(errs, err)
if i == c.attempts-1 {
return errs
}
// 指数退避 + 抖动
jitter := time.Duration(rand.Int63n(int64(backoff)))
sleep := backoff + jitter
timer := time.NewTimer(sleep)
select {
case <-ctx.Done():
timer.Stop()
return ctx.Err()
case <-timer.C:
}
backoff *= 2
if backoff > c.maxDelay {
backoff = c.maxDelay
}
}
return errs
}
测试:
package examples
import (
"context"
"errors"
"fmt"
"testing"
"time"
)
func TestDo(t *testing.T) {
ctx := context.Background()
err := Do(ctx,
func() error {
return callRemote() // 可能失败
},
Attempts(5),
Delay(time.Second*1),
MaxDelay(time.Second*10),
)
if err != nil {
fmt.Println(err.Error())
}
}
func callRemote() error {
fmt.Println("do error")
return errors.New("call Remote error")
//return nil
}
测试执行结果输出:

image.png
上述实现存在以下问题:
- 不支持带返回值的函数重试
- 指数退避的策略不支持用户自选
新增DoWithData函数, 并修改Do方法实现:
type RetryableFuncWithData[T any] func() (T, error)
func DoWithData[T any](ctx context.Context, retryableFunc RetryableFuncWithData[T], opts ...Option) (T, error) {
var emptyT T
var errs Error
c := newDefaultConfig()
for _, opt := range opts {
opt(c)
}
backoff := c.delay
for i := 0; i < c.attempts; i++ {
t, err := retryableFunc()
if err == nil {
return t, nil
}
errs = append(errs, err)
if i == c.attempts-1 {
return emptyT, errs
}
sleep := c.delay
//抖动
jitter := time.Duration(rand.Int63n(int64(backoff)))
sleep = backoff + jitter
timer := time.NewTimer(sleep)
select {
case <-ctx.Done():
timer.Stop()
return emptyT, ctx.Err()
case <-timer.C:
}
// 指数退避
if c.backoff {
backoff *= 2
if backoff > c.maxDelay {
backoff = c.maxDelay
}
}
}
return emptyT, errs
}
func Do(ctx context.Context, fn func() error, opts ...Option) error {
c := newDefaultConfig()
for _, opt := range opts {
opt(c)
}
retryableFuncWithData := func() (any, error) {
return nil, fn()
}
_, err := DoWithData(ctx, retryableFuncWithData, opts...)
return err
}