Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

重试机制:自适应重试控制策略 #269

Open
flycash opened this issue Dec 8, 2024 · 7 comments
Open

重试机制:自适应重试控制策略 #269

flycash opened this issue Dec 8, 2024 · 7 comments

Comments

@flycash
Copy link
Contributor

flycash commented Dec 8, 2024

仅限中文

使用场景

在超时的情况下,我们一般会考虑使用重试策略。但是重试会有一个问题:如果超时本身是因为高负载引起的,那么重试就会进一步加重这个系统负担。

所以实际上在重试的时候要区别是偶发性超时还是频繁超时。

为了达成这个目标,一种比较简单的做法是借助滑动窗口算法统计窗口内超时请求的比率。如果超时请求已经超过一定比率了,那么就不需要重试了。

可行方案

如果你有设计思路或者解决方案,请在这里提供。你可以提供多个方案,并且给出自己的选择

我们需要一个新的装饰器,这个装饰器只用来判定要不要执行重试,具体重试间隔,重试次数上限交给被装饰的来解决。整个结构体定义如下:

type AdaptiveTimeoutRetryStrategy struct {
    s Strategy 
    threshold int // 比特数量
    n int // 字节数量,如果为了保持和 threshold 一直,也可以是比特数量,但是注意用户可能会传入乱七八糟的比特数量
}

并且,我们这个方案必然是用在高并发场景下的——低并发场景你无脑重试就可以了,不存在什么系统负载高的问题。

因此为了节省磁盘空间,我们使用比特级别的 ring buffer 来实现。在整个实现里面,使用 n 个 byte 来作为环,也就是说有 8 * n 个比特。而后,如果一个请求过来,超时了,那么就把对应的比特位标记为 1。如果没超时,那么就标记为 0。

每次要重试的时候,就需要计算一下这些 byte 里面有多少个比特是 1,而后和 threshold 进行比较。如果超过了 threshold,那么就不能执行重试了。

但是在我们已有的接口设计中有一个小问题,就是 Next 方法不接收任何参数,所以为了适配这个,需要额外提供一个方法来上传它的执行结果并且询问下一个要不要执行重试。

在这种情况下,暂时定义一个新的方法:

// err 是上一次执行的结果,可以考虑定义一个 Result 结构体,但是我觉得没啥必要
func (s *AdaptiveTimeoutRetryStrategy) ReportAndNext(ctx context.Context, err error) (time.Duration, bool) {

}

其它

任何你觉得有利于解决问题的补充说明

这种设计会有一个问题,我们无法把 AdaptiveTimeoutRetryStrategy 传入到 retry.Retry 方法里面。所以另外一个可行的考虑是直接引入一个不兼容的变更,将 Next 方法改为:

Next(ctx context.Context, err error) (time.Duration, bool)

当然这对于我们来说就好多了,不过用户就要修改代码了。我个人认为其实我们的用户也不是很多,所以这样改也不是不可以。

你使用的是 ekit 哪个版本?

你设置的的 Go 环境?

上传 go env 的结果

@flycash
Copy link
Contributor Author

flycash commented Dec 8, 2024

用 ReportAndNext 还是用 Next,我觉得可以稍微讨论一下。如果你不想讨论,就用

Next(ctx context.Context, err error) (time.Duration, bool)

@Stone-afk
Copy link
Contributor

这里用Next会好一些,增加ReportAndNext 接口变复杂了,需要额外理解 ReportAndNext 和 Next 的区别,而且两个方法功能有重叠的,不如一了白了,都统一设计

@flycash
Copy link
Contributor Author

flycash commented Dec 9, 2024

可以只用 Next。我在想的是,如果是 Next 传入了 ctx 之后,我的 retry.Retry 方法不就是很没用了吗?因为我可以直接在实现 Next 的时候就直接阻塞了,都不需要返回 time.Duration

@Stone-afk
Copy link
Contributor

要么再显示支持wait 方法type RetryStrategy interface { Next(ctx context.Context, err error) (time.Duration, bool) Wait(ctx context.Context, interval time.Duration) error } 保持 retry.Retry 的核心职责不变, 语义也更直观,不过维护的方法也变多啦

@Stone-afk
Copy link
Contributor

第二种,就是传ctx就传,不管就行了,说明只返回时间戳

@Stone-afk
Copy link
Contributor

第三种,直接废弃 retry.Retry 😂

@flycash
Copy link
Contributor Author

flycash commented Dec 12, 2024

就用这个:

Next(ctx context.Context, err error) (time.Duration, bool)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants