Go微服务: redis分布式锁在集群中可能遇到的问题及其解决方案

news2025/1/19 19:24:27

概述

  • 我们的 redis 一般都是集群来给我们程序提供服务的,单体的redis现在也不多见
  • 看到上面是主节点redis和下面是6个重节点redis,主节点和重节点的通讯都是畅通没问题的
  • 这个时候,我们有 gorouting 写我们的数据,那它就会用到我们的setNX
  • 写完数据内部是自动同步的,就是你的这个数据通过主节点同步到这些从节点了
  • 下面又有我们的 gorouting 去读我们的从节点,但是,我们是在高并发和网络不确定的情况下可能会遇到一些问题

可能会遇到的一些问题


1 )集群方面

  • 如果,我们上面是一个gorouting,在主节点上,它用setNX写数据,如果主节点挂了
  • 集群就从我们的所有子节点中抽取一个节点,当成主节点顶上去,集群又可以正常工作了
  • 这个时候,有一个gorouting在右下角,它又来读数据了,由于我们上面刚写了数据
  • 还没有来得及同步到最后一个 redis 这个节点上, 但是面临着新的gorouting读取数据或操作
  • 这个时候最后这一台redis它是拿不到那个锁的,是没有同步到的
  • 最后来的 gorouting,就认为你没有锁, 或者说我要的资源,你没锁住
  • 那其他 gorouting 就认为它是无主的, 就可以锁, 这个时候就会造成一些问题

2 )网络方面

  • 正常的时候,写数据还有下面的从节点去获取数据,获取锁,都是没问题的
  • 如果是网络抖动或不通会有一些问题,由于redis它是网络传输的
  • 如果说我们右边的这网络络不通了,相当于右边的 redis 没有和主节点通讯
  • 这个时候我们的一个gorouting就来获取锁进行数据的操作
  • 如果这个时候,我要操作的资源没有上锁,那这个gorouting就认为它是还没有被加锁,就把这个锁锁上了
  • 所以这个地方也是有可能出问题的风险

解决方案


1 )使用 redLock

  • 锁不住资源,有可能因为节点挂了或网络抖动, 我们现在尝试使用 redLock 来解决这一个问题
  • redLock它没有master节点,也没有这个slave从节点,都是独立的
  • 每一个redis,都是有一个 SetNx 这么一个锁, 现在有两个协程来申请锁
  • redis集群的一般是7个,而不是说双数的, 如果双数的那我左边的 gorouting 获得3个
  • 右边的 gorouting 获得3个,他就要重新再做选举投票之类的东西
  • 基于redLock, 当左边的 gorouting 抢到了4个,那右边的只有3个就应该释放掉
  • 为下一次再运行做准备,右边这个锁就消失了

2 ) 源码

  • redlock把原来的master,slave这种模式改成了平等的模式,最终解决了问题

2.1 ) NewMutex

  • 在源码的 NewMutex 函数中
    // NewMutex returns a new distributed mutex with given name.
    func (r *Redsync) NewMutex(name string, options ...Option) *Mutex {
    	m := &Mutex{
    		name:   name,
    		expiry: 8 * time.Second,
    		tries:  32, // 重试 32 次
    		delayFunc: func(tries int) time.Duration {
    			return time.Duration(rand.Intn(maxRetryDelayMilliSec-minRetryDelayMilliSec)+minRetryDelayMilliSec) * time.Millisecond
    		},
    		genValueFunc:  genValue,
    		driftFactor:   0.01, // 漂移因子
    		timeoutFactor: 0.05, // 超时因子
    		quorum:        len(r.pools)/2 + 1, // 法定数,找一半+1,大多数
    		pools:         r.pools,
    	}
    	for _, o := range options {
    		o.Apply(m)
    	}
    	if m.shuffle {
    		randomPools(m.pools)
    	}
    	return m
    }
    
  • 上面 driftFactor 是说我们的服务器的时钟漂移
    • 比如说我们的A服务器是中午12点,但是B服务器是中午11点59分30秒
    • C服务器是中午的12点0分30秒,相当于它们每台服务器相差30秒
    • 这就是服务器的时间漂移,它不准,那这会导致什么呢?
  • 假如说我们的这个过期时间是8秒,那你差了30秒,肯定就是有的服务器会先释放锁
  • 那先释放锁,其他人就可以拿到锁,所以他就设置了一个因子
  • 关于 quorum 就如同上面的例子,7台服务器拿到了4台就是成功的

