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

概述

  • 我们的 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 源码锁的机制,通过源代码可以更好的理解框架
  • 即使上面一些细节点看不懂,跳过即可,前期可以先看大的实现脉络帮助我们理解

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

labelme 标注岩石薄片数据集流程

labelme 数据标注使用流程 1.打开anaconda环境2.打开labelme工具3.打开数据集文件夹4.开始标注5. 标注完成6. 修改labels.txt文件7. 将标注结果可视化8. 完成json转图片9. 全部命令总结 1.打开anaconda环境 2.打开labelme工具 输入下列两条命令&#xff0c;打开labelme工具 &a…

怎么看电脑实时充电功率

因为我想测试不同的充电器给电脑充电的速度&#xff0c;所以就想找一款软件可以看电脑当前充电功率的软件&#xff0c;我给一个图 直接搜索就可以下载了&#xff0c;charge rate就是功率&#xff0c;这里是毫瓦&#xff0c;换算单位是 1000mw1w 所以我这里充电功率是65w&…

毫秒级响应!清科优能应用 TDengine 建设虚拟电厂运营管理平台

小T导读&#xff1a;在清科优能的虚拟电厂运营管理平台建设中&#xff0c;项目初期预计涉及约一万台设备、总数据采集量达数十万&#xff0c;在数据库选择上&#xff0c;其希望能支持至少两千台设备的并发数据处理。本文介绍了清科优能的数据库选型经验以及最终应用效果&#x…

探索产业园的独特产业定位与价值

数字影像产业园的产业定位独特且全面&#xff0c;涵盖了数字贸易、数字服务、数字文旅和数字基建四大主导产业方向&#xff0c;体现了园区在数字化转型和产业升级方面的前瞻性和创新性。 一、数字贸易的推动者 数字影像产业园致力于推动数字贸易的发展&#xff0c;搭建全球化、…

如何使用nginx部署https网站(亲测可行)

公司本来有网站sqlynx.com是http运行的&#xff0c;但因为产品出海&#xff0c;基本上都要求使用https&#xff0c;但又需要兼容已有的http服务&#xff0c;所以我自己尝试做了一次https的部署&#xff0c;目前是正常可用的。 目录 步骤 1&#xff1a;安装 Nginx 步骤 2&…

一个自定义流程的平台

脚本语言使用的是C#&#xff0c;当用户发布一个新的流程时&#xff0c;会把C#的脚本编译成dll&#xff0c;然后添加到微服务中&#xff0c;因为有了硬编译&#xff0c;所以执行速度是非常快的。逻辑脚本支持调试&#xff0c;可以断点和逐行调试。平台提供了调试工具&#xff0c…

redis高可用-集群部署

一&#xff1a;背景 前面我们实现了redis的主从同步和哨兵模式&#xff0c;解决了单机redis的故障转移和流量分担&#xff0c;但是不管是主从或者哨兵模式都是一个主服务对应一个或者多个从服务&#xff0c;并且主服务和从服务的数据是一样的&#xff0c;这样就实现不了redis大…

从零入手人工智能(4)—— 逻辑回归

1.小故事 一家金融科技公司&#xff0c;公司的首席执行官找到团队提出了一个紧迫的问题&#xff1a;“我们如何提前知道哪些客户可能会违约贷款&#xff1f;” 这让团队陷入了沉思&#xff0c;经过激烈讨论团队中的数据分析师提议&#xff1a;“我们可以尝试使用逻辑回归来预测…

node版本过高出现ERR_OSSL_EVP_UNSUPPORTED错误

错误原因&#xff1a; 新版本的nodejs使用的openssl和旧版本不同&#xff0c;导致出错 解决方法&#xff1a; 1.将node版本重新换回16.x 2 windows 下 在package.json文件下添加set NODE_OPTIONS--openssl-legacy-provider && "scripts": {"dev"…

配置 python 脚本操作Excel 环境

在已装python的前提下 一、安装依赖库 pip install pandas pip install openpyxl安装完后&#xff0c;可以在 Python 中运行以下命令来查看 pandas 或 openpyxl 的安装路径&#xff1a; import pandas as pd print(pd.__path__)import openpyxl print(openpyxl.__path__)二、测…

​Claude 3.5 最新体验:助力硕博生与科研人员高效完成论文,超越ChatGPT4o !

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 要不说AI领域的进展真的是日新月异&#xff0c;发展速度已经大大超过预期进度。娜姐本来在准备AI降重工具的测评文章&#xff08;最近好多小伙伴需要&#xff09;。 昨天晚上…

机器学习算法的电影推荐系统以及票房预测系统

一、实验概述 1. 实验目标 本项目希望基于电影数据集&#xff0c;依据电影的简介、关键词、预算、票房、用户评分等特征来对电影进行分析&#xff0c;并完成以下任务&#xff1a; 对电影特征的可视化分析对电影票房的预测多功能个性化的电影推荐算法 2. 数据集 针对票房预…

leetcode144. 二叉树的前序遍历

一、题目描述&#xff1a; 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 二、输入输出实例&#xff1a; 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,2,3]示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[]…

经纬恒润EAS.HSM:驱动硬件信息安全

概述 HSM&#xff08;Hardware Security Module&#xff09;硬件安全模块&#xff0c;是一种用于保护和管理强认证系统所使用的密钥&#xff0c;并同时提供相关密码学操作的计算机硬件设备。 HSM 在汽车信息安全中扮演着至关重要的角色。随着汽车智能化和网联化的快速发展&am…

微型操作系统内核源码详解系列五(3):cm3下调度的开启

系列一&#xff1a;微型操作系统内核源码详解系列一&#xff1a;rtos内核源码概论篇&#xff08;以freertos为例&#xff09;-CSDN博客 系列二&#xff1a;微型操作系统内核源码详解系列二&#xff1a;数据结构和对象篇&#xff08;以freertos为例&#xff09;-CSDN博客 系列…

Pyqt QCustomPlot 简介、安装与实用代码示例(一)

目录 简介安装实用代码示例带有填充的简单衰减正弦函数及其红色的指数包络线具有数据点的 sinc 函数、相应的误差条和 2--sigma 置信带几种散点样式的演示展示 QCustomPlot 在设计绘图方面的多功能性 结语 所有文章除特别声明外&#xff0c;均采用 CC BY-NC-SA 4.0 许可协议。转…

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…

vmware workstation下centos7屏幕切换及大小调整

虚拟机版本&#xff1a;vmware workstation15.5.2 操作系统版本&#xff1a;centos 7.9.2009 一 图形界面和命令行界面切换方法 在CentOS 7中&#xff0c;可以使用以下方法切换界面&#xff1a; 1 使用快捷键切换&#xff1a;按下Ctrl Alt F2&#xff08;或F3&#xff0…

Vue70-路由的几个注意点

一、路由组件和一般组件 1-1、一般组件 1-2、路由组件 不用写组件标签。靠路由规则匹配出来&#xff0c;由路由器渲染出来的组件。 1-3、注意点1 一般组件和路由组件&#xff0c;一般放在不同的文件夹&#xff0c;便于管理。 一般组件放在components文件夹下。 1-4、注意点…

【SpringBoot】SpringBoot:打造现代化微服务架构

文章目录 引言微服务架构概述什么是微服务架构微服务的优势 使用SpringBoot构建微服务创建SpringBoot微服务项目示例&#xff1a;创建订单服务 配置数据库创建实体类和Repository创建服务层和控制器 微服务间通信使用RestTemplate进行同步通信示例&#xff1a;调用用户服务 使用…