分布式锁实现(mysql,以及redis)以及分布式的概念(续)redsync包使用

news2024/9/23 1:23:32

道生一,一生二,二生三,三生万物

这张尽量结合上一章进行使用:上一章

这章主要是讲如何通过redis实现分布式锁的

redis实现

这里我用redis去实现:

技术:golangredis数据结构

这里是有一个大体的实现思路:主要是使用redis中这些语法

redis命令说明:

  1. setnx命令:set if not exists,当且仅当 key 不存在时,将 key 的值设为 value。若给定的 key 已经存在,则 SETNX不做任何动作。
    • 返回1,说明该进程获得锁,将密钥的值设为值
    • 返回0,说明其他进程已经获得了锁,进程不能进入临界区命令格式:设置锁。
  2. get命令:获取键的值
    • 如果存在,则返回
    • 如果不存在,则返回nil命令格式:获取锁
  3. getset命令:该方法是原子的,对键设置newvalue这个值,并且返回键原来的旧值。
    • 命令格式:设置锁并设置键新值
  4. del命令:删除redis中指定的key
    • 命令格式:del lock.key

看了很多博客,这里总结一些比较常用的一些方法:

方案1:

在这里插入图片描述
原理:基于set命令的分布式锁
使用:set命令
存在问题:可能产生死锁

  • 原因:假设线程获取了锁之后,在执行任务的过程中挂掉,来不及显示地执行del命令释放锁,那么竞争该锁的线程都会执行不了,产生死锁的情况。
  • 解决办法:设置锁超时时间
    • 原理:可以使用expire命令设置锁超时时间
    • 使用:setnx 的 key 必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。
    • 存在问题:可能产生死锁
      • 问题原因:setnx 和 expire 不是原子性的操作:
        假设某个线程执行 setnx 命令,成功获得了锁,但是还没来得及执行expire 命令,服务器就挂掉了,这样一来,这把锁就没有设置过期时间了,变成了死锁,别的线程再也没有办法获得锁了
      • 使用:setnx 的 key 必须设置一个超时时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放
      • 解决办法:redis 的 set 命令支持在获取锁的同时设置 key 的过期时间
      • 存在问题:锁过期提前自动释放,线程A删除了线程B的锁
        • 问题原因:锁过期提前自动释放
          1. 假如线程A成功得到了锁,并且设置的超时时间是 30 秒。如果某些原因导致线程 A 执行的很慢,过了 30 秒都没执行完,这时候锁过期自动释放,线程 B 得到了锁。
          2. 随后,线程A执行完任务,接着执行del指令来释放锁。但这时候线程 B 还没执行完,线程A实际上删除的是线程B加的锁。
        • 使用:在加锁的时候把当前的线程 ID 当做value,并在删除之前验证 key 对应的 value 是不是自己线程的 ID
        • 解决办法:可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁
        • 存在问题:get操作、判断和释放锁是两个独立操作,非原子操作
          • 问题原因:判断和释放锁是两个独立操作
          • 解决办法:对于非原子性的问题,我们可以使用Lua脚本来确保操作的原子性

诺是想要更好的体验可以通过我的飞书观看:飞升思维导图

方式2:

在这里插入图片描述
这里的一些出现的方法是java中的。诺是需要可以改成自己的所属语言,这张图较为清晰我也就不做多余的说名,详情可以看我的飞书:飞书思维导图

具体的实现操作:

const (
	//解锁,使用lua变成原子性
	unLockScript = "if redis.call('get',KEYS[1])==ARGV[1]" +
		"then redis.call('del',KEYS[1]) " +
		"return 1 " +
		"else " +
		"return 0 " +
		"end"
	//续期(看门狗)
	watchLogScript = "if redis.call('get',KEYS[1])==ARGV[1] then return redis.call('expire',KEYS[1],ARGV[2]) else return 0 end"
)

type DispersedLock struct {
	key            string        //锁
	value          string        //锁的值,随机值(可以用userId+requestId)
	expire         int           //锁过期时间,单位毫秒
	lockClient     redis.Cmdable //启用锁的客户端,redis目前
	unLockScript   string        //lua 脚本
	watchLogScript string        //看门狗 lua
	unlockChan     chan struct{} //通知通道
}

