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

news2024/10/7 18:22:54

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

我旁边的一位老哥跟我说,你知道分布式是是用来干什么的嘛?一句话给我干懵了,我能隐含知道,大概是用来做分压处理的,并增加系统稳定性的。但是具体如何,我却道不出个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.Model
	RWLock
	Count int64
}

func (Stock) TableName() string {
	return "stock"
}

这三个是状态值,分别代表:无锁,读锁,写锁

statusUnLock = “Unlock”
statusReadLock = “ReadLock”
statusWirteLock = “WirteLock”

gorm.Model中包含了:一下字段:

//主键id
	ID        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 
set
 	lock_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 
set
 	lock_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 
set
 	lock_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 
set
 	lock_status={statusUnLock},
	 read_lock_count =0 ,//这个不是参数
	 lock_reason={lockReason} 
 where 
 	id={s.ID} and statusWirteLoc={statusWirteLock}

redis实现

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

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

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

相关文章

最优传输学习及问题总结

文章目录 参考内容lam0.1lam3lam10lam50lam100lam300画图线性规划matlabpython代码 参考内容 https://blog.csdn.net/qq_41129489/article/details/128830589 https://zhuanlan.zhihu.com/p/542379144 我主要想强调的是这个例子的解法存在的一些细节问题 lam0.1 lam 0.1P,…

《WebKit 技术内幕》之五(3): HTML解释器和DOM 模型

3 DOM的事件机制 基于 WebKit 的浏览器事件处理过程&#xff1a;首先检测事件发生处的元素有无监听者&#xff0c;如果网页的相关节点注册了事件的监听者则浏览器会将事件派发给 WebKit 内核来处理。另外浏览器可能也需要处理这样的事件&#xff08;浏览器对于有些事件必须响应…

D - Left Right Operation

思路&#xff1a; 1、求前缀和 2、从后往前遍历&#xff0c;把某个后缀都变为R&#xff0c;记录最多让数组和减小多少 3、从前往后遍历&#xff0c;把某个前缀都变为L&#xff0c;记录最小答案&#xff08;前i个变为L&#xff0c;后面的n-i个数让减小最多的后缀变为R&#x…

项目管理十大知识领域之项目采购管理

一、项目采购管理的定义与概述 项目采购管理是指在项目实施过程中&#xff0c;对相关产品、服务或工程进行采购的管理活动。其概述包括确定采购需求、制定采购计划、供应商选择、合同签订、供应管理和结算支付等环节。项目采购管理的定义还涉及对采购目标的明确界定&#xff0…

[小程序]使用代码渲染页面

一、条件渲染 1.单个控制 使用wx:if"{{条件}}"来判断是否需要渲染这段代码&#xff0c;同时可以结合wx:elif和wx:else来判断 <view wx:if"{{type0}}">0</view> <view wx:elif"{{type1}}">1</view> <view wx:else>…

数字IC后端设计实现 | PR工具中到底应该如何控制density和congestion?(ICC2Innovus)

吾爱IC社区星友提问&#xff1a;请教星主和各位大佬&#xff0c;对于一个模块如果不加干预工具会让inst挤成一团&#xff0c;后面eco修时序就没有空间了。如果全都加instPadding会导致面积不够overlap&#xff0c;大家一般怎么处理这种问题&#xff1f; 在数字IC后端设计实现中…

C#中ArrayList运行机制及其涉及的装箱拆箱

C#中ArrayList运行机制及其涉及的装箱拆箱 1.1 基本用法1.1.1 属性1.1.2 方法 1.2 内部实现1.3 装箱1.4 拆箱1.5 object对象的相等性比较1.6 总结1.7 其他简单结构类 1.1 基本用法 命名空间&#xff1a; using System.Collections; 1.1.1 属性 Capacity&#xff1a;获取或设…

Barrel Shifter RTL Combinational Circuit

在本博客中&#xff0c;将围绕许多设计中存在的非常有用的电路&#xff08;桶形移位器电路&#xff09;设计电路。将从最简单的方法开始实现固定位宽字的单向旋转桶形移位器&#xff0c;最后设计一个具有可参数化字宽的多功能双向桶形移位器电路。 Barrel Shifter 桶形移位器…

【Python学习】Python学习22- CGI编程

目录 【Python学习】Python学习22- CGI编程 前言CGI工作流程Web 服务器支持及配置Http头部参考 文章所属专区 Python学习 前言 本章节主要说明Python的CGI接口 CGI 目前由 NCSA 维护&#xff0c;NCSA 定义 CGI 如下&#xff1a; CGI(Common Gateway Interface)&#xff0c;通…

