前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 的重试弹性模式

golang 的重试弹性模式

作者头像
每周聚焦
发布2024-07-01 00:32:04
500
发布2024-07-01 00:32:04

golang 的可重构弹性模式。 创建重试器需要两个参数: 重试间隔的时间(隐含重试次数) 决定重试哪些错误的分类器

仓库给的例子:

代码语言:javascript
复制
go复制代码r := retrier.New(retrier.ConstantBackoff(3, 100*time.Millisecond), nil)

err := r.Run(func() error {
	// do some work
	return nil
})

if err != nil {
	// handle the case where the work failed three times
}

创建重试器时,传入了两个参数,一个是重试时间的间隔(它是一个 time.Duration 类型的数组,数组的长度就是它隐含的重试次数),另一个是分类器,可以决定哪些错误需要重试,哪些错误不需要重试。

重试器的结构体

代码语言:javascript
复制
go复制代码// Retrier implements the "retriable" resiliency pattern, abstracting out the process of retrying a failed action
// a certain number of times with an optional back-off between each retry.
// Retrier 实现了 "可重试 "弹性模式,将重试失败操作的过程抽象为
// 重试一定次数,每次重试之间可选择后退。
type Retrier struct {
 // 重试时间的间隔
	backoff []time.Duration
	// 分类器
	class   Classifier
	// 基数
	jitter  float64
	// 随机数种子
	rand    *rand.Rand
	// 计算休眠时间的锁
	randMu  sync.Mutex
}

新建一个重试器的函数

代码语言:javascript
复制
go复制代码// New constructs a Retrier with the given backoff pattern and classifier. The length of the backoff pattern
// indicates how many times an action will be retried, and the value at each index indicates the amount of time
// waited before each subsequent retry. The classifier is used to determine which errors should be retried and
// which should cause the retrier to fail fast. The DefaultClassifier is used if nil is passed.
// New 使用给定的后退模式和分类器构建一个 Retrier。后退模式的长度
// 每个索引的值表示每次重试前等待的时间。
// 每次重试前等待的时间。分类器用于确定哪些错误应重试,哪些错误应导致重试。
// 哪些错误会导致重试快速失败。如果传入的是 nil,则使用 DefaultClassifier。
func New(backoff []time.Duration, class Classifier) *Retrier {
  // 如果分类器为 nil,则使用默认的分类器
	if class == nil {
		class = DefaultClassifier{}
	}

	return &Retrier{
		backoff: backoff,
		class:   class,
		rand:    rand.New(rand.NewSource(time.Now().UnixNano())),
	}
}

如果传入的分类器为 nil,则使用默认的分类器

有三种不同的分类器

  • 默认分类器
  • 白名单分类器
  • 黑名单分类器

默认分类器以最简单的方式对错误进行分类。如果错误为 nil,则返回 Succeed,否则返回 Retry

WhitelistClassifier 根据白名单对错误进行分类。如果错误为 nil,则返回 Succeed;如果错误在白名单中,则返回 Retry;否则,它将返回 Fail。

BlacklistClassifier 根据黑名单对错误进行分类。如果错误为 nil,则返回 Succeed;如果错误在黑名单中,则返回 Fail;否则,它将返回 Retry。

重试器的执行有两个函数

一个是执行时,不用传入上下文字段的,实际执行还是调用了需要传入上下文字段的 RunCtx函数,只是传了个非 nil 的空 Context

代码语言:javascript
复制
go复制代码// Run executes the given work function by executing RunCtx without context.Context.
func (r *Retrier) Run(work func() error) error {
	return r.RunCtx(context.Background(), func(ctx context.Context) error {
		// never use ctx
		return work()
	})
}

一个是执行时,需要传入上下文字段的

代码语言:javascript
复制
go复制代码// RunCtx executes the given work function, then classifies its return value based on the classifier used
// to construct the Retrier. If the result is Succeed or Fail, the return value of the work function is
// returned to the caller. If the result is Retry, then Run sleeps according to the its backoff policy
// before retrying. If the total number of retries is exceeded then the return value of the work function
// is returned to the caller regardless.
// 分类器对其返回值进行分类。
// 构造 Retrier 所使用的分类器对其返回值进行分类。如果结果是 "成功 "或 "失败",工作函数的返回值将
// 返回给调用者。如果结果是重试,运行将根据其后退策略休眠,然后再重试。
// 在重试之前休眠。如果超过了重试的总次数,则工作函数的返回值
// 返回给调用者。
func (r *Retrier) RunCtx(ctx context.Context, work func(ctx context.Context) error) error {
	// 刚开始重试次数为 0
	retries := 0
	for {
	  // 执行工作函数(即我们想要进行处理的逻辑)
		ret := work(ctx)
	  // 分类器根据返回值,判断是否需要重试
		switch r.class.Classify(ret) {
		case Succeed, Fail:
			return ret
		case Retry:
		  // 如果重试次数大于等于隐含的重试次数,返回工作函数的返回值
			if retries >= len(r.backoff) {
				return ret
			}
			// 如果重试次数小于隐含的重试次数,根据当前已重试的次数,计算休眠的时间
			timeout := time.After(r.calcSleep(retries))
			// 执行休眠函数
			if err := r.sleep(ctx, timeout); err != nil {
				return err
			}

			retries++
		}
	}
}

计算休眠时间的函数

这里不理解的是为什么要加锁,看了测试用例,有可能会并发执行 Run 函数,但实际有场景会用得上吗?

这里还有一个基数的作为休息时间的随机性种子,可以通过 SetJitter 函数设置,jitter 的范围在 [0,1],否则设置无效,设置了基数后,回退时间在一定的范围内,比如你设置了基数为 0.25, backoff[i] 为 10 * time.Millisecond,那么这时的回退时间在 (7500 * time.Microsecond,12500*time.Microsecond)的范围内

代码语言:javascript
复制
go复制代码func (r *Retrier) calcSleep(i int) time.Duration {
	// lock unsafe rand prng
	r.randMu.Lock()
	defer r.randMu.Unlock()
	// take a random float in the range (-r.jitter, +r.jitter) and multiply it by the base amount
	return r.backoff[i] + time.Duration(((r.rand.Float64()*2)-1)*r.jitter*float64(r.backoff[i]))
}

休眠函数

从代码可以看出,如果到达时间范围了会返回 nil,然后 RunCtx 函数增加重试次数,继续重试,如果传入的上下文有带超时时长,这时候超时时间到了,返回错误,RunCtx 直接退出,这点也就是使用 Run 和 RunCtx 函数的唯一区别

代码语言:javascript
复制
go复制代码func (r *Retrier) sleep(ctx context.Context, t <-chan time.Time) error {
	select {
	case <-t:
		return nil
	case <-ctx.Done():
		return ctx.Err()
	}
}

创建重试器,传递超时时长时,有三个辅助函数

  • ConstantBackoff
    • ConstantBackoff 生成一个简单的回退策略,即重试“n”次,并在每次重试后等待相同的时间。
  • ExponentialBackoff
    • ExponentialBackoff 生成一个简单的回退策略,即重试 'n' 次,并将每次重试后等待的时间加倍。
  • LimitedExponentialBackoff
    • LimitedExponentialBackoff 生成一个简单的回退策略,即重试 'n' 次,并将每次重试后等待的时间加倍。如果回退达到 'limitAmount' ,则此后回退将填充 'limitAmount' 。

本文系转载,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文系转载前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档