avast/retry-go
GitHub: avast/retry-go
一个简洁易用的 Go 语言重试库,为任意函数调用提供灵活的重试策略与延迟退避机制。
Stars: 2939 | Forks: 177
# 重试
[](https://github.com/avast/retry-go/releases/latest)
[](LICENSE.md)

[](https://goreportcard.com/report/github.com/avast/retry-go)
[](https://pkg.go.dev/github.com/avast/retry-go/v4)
[](https://codecov.io/github/avast/retry-go?branch=main)
[](https://sourcegraph.com/github.com/avast/retry-go?badge)
用于重试机制的简单库
灵感略受以下项目启发
[Try::Tiny::Retry](https://metacpan.org/pod/Try::Tiny::Retry)
# 概要
带重试的 HTTP GET:
```
url := "http://example.com"
var body []byte
err := retry.New(
retry.Attempts(5),
retry.Delay(100*time.Millisecond),
).Do(
func() error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
return nil
},
)
if err != nil {
// handle error
}
fmt.Println(string(body))
```
带重试和数据的 HTTP GET:
```
url := "http://example.com"
body, err := retry.DoWithData(retry.New(),
func() ([]byte, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return body, nil
},
)
if err != nil {
// handle error
}
fmt.Println(string(body))
```
用于高频重试操作的可复用 Retrier:
```
// Create retrier once, reuse many times
retrier := retry.New(
retry.Attempts(5),
retry.Delay(100*time.Millisecond),
)
// Minimal allocations in happy path
for {
err := retrier.Do(
func() error {
return doWork()
},
)
if err != nil {
// handle error
}
}
```
[更多示例](https://github.com/avast/retry-go/tree/main/examples)
# 参见
* [codeGROOVE-dev/retry](https://github.com/codeGROOVE-dev/retry) - 现代分支
专注于正确性、可靠性和效率的 avast/retry-go/v4。100% API 兼容的直接替代品。看起来真的很不错。
* [giantswarm/retry-go](https://github.com/giantswarm/retry-go) - 略显复杂的接口。
* [sethgrid/pester](https://github.com/sethgrid/pester) - 仅用于带重试和退避的 http 调用的 http 重试
* [cenkalti/backoff](https://github.com/cenkalti/backoff) - Google 的 HTTP Client Library for Java 中指数退避算法的 Go 移植版。接口确实很复杂。
* [rafaeljesus/retry-go](https://github.com/rafaeljesus/retry-go) - 看起来不错,与本包稍微有点类似,但没有“简单的” `Retry` 方法
* [matryer/try](https://github.com/matryer/try) - 非常流行的包,接口不够直观(对我来说)
# 破坏性更新
* 5.0.0
- 完全重新设计了 API:基于方法的重试操作
- 将 `Config` 类型重命名为 `Retrier`
- 将 `NewConfig()` 重命名为 `New()`
- 从包级函数更改为方法:`retry.Do(func, config)` → `retry.New(opts...).Do(func)`
- `DelayTypeFunc` 签名更改:`func(n uint, err error, config *Config)` → `func(n uint, err error, r *Retrier)`
- 迁移:`retry.Do(func, opts...)` → `retry.New(opts...).Do(func)`(简单的查找与替换)
- 此更改提高了性能,简化了 API,并提供了更整洁的接口
- `Unwrap()` 现在返回 `[]error` 而不是 `error` 以支持 Go 1.20 的多重错误包装。
- `errors.Unwrap(err)` 现在将返回 `nil`(与 `errors.Join` 相同)。使用 `errors.Is` 或 `errors.As` 来检查包装的错误。
* 4.0.0
- 通过 PR [#49](https://github.com/avast/retry-go/pull/49) 设置 `Attempts(0)` 即可进行无限重试
* 3.0.0
- `DelayTypeFunc` 接受一个新参数 `err` - 此破坏性更新仅会影响您的自定义延迟函数。此更改允许[根据错误制定延迟函数](examples/delay_based_on_error_test.go)。
* 1.0.2 -> 2.0.0
- `retry.Delay` 的参数是最终延迟(不再乘以 `retry.Units`)
- 删除了 `retry.Units` 函数
- [关于此破坏性更新的更多信息](https://github.com/avast/retry-go/issues/7)
* 0.3.0 -> 1.0.0
- `retry.Retry` 函数已更改为 `retry.Do` 函数
- `retry.RetryCustom` (OnRetry) 和 `retry.RetryCustomWithOpts` 函数现在通过生成选项的函数(即 `retry.OnRetry`)来实现
## 用法
#### func BackOffDelay
```
func BackOffDelay(n uint, _ error, config DelayContext) time.Duration
```
BackOffDelay 是一种 DelayType,它会增加连续重试之间的延迟
#### func FixedDelay
```
func FixedDelay(_ uint, _ error, config DelayContext) time.Duration
```
FixedDelay 是一种 DelayType,它在所有迭代中保持延迟不变
#### func FullJitterBackoffDelay
```
func FullJitterBackoffDelay(n uint, err error, config DelayContext) time.Duration
```
FullJitterBackoffDelay 是一个 DelayTypeFunc,它使用带有 full jitter 的指数退避来计算延迟。延迟是一个介于 0 和当前退避上限之间的随机值。公式:sleep = random_between(0, min(cap, base *
2^attempt)) 它使用 config.Delay 作为基础延迟,并使用 config.MaxDelay 作为上限。
#### func IsRecoverable
```
func IsRecoverable(err error) bool
```
IsRecoverable 检查 error 是否为 `unrecoverableError` 的实例
#### func RandomDelay
```
func RandomDelay(_ uint, _ error, config DelayContext) time.Duration
```
RandomDelay 是一种 DelayType,它会选取一个不超过 maxJitter 的随机延迟
#### func Unrecoverable
```
func Unrecoverable(err error) error
```
Unrecoverable 将 error 包装在 `unrecoverableError` 结构体中
#### type DelayContext
```
type DelayContext interface {
Delay() time.Duration
MaxJitter() time.Duration
MaxBackOffN() uint
MaxDelay() time.Duration
}
```
DelayContext 提供延迟计算所需的配置值。
#### type DelayTypeFunc
```
type DelayTypeFunc func(n uint, err error, config DelayContext) time.Duration
```
当可重试函数在 `n` 次尝试后因 `err` 失败时,会调用 DelayTypeFunc 返回下一次要等待的延迟。
#### func CombineDelay
```
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc
```
CombineDelay 是一种将所有指定的延迟组合成一个新的 DelayTypeFunc 的 DelayType
#### type Error
```
type Error []error
```
Error 类型表示重试中的错误列表
#### func (Error) As
```
func (e Error) As(target interface{}) bool
```
#### func (Error) Error
```
func (e Error) Error() string
```
Error 方法返回 Error 的字符串表示形式。它是
error 接口的实现
#### func (Error) Is
```
func (e Error) Is(target error) bool
```
#### func (Error) LastError
```
func (e Error) LastError() error
```
LastError 返回错误列表中的最后一个错误。
对于从 retry-go v4.x 迁移过来的用户来说,这是一个便捷方法,在 v4.x 中
errors.Unwrap(err) 返回的是最后一个错误。在 v5.0.0 中,为了兼容 Go 1.20 而切换到了 Unwrap() []error,
因此 errors.Unwrap(err) 返回 nil。
迁移示例:
```
// v4.x code:
lastErr := errors.Unwrap(retryErr)
// v5.0.0 code (option 1 - recommended):
if errors.Is(retryErr, specificError) { ... }
// v5.0.0 code (option 2 - if you need the last error):
lastErr := retryErr.(retry.Error).LastError()
```
注意:建议使用 errors.Is 或 errors.As,因为它们会检查所有包装的
错误,而不仅仅是最后一个。
#### func (Error) Unwrap
```
func (e Error) Unwrap() []error
```
Unwrap 返回此 Error 正在包装的错误列表。
此方法实现了 Go 1.20 中引入的用于多重错误解包的 Unwrap() []error 接口。这使得 errors.Is 和 errors.As 可以遍历所有包装的错误,而不仅仅是最后一个。
重要提示:errors.Unwrap(err) 将返回 nil,因为标准库的
errors.Unwrap 函数只会调用 Unwrap() error,而不会调用 Unwrap() []error。这与
Go 1.20 中 errors.Join 的行为相同。
示例 - 使用 errors.Is 检查特定错误:
```
err := retry.New(retry.Attempts(3)).Do(func() error {
return os.ErrNotExist
})
if errors.Is(err, os.ErrNotExist) {
// Handle not exist error
}
```
示例 - 使用 errors.As 提取错误详情:
```
var pathErr *fs.PathError
if errors.As(err, &pathErr) {
fmt.Println("Failed at path:", pathErr.Path)
}
```
示例 - 直接获取最后一个错误(用于迁移):
```
if retryErr, ok := err.(retry.Error); ok {
lastErr := retryErr.LastError()
}
```
另见:LastError() 用于直接访问最后一个错误。
#### func (Error) WrappedErrors
```
func (e Error) WrappedErrors() []error
```
WrappedErrors 返回此 Error 正在包装的错误列表。它是
[errwrap](https://github.com/hashicorp/errwrap) 包中 `errwrap.Wrapper` 接口的实现,以便 `retry.Error` 可以与该库一起使用。
#### type OnRetryFunc
```
type OnRetryFunc func(attempt uint, err error)
```
OnRetry 函数的函数签名
#### type Option
```
type Option func(*retrierCore)
```
Option 表示一个重试选项。
#### func Attempts
```
func Attempts(attempts uint) Option
```
Attempts 设置重试次数。设置为 0 将一直重试,直到被重试的函数成功为止。默认值为 10
#### func AttemptsForError
```
func AttemptsForError(attempts uint, err error) Option
```
AttemptsForError 设置在执行导致给定 `err` 的情况下的重试次数。
针对给定 `err` 的重试也会计入总重试次数。如果任一给定重试次数耗尽,重试就会停止。
在 4.3.0 版本中添加
#### func Context
```
func Context(ctx context.Context) Option
```
Context 允许设置重试的 context,默认为 Background context
立即取消的示例(也许这不是最好的示例,但它足以描述行为;我希望如此)
```
ctx, cancel := context.WithCancel(context.Background())
cancel()
retry.New(
retry.Context(ctx),
).Do(
func() error {
...
},
)
```
#### func Delay
```
func Delay(delay time.Duration) Option
```
Delay 设置重试之间的延迟,默认为 100ms
#### func DelayType
```
func DelayType(delayType DelayTypeFunc) Option
```
DelayType 设置重试之间的延迟类型,默认是 BackOffDelay 和 RandomDelay 的组合,用于带抖动的指数退避
#### func LastErrorOnly
```
func LastErrorOnly(lastErrorOnly bool) Option
```
仅返回来自被重试函数的直接最后一个错误,默认值为 false(返回包含所有内容的包装错误)
#### func MaxDelay
```
func MaxDelay(maxDelay time.Duration) Option
```
MaxDelay 设置重试之间的最大延迟,默认不应用
#### func MaxJitter
```
func MaxJitter(maxJitter time.Duration) Option
```
MaxJitter 为 RandomDelay 设置重试之间的最大随机 Jitter
#### func OnRetry
```
func OnRetry(onRetry OnRetryFunc) Option
```
OnRetry 函数回调在每次重试时被调用
记录每次重试的示例:
```
retry.New(
retry.OnRetry(func(n uint, err error) {
log.Printf("#%d: %s\n", n, err)
}),
).Do(
func() error {
return errors.New("some error")
},
)
```
#### func RetryIf
```
func RetryIf(retryIf RetryIfFunc) Option
```
RetryIf 控制在发生错误后是否应尝试重试(假设还有剩余的重试次数)
在特定错误下跳过重试的示例:
```
retry.New(
retry.RetryIf(func(err error) bool {
if err.Error() == "special error" {
return false
}
return true
}),
).Do(
func() error {
return errors.New("special error")
},
)
```
默认情况下,如果错误是使用 `retry.Unrecoverable` 包装的,RetryIf 会停止执行,因此上面的示例也可以简写为:
```
retry.New().Do(
func() error {
return retry.Unrecoverable(errors.New("special error"))
},
)
```
#### func UntilSucceeded
```
func UntilSucceeded() Option
```
UntilSucceeded 将重试直到被重试的函数成功为止。等同于设置 Attempts(0)。
#### func WithTimer
```
func WithTimer(t Timer) Option
```
WithTimer 提供了一种替换 timer 模块实现的方法。这主要用于模拟/测试,在这些情况下,您可能不想明确地等待设定的重试持续时间。
在 time.After 上增加 print 语句的示例
```
type struct MyTimer {}
func (t *MyTimer) After(d time.Duration) <- chan time.Time {
fmt.Print("Timer called!")
return time.After(d)
}
retry.New(
retry.WithTimer(&MyTimer{}),
).Do(
func() error { ... },
)
```
#### func WrapContextErrorWithLastError
```
func WrapContextErrorWithLastError(wrapContextErrorWithLastError bool) Option
```
WrapContextErrorWithLastError 允许返回与被重试函数返回的最后一个错误包装在一起的 context 错误。这仅在 Attempts 设置为 0 以进行无限重试并使用 context 进行取消/超时时适用
默认为 false
```
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
retry.New(
retry.Context(ctx),
retry.Attempts(0),
retry.WrapContextErrorWithLastError(true),
).Do(
func() error {
...
},
)
```
#### type Retrier
```
type Retrier struct {
}
```
Retrier 用于仅返回 error 的重试操作。
#### func New
```
func New(opts ...Option) *Retrier
```
New 使用给定的选项创建一个新的 Retrier。返回的 Retrier 可以在多次重试操作中安全地复用。
#### func (Retrier) Delay
```
func (r Retrier) Delay() time.Duration
```
Delay 实现了 DelayContext
#### func (*Retrier) Do
```
func (r *Retrier) Do(retryableFunc RetryableFunc) error
```
Do 使用此 Retrier 的配置执行可重试函数。
#### func (Retrier) MaxBackOffN
```
func (r Retrier) MaxBackOffN() uint
```
MaxBackOffN 实现了 DelayContext
#### func (Retrier) MaxDelay
```
func (r Retrier) MaxDelay() time.Duration
```
MaxDelay 实现了 DelayContext
#### func (Retrier) MaxJitter
```
func (r Retrier) MaxJitter() time.Duration
```
MaxJitter 实现了 DelayContext
#### type RetrierWithData
```
type RetrierWithData[T any] struct {
}
```
RetrierWithData 用于返回数据和 error 的重试操作。
#### func NewWithData
```
func NewWithData[T any](opts ...Option) *RetrierWithData[T]
```
NewWithData 使用给定的选项创建一个新的 RetrierWithData[T]。返回的 retrier 可以在多次重试操作中安全地复用。
#### func (RetrierWithData) Delay
```
func (r RetrierWithData) Delay() time.Duration
```
Delay 实现了 DelayContext
#### func (*RetrierWithData[T]) Do
```
func (r *RetrierWithData[T]) Do(retryableFunc RetryableFuncWithData[T]) (T, error)
```
Do 使用此 RetrierWithData 的配置执行可重试函数。
#### func (RetrierWithData) MaxBackOffN
```
func (r RetrierWithData) MaxBackOffN() uint
```
MaxBackOffN 实现了 DelayContext
#### func (RetrierWithData) MaxDelay
```
func (r RetrierWithData) MaxDelay() time.Duration
```
MaxDelay 实现了 DelayContext
#### func (RetrierWithData) MaxJitter
```
func (r RetrierWithData) MaxJitter() time.Duration
```
MaxJitter 实现了 DelayContext
#### type RetryIfFunc
```
type RetryIfFunc func(error) bool
```
retry if 函数的函数签名
#### type RetryableFunc
```
type RetryableFunc func() error
```
可重试函数的函数签名
#### type RetryableFuncWithData
```
type RetryableFuncWithData[T any] func() (T, error)
```
带数据的可重试函数的函数签名
#### type Timer
```
type Timer interface {
After(time.Duration) <-chan time.Time
}
```
Timer 表示用于跟踪重试时间的计时器。
### Makefile
Makefile 提供了几个方便的规则,比如 README.md `generator`、用于准备构建/开发环境的 `setup`、`test`、`cover` 等...
尝试 `make help` 获取更多信息。
### 在 Pull Request 之前
请尝试:
* 运行测试 (`make test`)
* 运行 linter (`make lint`)
* 如果您的 IDE 没有自动执行 `go fmt`,请运行 `go fmt` (`make fmt`)
README
README.md 是通过模板 [.godocdown.tmpl](.godocdown.tmpl) 和代码文档经由 [godocdown](https://github.com/robertkrimen/godocdown) 生成的。
切勿直接编辑 README.md,因为您的更改将会丢失。
标签:EVTX分析, 日志审计