2.2 ) Lock

  • 回到锁定的函数 Lock 中,进入其 lockContext
    // lockContext locks m. In case it returns an error on failure, you may retry to acquire the lock by calling this method again.
    func (m *Mutex) lockContext(ctx context.Context, tries int) error {
    	if ctx == nil {
    		ctx = context.Background()
    	}
    	// 获取 base64 的值
    	value, err := m.genValueFunc()
    	if err != nil {
    		return err
    	}
    
    	var timer *time.Timer
    	// 对默认32次循环的操作
    	for i := 0; i < tries; i++ {
    		if i != 0 {
    			if timer == nil {
    				timer = time.NewTimer(m.delayFunc(i))
    			} else {
    				timer.Reset(m.delayFunc(i))
    			}
    
    			select {
    			// 如果 完成 状态,则返回 ErrFailed
    			case <-ctx.Done():
    				timer.Stop()
    				// Exit early if the context is done.
    				return ErrFailed
    			// 没有完成,则不动
    			case <-timer.C:
    				// Fall-through when the delay timer completes.
    			}
    		}
    
    		start := time.Now()
    		// 拿到计数器和错误信息
    		n, err := func() (int, error) {
    			ctx, cancel := context.WithTimeout(ctx, time.Duration(int64(float64(m.expiry)*m.timeoutFactor)))
    			defer cancel()
    			/// 注意这里,最终的函数就是执行的这里
    			return m.actOnPoolsAsync(func(pool redis.Pool) (bool, error) {
    				return m.acquire(ctx, pool, value)
    			})
    		}()
    
    		now := time.Now()
    		// 下面是核心算法: 过期时间 - 拿锁的时间 - 漂移因子可能的时间
    		until := now.Add(m.expiry - now.Sub(start) - time.Duration(int64(float64(m.expiry)*m.driftFactor)))
    		// 判断是否是大多数并且没有过期,则直接进行赋值
    		if n >= m.quorum && now.Before(until) {
    			m.value = value
    			m.until = until
    			return nil
    		}
    		// 否则 release 进行释放
    		_, _ = func() (int, error) {
    			ctx, cancel := context.WithTimeout(ctx, time.Duration(int64(float64(m.expiry)*m.timeoutFactor)))
    			defer cancel()
    			return m.actOnPoolsAsync(func(pool redis.Pool) (bool, error) {
    				return m.release(ctx, pool, value)
    			})
    		}()
    		if i == tries-1 && err != nil {
    			return err
    		}
    	}
    
    	return ErrFailed
    }
    
  • 进入 actOnPoolsAsync 这里参数是一个函数
    func (m *Mutex) actOnPoolsAsync(actFn func(redis.Pool) (bool, error)) (int, error) {
    	type result struct {
    		node     int
    		statusOK bool
    		err      error
    	}
    	// 创建 channel
    	ch := make(chan result, len(m.pools))
    	// 循环 pools
    	for node, pool := range m.pools {
    		// 开协程提速
    		go func(node int, pool redis.Pool) {
    			r := result{node: node}
    			r.statusOK, r.err = actFn(pool)
    			ch <- r
    		}(node, pool)
    	}
    
    	var (
    		n     = 0 // 计数器
    		taken []int
    		err   error // 错误
    	)
    	
    	// 循环 pools
    	for range m.pools {
    		// 从 channel 中拿到结果
    		r := <-ch
    		if r.statusOK {
    			n++ // 计数器加加
    		} else if r.err == ErrLockAlreadyExpired {
    			err = multierror.Append(err, ErrLockAlreadyExpired)
    		} else if r.err != nil {
    			err = multierror.Append(err, &RedisError{Node: r.node, Err: r.err})
    		} else {
    			taken = append(taken, r.node)
    			err = multierror.Append(err, &ErrNodeTaken{Node: r.node})
    		}
    
    		if m.failFast {
    			// fast retrun
    			if n >= m.quorum {
    				return n, err
    			}
    
    			// fail fast
    			if len(taken) >= m.quorum {
    				return n, &ErrTaken{Nodes: taken}
    			}
    		}
    	}
    
    	if len(taken) >= m.quorum {
    		return n, &ErrTaken{Nodes: taken}
    	}
    	return n, err
    }
    
  • 以上就是 redLock 源码锁的机制,通过源代码可以更好的理解框架
  • 即使上面一些细节点看不懂,跳过即可,前期可以先看大的实现脉络帮助我们理解

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《沃趣 分手后霸道少爷宠爆我》盛大开机典礼

