Golang Go语言中代码分享:延迟时间指数递增的重试机制实现

发布于 1周前 作者 sinazl 来自 Go语言

在工作中, 我们经常会碰到重试的需求, 很多人会用定时任务来实现. 如果有更高的要求, 可以尝试下"延迟时间指数递增"的策略, 相比定时任务, 更加节省 CPU 和数据库资源.

为简化逻辑, 代码中没有使用 RedisMySQL 维护失败次数.

Quick Start

package main

import ( “errors” “fmt” “github.com/lxzan/memorycache” “math” “sync/atomic” “time” )

const ( MaxRetryTimes = 17 // 最大重试次数 FirstDelay = 5 * time.Second // 初始延迟 )

func NewRetryer() Retryer { return &Retryer{ mc: memorycache.New[string, string]( memorycache.WithBucketNum(16), memorycache.WithBucketSize(16, math.MaxInt64), memorycache.WithInterval(time.Second, 5time.Second), ), } }

type Retryer struct { mc *memorycache.MemoryCache[string, string] }

func (c *Retryer) Add(jobId string, jobFunc func() error) { // 检查任务是否已存在 if _, exist := c.mc.GetOrCreate(jobId, jobId, -1); exist { return }

var callback = func(element *memorycache.Element[string, string], reason memorycache.Reason) {
	// 只处理过期触发的回调
	if reason != memorycache.ReasonExpired {
		return
	}

	// 回调函数必须是非阻塞的; 酌情使用任务队列, 控制最大并发.
	go func(id string) {
		if err := jobFunc(); err == nil {
			c.delete(id)
		}
	}(element.Value)
}

// 注册延迟及回调函数
var failedTimes = 1
var delay, delta = FirstDelay, FirstDelay
for i := 1; i <= MaxRetryTimes; i++ {
	if i >= failedTimes {
		var key = fmt.Sprintf("%s-%d", jobId, i)
		c.mc.SetWithCallback(key, jobId, delay, callback)
	}
	delta *= 2
	delay += delta
}

}

// 任务执行成功, 删除剩余的重试任务 func (c *Retryer) delete(jobId string) { for i := 1; i <= MaxRetryTimes; i++ { var key = fmt.Sprintf("%s-%d", jobId, i) c.mc.Delete(key) } c.mc.Delete(jobId) }

// 模拟一个任务 func newJob() (jobId string, jobFunc func() error) { var counter = new(atomic.Int64) return “1”, func() error { defer counter.Add(1) serial := counter.Load() if serial < 5 { fmt.Printf(“serial=%d, t=%d, success=false\n”, serial, time.Now().Unix()) return errors.New(“test”) } fmt.Printf(“serial=%d, t=%d, success=true\n”, serial, time.Now().Unix()) return nil } }

func main() { retryer := NewRetryer() jobId, jobFunc := newJob() if err := jobFunc(); err != nil { retryer.Add(jobId, jobFunc) } select {} }

Output

serial=0, t=1705196714, success=false
serial=1, t=1705196719, success=false
serial=2, t=1705196729, success=false
serial=3, t=1705196749, success=false
serial=4, t=1705196789, success=false
serial=5, t=1705196869, success=true

Golang Go语言中代码分享:延迟时间指数递增的重试机制实现

更多关于Golang Go语言中代码分享:延迟时间指数递增的重试机制实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html

9 回复

指数退避么

更多关于Golang Go语言中代码分享:延迟时间指数递增的重试机制实现的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


单调递增

这有啥好分享的,又不是大学生

现在连重试都需要上 redis 了吗。。

本地重试不需要,分布式异步重试就得有地方存储了

我就用 redis 保存下失败次数,不想用 mysql

设计上
建议独立一个服务做调度功能
比如延迟调度、循环调度、递增调度 通过 GRPC 进行 timeout 或者 callback

分布式是该这样

在Go语言中实现延迟时间指数递增的重试机制,可以通过使用time.Sleepmath/rand包来实现。这种机制常用于网络请求、数据库连接等可能因短暂故障而失败的场景,通过指数级增长的延迟时间减少重试频率,避免频繁的重试导致系统负载过高。

以下是一个简单的实现示例:

package main

import (
	"fmt"
	"math/rand"
	"time"
)

func retryWithExponentialBackoff(attempts int, delay time.Duration, maxDelay time.Duration) {
	var sleep time.Duration
	for i := 0; i < attempts; i++ {
		// 执行操作,这里用fmt.Println模拟
		fmt.Println("Attempt", i+1)
		// 假设操作失败,进行重试
		// 延迟时间按指数递增,但不超过maxDelay
		sleep = delay * time.Duration(1<<uint(i))
		if sleep > maxDelay {
			sleep = maxDelay
		}
		// 加入随机抖动,避免所有请求集中在同一时间
		sleep = addJitter(sleep, 0.2)
		time.Sleep(sleep)
	}
}

func addJitter(d time.Duration, jitterFraction float64) time.Duration {
	jitter := time.Duration(rand.Float64() * jitterFraction * float64(d))
	return d - jitter/2 + jitter // 保证结果为正数
}

func main() {
	retryWithExponentialBackoff(5, 200*time.Millisecond, 2*time.Second)
}

此代码展示了如何设置重试次数、初始延迟时间和最大延迟时间,并通过指数递增和随机抖动来优化重试策略。

回到顶部