分布式锁实现(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…...

【软件测试】学习笔记-网站可扩展性架构设计
可扩展性,指的是网站的架构设计能够快速适应需求的变化,当需要增加新的功能实现时,对原有架构不需要做修改或者做很少的修改就能够快速实现新的业务需求。 从这个定义中,我们很容易就可以得出衡量网站可扩展性设计优秀与否的主要标…...

深度学习常用代码总结(k-means, NMS)
目录 一、k-means 算法 二、NMS 一、k-means 算法 k-means 是一种无监督聚类算法,常用的聚类算法还有 DBSCAN。k-means 由于其原理简单,可解释强,实现方便,收敛速度快,在数据挖掘、数据分析、异常检测、模式识别、金…...
数据结构·顺序表应用
本节应用是要用顺序表实现一个通讯录,收录联系人的姓名、性别、电话号码、住址、年龄 顺序表的实现在上一节中已经完成了,本节的任务其实就是应用上节写出来的代码的那些接口函数功能,做出来一个好看的,可…...

第一个 OpenGL 程序:旋转的立方体(VS2022 / MFC)
文章目录 OpenGL API开发环境在 MFC 中使用 OpenGL初始化 OpenGL绘制图形重置视口大小 创建 MFC 对话框项目添加 OpenGL 头文件和库文件初始化 OpenGL画一个正方形OpenGL 坐标系改变默认颜色 重置视口大小绘制立方体使用箭头按键旋转立方体深度测试添加纹理应用纹理换一个纹理 …...

剩余银饰的重量 - 华为OD统一考试
OD统一考试(C卷) 分值: 100分 题解: Java / Python / C 题目描述 有N块二手市场收集的银饰,每块银饰的重量都是正整数,收集到的银饰会被熔化用于打造新的饰品。 每一回合,从中选出三块 最重的…...

redis远程连接不上解决办法
问题描述: redis远程服务端运行在192.168.3.90计算机上,客户端计算机(ip:192.168.3.110)通过redsi-cli.exe客户端工具连接时,没有反应,连接不上。 如图所示: 解决步骤: 步骤一&…...

利用Anaconda安装pytorch和paddle深度学习环境+pycharm安装后不能调用pytorch和paddlepaddle框架
问题现象: 之前安装后不能在添加pytorch和paddlepaddle框架 原因(疑似): 在终端中显示pytorch和paddle在C盘但是安装是安装在J盘 解决办法: 卸载、删除文件重新安装后可以看到文件位置在J盘中 但是选择时还是显示C…...

Eclipses安装教程
一、下载开发工具包 1、开发工具包JDK 下载地址链接:https://www.oracle.com/cn/java/technologies/downloads/ 下载教程: 1)点击链接,可以跳转到页面 2)下滑页面,找到开发工具包 3) 记住下载之…...

安装python版opencv的一些问题
安装python版opencv的一些问题 OpenCV是知名的开源计算机视觉算法库,提供了C\Python\Java版共享库。 在Python中使用OpenCV格外简单,一句命令就能安装,一行import就能引入,可谓是神器。然而,在实际使用中可能遇到一些…...

RabbitMQ入门实战
RabbitMQ 是一个开源的消息中间件,实现了高级消息队列协议(AMQP),用于在分布式系统中进行消息传递。它能够在应用之间传递消息,解耦应用组件,提高系统的可伸缩性和可维护性。RabbitMQ 使用高级消息队列协议…...

vue3-模版引用ref
1. 介绍 概念:通过 ref标识 获取真实的 dom对象或者组件实例对象 2. 基本使用 实现步骤: 调用ref函数生成一个ref对象 通过ref标识绑定ref对象到标签 代码如下: 父组件: <script setup> import { onMounted, ref } …...