分布式锁实现(mysql,以及redis)以及分布式的概念
道生一,一生二,二生三,三生万物
我旁边的一位老哥跟我说,你知道分布式是是用来干什么的嘛?一句话给我干懵了,我能隐含知道,大概是用来做分压处理的,并增加系统稳定性的。但是具体如何,我却道不出个1,2,3。现在就将这些做一个详细的总结。至少以后碰到面试官可以说上个123。
那么就正式进入正题:
文章目录
- 道生一,一生二,二生三,三生万物
- 分布式概念
- 分布式锁
- 分布式锁实现的方案
- 数据库实现(Mysql)
- 方式1:乐观锁
- 方式2:读写锁的实现
- 第一步:链接数据库
- 第二部:建立数据库表
- 第三步:读方法(读操作)
- 读加锁操作
- 读解锁操作
- 第四步:写方法(写操作)
- 写加锁
- 写解锁
- redis实现
分布式概念
分布式是一种,将一个大问题拆分成多个小问题,并分别由多个节点协同完成的计算机解决方案。
既然是解决问题,那么是解决什么呢?
分布式的目的:是为了解决单个机器无法满足性能要求的问题
至于为什么单个机器无法满足,其实回头细想,现代的计算数据都是突出一个字,
大,这个大字就意味着处理数据的运算速度也得大,但是因为现代材料局限,一个主机的运算的能力有限,然而数据是成指数倍的增长,为了解决这个问题就是增加主机的数量,但是这就出现了另一个问题:->:如何将这些主机有机结合起来?分布式就是解决这个问题的。其实很多公司其实都不需要分布式,但是,时代进步的。不管现在是不是要用,但是未来的事情谁又说的清?
这个是网图,但是我认为这个是可以将现在大部分的分布式知识做一个较为全面的总结的

分布式这种框架的诞生,伴随着许多的问题,包括硬件,也包括软件,比如:
- 数据一致性
- 数据乱序
- 数据丢失
- 分库分表的扩容
- …
问题可以说是非常多。
但是大部分是可以用技术手段去避免很多的问题。而分布式加锁就是一个较为有效的手段。不过就是牺牲了一部分的时效性,但是对于人类来说,这点时间的损耗,就是一个眨眼的功夫。希望日后会出现更多手段,去解决这方面的问题。
接下来就是对于分布式锁介绍
分布式锁
分布式锁的应用场景,这些既是应用场景也是问题所在:
- 处理缓存击穿
- 处理缓存雪崩
- 重试处理
- 幂等性
- 数据不一致的问题
- …
基本上很多分布式应用上的问题基本可以用分布式锁处理
那么什么算是一个合格的分布式锁呢?众多巨人的文章中无不透露以下这几点:
互斥性:这个是锁的基本功能,即:同一时刻只允许一个客户端持有锁避免死锁:获取到锁的客户端出现问题了,没有办法解锁,所以要避免死锁。让系统继续运行下去(有些也叫安全性)容错:既然是服务器,那么提供锁的系统也是有可能崩溃的,所以要保证这一点。
这些属性,将会贯穿这篇文章。
分布式锁实现的方案
这些我会举出大体的实现思路,并不会全部去实现。
🌝每种实现类型中都有不同实现方式 ,比如mysql有悲观锁和乐观锁,读写锁这些方式的实现方式
常见的实现方案有:
- 数据库实现(我这里用的是mysql)
- redis实现
- zookeeper实现
- Redlock 算法实现
这里都会说到,但是对于实现来说,我目前就是只说MySQL的实现,redis的实现
语言对于程序员来说只是说某种工具而言,真正重要的是逻辑(算法)和数据结构,这个才是一个程序员安生立命的本钱。
所以这篇文章我会用go语言实现,其他语言的版本这里就不多说了。但是我会将常见的语言包说出,方便友友们能够快速查到相关的资料。
数据库实现(Mysql)
这里我用MySQL去实现:
技术:golang,gorm,数据结构
方式1:乐观锁
实现方式:通过对数据表添加一个字段 Version实现(数据版本(Version)记录机制实现)
主要逻辑:为数据库表增加一个数字类型的 version 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一
如果对于跟新操作,需要先判断当前version与数据库中的version版本号是否对应,对应的上就允许跟新,诺是不相同,就会导致冲突,此时就更新失败。
是不是很简单?是的确实很简单。画个图解释一下


