深入理解Redis锁与Backoff重试机制在Go中的实现

文章目录

    • 流程图
    • Redis锁的深入实现
    • Backoff重试策略的深入探讨
    • 结合Redis锁与Backoff策略的高级应用
    • 具体实现
    • 结论

在构建分布式系统时,确保数据的一致性和操作的原子性是至关重要的。Redis锁作为一种高效且广泛使用的分布式锁机制,能够帮助我们在多进程或分布式环境中同步访问共享资源。本文将深入探讨如何在Go语言中实现Redis锁,并结合Backoff重试策略来优化锁的获取过程,确保系统的健壮性和可靠性。

流程图

获取成功
获取失败
开始
尝试获取锁
执行操作
操作成功?
释放锁
重试操作
应用Backoff策略
重试成功?
结束

Redis锁的深入实现

在Go语言中,我们使用github.com/gomodule/redigo/redis包来操作Redis。Redis锁的实现依赖于Redis的SET命令,该命令支持设置键值对,并且可以带有过期时间(EX选项)和仅当键不存在时才设置(NX选项)。以下是一个更详细的Redis锁实现示例:

func SetWithContext(ctx context.Context, redisPool *redis.Pool, key string, expireSecond uint32) (bool, string, error) {// ...省略部分代码...conn, err := redisPool.GetContext(ctx)if err != nil {return false, "", err}defer conn.Close()randVal := generateRandVal() // 生成随机值_, err = conn.Do("SET", key, randVal, "NX", "EX", int(expireSecond))if err != nil {return false, "", err}return true, randVal, nil
}

在上述代码中,generateRandVal()函数用于生成一个唯一的随机值,这个值在释放锁时用来验证是否是锁的持有者。expireSecond参数确保了即使客户端崩溃或网络问题发生,锁也会在一定时间后自动释放,避免死锁。

释放锁时,我们使用Lua脚本来确保只有持有锁的客户端才能删除键:

func ReleaseWithContext(ctx context.Context, redisPool *redis.Pool, key string, randVal string) error {// ...省略部分代码...conn, err := redisPool.GetContext(ctx)if err != nil {return err}defer conn.Close()script := `if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])elsereturn 0end`_, err = conn.Do("EVAL", script, 1, key, randVal)return err
}

Backoff重试策略的深入探讨

在分布式系统中,获取锁可能会因为网络延迟、高负载或其他原因而失败。Backoff重试策略通过在重试之间引入等待时间来减轻这些问题的影响。在提供的代码中,我们定义了多种Backoff策略,每种策略都有其特定的使用场景和优势。

例如,指数退避策略ExponentialBackoff的实现如下:

func (b *ExponentialBackoff) Next(retry int) (time.Duration, bool) {// ...省略部分代码...m := math.Min(r*b.t*math.Pow(b.f, float64(retry)), b.m)if m >= b.m {return 0, false}d := time.Duration(int64(m)) * time.Millisecondreturn d, true
}

在这个策略中,重试间隔随重试次数的增加而指数级增长,但有一个最大值限制。这有助于在遇到连续失败时,逐步增加等待时间,避免立即重载系统。

结合Redis锁与Backoff策略的高级应用

将Redis锁与Backoff策略结合起来,可以创建一个健壮的锁获取机制。例如,我们可以定义一个MustSetRetry方法,该方法会不断尝试获取锁,直到成功为止:

