分布式锁实现(mysql,以及redis)以及分布式的概念

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

我旁边的一位老哥跟我说,你知道分布式是是用来干什么的嘛?一句话给我干懵了,我能隐含知道,大概是用来做分压处理的,并增加系统稳定性的。但是具体如何,我却道不出个1,2,3。现在就将这些做一个详细的总结。至少以后碰到面试官可以说上个123。

那么就正式进入正题:

文章目录

  • 道生一,一生二,二生三,三生万物
  • 分布式概念
  • 分布式锁
  • 分布式锁实现的方案
  • 数据库实现(Mysql)
    • 方式1:乐观锁
    • 方式2:读写锁的实现
      • 第一步:链接数据库
      • 第二部:建立数据库表
      • 第三步:读方法(读操作)
        • 读加锁操作
        • 读解锁操作
      • 第四步:写方法(写操作)
        • 写加锁
        • 写解锁
  • redis实现

分布式概念

分布式是一种,将一个大问题拆分成多个小问题,并分别由多个节点协同完成的计算机解决方案

既然是解决问题,那么是解决什么呢?

分布式的目的:是为了解决单个机器无法满足性能要求的问题

至于为什么单个机器无法满足,其实回头细想,现代的计算数据都是突出一个字,,这个大字就意味着处理数据的运算速度也得大,但是因为现代材料局限,一个主机的运算的能力有限,然而数据是成指数倍的增长,为了解决这个问题就是增加主机的数量,但是这就出现了另一个问题:->:如何将这些主机有机结合起来?分布式就是解决这个问题的。其实很多公司其实都不需要分布式,但是,时代进步的。不管现在是不是要用,但是未来的事情谁又说的清?

这个是网图,但是我认为这个是可以将现在大部分的分布式知识做一个较为全面的总结的
请添加图片描述

分布式这种框架的诞生,伴随着许多的问题,包括硬件,也包括软件,比如:

  1. 数据一致性
  2. 数据乱序
  3. 数据丢失
  4. 分库分表的扩容

问题可以说是非常多。

但是大部分是可以用技术手段去避免很多的问题。而分布式加锁就是一个较为有效的手段。不过就是牺牲了一部分的时效性,但是对于人类来说,这点时间的损耗,就是一个眨眼的功夫。希望日后会出现更多手段,去解决这方面的问题。

接下来就是对于分布式锁介绍

分布式锁

分布式锁的应用场景,这些既是应用场景也是问题所在:

  1. 处理缓存击穿
  2. 处理缓存雪崩
  3. 重试处理
  4. 幂等性
  5. 数据不一致的问题

基本上很多分布式应用上的问题基本可以用分布式锁处理

那么什么算是一个合格的分布式锁呢?众多巨人的文章中无不透露以下这几点:

  1. 互斥性:这个是锁的基本功能,即:同一时刻只允许一个客户端持有锁
  2. 避免死锁:获取到锁的客户端出现问题了,没有办法解锁,所以要避免死锁。让系统继续运行下去(有些也叫安全性)
  3. 容错:既然是服务器,那么提供锁的系统也是有可能崩溃的,所以要保证这一点。

这些属性,将会贯穿这篇文章。

分布式锁实现的方案

这些我会举出大体的实现思路,并不会全部去实现。

🌝每种实现类型中都有不同实现方式 ,比如mysql有悲观锁和乐观锁,读写锁这些方式的实现方式

常见的实现方案有:

  1. 数据库实现(我这里用的是mysql)
  2. redis实现
  3. zookeeper实现
  4. Redlock 算法实现

这里都会说到,但是对于实现来说,我目前就是只说MySQL的实现,redis的实现

语言对于程序员来说只是说某种工具而言,真正重要的是逻辑(算法)数据结构,这个才是一个程序员安生立命的本钱。

所以这篇文章我会用go语言实现,其他语言的版本这里就不多说了。但是我会将常见的语言包说出,方便友友们能够快速查到相关的资料。

数据库实现(Mysql)

这里我用MySQL去实现:

技术:golanggorm数据结构

方式1:乐观锁

实现方式:通过对数据表添加一个字段 Version实现(数据版本(Version)记录机制实现)

主要逻辑:为数据库表增加一个数字类型的 version 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加

如果对于跟新操作,需要先判断当前version与数据库中的version版本号是否对应,对应的上就允许跟新,诺是不相同,就会导致冲突,此时就更新失败。

是不是很简单?是的确实很简单。画个图解释一下

在这里插入图片描述
在这里插入图片描述

那么悲观锁如何实现我相信大家肯定也就明白了。但是这里我就浅浅提一点:

sql语句后添加for update