机器学习没那么难,Azure AutoML帮你简单3步实现自动化模型训练

在Machine Learning 这个领域&#xff0c;通常训练一个业务模型的难点并不在于算法的选择&#xff0c;而在于前期的数据清理和特征工程这些纷繁复杂的工作&#xff0c;训练过程中的问题在于参数的反复迭代优化。 AutoML 是 Azure Databricks 的一项功能&#xff0c;它自动的对…

GRU门控循环单元神经网络的MATLAB实现(含源代码)

在深度学习领域&#xff0c;循环神经网络&#xff08;RNN&#xff09;因其在处理序列数据方面的卓越能力而受到广泛关注。GRU&#xff08;门控循环单元&#xff09;作为RNN的一种变体&#xff0c;以其在捕捉时间序列长距离依赖关系方面的高效性而备受推崇。在本文中&#xff0c…

ros2学习笔记-CLI工具,记录命令对应操作。

目录 环境变量turtlesim和rqt以初始状态打开rqt node启动节点查看节点列表查看节点更多信息命令行参数 --ros-args topic话题列表话题类型话题列表&#xff0c;附加话题类型根据类型查找话题名查看话题发布的数据查看话题的详细信息查看类型的详细信息给话题发布消息&#xff0…

推荐两个工具:DeepSpeed-FastGen和DataTrove

DeepSpeed-FastGen 通过 MII 和 DeepSpeed-Inference 加速LLM生成文本 仓库地址&#xff1a;https://github.com/microsoft/DeepSpeed/tree/master/blogs/deepspeed-fastgen GPT-4 和 LLaMA 等大型语言模型 (LLM) 已成为服务于各个级别的人工智能应用程序的主要工作负载。从一…

UE5 独立程序的网络TCP/UDP服务器与客户端基础流程

引擎源码版&#xff0c;复制\Engine\Source\Programs\路径下的BlankProgram空项目示例。 重命名BlankProgram&#xff0c;例如CustomTcpProgram&#xff0c;并修改项目名称。 修改.Build.cs内容 修改Target.cs内容 修改Private文件夹内.h.cpp文件名并修改.cpp内容 刷新引擎 …

SpringMVC获取参数与页面跳转

获取参数 第一种 直接当成方法的参数&#xff0c;需要与前台的name一致 相当于Request.getAttribute("username") Controller 第二种 使用对象接收 页面的name也要和对象的字段一致 创建一个对应的实体类 Controller 将参数更换为User对象就行 SpringMVC获取到…

【设计模式】你知道游戏SL大法是什么设计模式吗?

什么是备忘录模式&#xff1f; 老规矩&#xff0c;我们先来看看备忘录模式 (Memento) 的定义&#xff1a;在不破坏封装性的前提下&#xff0c;捕获一个对象的内部状态&#xff0c;并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。 它的UML类图如下&a…

keep-alive组件缓存

keep-alive组件缓存 从a跳b&#xff0c;a已经销毁&#xff0c;b重新渲染&#xff1b;b跳a&#xff0c;b销毁a重新渲染 源组件销毁&#xff0c;目标组件渲染 组件缓存&#xff1a;组件实例等相关&#xff08; 包括vnode&#xff09;存储起来 重新渲染指的是&#xff1a;把视图重…

MySQL---多表查询综合练习

创建dept表 CREATE TABLE dept ( deptno INT(2) NOT NULL COMMENT 部门编号, dname VARCHAR (15) COMMENT 部门名称, loc VARCHAR (20) COMMENT 地理位置 ); 添加dept表主键 mysql> alter table dept add primary key(deptno); Query OK, 0 rows affected (0.02 s…

正则表达式初版

一、简介 REGEXP&#xff1a; Regular Expressions&#xff0c;由一类特殊字符及文本字符所编写的模式&#xff0c;其中有些字符&#xff08;元字符&#xff09;不表示字符字面意义&#xff0c;而表示控制或通配的功能&#xff0c;类似于增强版的通配符功能&#xff0c;但与通…

YOLOv5改进 | 主干篇 | 华为GhostnetV1一种移动端的专用特征提取网络

一、本文介绍 本文给大家带来的改进机制是华为移动端模型Ghostnetv1,华为GhostnetV1一种移动端的专用特征提取网络,旨在在计算资源有限的嵌入式设备上实现高性能的图像分类。GhostNet的关键思想在于通过引入Ghost模块,以较低的计算成本增加了特征图的数量,从而提高了模型的…