




















retry-go 实现非常优美的 retry 库
2022/06/15 仿写实现同样的功能 https://github.com/nickChenyx/retry-go-dummy
SomeErr& AnotherErr ,用来测试 retry-go 库的不同函数retry.Do(func() error, ...opt) 使用 Do 函数立马开始进行 retry 操作,简单的使用一个 func() error 包括将要被 retry 的代码retry.DelayType(func(n uint, err error, config *retry.Config) time.Duration) 主要能力是提供每次重试延迟的时间retry.OnRetry(func(n uint, err error) 主要能力是再触发 retry 操作的时候,前置执行该函数,可用于日志记录等retry.RetryIf(func(err error) bool 主要能力是判断是否要触发 retry,可以根据不同的错误类型选择是否要进行 retry 操作retry.Attempts(uint) 主要是设置重试次数,限制重试的时间retry.BackOffDelay(n, err, config) 使用在 retry.DelayType(...) 中,可以设置指数级增长的 delay 时间type SomeErr struct {
err string
retryAfter time.Duration
}
func (err SomeErr) Error() string {
return err.err
}
type AnotherErr struct {
err string
}
func (err AnotherErr) Error() string {
return err.err
}
func TestHttpGet(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "hello")
}))
defer ts.Close()
var body []byte
var retrySum uint
err := retry.Do(
func() error {
resp, err := http.Get(ts.URL)
ri := rand.Intn(10)
if ri < 3 {
err = SomeErr{
err: "some err",
retryAfter: time.Second,
}
} else if ri < 6 {
err = AnotherErr{
err: "another err",
}
}
if err == nil {
defer func() {
if err := resp.Body.Close(); err != nil {
panic(err)
}
}()
body, err = ioutil.ReadAll(resp.Body)
}
return err
},
retry.DelayType(func(n uint, err error, config *retry.Config) time.Duration {
switch e := err.(type) {
case SomeErr:
return e.retryAfter
case AnotherErr:
return retry.BackOffDelay(n, err, config)
default:
return time.Second
}
}),
retry.OnRetry(func(n uint, err error) { retrySum += 1 }),
retry.RetryIf(func(err error) bool {
switch err.(type) {
case SomeErr, AnotherErr:
return true
default:
return false
}
}),
retry.Attempts(3),
)
assert.NoError(t, err)
assert.NotEmpty(t, body)
}
type RetryableFunc func() error
func Do(retryableFunc RetryableFunc, opts ...Option) error
这里声明了三部分内容:
这里可以看出,待执行的任务会被 func() 包裹,没有额外的入参,但是可以抛出一个 error 作为任务异常的标志。后续重试行为依赖这个 error 信息
Do 函数提供了扩展能力,此处用的是 Options 模式(可以看另一篇 Options-Pattern 文章了解更多)
Do 函数返回了 error,此处描述的是整个 retry 结束过后,任务尚未成功,需要有一个结果
从默认配置中探索
retry-go库的设计思路
func newDefaultRetryConfig() *Config {
return &Config{
attempts: uint(10),
delay: 100 * time.Millisecond,
maxJitter: 100 * time.Millisecond,
onRetry: func(n uint, err error) {},
retryIf: IsRecoverable,
delayType: CombineDelay(BackOffDelay, RandomDelay),
lastErrorOnly: false,
context: context.Background(),
}
}
当 Do 函数的 options 为空时,该配置就是实际执行 Do 函数的运行时配置了。罗列一下配置项:
[0, maxJitter) 之间IsRecoverable 作用如下:func IsRecoverable(err error) bool {
_, isUnrecoverable := err.(unrecoverableError)
return !isUnrecoverable
}
可以看到这里当错误 err 是 unrecoverableError 时,就不会重试。也就是 retry-go 自定义了一个不可恢复的异常,同时提供了 Unrecoverable函数封装一个 unrecoverableError。如果用户知道了这个特性,就可以利用起来,从而中断重试。下面是 unrecoverableError 的定义:
type unrecoverableError struct {
error
}
func Unrecoverable(err error) error {
return unrecoverableError{err}
}
// CombineDelay(BackOffDelay, RandomDelay),
func CombineDelay(delays ...DelayTypeFunc) DelayTypeFunc {
const maxInt64 = uint64(math.MaxInt64)
return func(n uint, err error, config *Config) time.Duration {
var total uint64
for _, delay := range delays {
total += uint64(delay(n, err, config))
if total > maxInt64 {
total = maxInt64
}
}
return time.Duration(total)
}
}
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 nickchenyx@gmail.com
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。