当前位置: 首页 > news >正文

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

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

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

相关文章:

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

道生一&#xff0c;一生二&#xff0c;二生三&#xff0c;三生万物 我旁边的一位老哥跟我说&#xff0c;你知道分布式是是用来干什么的嘛&#xff1f;一句话给我干懵了&#xff0c;我能隐含知道&#xff0c;大概是用来做分压处理的&#xff0c;并增加系统稳定性的。但是具体如…...

实现分布式锁:Zookeeper vs Redis

目录 引言 1. Zookeeper分布式锁 1.1特点和优势&#xff1a; 强一致性 顺序节点 Watch机制 1.2 Zookeeper分布式锁代码示例 2. Redis分布式锁 2.1特点和优势&#xff1a; 简单高效 可续租性 灵活性 2.2Redis分布式锁代码示例 3.对比和选择 3.1 一致性要求 3.2…...

电脑录屏必备技能,让分享变得更加简单!

随着网络技术的飞速发展&#xff0c;电脑录屏已经成为日常工作和学习中不可或缺的一部分。无论是录制在线课程、游戏解说、软件教程&#xff0c;还是远程会议、演示文稿等&#xff0c;电脑录屏都有着广泛的应用。接下来&#xff0c;本文将介绍三种常见的电脑录屏方法&#xff0…...

重构改善既有代码的设计-学习(一):封装

1、封装记录&#xff08;Encapsulate Record&#xff09; 一些记录性结构&#xff08;例如hash、map、hashmap、dictionary等&#xff09;&#xff0c;一条记录上持有什么字段往往不够直观。如果其使用范围比较宽&#xff0c;这个问题往往会造成许多困扰。所以&#xff0c;记录…...

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&#xff0c;执行下方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列表的遍历 三、数据容器&#xff1a;tuple&#xff08;元组&#xff09;(1),tuple元组定义(2),tuple元组的索引(3),tuple元组的常见操作(4),tuple元组的遍…...

【H3C】配置AAA认证和Telnet远程登陆,S5130 Series交换机

AAA配置步骤为&#xff1a; 1.开启telent远程登陆服务 2.创建用户&#xff0c;设置用户名、密码、用户的服务类型 3.配置终端登录的数量 4.配置vlan-if的ip地址&#xff0c;用来远程登陆 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&#xff09;创建两个实验表&#xff0c;并…...

2024年上海高考数学最后四个多月的备考攻略,目标140+

亲爱的同学们&#xff0c;寒假已经来临&#xff0c;春节即将到来&#xff0c;距离2024年上海高考已经余额不足5个月了。作为让许多学子头疼&#xff0c;也是拉分大户的数学科目&#xff0c;你准备好了吗&#xff1f;今天&#xff0c;六分成长为您分享上海高考数学最后四个多月的…...

SSL证书自动化管理有什么好处?如何实现SSL证书自动化?

SSL证书是用于加密网站与用户之间传输数据的关键元素&#xff0c;在维护网络安全方面&#xff0c;管理SSL证书与部署SSL证书一样重要。定期更新、监测和更换SSL证书&#xff0c;可以确保网站的安全性和合规性。而自动化管理可以为此节省时间&#xff0c;并避免人为错误和不必要…...

路由器初始化配置、功能配置

实验环境 拓扑图 Ip规划表&#xff08;各组使用自己的IP规划表&#xff09; 部门 主机数量 网络地址 子网掩码 网关 可用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运行在服务端的开发平台&#xff0c;是使用事件驱动&#xff0c;异步非阻塞I/O&#xff0c;单线程&…...

海外抖音TikTok、正在内测 AI 生成歌曲功能,依靠大语言模型 Bloom 进行文本生成歌曲

近日&#xff0c;据外媒The Verge报道&#xff0c;TikTok正在测试一项新功能&#xff0c;利用大语言模型Bloom的AI能力&#xff0c;允许用户上传歌词文本&#xff0c;并使用AI为其添加声音。这一创新旨在为用户提供更多创作音乐的工具和选项。 Bloom 是由AI初创公司Hugging Fac…...

【ARM 嵌入式 编译系列 3.6 -- 删除lib中的某个文件】