南京五聚文化传媒有限公司自豪地宣布&#xff0c;引人入胜的2024年度短剧巨作——《沃趣 分手后霸道少爷宠爆我》——今日正式开拍&#xff01;在星辰下的华丽舞台上&#xff0c;我们汇集了业界的精英力量&#xff0c;准备讲述一个关于爱、错位与重生的故事。 典礼精彩亮点 1.…

openh264 宏块级码率控制源码分析

openh264 宏块级码率控制函数关系 宏块级核心函数分析 WelsRcMbInitGom函数 功能&#xff1a;openh264 码率控制框架中宏块级码率控制函数&#xff0c;根据是否启用GOM QP来决定如何设置宏块的QP值&#xff0c;以控制编码的质量和比特率。原理过程&#xff1a; 函数参数&…

高效管理:好用的项目管理工具推荐

在当今快速变化的商业环境中&#xff0c;高效的项目管理工具能够显著提升团队的生产力和项目的成功率&#xff0c;还能有效地跟踪项目进度。所以&#xff0c;一款优秀的项目管理工具首先要具备先进的项目管理理念&#xff0c;支持多种研发管理和项目管理方法论&#xff0c;才能…

“打造智能售货机系统,基于ruoyi微服务版本生成基础代码“

目录 # 开篇 1. 菜单 2. 字典配置 3. 表配置 3.1 导入表 3.2 区域管理 3.3 合作商管理 3.4 点位管理 4. 代码导入 4.1 后端代码生成 4.2 前端代码生成 5. 数据库代码执行 6. 点位管理菜单顺序修改 7. 页面展示 8. 附加设备表 8.1 新增设备管理菜单 8.2 创建字…

高效处理大数据:Kafka的13个核心概念详解

我是小米,一个喜欢分享技术的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! 大家好,我是你们的小米!今天我们来深入探讨一下Kafka这个强大而复杂的数据流平台。Kafka被广泛应用于高吞吐量、低延迟的数据流应用场景中。那么,我…

wordpress站群搭建3api代码生成和swagger使用

海鸥技术下午茶-wordpress站群搭建3api代码生成和swagger使用 目标:实现api编写和swagger使用 0.本次需要使用到的脚手架命令 生成 http server 代码 goctl api go -api all.api -dir ..生成swagger文档 goctl api plugin -plugin goctl-swagger"swagger -filename st…

【算法训练记录——Day32】

Day32——贪心算法Ⅱ 1.leetcode122买卖股票的最佳时机II2.leetcode55跳跃游戏3.leetcode45跳跃游戏II4.eetcode1005K次取反后最大化的数组和 目标&#xff1a; leetcode122买卖股票的最佳时机IIleetcode55跳跃游戏leetcode45跳跃游戏IIleetcode1005K次取反后最大化的数组和 1…

数据结构经典面试之链表——C#和C++篇

文章目录 一、链表的基本概念二、链表的操作三、定义链表的节点结构体&#xff08;C#&#xff09;四、定义链表的基本操作类&#xff08;C#&#xff09;五、创建一个链表实例并进行基本操作演示&#xff08;C#&#xff09;六、编写一个自定义链表操作函数&#xff08;C&#xf…

搭建一个简单的xxljob

数据库表结构&#xff1a; YyJobInfo&#xff1a; public class YyJobInfo {//定时任务idprivate int id;//该定时任务所属的执行器的idprivate int jobGroup;//定时任务描述private String jobDesc;//定时任务添加的时间private Date addTime;//定时任务的更新时间private D…

centos7将jar包制作服务并设置开机服务

为了避免服务器因为一些原因重启导致java服务没启动&#xff0c;我们将jar包制作为service并开机启动 制作过程 在网上找了一些关于java jar包制作开启启动的&#xff0c;大致流程为先做一个xxx.service&#xff0c;放在/etc/systemd/system/目录下&#xff0c;然后执行syste…

