前言
在编写应用程序时,有时候会遇到一些短暂的错误,例如网络请求、服务链接终端失败等,这些错误可能导致函数执行失败。
但是如果稍后执行可能会成功,那么在一些业务场景下就需要重试了,重试的概念很简单,这里就不做过多阐述了
最近也正好在转golang语言,重试机制正好可以拿来练手,重试功能一般需要支持以下参数
- execFunc:需要被执行的重试的函数
- interval:重试的间隔时长
- attempts:尝试次数
- conditionMode:重试的条件模式,error和bool模式(这个参数用于控制传递的执行函数返回值类型检测
代码
package retryimpl
import (
"fmt"
"time"
)
// RetryOptionV2 配置选项函数
type RetryOptionV2 func(retry *RetryV2)
// RetryFunc 不带返回值的重试函数
type RetryFunc func() error
// RetryFuncWithData 带返回值的重试函数
type RetryFuncWithData func() (any, error)
// RetryV2 重试类
type RetryV2 struct {
interval time.Duration // 重试的间隔时长
attempts int // 重试次数
}
// NewRetryV2 构造函数
func NewRetryV2(opts ...RetryOptionV2) *RetryV2 {
retry := RetryV2{
interval: DefaultInterval,
attempts: DefaultAttempts,
}
for _, opt := range opts {
opt(&retry)
}
return &retry
}
// WithIntervalV2 重试的时间间隔配置
func WithIntervalV2(interval time.Duration) RetryOptionV2 {
return func(retry *RetryV2) {
retry.interval = interval
}
}
// WithAttemptsV2 重试的次数
func WithAttemptsV2(attempts int) RetryOptionV2 {
return func(retry *RetryV2) {
retry.attempts = attempts
}
}
// DoV2 对外暴露的执行函数
func (r *RetryV2) DoV2(executeFunc RetryFunc) error {
fmt.Println("[Retry.DoV2] begin execute func...")
retryFuncWithData := func() (any, error) {
return nil, executeFunc()
}
_, err := r.DoV2WithData(retryFuncWithData)
return err
}
// DoV2WithData 对外暴露知的执行函数可以返回数据
func (r *RetryV2) DoV2WithData(execWithDataFunc RetryFuncWithData) (any, error) {
fmt.Println("[Retry.DoV2WithData] begin execute func...")
n := 0
for n < r.attempts {
res, err := execWithDataFunc()
if err == nil {
return res, nil
}
n++
time.Sleep(r.interval)
}
return nil, nil
}
测试验证
package retryimpl
import (
"errors"
"fmt"
"testing"
"time"
)
// TestRetryV2_DoFunc
func TestRetryV2_DoFunc(t *testing.T) {
testSuites := []struct {
exceptExecCount int
actualExecCount int
}{
{exceptExecCount: 3, actualExecCount: 0},
{exceptExecCount: 1, actualExecCount: 1},
}
for _, testSuite := range testSuites {
retry := NewRetryV2(
WithAttemptsV2(testSuite.exceptExecCount),
WithIntervalV2(1*time.Second),
)
err := retry.DoV2(func() error {
fmt.Println("[TestRetry_DoFuncBoolMode] was called ...")
if testSuite.exceptExecCount == 1 {
return nil
}
testSuite.actualExecCount++
return errors.New("raise error")
})
if err != nil {
t.Errorf("[TestRetryV2_DoFunc] retyr.DoV2 execute failed and err:%+v", err)
continue
}
if testSuite.actualExecCount != testSuite.exceptExecCount {
t.Errorf("[TestRetryV2_DoFunc] got actualExecCount:%v != exceptExecCount:%v", testSuite.actualExecCount, testSuite.exceptExecCount)
}
}
}
// TestRetryV2_DoFuncWithData
func TestRetryV2_DoFuncWithData(t *testing.T) {
testSuites := []struct {
exceptExecCount int
resMessage string
}{
{exceptExecCount: 3, resMessage: "fail"},
{exceptExecCount: 1, resMessage: "ok"},
}
for _, testSuite := range testSuites {
retry := NewRetryV2(
WithAttemptsV2(testSuite.exceptExecCount),
WithIntervalV2(1*time.Second),
)
res, err := retry.DoV2WithData(func() (any, error) {
fmt.Println("[TestRetryV2_DoFuncWithData] DoV2WithData was called ...")
if testSuite.exceptExecCount == 1 {
return testSuite.resMessage, nil
}
return testSuite.resMessage, errors.New("raise error")
})
if err != nil {
t.Errorf("[TestRetryV2_DoFuncWithData] retyr.DoV2 execute failed and err:%+v", err)
continue
}
if val, ok := res.(string); ok && val != testSuite.resMessage {
t.Errorf("[TestRetryV2_DoFuncWithData] got unexcept result:%+v", val)
continue
}
t.Logf("[TestRetryV2_DoFuncWithData] got result:%+v", testSuite.resMessage)
}
}