逻辑实现:

  1. 通过添加线程做轮询等待,然后抢锁
  2. 添加过期时间
  3. 更新版本号

接下来重点是读写锁的实现

方式2:读写锁的实现

而这两种锁的实现,需要满足一下几个特点:

  1. 执行操作的环境是分布式的(当然单机不是不能用)
  2. 读操作,不做限制,里面资源是共享的。可以支持多个线程或者协程对资源的读取的操作。
  3. 写操作,是互斥的,也就是说明一个时刻只允许一个协程或者进程对资源进行访问。
  4. 读操作,写操作两者是互斥的。不能同时存在

ps:相当于对于读写这两个操作来说,都有自己的申请锁和解锁的方法,读模式共享,写模式互斥

读写锁的有点在于:

  1. 分布式读写锁是比分布式锁粒度更小的锁
  2. 对于业务场景更加灵活

所以综上的出:读写锁的状态有:读加锁状态、写加锁状态、无锁状态

当前锁状态读锁请求写锁请求
无锁状态可以可以
读锁状态可以不可以
写锁状态不可以不可以

第一步:链接数据库

每个程序员的链接手法各不相同,所以这里就不献丑了。只要连上数据库就好。然后将客户端暴露出来。

第二部:建立数据库表

要包含一下这几个字段。

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实现

今天太晚了,先不写,明天补上:连接

续:今天补上了:请看这篇文章

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

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

相关文章

特征融合篇 | YOLOv8 引入长颈特征融合网络 Giraffe FPN

在本报告中,我们介绍了一种名为DAMO-YOLO的快速而准确的目标检测方法,其性能优于现有的YOLO系列。DAMO-YOLO是在YOLO的基础上通过引入一些新技术而扩展的,这些技术包括神经架构搜索(NAS)、高效的重参数化广义FPN(RepGFPN)、带有AlignedOTA标签分配的轻量级头部以及蒸馏增…

android usb2.0 协议基础(1)

1-USB 基本知识 USB的重要关键概念: 1、 端点&#xff1a;位于USB设备或主机上的一个数据缓冲区&#xff0c;用来存放和发送USB的各种数据&#xff0c;每一个端点都有惟一的确定地址&#xff0c;有不同的传输特性&#xff08;如输入端点、输出端点、配置端点、批量传输端点) 2…

ORB-SLAM 论文阅读

论文链接 ORB-SLAM 0. Abstract 本文提出了 ORB-SLAM&#xff0c;一种基于特征的单目同步定位和建图 (SLAM) 系统该系统对严重的运动杂波具有鲁棒性&#xff0c;允许宽基线环路闭合和重新定位&#xff0c;并包括全自动初始化选择重建的点和关键帧的适者生存策略具有出色的鲁棒…

Android 基础技术——View 的宽高

笔者希望做一个系列&#xff0c;整理 Android 基础技术&#xff0c;本章是关于 View 的宽高 Activity Resume 的时候设置或者获取view的宽高是否有效? 回答&#xff1a;不确定。 首次 onResume 无效&#xff0c;二次 onResume 就有效了。 回顾「Android 基础技术——addView 流…

Eclipses安装教程

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

初识C语言·自定义类型(1)

目录 1 联合体类型的声明 2 联合体的特点 3 联合体的大小计算 4 枚举类型类型的声明 5 枚举的优点 1 联合体类型的声明 联合体&#xff0c;顾名思义&#xff0c;是多个对象连在一起的&#xff0c;即联合体的成员都是共用空间的&#xff0c;所以联合体也叫做共用体&#xf…

Java开发分析工具 JProfiler的详细使用方法解析(附 JProfiler for Mac许可证秘钥)

JProfiler 是一款功能强大的Java代码分析工具&#xff0c;JProfiler的直观UI可帮助您解决性能瓶颈&#xff0c;确定内存泄漏并了解线程问题且JProfiler Mac破解版配置会话非常简单&#xff0c;第三方集成使得入门变得轻而易举&#xff0c;并且以自然的方式呈现数据分析。 解…

万物简单AIoT 端云一体实战案例学习 之 快速开始

学物联网,来万物简单IoT物联网!! 下图是本案的3步导学,每个步骤中实现的功能请参考图中的说明。 1、简介 物联网具有场景多且复杂、链路长且开发门槛高等特点,让很多想学习或正在学习物联网的学生或开发者有点不知所措,甚至直接就放弃了。    万物简单AIoT物联网教育…

72.批量执行Redis命令的4种方式!

文章目录 前言一、Redis命令执行过程二、原生批量命令三、pipeline(管道)四、Lua脚本五、Redis事务六、Redis Cluster模式下该如何正确使用批量命令操作&#xff1f; 前言 在我们的印象中Redis命令好像都是一个个单条进行执行的&#xff0c;但实际上我们是可以批量执行Redis命…

