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

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

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

这章主要是讲如何通过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
}

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

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

相关文章

2024 年 10 款顶级的数据恢复软件榜单

2024年&#xff0c;随着人工智能、云计算等技术的不断发展&#xff0c;数据已经成为我们生活中不可或缺的一部分。然而&#xff0c;数据丢失的风险仍然存在。删除文件、病毒攻击、硬件损坏和其他情况都可能导致数据丢失。而数据恢复软件就成为解决这一问题的有效方案。 2024 年…

springCloud的ribbon和feign

ribbon方式调用 就是将原来的具体地址&#xff0c;改为了通过服务名去调用。注册中心中有多个服务&#xff0c;相同服务名&#xff0c;就会算作可以调用的服务。 首先得有一个注册中心&#xff0c;然后各种服务注册进去&#xff0c;然后利用ribbon或者feign去调用。 ribbon是直…

微认证 openEuler社区开源贡献实践

文章目录 1. 开源与开源社区2. openEuler 社区概述3.参与openEuler社区贡献4.openEuler软件包开发Linux软件管理——源码编译 1. 开源与开源社区 Richard Matthew Stallman&#xff0c;1983年9月推出GNU项目&#xff0c;并发起自由软件运动(free software movement或free/open…

多维时序 | Matlab实现RIME-TCN-Multihead-Attention霜冰算法优化时间卷积网络结合多头注意力机制多变量时间序列预测

多维时序 | Matlab实现RIME-TCN-Multihead-Attention霜冰算法优化时间卷积网络结合多头注意力机制多变量时间序列预测 目录 多维时序 | Matlab实现RIME-TCN-Multihead-Attention霜冰算法优化时间卷积网络结合多头注意力机制多变量时间序列预测效果一览基本介绍程序设计参考资料…

Dify学习笔记-应用发布(四)

1、发布为公开 Web 站点 使用 Dify 创建 AI 应用的一个好处在于&#xff0c;你可以在几分钟内就发布一个可供用户使用的 Web 应用&#xff0c;该应用将根据你的 Prompt 编排工作。 如果你使用的是自部署的开源版&#xff0c;该应用将运行在你的服务器上 如果你使用的是云服务&…

3.确认弹窗(ConfirmPopup)

愿你出走半生,归来仍是少年&#xff01; 环境&#xff1a;.NET 7 在开发中&#xff0c;最常用的弹窗之一表示确认弹窗&#xff0c;为了减少重复的开发工作&#xff0c;所以需要基于Popup进行封装。 1.布局 分为标题、确认内容、按钮三个区域&#xff0c;都是可供调整的。 &l…

java复习篇 数据结构:链表第二节 哨兵

目录 单向链表哨兵 初始 头插 思路 代码 尾插 思路 遍历 遍历验证头插 尾插代码 尾插测试 get 思路 代码 测试 insert 思路 代码 测试 remove 移除头结点 提问 移除指定位置 测试 单向链表哨兵 单向链表里面有一个特殊的节点称为哨兵节点&#xff0c;…

MacOS 无法ping 通 github.com 解决方案

ping github.com 会显示请求超时&#xff1a; PING github.com (192.30.253.112): 56 data bytes Request timeout for icmp_seq 0 Request timeout for icmp_seq 1 Request timeout for icmp_seq 2 Request timeout for icmp_seq 3 Request timeout for icmp_seq 4 Request …

一文了解Ceph原理以及常见ceph指令

一、Ceph介绍 什么是分布式存储&#xff1f; 与集中式存储相反&#xff0c;分布式存储通常采用存储单元集群的形式。并且具有在集群节点之间进行数据同步和协调的机制。其目的是为了通过服务器解决大规模&#xff0c;高并发情况下的Web访问问题。 Ceph是一个统一的、分布式的存…

如何利用H5页面引导关注公众号-数灵通

随着流量获取成本的增加&#xff0c;许多企业开始寻找新的引流渠道来储存流量。H5小活动成为了一种有效的引流方式&#xff0c;并且在客户之间传递&#xff0c;形成了裂变效应。企业开始将目光转向H5网站&#xff0c;希望通过引导客户关注公众号来提升品牌影响力。 为了实现这一…