func (r *RedisLock) MustSetRetry(ctx context.Context, key string) (string, error) {op := func() (string, error) {return r.MustSet(ctx, key)}notifyFunc := func(err error) {// ...错误处理逻辑...}return mustSetRetryNotify(op, r.backoff, notifyFunc)
}

在这个方法中,mustSetRetryNotify函数负责执行重试逻辑,直到MustSet方法成功获取锁或达到最大重试次数。通过这种方式,我们能够确保即使在高竞争环境下,也能以一种可控和安全的方式获取锁。

具体实现

  1. backoff
package lockimport ("math""math/rand""sync""time"
)// BackoffFunc specifies the signature of a function that returns the
// time to wait before the next call to a resource. To stop retrying
// return false in the 2nd return value.
type BackoffFunc func(retry int) (time.Duration, bool)// Backoff allows callers to implement their own Backoff strategy.
type Backoff interface {// Next implements a BackoffFunc.Next(retry int) (time.Duration, bool)
}// -- ZeroBackoff --// ZeroBackoff is a fixed backoff policy whose backoff time is always zero,
// meaning that the operation is retried immediately without waiting,
// indefinitely.
type ZeroBackoff struct{}// Next implements BackoffFunc for ZeroBackoff.
func (b ZeroBackoff) Next(retry int) (time.Duration, bool) {return 0, true
}// -- StopBackoff --// StopBackoff is a fixed backoff policy that always returns false for
// Next(), meaning that the operation should never be retried.
type StopBackoff struct{}// Next implements BackoffFunc for StopBackoff.
func (b StopBackoff) Next(retry int) (time.Duration, bool) {return 0, false
}// -- ConstantBackoff --// ConstantBackoff is a backoff policy that always returns the same delay.
type ConstantBackoff struct {interval time.Duration
}// NewConstantBackoff returns a new ConstantBackoff.
func NewConstantBackoff(interval time.Duration) *ConstantBackoff {return &ConstantBackoff{interval: interval}
}// Next implements BackoffFunc for ConstantBackoff.
func (b *ConstantBackoff) Next(retry int) (time.Duration, bool) {return b.interval, true
}// -- Exponential --// ExponentialBackoff implements the simple exponential backoff described by
// Douglas Thain at http://dthain.blogspot.de/2009/02/exponential-backoff-in-distributed.html.
type ExponentialBackoff struct {t float64 // initial timeout (in msec)f float64 // exponential factor (e.g. 2)m float64 // maximum timeout (in msec)
}// NewExponentialBackoff returns a ExponentialBackoff backoff policy.
// Use initialTimeout to set the first/minimal interval
// and maxTimeout to set the maximum wait interval.
func NewExponentialBackoff(initialTimeout, maxTimeout time.Duration) *ExponentialBackoff {return &ExponentialBackoff{t: float64(int64(initialTimeout / time.Millisecond)),f: 2.0,m: float64(int64(maxTimeout / time.Millisecond)),}
}// Next implements BackoffFunc for ExponentialBackoff.
func (b *ExponentialBackoff) Next(retry int) (time.Duration, bool) {r := 1.0 + rand.Float64() // random number in [1..2]m := math.Min(r*b.t*math.Pow(b.f, float64(retry)), b.m)if m >= b.m {return 0, false}d := time.Duration(int64(m)) * time.Millisecondreturn d, true
}// -- Simple Backoff --// SimpleBackoff takes a list of fixed values for backoff intervals.
// Each call to Next returns the next value from that fixed list.
// After each value is returned, subsequent calls to Next will only return
// the last element. The values are optionally "jittered" (off by default).
type SimpleBackoff struct {sync.Mutexticks  []intjitter bool
}// NewSimpleBackoff creates a SimpleBackoff algorithm with the specified
// list of fixed intervals in milliseconds.
func NewSimpleBackoff(ticks ...int) *SimpleBackoff {return &SimpleBackoff{ticks:  ticks,jitter: false,}
}// Jitter enables or disables jittering values.
func (b *SimpleBackoff) Jitter(flag bool) *SimpleBackoff {b.Lock()b.jitter = flagb.Unlock()return b
}// jitter randomizes the interval to return a value of [0.5*millis .. 1.5*millis].
func jitter(millis int) int {if millis <= 0 {return 0}return millis/2 + rand.Intn(millis)
}// Next implements BackoffFunc for SimpleBackoff.
func (b *SimpleBackoff) Next(retry int) (time.Duration, bool) {b.Lock()defer b.Unlock()if retry >= len(b.ticks) {return 0, false}ms := b.ticks[retry]if b.jitter {ms = jitter(ms)}return time.Duration(ms) * time.Millisecond, true
}

关键Backoff策略:

  • ZeroBackoff: 不等待,立即重试。
  • StopBackoff: 从不重试。
  • ConstantBackoff: 固定等待时间。
  • ExponentialBackoff: 指数增长的等待时间。
  • SimpleBackoff: 提供一组固定的等待时间,可选择是否添加随机抖动。
package lockimport ("context""errors""fmt""time""github.com/gomodule/redigo/redis"
)var (// 防止孤儿lock没release// 目前expire过期时间的敏感度是考虑为一致的敏感度defaultExpireSecond uint32 = 30
)var (ErrLockSet     = errors.New("lock set err")ErrLockRelease = errors.New("lock release err")ErrLockFail    = errors.New("lock fail")
)// RedisLockIFace 在common redis上封一层浅封装
// 将redis pool 与expire second作为redis lock已知数据
type RedisLockIFace interface {MustSet(ctx context.Context, k string) (string, error)MustSetRetry(ctx context.Context, k string) (string, error) // 必须设置成功并有重试机制Release(ctx context.Context, k string, randVal string) error
}// RedisLock nil的实现默认为true
type RedisLock struct {redisPool    *redis.PoolexpireSecond uint32backoff      Backoff
}// An Option configures a RedisLock.
type Option interface {apply(*RedisLock)
}// optionFunc wraps a func so it satisfies the Option interface.
type optionFunc func(*RedisLock)func (f optionFunc) apply(log *RedisLock) {f(log)
}// WithBackoff backoff set
func WithBackoff(b Backoff) Option {return optionFunc(func(r *RedisLock) {r.backoff = b})
}func NewRedisLock(redisPool *redis.Pool, opts ...Option) *RedisLock {r := &RedisLock{redisPool:    redisPool,expireSecond: defaultExpireSecond,backoff:      NewExponentialBackoff(30*time.Millisecond, 500*time.Millisecond), // default backoff}for _, opt := range opts {opt.apply(r)}return r
}func (r *RedisLock) Set(ctx context.Context, key string) (bool, string, error) {if r == nil {return true, "", nil}isLock, randVal, err := SetWithContext(ctx, r.redisPool, key, r.expireSecond)if err != nil {return isLock, randVal, ErrLockSet}return isLock, randVal, err
}// MustSetRetry 必须设置成功并带有重试功能
func (r *RedisLock) MustSetRetry(ctx context.Context, key string) (string, error) {op := func() (string, error) {return r.MustSet(ctx, key)}notifyFunc := func(err error) {if err == ErrLockFail {fmt.Printf("RedisLock.MustSetRetry redis must set err: %v", err)} else {fmt.Printf("RedisLock.MustSetRetry redis must set err: %v", err)}}return mustSetRetryNotify(op, r.backoff, notifyFunc)
}func (r *RedisLock) MustSet(ctx context.Context, key string) (string, error) {isLock, randVal, err := r.Set(ctx, key)if err != nil {return "", err}if !isLock {return "", ErrLockFail}return randVal, nil
}func (r *RedisLock) Release(ctx context.Context, key string, randVal string) error {if r == nil {fmt.Printf("that the implementation of redis lock is nil")return nil}err := ReleaseWithContext(ctx, r.redisPool, key, randVal)if err != nil {fmt.Printf("s.RedisLock.ReleaseWithContext fail, err: %v", err)return ErrLockRelease}return nil
}func SetWithContext(ctx context.Context, redisPool *redis.Pool, key string, expireSecond uint32) (bool, string, error) {if expireSecond == 0 {return false, "", fmt.Errorf("expireSecond参数必须大于0")}conn, _ := redisPool.GetContext(ctx)defer conn.Close()randVal := time.Now().Format("2006-01-02 15:04:05.000")reply, err := conn.Do("SET", key, randVal, "NX", "PX", expireSecond*1000)if err != nil {return false, "", err}if reply == nil {return false, "", nil}return true, randVal, nil
}func ReleaseWithContext(ctx context.Context, redisPool *redis.Pool, key string, randVal string) error {conn, _ := redisPool.GetContext(ctx)defer conn.Close()luaScript := `if redis.call("get", KEYS[1]) == ARGV[1] thenreturn redis.call("del", KEYS[1])elsereturn 0end;`script := redis.NewScript(1, luaScript)_, err := script.Do(conn, key, randVal)return err
}
  1. 重试
package lockimport "time"type mustSetOperation func() (string, error)type ErrNotify func(error)func mustSetRetryNotify1(operation mustSetOperation, b Backoff, notify ErrNotify) (string, error) {var err errorvar randVal stringvar wait time.Durationvar retry boolvar n intfor {if randVal, err = operation(); err == nil {return randVal, nil}if b == nil {return "", err}n++wait, retry = b.Next(n)if !retry {return "", err}if notify != nil {notify(err)}time.Sleep(wait)}}
  1. 使用

func main() {backoff := lock.NewExponentialBackoff(time.Duration(20)*time.Millisecond,time.Duration(1000)*time.Millisecond,)redisPool := &redis.Pool{MaxIdle:     3,IdleTimeout: 240 * time.Second,// Dial or DialContext must be set. When both are set, DialContext takes precedence over Dial.Dial: func() (redis.Conn, error) {return redis.Dial("tcp","redis host",redis.DialPassword("redis password"),)},}redisLock := lock.NewRedisLock(redisPool, lock.WithBackoff(backoff))ctx := context.Background()s, err := redisLock.MustSetRetry(ctx, "lock_user")if err != nil && err == lock.ErrLockFail {fmt.Println(err)return}time.Sleep(20 * time.Second)defer func() {_ = redisLock.Release(ctx, "lock_user", s)}()return
}

结论

通过深入理解Redis锁和Backoff重试策略的实现,我们可以构建出既能够保证资源访问的原子性,又能在面对网络波动或系统负载时保持稳定性的分布式锁机制。这不仅提高了系统的可用性,也增强了系统的容错能力。在实际开发中,合理选择和调整这些策略对于确保系统的高性能和高可靠性至关重要。通过精心设计的锁机制和重试策略,我们可以为分布式系统提供一个坚实的基础,以应对各种挑战和压力。

关注我

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/448799.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

文章解读与仿真程序复现思路——电网技术EI\CSCD\北大核心《计及多重不确定性和时间相关性的虚拟电厂参与碳-绿证协同交易优化调度》

本专栏栏目提供文章与程序复现思路&#xff0c;具体已有的论文与论文源程序可翻阅本博主免费的专栏栏目《论文与完整程序》 论文与完整源程序_电网论文源程序的博客-CSDN博客https://blog.csdn.net/liang674027206/category_12531414.html 电网论文源程序-CSDN博客电网论文源…

Gin框架操作指南07:路由与中间件

官方文档地址&#xff08;中文&#xff09;&#xff1a;https://gin-gonic.com/zh-cn/docs/ 注&#xff1a;本教程采用工作区机制&#xff0c;所以一个项目下载了Gin框架&#xff0c;其余项目就无需重复下载&#xff0c;想了解的读者可阅读第一节&#xff1a;Gin操作指南&#…

武汉正向科技|焦炉移动机车连锁控制系统的介绍

焦炉车辆连锁控制系统是采用格雷母线定位技术&#xff0c;无线数据传输技术以及计算机技术&#xff0c;实现推焦车、拦焦车、熄焦车、装煤车、导烟车的集中监控和系统管理&#xff0c;以及车间作业计划管理&#xff0c;作业实绩管理&#xff0c;作业联锁控制及安全控制等。 焦炉…

超详细的finalshell安装数据库以及数据库的基本操作

一、下载 MySQL Enterprise Edition Downloads | Oraclehttps://www.oracle.com/mysql/technologies/mysql-enterprise-edition-downloads.html 这边有不同的版本&#xff0c;要看你的操作系统&#xff08;centos7 / centos8&#xff09;安装的是哪个版本 我把连接提取出来了&…

JAVA中类和对象

一.类的创建&#xff1a; 比如我们创建狗类&#xff1a; public class Dog {public String name; //名字public int age; //年龄public double tall; //身高//狗的行为public void bark() {System.out.println("汪汪汪~~~");}public void wag() {System.out.printl…

揭秘提升3DMAX效率的6款必备神级插件!

对于3DMax新手来说,掌握一些高效、实用的插件能够大大提升工作效率和创作质量。以下是6个不能错过的神级插件推荐: 第1个:3DMAX造山地形插件Mountain是一款专为3dMax设计的插件,旨在帮助用户轻松快速地创建逼真的山脉地形。以下是对该插件的详细介绍: 一、插件概述 Mou…

手写模拟Spring的基本功能

文章目录 1. Spring的基本功能2. 容器启动 容器启动&#xff0c;即创建容器对象并赋予配置对象3. BeanDefinition扫描4. Bean的生命周期5. 单例Bean与多例Bean6. 依赖注入7. AOP8. Aware 回调9. 初始化10. BeanPostProcessor附录&#xff1a; 1. Spring的基本功能 2. 容器启动 …

VSCode图标的含义,以及DataLoader代码的使用

1 问题 vscode中的那些图标的含义分别是什么Dataloader代码怎么做 2 方法 去网站里面搜索各种图形代表的含义然后进行理解和记忆 长方体&#xff1a;变量 紫色立方体&#xff1a; 库中预定义的枚举&#xff1a; 两个矩形块&#xff1a;枚举 自定义的枚举 橙色树状结构&#xff…

LinkedList和链表(上)

1. 顺序表ArrayList的缺点和优点 优点: 1> 在给定下标进行查找的时候,时间复杂度是O(1) 缺点: 1> 插入数据必须移动其他数据,最坏情况下,插入到0位置,时间复杂度为O(N) 2> 删除数据也需要移动数据,最坏情况下,就是删除0位置.时间复杂度为O(N) 3> 扩容之后(1.5倍扩容…

SQL JOIN的学习

SQL JOIN (w3school.com.cn) 之前跟着老师学数据库的时候学过&#xff0c;最近又比较频繁的在使用。 再复习一下。 Id_P是主键 Id_O是主键 1. SELECT Persons.LastName, Persons.FirstName, Orders.OrderNo FROM Persons, Orders WHERE Persons.Id_P Orders.Id_P 2. SEL…

【JVM】—深入理解G1回收器——概念详解

深入理解G1回收器——概念详解 ⭐⭐⭐⭐⭐⭐ Github主页&#x1f449;https://github.com/A-BigTree 笔记链接&#x1f449;https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以&#xff0c;麻烦各位看官顺手点个star~&#x1f60a; 文章目录 深入理解G1回收器…

保护企业终端安全,天锐DLP帮助企业智能管控终端资产

为有效预防员工非法调包公司的软硬件终端资产&#xff0c;企业管理员必须建立高效的企业终端安全管控机制&#xff0c;确保能够即时洞察并确认公司所有软硬件资产的状态变化。这要求企业要有一套能够全面管理终端资产的管理系统&#xff0c;确保任何未经授权的资产变动都能被迅…

Shiro认证 -- (Authentication)

Apache Shiro是一个功能强大的Java安全框架&#xff0c;提供了身份验证&#xff08;Authentication&#xff09;、授权&#xff08;Authorization&#xff09;、加密&#xff08;Cryptography&#xff09;、会话管理&#xff08;Session Management&#xff09;、与Web集成、缓…

【WEB应用安全测试指南–蓝队安全测试2】--超详细-可直接进行实战!!!亲测-可进行安全及渗透测试

安全基础理论入门知识参考上一篇《WEB应用安全测试指南蓝队安全测试1》 WEB应用安全测试指南2 一、文件 I/O 类1.1、任意文件上传1.2、任意文件下载1.3、文件包含 二、接口安全类2.1、短信炸弹2.2、邮件炸弹2.3、短信内容可控2.4、邮件内容可控 三、逻辑流程类3.1、越权3.2、未…

深度学习论文: EfficientCrackNet: A Lightweight Model for Crack Segmentation

深度学习论文: EfficientCrackNet: A Lightweight Model for Crack Segmentation EfficientCrackNet: A Lightweight Model for Crack Segmentation PDF: https://arxiv.org/pdf/2409.18099v1 PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代码: https:/…

红日安全vulnstack (一)

目录 环境搭建 本机双网卡 Kali IP 靶机IP Web GetShell 前期信息收集 Yxcms后台模板 Getshell PHPMyAdmin日志 Getshell into outfile写入一句话 X phpmyadmin 日志写入一句话 后渗透 MSF 生成木马上线 提取用户hash值 **hash**加密方式 MSF权限Shell至CS CS …

光标在单词中间,如何通过快捷键选择当前单词?

工具》选项>环境》键盘 &#xff1a;把应用修改成visual studio 6或者 visual assist就可以了

IO编程--单字符、字符串、格式化、模块化实现文件拷贝以及登录注册

一、完成标准io的单字符、字符串、格式化、模块化实现两个文件的拷贝 代码如下&#xff1a; 1.单字符 #include <myhead.h> int main(int argc, const char *argv[]) {//打开文件FILE* fpfopen("test.txt","r"); FILE* fqfopen("copy_test.txt&…

第九课:Python学习之函数基础

函数基础 目标 函数的快速体验函数的基本使用函数的参数函数的返回值函数的嵌套调用在模块中定义函数 01. 函数的快速体验 1.1 快速体验 所谓函数&#xff0c;就是把 具有独立功能的代码块 组织为一个小模块&#xff0c;在需要的时候 调用函数的使用包含两个步骤&#xff…

基于GRNN广义回归网络和MFCC的语音情绪识别matlab仿真,对比SVM和KNN

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 (完整程序运行后无水印) filePath Test_data\悲伤1.wav 类型&#xff1a;悲伤 识别置信度 Vmax 0.9559 2.算法运行软件版本 matlab2022a 3.部…