请阅读【嵌入式开发学习必备专栏 之 ARM GCC 编译专栏】 文章目录 删除lib中的某个文件 删除lib中的某个文件 比如&#xff0c;如果要删除 libc.a 静态库中的特定对象文件并重新使用这个静态库&#xff0c;在终端中可以使用 ar 命令。ar 是一个归档工具&#xff0c;它可以创建…...

web架构师编辑器内容-图层拖动排序功能的开发

新的学习方法 用手写简单方法实现一个功能然后用比较成熟的第三方解决方案即能学习原理又能学习第三方库的使用 从两个DEMO开始 Vue Draggable Next: Vue Draggable NextReact Sortable HOC: React Sortable HOC 列表排序的三个阶段 拖动开始&#xff08;dragstart&#x…...

3.RHCSA脚本配置及通过node2改密码

运行脚本发现node2不成功 脚本破解 选第二个 Ctrl x 换行 破解成功后做node2的改密码题 回到redhat, 发现检测程序检测密码题成功,得了8分....

AtCoder Regular Contest 170(A~B)

A - Yet Another AB Problem 给你两个字符串S和T&#xff0c;你可以对S执行操作&#xff0c;选择两个字符&#xff0c;将前面的改为A&#xff0c;后面的改为B&#xff0c;最少操作几次可以把S改成T。如果改不成就输出-1。 从左往右一个一个改过去&#xff0c;分类讨论&#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…...

【软件测试】学习笔记-网站可扩展性架构设计

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

【JVM】- 内存结构

引言 JVM&#xff1a;Java Virtual Machine 定义&#xff1a;Java虚拟机&#xff0c;Java二进制字节码的运行环境好处&#xff1a; 一次编写&#xff0c;到处运行自动内存管理&#xff0c;垃圾回收的功能数组下标越界检查&#xff08;会抛异常&#xff0c;不会覆盖到其他代码…...

Python爬虫(二):爬虫完整流程

爬虫完整流程详解&#xff08;7大核心步骤实战技巧&#xff09; 一、爬虫完整工作流程 以下是爬虫开发的完整流程&#xff0c;我将结合具体技术点和实战经验展开说明&#xff1a; 1. 目标分析与前期准备 网站技术分析&#xff1a; 使用浏览器开发者工具&#xff08;F12&…...

Swagger和OpenApi的前世今生

Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章&#xff0c;二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑&#xff1a; &#x1f504; 一、起源与初创期&#xff1a;Swagger的诞生&#xff08;2010-2014&#xff09; 核心…...

MySQL账号权限管理指南:安全创建账户与精细授权技巧

在MySQL数据库管理中&#xff0c;合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号&#xff1f; 最小权限原则&#xf…...

Linux 内存管理实战精讲:核心原理与面试常考点全解析

Linux 内存管理实战精讲&#xff1a;核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用&#xff0c;还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...

【笔记】WSL 中 Rust 安装与测试完整记录

#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统&#xff1a;Ubuntu 24.04 LTS (WSL2)架构&#xff1a;x86_64 (GNU/Linux)Rust 版本&#xff1a;rustc 1.87.0 (2025-05-09)Cargo 版本&#xff1a;cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...

Webpack性能优化:构建速度与体积优化策略

一、构建速度优化 1、​​升级Webpack和Node.js​​ ​​优化效果​​&#xff1a;Webpack 4比Webpack 3构建时间降低60%-98%。​​原因​​&#xff1a; V8引擎优化&#xff08;for of替代forEach、Map/Set替代Object&#xff09;。默认使用更快的md4哈希算法。AST直接从Loa…...

sshd代码修改banner

sshd服务连接之后会收到字符串&#xff1a; SSH-2.0-OpenSSH_9.5 容易被hacker识别此服务为sshd服务。 是否可以通过修改此banner达到让人无法识别此服务的目的呢&#xff1f; 不能。因为这是写的SSH的协议中的。 也就是协议规定了banner必须这么写。 SSH- 开头&#xff0c…...

rm视觉学习1-自瞄部分

首先先感谢中南大学的开源&#xff0c;提供了很全面的思路&#xff0c;减少了很多基础性的开发研究 我看的阅读的是中南大学FYT战队开源视觉代码 链接&#xff1a;https://github.com/CSU-FYT-Vision/FYT2024_vision.git 1.框架&#xff1a; 代码框架结构&#xff1a;readme有…...