avast/retry-go

GitHub: avast/retry-go

一个简洁易用的 Go 语言重试库,为任意函数调用提供灵活的重试策略与延迟退避机制。

Stars: 2939 | Forks: 177

# 重试 [![发布](https://img.shields.io/github/release/avast/retry-go.svg?style=flat-square)](https://github.com/avast/retry-go/releases/latest) [![软件许可证](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) ![GitHub Actions](https://static.pigsec.cn/wp-content/uploads/repos/cas/31/31c96a8fe552af584b5983afc39ddf2e71ca6806185aad0eda564c77331e5b3e.svg) [![Go Report Card](https://goreportcard.com/badge/github.com/avast/retry-go?style=flat-square)](https://goreportcard.com/report/github.com/avast/retry-go) [![Go 参考](https://pkg.go.dev/badge/github.com/avast/retry-go/v4.svg)](https://pkg.go.dev/github.com/avast/retry-go/v4) [![codecov.io](https://codecov.io/github/avast/retry-go/coverage.svg?branch=main)](https://codecov.io/github/avast/retry-go?branch=main) [![Sourcegraph](https://sourcegraph.com/github.com/avast/retry-go/-/badge.svg)](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分析, 日志审计