ASP.NET Core 6.0 启动方式

启动方式 Visualstudio 2022启动 IIS Express IIS Express 是一个专为开发人员优化的轻型独立版本的 IIS。 借助 IIS Express,可以轻松地使用最新版本的 IIS 开发和测试网站。 控制台版面 直接在浏览器输入监听的地址,监听的是 http://localhost:5137 脚本启动 dotnet run…

交易中的特殊存在

在交易的广袤天空中&#xff0c;有一群特殊的存在——他们&#xff0c;是Eagle Trader。 他们以鹰眼般的洞察力&#xff0c;捕捉市场的微妙变化&#xff0c;每一次决策都如同猎食者般精准&#xff1b;他们运用策略&#xff0c;如同雄鹰在风中翱翔&#xff0c;利用风向&#xf…

Setapp AI 怎么样,值得订阅吗?

Setapp 是一款提供 240 多款 Mac 软件库&#xff0c;可以满足 Mac 和 iOS 用户的各种需求。只需支付一笔订阅费&#xff0c;就可以使用一系列应用程序&#xff0c;这些应用程序可以简化任务、提高创造力和组织工作流程。不过&#xff0c;Setapp 并不只注重应用程序的数量&#…

R语言——类与对象

已知2024年4月23日是星期五&#xff0c;编写一个函数day.in.a.week (x, y,z)&#xff0c;参数x和y和z分别代表年月日&#xff0c;判断这一天是否存在&#xff08;例如&#xff0c;2018年没有2月29日&#xff0c;也没有11月31日&#xff09;&#xff0c;如果不存在&#xff0c;返…

2024097期传足14场胜负前瞻

2024097期售止时间为6月22日&#xff08;周六&#xff09;20点30分&#xff0c;敬请留意&#xff1a; 本期14场由欧洲杯、美洲杯、美职联组成&#xff0c;1.5以下赔率2场&#xff0c;1.5-2.0赔率10场&#xff0c;其他场次是平半盘、平盘。本期14场难度中等。以下为基础盘前瞻&a…

针对河南大学数据结构傻逼学堂在线的自动化脚本

首先展示一下我们的答案 {1: [对象], 2: [关系]} {1: [非数值计算], 2: [操作]} {1: [线性表]} [D] [B] [B] [C] [C] {1: [操作]} {1: [数据关系, 数据对象上关系的集合]} {1: [性质相同]} {1: [物理结构]} {1: [存储结构, 操作表示]} [C] [B] [D] [B] [D] [true] [false] [fa…

css文字镂空加描边

css文字镂空加描边 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>文字镂空</title><style>/* 公用样式 */html,body{width: 100%;height: 100%;position: relative;}/* html{overflow-y: scroll;} */*{margi…

(Python)可变类型不可变类型;引用传递值传递;浅拷贝深拷贝

从一段代码开始说事&#xff0c;先上代码&#xff1a; a [[1],[2],[3]] b [[4,5],[6,7],[7,8]] for i,j in zip(a,b):print(i,j)i [9]#i[0] 8j[:2][1,2]print(i, j) print(a) print(b) 运行的结果&#xff1a; [1] [4, 5] [9] [1, 2] [2] [6, 7] [9] [1, 2] [3] [7, 8] …

淘宝扭蛋机小程序:互联网时代下行业的发展动力

近几年&#xff0c;扭蛋机在潮玩市场风靡&#xff0c;与各类IP合作&#xff0c;推出各种新颖有趣的扭蛋商品&#xff0c;吸引了众多的IP粉丝&#xff0c;他们会通过扭蛋机进行抽奖&#xff0c;获得喜欢的商品。 目前&#xff0c;移动应用程序不断升级优化&#xff0c;“互联网…

RGB彩色模型理解与编程实例

一、引言 RGB彩色模型中的R、G和B为三原色&#xff0c;通常R、G和B分别用8位表示&#xff0c;因此24位的RGB 真彩色图像能表示16777216种颜色。在如右图所示RGB彩色立方体可知&#xff0c;任意两种原色混合可以合成一种新的颜色。红&#xff08;1&#xff0c;0&#xff0c;0&a…