分布式锁实现(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        //看门狗 luaunlockChan     chan struct{} //通知通道
}func (d DispersedLock) getScript(ctx context.Context, script string) string {result, _ := d.lockClient.ScriptLoad(ctx, script).Result()return result
}var scriptMap sync.Mapfunc NewLockRedis(ctx context.Context, cmdable redis.Cmdable, key string, expire int, value string) *DispersedLock {lock := &DispersedLock{key:    key,value:  value,expire: expire,}lock.lockClient = cmdablelockScrip, _ := 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.Millisecondticker := 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 := 0status := 0loop:for {select {case <-cancel.Done():break loopdefault:}if d.Lock(ctx) {ticker.Stop()cannel()break} else {<-ticker.C}count++//判断是否大于最大获取次数,达到最大直接退出循环if count >= lockMaxLoopNum {status = 1break}}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 + ":" + mutexNamemutex := 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.mzph.cn/news/645932.shtml

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

相关文章

使用Python的pygame库实现自动追踪目标的Snake游戏

和上一期不同的目标追踪入门不同的是&#xff0c;这期是自动追踪科学游戏&#xff0c;话不多说&#xff0c;321上链接 一、项目背景 Snake游戏是一款经典的游戏&#xff0c;玩家需要控制一条蛇在屏幕上移动&#xff0c;吃掉食物并避免撞到自己的身体或墙壁。传统的Snake游戏通常…

校园跑腿小程序源码系统+代取快递+食堂超市代买+跑腿 带完整的安装代码包以及搭建教程

随着移动互联网的普及&#xff0c;人们越来越依赖于手机应用来解决日常生活中的各种问题。特别是在校园内&#xff0c;由于快递点距离宿舍较远、食堂排队人数过多等情况&#xff0c;学生对于便捷、高效的服务需求愈发强烈。在此背景下&#xff0c;校园跑腿小程序源码系统应运而…

蓝桥杯备赛 week 3 —— 高精度(C/C++,零基础,配图)

目录 &#x1f308;前言&#xff1a; &#x1f4c1; 高精度的概念 &#x1f4c1; 高精度加法和其模板 &#x1f4c1; 高精度减法和其模板 &#x1f4c1; 高精度乘法和其模板 &#x1f4c1; 高精度除法和其模板 &#x1f4c1; 总结 &#x1f308;前言&#xff1a; 这篇文…

Linux/Academy

Enumeration nmap 首先扫描目标端口对外开放情况 nmap -p- 10.10.10.215 -T4 发现对外开放了22,80,33060三个端口&#xff0c;端口详细信息如下 结果显示80端口运行着http&#xff0c;且给出了域名academy.htb&#xff0c;现将ip与域名写到/et/hosts中&#xff0c;然后从ht…

【12.PWM输出】蓝桥杯嵌入式一周拿奖速成系列

系列文章目录 蓝桥杯嵌入式系列文章目录(更多此系列文章可见) PWM输出 系列文章目录一、STM32CUBEMX配置二、项目代码1.main.c --> PWMOutputProcess 总结 一、STM32CUBEMX配置 STM32CUBEMX PA6 ->TIM16_CH1; PA7-> TIM17_CH1 预分频设置为79,自动重装载设置999PWM输…

PyQtGraph 之PlotCurveItem 详解

PyQtGraph 之PlotCurveItem 详解 PlotCurveItem 是 PyQtGraph 中用于显示曲线的图形项。以下是 PlotCurveItem 的主要参数和属性&#xff1a; 创建 PlotCurveItem 对象 import pyqtgraph as pg# 创建一个 PlotCurveItem curve pg.PlotCurveItem()常用的参数和属性 setData(…

资源管理核心考点梳理

个人总结&#xff0c;仅供参考&#xff0c;欢迎加好友一起讨论 PMP - 资源管理核心考点梳理 资源管理包括人力资源和实物资源管理。学习的重点是人力资源的管理&#xff0c;这一章是考试的重点章节&#xff0c;在新考纲中&#xff0c;“人”这一模块在题目种的比例是42%。 01 …

14.块参照的旋转(BlockReference)

愿你出走半生,归来仍是少年&#xff01; 环境&#xff1a;.NET FrameWork4.5、ObjectArx 2016 64bit、Entity Framework 6. 在排水管网数据的编图时&#xff0c;时常会遇见针对雨水箅等进行旋转。由于数据存储在数据库内&#xff0c;通过CAD自带的旋转功能只能变更图面而无法…

SVG 矩形 – SVG Rectangle (3)

简介 rect 元素用于创建 SVG 矩形和矩形图形的变体。有六个属性决定矩形在屏幕上的形状和位置 x, y – 矩形左上角的 x, y 坐标width、height – 矩形的宽度和高度rx、ry – 矩形角的 x 和 y 半径 如果没有设置 x 和 y 属性&#xff0c;则矩形的左上角放置在点 (0,0) 处。 如…

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

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

C语言第八弹---一维数组

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 一维数组 1、数组的概念 2、⼀维数组的创建和初始化 2.1、数组创建 2.2、数组的初始化 2.3、数组的类型 3、⼀维数组的使用 3.1、数组下标 3.2、数组元素…

Vscode配置python代码开发

文章目录 1. 配置python运行环境2. 常用插件说明3. Vscode配置文件说明3.1 setting.json配置说明3.2 launch.json配置说明 4. 远程开发5. 其他配置 1. 配置python运行环境 安装python插件&#xff1a;点击VSCode左侧边栏中的扩展图标&#xff08;或按 CtrlShiftX&#xff09;&a…

从方法论到最佳实践,深度解析企业云原生 DevSecOps 体系构建

作者&#xff1a;匡大虎 引言 安全一直是企业上云关注的核心问题。随着云原生对云计算基础设施和企业应用架构的重定义&#xff0c;传统的企业安全防护架构已经不能够满足新时期下的安全防护要求。为此企业安全人员需要针对云原生时代的安全挑战重新进行系统性的威胁分析并构…

深度视觉目标跟踪进展综述-论文笔记

中科大学报上的一篇综述&#xff0c;总结得很详细&#xff0c;整理了相关笔记。 1 引言 目标跟踪旨在基于初始帧中指定的感兴趣目标( 一般用矩形框表示) &#xff0c;在后续帧中对该目标进行持续的定位。 基于深度学习的跟踪算法&#xff0c;采用的框架包括相关滤波器、分类…

Rust 通用代码生成器莲花发布红莲尝鲜版二十视频,支持 Nodejs 21,18 和 14

Rust 通用代码生成器莲花发布红莲尝鲜版二十视频&#xff0c;支持 Nodejs 21,18 和 14 Rust 通用代码生成器莲花发布红莲尝鲜版二十视频。此版本开始支持 Nodejs21,18 加上原来支持的 Nodejs 14。现在莲花支持三种 Nodejs 环境。适应性大大增强&#xff0c;也给您的使用带来了…

IDEA配置Maven教程

1.Maven下载 首先我们进入maven官方网站Maven – Welcome to Apache Maven&#xff0c;进入网页后&#xff0c;点击Download去下载 下载免安装版&#xff0c;解压即可,解压至磁盘任意目录&#xff0c;尽量不要取中文名如下图&#xff1a; 2.配置Maven环境变量 复制Maven所在的…

cms中getshell的各种姿势

cms中getshell的各种姿势 wordpress----getshell 这里wordpress后台&#xff0c;外观&#xff0c;主题&#xff0c;编辑&#xff0c;修改其中的404模版&#xff0c;保存后就可拿到shell 直接访问&#xff0c;就可以成功连接 另外&#xff0c;在主题中&#xff0c;可以上传 …

[蓝桥杯]真题讲解:景区导游(DFS遍历、图的存储、树上前缀和与LCA)

蓝桥杯真题讲解&#xff1a; 一、视频讲解二、暴力代码三、正解代码 一、视频讲解 视频讲解 二、暴力代码 //暴力代码&#xff1a;DFS #include<bits/stdc.h> #define endl \n #define deb(x) cout << #x << " " << x << \n; #de…

算法练习-螺旋矩阵(思路+流程图+代码)

难度参考 难度&#xff1a;中等 分类&#xff1a;数组 难度与分类由我所参与的培训课程提供&#xff0c;但需要注意的是&#xff0c;难度与分类仅供参考。以下内容均为个人笔记&#xff0c;旨在督促自己认真学习。 题目 给定一个正整数n&#xff0c;生成一个包含1到 n^2 所有元…

网络组件、设备和关系网络图【推荐】

目录 网络上的设备&#xff1a; 设备和台式计算机&#xff1a; 防火墙&#xff1a; 服务器&#xff1a; 集线器和交换机&#xff1a; 路由器&#xff1a; 调制解调器和无线接入点调制解调器&#xff1a; 无线接入点&#xff1a; 网络架构&#xff08;有时称为网络设计&…