那么悲观锁如何实现我相信大家肯定也就明白了。但是这里我就浅浅提一点:
在sql语句后添加for update
逻辑实现:
- 通过添加线程做轮询等待,然后抢锁
- 添加过期时间
- 更新版本号
接下来重点是读写锁的实现
方式2:读写锁的实现
而这两种锁的实现,需要满足一下几个特点:
- 执行操作的环境是分布式的(当然单机不是不能用)
- 读操作,不做限制,里面资源是共享的。可以支持多个线程或者协程对资源的读取的操作。
- 写操作,是互斥的,也就是说明一个时刻只允许一个协程或者进程对资源进行访问。
- 读操作,写操作两者是互斥的。不能同时存在
ps:相当于对于读写这两个操作来说,都有自己的申请锁和解锁的方法,读模式共享,写模式互斥
读写锁的有点在于:
- 分布式读写锁是比分布式锁粒度更小的锁
- 对于业务场景更加灵活
所以综上的出:读写锁的状态有:读加锁状态、写加锁状态、无锁状态
| 当前锁状态 | 读锁请求 | 写锁请求 |
|---|---|---|
| 无锁状态 | 可以 | 可以 |
| 读锁状态 | 可以 | 不可以 |
| 写锁状态 | 不可以 | 不可以 |
第一步:链接数据库
每个程序员的链接手法各不相同,所以这里就不献丑了。只要连上数据库就好。然后将客户端暴露出来。
第二部:建立数据库表
要包含一下这几个字段。
var (statusUnLock = "Unlock"statusReadLock = "ReadLock"statusWirteLock = "WirteLock"
)type RWLock struct {
//表示某条数据加锁的状态,只能是读锁、写锁、无锁状态中的一种,默认状态为无锁状态LockStatus string `gorm:"default:'Unlock'"` //ReadLockCount 字段则记录当前并发访问的 goroutine(可以理解成线程) 数量ReadLockCount uint32 `gorm:"default:0"` LockReason string //记录当前加锁的原因
}// Stock 存储锁
type Stock struct {gorm.ModelRWLockCount int64
}func (Stock) TableName() string {return "stock"
}
这三个是状态值,分别代表:无锁,读锁,写锁
statusUnLock = “Unlock”
statusReadLock = “ReadLock”
statusWirteLock = “WirteLock”
gorm.Model中包含了:一下字段:
//主键idID uint `gorm:"primarykey"`//创建时间CreatedAt time.Time//更新时间UpdatedAt time.Time//删除时间,软删除DeletedAt DeletedAt `gorm:"index"`
这里我们通过的方式是,建立一个锁表来管理整个锁。相应的字段的功能我这里就不做赘述,在代码中已经有了。
第三步:读方法(读操作)
读加锁操作
// ReadLock 读锁
func (s Stock) ReadLock(ctx context.Context, db *gormX.DataBD, lockReason string) error {fields := map[string]interface{}{"lock_status": statusReadLock,"read_lock_count": gorm.Expr("read_lock_count + ?", 1),"lock_reason": lockReason,}//将所属的id锁的写状态改为读状态result := db.DB(ctx).Model(&Stock{}).Where("id=? AND lock_status=?", s.ID, statusWirteLock).Updates(fields)if result.Error != nil {return result.Error}if result.RowsAffected == 0 {return errors.New("重入锁失败,受影响的行数为 0")}return nil
}
我将对这里的代码做一个解释:
context.Context:上下文,用来管理请求
db *gormX.DataBD: 用来处理mysql连接的
lockReason:对每个锁进行备注
这里的读锁就是做一个统计,统计有多少个线程,是读锁状态
result := db.DB(ctx).Model(&Stock{}).Where("id=? AND lock_status=?", s.ID, statusWirteLock).Updates(fields):
这个整个sql的翻译,{参数}
update stock
setlock_status={statusReadLock},read_lock_count =read_lock_count +1 ,//这个不是参数lock_reason={lockReason} where id={s.ID} and statusWirteLoc={statusWirteLock}
这里为什么是update呢?因为在这里gorm中,update没有的数据的话,会变成insert插入数据。其他语言在做的时候一定要注意。
读解锁操作
// UnReadLock 读解锁
func (s Stock) UnReadLock(ctx context.Context, db *gormX.DataBD, UnLockReason string) error {fields := map[string]interface{}{"read_lock_count": gorm.Expr("if(read_lock_count>0),read_lock_count-1,0"),"lock_status": gorm.Expr("if(lock_status < 1),?,?", statusUnLock, statusReadLock),"lock_reason": UnLockReason,}result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusReadLock).UpdateColumns(fields)if result.Error != nil {return result.Error}if result.RowsAffected == 0 {return errors.New("解读锁失败,受影响的行数为 0")}return nil
}
这里将读操作做完的业务进行释放后,在表中做统计减少的操作。
我将对这里的代码做一个解释:
result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusReadLock).UpdateColumns(fields)
update stock
setlock_status=(if(read_lock_count>0),read_lock_count-1,0),read_lock_count =(if(lock_status < 1),{statusUnLock},{statusReadLock}),lock_reason={lockReason} where id={s.ID} and statusWirteLoc={statusReadLock}
第四步:写方法(写操作)
写加锁
// WriteLock 写锁
func (s Stock) WriteLock(ctx context.Context, db *gormX.DataBD, lockReason string) error {fields := map[string]interface{}{"read_lock_count": 0,"lock_status": statusWirteLock,"lock_reason": lockReason,}result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusUnLock).Updates(fields)if result.Error != nil {return result.Error}if result.RowsAffected == 0 {return errors.New("写入锁失败,受影响的行数为 0")}return nil
}
这里不对线程进行统计,因为这是互斥的。并将锁写入状态
我将对这里的代码做一个解释:
result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusUnLock).Updates(fields)
update stock
setlock_status={statusWirteLock},read_lock_count =0 ,//这个不是参数lock_reason={lockReason} where id={s.ID} and statusWirteLoc={statusWirteLock}
这里为什么是update呢?因为在这里gorm中,update没有的数据的话,会变成insert插入数据。其他语言在做的时候一定要注意。
写解锁
// UnWriteLock 写解锁
func (s Stock) UnWriteLock(ctx context.Context, db gormX.DataBD, UnLockReason string) error {fields := map[string]interface{}{"read_lock_count": 0,"lock_status": statusUnLock,"lock_reason": UnLockReason,}result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusWirteLock).Updates(fields)if result.Error != nil {return result.Error}if result.RowsAffected == 0 {return errors.New("解写锁失败,受影响的行数为 0")}return nil
}
这里不对线程进行统计,因为这是互斥的。并修改其状态
我将对这里的代码做一个解释:
result := db.DB(ctx).Model(&Stock{}).Where("id=? and lock_status=?", s.ID, statusWirteLock).Updates(fields)
update stock
setlock_status={statusUnLock},read_lock_count =0 ,//这个不是参数lock_reason={lockReason} where id={s.ID} and statusWirteLoc={statusWirteLock}
redis实现
今天太晚了,先不写,明天补上:连接
续:今天补上了:请看这篇文章
相关文章:
分布式锁实现(mysql,以及redis)以及分布式的概念
道生一,一生二,二生三,三生万物 我旁边的一位老哥跟我说,你知道分布式是是用来干什么的嘛?一句话给我干懵了,我能隐含知道,大概是用来做分压处理的,并增加系统稳定性的。但是具体如…...
实现分布式锁:Zookeeper vs Redis
目录 引言 1. Zookeeper分布式锁 1.1特点和优势: 强一致性 顺序节点 Watch机制 1.2 Zookeeper分布式锁代码示例 2. Redis分布式锁 2.1特点和优势: 简单高效 可续租性 灵活性 2.2Redis分布式锁代码示例 3.对比和选择 3.1 一致性要求 3.2…...
电脑录屏必备技能,让分享变得更加简单!
随着网络技术的飞速发展,电脑录屏已经成为日常工作和学习中不可或缺的一部分。无论是录制在线课程、游戏解说、软件教程,还是远程会议、演示文稿等,电脑录屏都有着广泛的应用。接下来,本文将介绍三种常见的电脑录屏方法࿰…...
重构改善既有代码的设计-学习(一):封装
1、封装记录(Encapsulate Record) 一些记录性结构(例如hash、map、hashmap、dictionary等),一条记录上持有什么字段往往不够直观。如果其使用范围比较宽,这个问题往往会造成许多困扰。所以,记录…...
Python图像处理【19】基于霍夫变换的目标检测
基于霍夫变换的目标检测 0. 前言1. 使用圆形霍夫变换统计图像中圆形对象2. 使用渐进概率霍夫变换检测直线2.1 渐进霍夫变换原理2.2 直线检测 3. 使用广义霍夫变换检测任意形状的对象3.1 广义霍夫变换原理3.2 检测自定义形状 小结系列链接 0. 前言 霍夫变换 (Hough Transform,…...
Spring+SprinMVC+MyBatis注解方式简易模板
SpringSprinMVCMyBatis注解方式简易模板代码Demo GitHub访问 ssm-tpl-anno 一、数据准备 创建数据库test,执行下方SQL创建表ssm-tpl-cfg /*Navicat Premium Data TransferSource Server : 127.0.0.1Source Server Type : MySQLSource Server Version :…...
Python基础第五篇(Python数据容器)
文章目录 一、数据容器入门二、数据容器 list 列表(1),list 列表定义(2),list列表的索引(3),list列表的常见操作(4),list列表的遍历 三、数据容器:tuple(元组)(1),tuple元组定义(2),tuple元组的索引(3),tuple元组的常见操作(4),tuple元组的遍…...
【H3C】配置AAA认证和Telnet远程登陆,S5130 Series交换机
AAA配置步骤为: 1.开启telent远程登陆服务 2.创建用户,设置用户名、密码、用户的服务类型 3.配置终端登录的数量 4.配置vlan-if的ip地址,用来远程登陆 5.允许对应的vlan通过 1.开启telent远程登陆服务 sys …...
GaussDB数据库中的MERGE INTO介绍
一、前言 二、GaussDB MERGE INTO 语句的原理概述 1、MERGE INTO 语句原理 2、MERGE INTO 的语法 3、语法解释 三、GaussDB MERGE INTO 语句的应用场景 四、GaussDB MERGE INTO 语句的示例 1、示例场景举例 2、示例实现过程 1)创建两个实验表,并…...
2024年上海高考数学最后四个多月的备考攻略,目标140+
亲爱的同学们,寒假已经来临,春节即将到来,距离2024年上海高考已经余额不足5个月了。作为让许多学子头疼,也是拉分大户的数学科目,你准备好了吗?今天,六分成长为您分享上海高考数学最后四个多月的…...
SSL证书自动化管理有什么好处?如何实现SSL证书自动化?
SSL证书是用于加密网站与用户之间传输数据的关键元素,在维护网络安全方面,管理SSL证书与部署SSL证书一样重要。定期更新、监测和更换SSL证书,可以确保网站的安全性和合规性。而自动化管理可以为此节省时间,并避免人为错误和不必要…...
路由器初始化配置、功能配置
实验环境 拓扑图 Ip规划表(各组使用自己的IP规划表) 部门 主机数量 网络地址 子网掩码 网关 可用ip Vlan 市场部 38 192.168.131.0 255.255.255.0 192.168.131.1 2-254 11 研发部 53 192.168.132.0 255.255.255.0 192.168.132.1 2-2…...
node介绍
1.node是什么 Node是一个基于Chrome V8引擎的JS运行环境。 Node不是一个独立的语言、node不是JS框架。 Node是一个除了浏览器之外的、可以让JS运行的环境 Node.js是一个让JS运行在服务端的开发平台,是使用事件驱动,异步非阻塞I/O,单线程&…...
海外抖音TikTok、正在内测 AI 生成歌曲功能,依靠大语言模型 Bloom 进行文本生成歌曲
近日,据外媒The Verge报道,TikTok正在测试一项新功能,利用大语言模型Bloom的AI能力,允许用户上传歌词文本,并使用AI为其添加声音。这一创新旨在为用户提供更多创作音乐的工具和选项。 Bloom 是由AI初创公司Hugging Fac…...
【ARM 嵌入式 编译系列 3.6 -- 删除lib中的某个文件】
请阅读【嵌入式开发学习必备专栏 之 ARM GCC 编译专栏】 文章目录 删除lib中的某个文件 删除lib中的某个文件 比如,如果要删除 libc.a 静态库中的特定对象文件并重新使用这个静态库,在终端中可以使用 ar 命令。ar 是一个归档工具,它可以创建…...
web架构师编辑器内容-图层拖动排序功能的开发
新的学习方法 用手写简单方法实现一个功能然后用比较成熟的第三方解决方案即能学习原理又能学习第三方库的使用 从两个DEMO开始 Vue Draggable Next: Vue Draggable NextReact Sortable HOC: React Sortable HOC 列表排序的三个阶段 拖动开始(dragstart&#x…...
3.RHCSA脚本配置及通过node2改密码
运行脚本发现node2不成功 脚本破解 选第二个 Ctrl x 换行 破解成功后做node2的改密码题 回到redhat, 发现检测程序检测密码题成功,得了8分....
AtCoder Regular Contest 170(A~B)
A - Yet Another AB Problem 给你两个字符串S和T,你可以对S执行操作,选择两个字符,将前面的改为A,后面的改为B,最少操作几次可以把S改成T。如果改不成就输出-1。 从左往右一个一个改过去,分类讨论&#x…...
rk1126, 实现 yolov8 目标检测
基于 RKNN 1126 实现 yolov8 目标检测 Ⓜ️ RKNN 模型转换 ONNX yolo export model./weights/yolov8s.pt formatonnx导出 RKNN 这里选择输出 concat 输入两个节点 onnx::Concat_425 和 onnx::Concat_426 from rknn.api import RKNNONNX_MODEL ./weights/yolov8s.onnxRKNN_MOD…...
【软件测试】学习笔记-网站可扩展性架构设计
可扩展性,指的是网站的架构设计能够快速适应需求的变化,当需要增加新的功能实现时,对原有架构不需要做修改或者做很少的修改就能够快速实现新的业务需求。 从这个定义中,我们很容易就可以得出衡量网站可扩展性设计优秀与否的主要标…...
嵌入式开发硬件知识体系与核心技能解析
嵌入式开发中的硬件知识体系构建1. 嵌入式开发的技术架构1.1 嵌入式系统技术分类现代嵌入式系统开发主要分为两大技术方向:嵌入式硬件开发:聚焦电路原理设计、PCB布局及硬件系统集成嵌入式软件开发:包含驱动层开发和应用程序开发两个层级1.2 …...
从LED驱动到充电桩:拆解PFC双环控制在5个真实产品里的不同玩法
从LED驱动到充电桩:拆解PFC双环控制在5个真实产品里的不同玩法 当你在深夜加班时,LED驱动电源的稳定输出让办公室保持明亮;当你为电动车充电时,充电桩高效转换着电网能量;这些场景背后都离不开一个关键技术——PFC双环…...
高效文件同步:SyncTrayzor在Windows上的完整解决方案
高效文件同步:SyncTrayzor在Windows上的完整解决方案 【免费下载链接】SyncTrayzor Windows tray utility / filesystem watcher / launcher for Syncthing 项目地址: https://gitcode.com/gh_mirrors/sy/SyncTrayzor SyncTrayzor是Windows平台上最实用的Syn…...
如何通过Bilibili-Evolved打造个性化B站体验?解锁高效视频浏览新方式
如何通过Bilibili-Evolved打造个性化B站体验?解锁高效视频浏览新方式 【免费下载链接】Bilibili-Evolved 强大的哔哩哔哩增强脚本 项目地址: https://gitcode.com/gh_mirrors/bi/Bilibili-Evolved 你是否曾经在B站浏览时遇到这样的困扰:界面广告太…...
Bongo-Cat-Mver:实时键盘动画工具的创新应用与实践指南
Bongo-Cat-Mver:实时键盘动画工具的创新应用与实践指南 【免费下载链接】Bongo-Cat-Mver An Bongo Cat overlay written in C 项目地址: https://gitcode.com/gh_mirrors/bo/Bongo-Cat-Mver 在直播、教学和演示场景中,如何让观众清晰感知键盘操作…...
DataX 实战:从零部署到多场景数据同步
1. DataX入门:为什么选择它作为数据同步工具 第一次接触DataX是在三年前的一个紧急项目里,当时需要把生产环境的MySQL数据实时同步到分析库。试过几种方案后,最终被DataX的稳定性和灵活性打动。作为阿里开源的数据同步工具,它最大…...
科学可视化入门:用OptiX 9.0 + SDL2 + OpenGL搭建你的第一个实时渲染窗口
科学可视化实战:从零构建OptiX 9.0实时渲染系统 光线追踪技术正在重塑科学可视化的未来。想象一下,你能够实时操控分子结构中的每一个原子,或者让宇宙射线在指尖流淌——这正是OptiX 9.0与SDL2/OpenGL组合带来的可能性。本文将带你跨越理论到…...
ICEM高效建模技巧:从快捷键到多点创建模式
1. ICEM快捷键:让你的建模效率翻倍 刚开始用ICEM建模那会儿,我总被繁琐的鼠标操作折磨得够呛。直到有天发现隔壁工位的同事建模速度比我快三倍,偷师学艺才知道——原来快捷键才是真正的生产力神器。这里分享几个我每天必用的核心快捷键组合&a…...
解锁Stable Diffusion隐藏玩法:用ChatGPT批量生成动漫角色Prompt全攻略
从零到大师:ChatGPT与Stable Diffusion打造专属动漫角色的终极指南 在数字艺术创作领域,AI绘画工具正掀起一场前所未有的革命。想象一下,你脑海中那个独特的动漫角色形象,不再需要数月的美术训练就能实现——只需要正确的工具组合…...
5分钟搞定!用PySide2+Python快速搭建串口助手(附完整源码)
5分钟搞定!用PySide2Python快速搭建串口助手(附完整源码) 1. 为什么选择PySide2开发串口工具? 在嵌入式开发和物联网项目中,串口调试工具就像工程师的"瑞士军刀"。传统方案如C/QT开发周期长,而Py…...