func (d DispersedLock) getScript(ctx context.Context, script string) string {
	result, _ := d.lockClient.ScriptLoad(ctx, script).Result()
	return result
}

var scriptMap sync.Map

func NewLockRedis(ctx context.Context, cmdable redis.Cmdable, key string, expire int, value string) *DispersedLock {
	lock := &DispersedLock{
		key:    key,
		value:  value,
		expire: expire,
	}
	lock.lockClient = cmdable
	lockScrip, _ := scriptMap.LoadOrStore("dispersed_lock", lock.getScript(ctx, unLockScript))
	lockWatch, _ := scriptMap.LoadOrStore("watch_log", lock.getScript(ctx, watchLogScript))
	lock.unLockScript = lockScrip.(string)
	lock.watchLogScript = lockWatch.(string)
	lock.unlockChan = make(chan struct{}, 0)
	return lock
}

func (d DispersedLock) Lock(ctx context.Context) bool {
	ok, _ := d.lockClient.SetNX(ctx, d.key, d.value, time.Duration(d.expire)*time.Millisecond).Result()
	if ok {
		go d.watchDog(ctx)
	}
	return ok
}
func (d DispersedLock) watchDog(ctx context.Context) {
	//创建一个定时器,每到工作时间的2/3就出发一次
	duration := time.Duration(d.expire*1e3*2/3) * time.Millisecond
	ticker := time.NewTicker(duration)
	//打包成原子
	for {
		select {
		case <-ticker.C:
			//脚本参数
			args := []interface{}{
				d.value,
				d.expire,
			}
			result, err := d.lockClient.Eval(ctx, d.watchLogScript, []string{d.key}, args...).Result()
			if err != nil {
				logS.LogM.ErrorF(ctx, "watchDog error %s", err)
				return
			}
			res, ok := result.(int64)
			if !ok {
				return
			}
			if res == 0 {
				return
			}
		case <-d.unlockChan:
			return
		}
	}
}

func (d DispersedLock) unlock(ctx context.Context) bool {
	//脚本参数
	args := []interface{}{
		d.value,
	}
	result, _ := d.lockClient.Eval(ctx, d.unLockScript, []string{d.key}, args...).Result()
	close(d.unlockChan)

	if result.(int64) > 0 {
		return true
	} else {
		return false
	}
}

const lockMaxLoopNum = 1000

// LoopLock 轮询等待
func (d DispersedLock) LoopLock(ctx context.Context, sleepTime int) bool {
	cancel, cannel := context.WithCancel(context.Background())
	ticker := time.NewTicker(time.Duration(sleepTime) * time.Millisecond)
	count := 0
	status := 0

loop:
	for {
		select {
		case <-cancel.Done():
			break loop
		default:
		}
		if d.Lock(ctx) {
			ticker.Stop()
			cannel()
			break
		} else {
			<-ticker.C
		}
		count++
		//判断是否大于最大获取次数,达到最大直接退出循环
		if count >= lockMaxLoopNum {
			status = 1
			break
		}
	}
	cannel()
	if status != 0 {

		return false
	}
	return true
}

这些就是通过redis去实现一个分布式锁的具体步骤,很多实现,估计很多其他语言的朋友们可能会有些蒙圈。但是没有关系。go 关键字你就当他是一个线程就可以了,select 关键字,你可以理解成队列+if的判断

推荐使用包

golangredsync

import "github.com/go-redsync/redsync/v4"

这个包基本上满足了市面上分布式锁的所有需求,包括续租:(但是这里的续租需要一定的条件才能触发,这个条件要达到redis实例的最大值时才能触发)。所以为了,方便使用,建议可以自己续写一个续租的方法。

这里献上我的:

// NewLock 实例化一个分布式锁,用来实现幂等,降低重试成本
func NewLock(mutexName string) *redsync.Mutex {
	pool := goredis.NewPool(configuration.RedisClient)
	rs := redsync.New(pool)
	newString := uuid.NewString()
	lockName := "Lock:" + newString + ":" + mutexName
	mutex := rs.NewMutex(lockName)
	return mutex
}