二层交换机和三层交换机

二层交换机&#xff1a;将源mac和端口进行转发&#xff0c;是同一个网段进行通信的&#xff0c;不能实现路由转发&#xff0c;若想跨网段则需要接入一个路由器 如&#xff1a;pc1 192.168.1.1 与 pc2 192.168.1.2通信需要经过二层交换机&#xff0c;二层交换机不能配置ip的&am…

【前端设计】输入框

欢迎来到前端设计专栏&#xff0c;本专栏收藏了一些好看且实用的前端作品&#xff0c;使用简单的html、css语法打造创意有趣的作品&#xff0c;为网站加入更多高级创意的元素。 html <!DOCTYPE html> <html lang"en"> <head><meta charset"…

RHCE上课笔记(前半部分)

第一部分 网络服务 第一章 例行性工作 1.单一执行的例行性工作 单一执行的例行性工作&#xff08;就像某一个时间点 的闹钟&#xff09;&#xff1a;仅处理执行一次 1.1 at命令&#xff1a;定时任务信息 [rhellocalhost ~]$ rpm -qa |grep -w at at-spi2-core-2.40.3-1.el9.x…

Nacos 在云原生架构下的演进

作者&#xff1a;之卫 背景 Nacos 提供的最核心能力是动态服务发现与动态配置管理能力&#xff0c;在云原生环境下&#xff0c;借助云产品&#xff0c;如 EDAS&#xff08;企业级分布式应用服务&#xff09;平台中&#xff0c;我们可以很轻松地使用 K8s 来托管 Nacos 体系的微…

《WebKit 技术内幕》之六(1): CSS解释器和样式布局

《WebKit 技术内幕》之六&#xff08;1&#xff09;&#xff1a;CSS解释器和样式布局 CSS解释器和规则匹配处于DOM树建立之后&#xff0c;RenderObject树之前&#xff0c;CSS解释器解释后的结果会保存起来&#xff0c;然后RenderObject树基于该结果来进行规范匹配和布局计算。当…

基于 GPT 和 Qdrant DB 向量数据库, 我构建了一个电影推荐系统

电影推荐系统自从机器学习时代开始以来就不断发展&#xff0c;逐步演进到当前的 transformers 和向量数据库的时代。 在本文中&#xff0c;我们将探讨如何在向量数据库中高效存储数千个视频文件&#xff0c;以构建最佳的推荐引擎。 在众多可用的向量数据库中&#xff0c;我们将…

Tomcat的maxParameterCountmaxPostSize参数

Tomcat的maxParameterCount&maxPostSize参数 Tomcat的maxParameterCount&maxPostSize参数1.问题1.1问题现象1.2 参数总结1.3 问题总结 2 Tomcat官网的解释2.1 到https://tomcat.apache.org/找到文档入口2.2 找到文档的Reference2.3 查看配置文件的参数 3 文档看不明白&…

GIS项目实战08:JetBrains IntelliJ IDEA 2022 激活

为什么选择 IntelliJ IDEA 使用编码辅助功能更快地编写高质量代码&#xff0c;这些功能可在您键入时搜索可能的错误并提供改进建议&#xff0c;同时无缝地向您介绍编码、新语言功能等方面的社区最佳实践。 IntelliJ IDEA 了解您的代码&#xff0c;并利用这些知识通过在每种上…

Istio

1、Istio介绍 Istio 是由 Google、IBM 和 Lyft 开源的微服务管理、保护和监控框架。 官网&#xff1a;https://istio.io/latest/zh/ 官方文档&#xff1a;https://istio.io/docs/ 中文官方文档&#xff1a;https://istio.io/zh/docs Github地址&#xff1a;https://github.com…

vectorCast添加边界值分析测试用例

1.1创建项目成功后会自动生成封装好的函数,在这些封装好的函数上点击右键,添加边界值分析测试用例,如下图所示。 1.2生成的用例模版是不可以直接运行的,需要我们分别点击它们,让它们自动生成相应测试用例。如下图所示,分别为变化前和变化后。 1.3点击选中生成的测试用例,…

【动态规划】【广度优先搜索】【状态压缩】847 访问所有节点的最短路径

作者推荐 视频算法专题 本文涉及知识点 动态规划汇总 广度优先搜索 状态压缩 LeetCode847 访问所有节点的最短路径 存在一个由 n 个节点组成的无向连通图&#xff0c;图中的节点按从 0 到 n - 1 编号。 给你一个数组 graph 表示这个图。其中&#xff0c;graph[i] 是一个列…