NOTE
retry
1. 是什么 调用远程服务失败,重新调用一次有可能就成功了 2. 什么时候需要重试 网络错误能重试,逻辑错误没必要 当网络错误长时间持续的时候不能使用重试,防止进一步压垮服务 不是幂等请求,不能重试 3. 重试策略 快速失败和重试的权衡 如果重试多次之后还是失败,那么不应该继续重试。参考断路器模式 4. Golang重试 4.1. retry 相当于golang版本的ribbon,定义重试策略 5. 参考 avast/retry\ go
这是历史学习笔记,可能存在过时或不完整的理解。
1. 是什么
调用远程服务失败,重新调用一次有可能就成功了
2. 什么时候需要重试
- 网络错误能重试,逻辑错误没必要
- 当网络错误长时间持续的时候不能使用重试,防止进一步压垮服务
- 不是幂等请求,不能重试
3. 重试策略
- 快速失败和重试的权衡
- 如果重试多次之后还是失败,那么不应该继续重试。参考断路器模式
4. Golang重试
4.1. retry
相当于golang版本的ribbon,定义重试策略
const (
defaultDelay = time.Millisecond * 100
defaultMaxDelay = time.Second * 1
defaultAttempts = uint(3)
)
// GetRetryOptions 获取重试参数
func GetRetryOptions(ctx context.Context, funcName string) []retry.Option {
delay := defaultDelay
maxDelay := defaultMaxDelay
attempts := defaultAttempts
deadline, ok := ctx.Deadline()
//如果上游设置了deadline,那么根据这个deadline来决定重试次数
if ok {
remainTime := deadline.Sub(time.Now())
if remainTime < defaultDelay {
return []retry.Option{
retry.Context(ctx),
retry.Attempts(1),
}
}
attempts = uint(remainTime / defaultDelay)
maxDelay = remainTime
}
return []retry.Option{
retry.Context(ctx),
retry.Attempts(attempts),
retry.Delay(delay),
retry.MaxDelay(maxDelay),
retry.RetryIf(func(err error) bool {
return constant.IsNetworkError(err)
}),
retry.OnRetry(func(n uint, err error) {
log.InfoContextf(ctx, "%s第%v次请求, err:%v", funcName, n+1,
err)
if err != nil {
metrics.Counter(fmt.Sprintf("%s第%v次请求失败", funcName,
n+1)).Incr()
}
}),
}
}
const (
// 成功
Suc = 0
// 不可重试
NonRetry = 1
// 可重试
Retry = 2
)
func TestGetRetryOptions(t *testing.T) {
convey.Convey("Retry", t, func() {
convey.Convey("【成功】请求一次", func() {
ctx := trpc.BackgroundContext()
requestTimes := 0
err := retry.Do(func() error {
return logic(ctx, Suc, &requestTimes)
}, GetRetryOptions(ctx, "logic")...)
convey.So(requestTimes == 1, convey.ShouldBeTrue)
convey.So(err, convey.ShouldBeNil)
})
convey.Convey("【失败】不可重试错误-只请求一次", func() {
ctx := trpc.BackgroundContext()
requestTimes := 0
err := retry.Do(func() error {
return logic(ctx, NonRetry, &requestTimes)
}, GetRetryOptions(ctx, "logic")...)
convey.So(requestTimes == 1, convey.ShouldBeTrue)
convey.So(err, convey.ShouldNotBeNil)
})
convey.Convey("【失败】可重试错误且上游未设置超时-默认请求3次", func() {
ctx := trpc.BackgroundContext()
requestTimes := 0
err := retry.Do(func() error {
return logic(ctx, Retry, &requestTimes)
}, GetRetryOptions(ctx, "logic")...)
convey.So(requestTimes == 3, convey.ShouldBeTrue)
convey.So(err, convey.ShouldNotBeNil)
})
convey.Convey("【失败】可重试错误且上游设置超时-根据上游时间计算请求次数", func() {
ctx, cancel := context.WithTimeout(trpc.BackgroundContext(), time.Millisecond*200)
defer cancel()
requestTimes := 0
err := retry.Do(func() error {
return logic(ctx, Retry, &requestTimes)
}, GetRetryOptions(ctx, "logic")...)
convey.So(requestTimes == 2, convey.ShouldBeTrue)
convey.So(err, convey.ShouldNotBeNil)
})
convey.Convey("【失败】可重试错误且上游设置超时-根据上游时间计算请求次数至少一次", func() {
ctx, cancel := context.WithTimeout(trpc.BackgroundContext(), time.Millisecond*100)
defer cancel()
requestTimes := 0
err := retry.Do(func() error {
return logic(ctx, Retry, &requestTimes)
}, GetRetryOptions(ctx, "logic")...)
convey.So(requestTimes == 1, convey.ShouldBeTrue)
convey.So(err, convey.ShouldNotBeNil)
})
convey.Convey("【失败】可重试错误且上游设置超时-context取消直接返回error请求0次", func() {
ctx, cancel := context.WithTimeout(trpc.BackgroundContext(), time.Millisecond*100)
cancel()
requestTimes := 0
err := retry.Do(func() error {
return logic(ctx, Retry, &requestTimes)
}, GetRetryOptions(ctx, "logic")...)
convey.So(requestTimes == 0, convey.ShouldBeTrue)
convey.So(err, convey.ShouldNotBeNil)
})
})
}
func logic(ctx context.Context, t int, requestTimes *int) error {
*requestTimes++
log.InfoContextf(ctx, "doing logic...")
switch t {
case Retry:
return errs.NewFrameError(errs.RetClientNetErr, "可重试错误")
case NonRetry:
return errs.New(-1, "不可重试错误")
default:
return nil
}
}
// IsNetworkError 网络错误
func IsNetworkError(e error) bool {
if e == nil {
return false
}
var code int
err, ok := e.(*errs.Error)
if ok {
code = errs.Code(err)
} else {
code = errors.Code(e)
}
return code == errs.RetClientNetErr || code == errs.RetClientTimeout ||
code == errs.RetClientConnectFail || code == errs.RetServerTimeout || code == errs.RetServerSystemErr
}
讨论
使用 GitHub 账号参与讨论,评论会保存在 GitHub Issues 中。在 GitHub 查看