// LockRelet 周期性续租,过去无可挽回,未来可以改变
// num定义时间:单位毫秒
// size定义续租的次数
func LockRelet(num int, size int, mutex *redsync.Mutex) chan bool {
	done := make(chan bool)
	if size <= 0 {
		return nil
	}
	go func() {
		ticker := time.NewTicker(time.Duration(num) * time.Millisecond)
		defer ticker.Stop()
		for size > 0 {
			size--
			select {
			case <-ticker.C:
				extend, err := mutex.Extend()
				if err != nil {
					logS.LogM.Panicf("Failed to extend lock:", err)
				} else if !extend {
					logS.LogM.Panicf("Failed to extend lock: not successes")
				}
			case <-done:
				return
			}
		}
	}()
	return done
}

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

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

相关文章

github 推送报错 ssh: connect to host github.com port 22: Connection timed out 解决

&#x1f680; 作者主页&#xff1a; 有来技术 &#x1f525; 开源项目&#xff1a; youlai-mall &#x1f343; vue3-element-admin &#x1f343; youlai-boot &#x1f33a; 仓库主页&#xff1a; Gitee &#x1f4ab; Github &#x1f4ab; GitCode &#x1f496; 欢迎点赞…

未来已来:概念车展漫游可视化的震撼之旅

随着科技的飞速发展&#xff0c;汽车行业正经历着前所未有的变革。而在这场变革中&#xff0c;概念车展无疑是一个引领潮流、展望未来的重要舞台。 想象一下&#xff0c;你站在一个巨大的展厅中&#xff0c;四周陈列着各式各样的概念车。它们有的造型独特&#xff0c;有的功能先…

[BJDCTF2020]ZJCTF,不过如此(特详解)

php特性 1.先看代码&#xff0c;提示了next.php&#xff0c;绕过题目的要求去回显next.php 2.可以看到要求存在text内容而且text内容强等于后面的字符串&#xff0c;而且先通过这个if才能执行下面的file参数。 3.看到用的是file_get_contents()函数打开text。想到用data://协…

真心话大冒险!关于自动驾驶的现状和未来,Mobileye的回答是?

过去的十年&#xff0c;可以说是从主动安全、辅助驾驶到自动驾驶快速演进的周期。这其中&#xff0c;无论是技术迭代&#xff0c;还是成本优化&#xff0c;以及技术和商业化路线的争论&#xff0c;备受行业关注。 同时&#xff0c;市场上的声音&#xff0c;也很多。有激进、谨慎…

详细Nginx和PHP-FPM的进程间通信使用

工作中考虑到PHP-FPM效率&#xff0c;发现PHP-FPM和NGINX的进程通信不止配置端口这一种方式:bowtie: Nginx和PHP-FPM的进程间通信有两种方式,一种是TCP,一种是UNIX Domain Socket. 其中TCP是IP加端口,可以跨服务器.而UNIX Domain Socket不经过网络,只能用于Nginx跟PHP-FPM都在同…

基于YOLOv8的摔倒行为检测系统(Python源码+Pyqt6界面+数据集)

&#x1f4a1;&#x1f4a1;&#x1f4a1;本文主要内容:通过实战基于YOLOv8的摔倒行为检测算法&#xff0c;从数据集制作到模型训练&#xff0c;最后设计成为检测UI界面 人体行为分析AI算法&#xff0c;是一种利用人工智能技术对人体行为进行检测、跟踪和分析的方法。通过计算…

linux基础指令【中篇】

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 补充上篇的细节1.sta…

C#~Winform代码调整快捷键

右移 选中要移动的代码 -> Tab 结果&#xff1a; 左移 选中要移动的代码 -> ShiftTab 结果&#xff1a; 自动调整 选中需要调整的代码 -> CtrlKD 调整后&#xff1a;

论文解读--Wideband 120 GHz to 140 GHz MIMO Radar:System Design and Imaging Results

120~140GHz宽带MIMO雷达&#xff1a;系统设计和成像结果 摘要 本文提出了一种工作频率在120 GHz ~ 140 GHz之间的宽带FMCW MIMO雷达传感器。该传感器基于SiGe技术制造的雷达芯片组&#xff0c;并使用MIMO方法来提高角度分辨率。MIMO操作通过发射机的时域复用实现。该雷达能够通…

[蓝桥杯]真题讲解:冶炼金属(暴力+二分)

