在Go语言项目里,调用第三方接口、访问数据库或执行远程过程调用时,经常会遇到瞬时错误。为了提升服务的容错能力,我们可以自己实现一个简单的错误重试机制,灵活控制最大重试次数与重试间隔,避免程序因为偶发故障直接崩溃。

基础重试逻辑
最直观的做法是用一个for循环,在达到最大重试次数之前不断执行目标函数,如果返回错误就等待指定间隔后再次尝试。
package main
import (
"errors"
"fmt"
"time"
)
// 模拟可能失败的操作
func doSomething() error {
return errors.New("临时错误")
}
// 简单重试:maxRetry为最大重试次数,interval为重试间隔
func retry(maxRetry int, interval time.Duration, fn func() error) error {
var err error
for i := 0; i <= maxRetry; i++ {
err = fn()
if err == nil {
return nil
}
if i < maxRetry {
time.Sleep(interval)
}
}
return fmt.Errorf("重试 %d 次后仍失败: %w", maxRetry, err)
}
func main() {
err := retry(3, 2*time.Second, doSomething)
if err != nil {
fmt.Println(err)
}
}
使用context控制取消与超时
在生产环境中,重试过程应该能被外部取消,或者受总体超时约束。我们可以把context.Context传入重试函数,在等待间隔前检查是否被取消。
package main
import (
"context"
"errors"
"fmt"
"time"
)
func doSomething() error {
return errors.New("临时错误")
}
// 带context的重试函数
func retryWithContext(ctx context.Context, maxRetry int, interval time.Duration, fn func() error) error {
var err error
for i := 0; i <= maxRetry; i++ {
select {
case <-ctx.Done():
return ctx.Err()
default:
}
err = fn()
if err == nil {
return nil
}
if i < maxRetry {
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(interval):
}
}
}
return fmt.Errorf("重试 %d 次后仍失败: %w", maxRetry, err)
}
func main() {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
err := retryWithContext(ctx, 3, 2*time.Second, doSomething)
if err != nil {
fmt.Println(err)
}
}
增加指数退避与抖动
如果所有客户端都用固定间隔重试,故障恢复时容易产生请求洪峰。我们可以在每次重试时让间隔乘以倍数,并加入随机抖动。
package main
import (
"context"
"errors"
"fmt"
"math/rand"
"time"
)
func doSomething() error {
return errors.New("临时错误")
}
// 指数退避重试
func retryBackoff(ctx context.Context, maxRetry int, base time.Duration, fn func() error) error {
var err error
for i := 0; i <= maxRetry; i++ {
if ctx.Err() != nil {
return ctx.Err()
}
err = fn()
if err == nil {
return nil
}
if i < maxRetry {
// 间隔 = base * 2^i + 随机抖动
sleep := base*time.Duration(1<<uint(i)) + time.Duration(rand.Intn(500))*time.Millisecond
select {
case <-ctx.Done():
return ctx.Err()
case <-time.After(sleep):
}
}
}
return fmt.Errorf("重试 %d 次后仍失败: %w", maxRetry, err)
}
func main() {
ctx := context.Background()
err := retryBackoff(ctx, 4, time.Second, doSomething)
if err != nil {
fmt.Println(err)
}
}
小结
通过以上方式,我们可以用Golang轻松实现带有最大重试次数和间隔控制的错误重试机制。在真实业务中,建议结合context做超时控制,并采用指数退避降低对依赖服务的冲击。对于不需要重试的永久性错误,也可以在fn内部做判断直接返回特定错误来中断重试。