143基于matlab的2D平面桁架有限元分析

基于matlab的2D平面桁架有限元分析&#xff0c;可以改变材料参数&#xff0c;输出平面结构外形&#xff0c;各桁架应力&#xff0c;位移及作用力。可查看节点力&#xff0c;程序已调通&#xff0c;可直接运行。 143 matlab 平面桁架 有限元分析 桁架应力 (xiaohongshu.com)

温湿度传感器原理解析,温湿度传感器的应用场景有哪些?

作为常见的检测装置&#xff0c;现在已经有大大小小几十种传感器出现在我们的日常生活中。作为能够测量环境温度和湿度的传感器&#xff0c;温湿度传感器正是最常见的传感器之一&#xff0c;作为温湿度监测系统的一部分&#xff0c;被广泛应用于智慧机房、智慧楼宇、智慧农业等…

重构改善既有代码的设计-学习(三):重新组织数据

1、拆分变量&#xff08;Split Variable&#xff09; 有些变量用于保存一段冗长代码的运算结果&#xff0c;以便稍后使用。这种变量应该只被赋值一次。 如果它们被赋值超过一次&#xff0c;就意味它们在函数中承担了一个以上的责任。如果变量承担多个责任&#xff0c;它就应该被…

外贸干货!社媒营销养号全攻略:10个必须知道的养号技巧

大家都知道&#xff0c;养号已经成为任何希望在WhatsApp、Facebook、TikTok等社交媒体平台上取得成功的跨境电商和营销人员的必备技能。在本文中&#xff0c;我们将深入探讨如何高效地进行养号&#xff0c;以及如何在海外社交媒体批量养号的过程中避免封号&#xff0c;确保你的…

Jenkins全局工具配置

目录 Jenkins全局工具全局工具配置Settings 文件配置Maven配置JDK配置Git配置 Jenkins全局工具 我们在安装了Jenkins之后&#xff0c;就可以开始使用Jenkins来进行一些自动化构建发布工作&#xff0c;但是开始之前我们还需要进行全局工具的配置&#xff0c;Jenkins全局工具配置…

如何使用 NFTScan API 检索 NFT 合约地址下 Transactions 数据

对于大多数人而言&#xff0c;获取某 NFT 合约地址下的全量交易记录是十分有挑战性的&#xff0c;不仅涉及到对区块链技术的深入了解以及使用相应的工具和资源&#xff0c;还需要处理区块链上的智能合约和交易数据&#xff0c;并将其与外部数据源进行整合分析。通常&#xff0c…

【UAT阶段】测试计划分享

前面我有分享UAT阶段注意事项&#xff0c;今天跟大家分享UAT测试计划包含哪些内容&#xff1a; 希望该计划能给大家在实际项目中有所帮助&#xff1b;

求职应聘找工作,你一定会遇到的人才测评

信息时代&#xff0c;越来越多的公司在招聘时引入了人才测评机制。企业和单位希望通过人才测评在广大的应聘者中&#xff0c;找到符合自己要求的人才。虽然很多应聘者能力和简历都比较出众&#xff0c;但却在最开始的人才测评中吃了亏。有的公司很看重人才测评结果。测评就相当…

WinSCP下载安装并实现远程SSH本地服务器上传文件

文章目录 1. 简介2. 软件下载安装&#xff1a;3. SSH链接服务器4. WinSCP使用公网TCP地址链接本地服务器5. WinSCP使用固定公网TCP地址访问服务器 1. 简介 ​ Winscp是一个支持SSH(Secure SHell)的可视化SCP(Secure Copy)文件传输软件&#xff0c;它的主要功能是在本地与远程计…

2017年认证杯SPSSPRO杯数学建模A题(第二阶段)安全的后视镜全过程文档及程序

2017年认证杯SPSSPRO杯数学建模 A题 安全的后视镜 原题再现&#xff1a; 汽车后视镜的视野对行车安全非常重要。一般来说&#xff0c;汽车的后视镜需要有良好的视野范围&#xff0c;以便驾驶员能够全面地了解车后方的道路情况。同时&#xff0c;后视镜也要使图像的畸变尽可能…