蓝桥杯真题视频讲解&#xff1a;冶炼金属&#xff08;暴力做法与二分做法&#xff09; 一、视频讲解二、暴力代码三、正解代码 一、视频讲解 视频讲解 二、暴力代码 //暴力代码 #include<bits/stdc.h> #define endl \n #define deb(x) cout << #x << &qu…

Python 中的多进程(01/2):简介

一、说明 本文简要而简明地介绍了 Python 编程语言中的多处理&#xff08;多进程&#xff09;。解释多处理的基本信息&#xff0c;如什么是多处理&#xff1f;为什么用多处理&#xff1f;在python中怎么办等。 二、什么是多处理&#xff1f; 多处理是指系统同时支持多个处理器的…

【Android】Android中的系统镜像由什么组成?

文章目录 总览Boot Loader 的加锁与解锁Boot 镜像内核RAM diskARM 中的设备树 (Device Tree) /System 和/Data 分区镜像参考 总览 各种Android设备都只能刷专门为相应型号的设备定制的镜像。 厂商会提供一套系统镜像把它作为“出厂默认”的 Android 系统刷在设备上。 一个完…

Obsidian - 使用小记(Typora切换过来)

文章目录 关于 Obsidian打开已有的 文件夹将图片改为 Typora 的保存文件夹 关于 Obsidian 官网 https://obsidian.md/github : https://github.com/obsidianmd 个人版免费 一直习惯用 Typora 编写markdown git 记录笔记&#xff0c;多次被安利 Obsidian 后&#xff0c;今天尝…

OFD格式文件预览解决方案

问题 项目中文件预览统一采用的是pc端转pdf后在移动端上面采用Pdfview这个组件进行查看&#xff0c;现后端暂不支持ofd转pdf采用ofd.umd.js查看ofd文件&#xff0c;用WebView直接访问后端给的预览地址会出现跨域问题。 解决办法 拿到pc端预览的ofd.umd.js文件编写预览ofd文件…

JSON-handle工具安装及使用

目录 介绍下载安装简单操作 介绍 JSON-Handle 是一款非常好用的用于操作json的浏览器插件&#xff0c;对于开发人员和测试人员来说是一款很好用的工具&#xff0c;如果你还没有用过&#xff0c;请赶紧下载安装吧&#xff0c;下面是安装过程和具体使用。 下载安装 点击下载JSON…

IaC基础设施即代码:使用Terraform 连接huaweicloud华为云 并创建后端OBS

目录 一、实验 1.环境 2.huaweicloud华为云创建用户 3.Windows使用Terraform 连接 huaweicloud 4.Windows给Terraform项目添加huaweicloud华为云OBS &#xff08;实现代码与资源分离&#xff09; 二、问题 1. Windows terraform 初始化失败 2.Terraform 初始化后端资源失…

Sulfo Cy2 Biotin,水溶性 Cy2 生物素,能够与各种氨基基团特异性结合

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;Sulfo Cyanine2 Biotin&#xff0c;Sulfo Cy2 Biotin&#xff0c;水溶性 Cy2 生物素&#xff0c;Sulfo-Cy2-Biotin&#xff0c;水溶性-Cy2-生物素 一、基本信息 产品简介&#xff1a;Sulfo Cyanine2 Biotin, also k…

el-dialog的close事件会执行两次

如果close事件执行的方法有传参&#xff0c;那么定义一个变量传参&#xff0c;而不是写死 如果写死

66.Spring是如何整合MyBatis将Mapper接口注册为Bean的原理?

原理 首先MyBatis的Mapper接口核心是JDK动态代理 Spring会排除接口&#xff0c;无法注册到IOC容器中 MyBatis 实现了BeanDefinitionRegistryPostProcessor 可以动态注册BeanDefinition 需要自定义扫描器&#xff08;继承Spring内部扫描器ClassPathBeanDefinitionScanner ) 重…

成熟的内外网数据交换方案,如何实现跨网传输?

网络迅速发展&#xff0c;我们可以从网络上查找到各式各样的信息&#xff0c;但是同时网络安全问题也随之严重。近几年&#xff0c;各种有关网络安全的新闻不断被报道&#xff0c;数据泄露给很多企业带来了严重打击&#xff0c;不仅是经济损失&#xff0c;严重者还会对企业的声…