分布式锁实现(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.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.mfbz.cn/a/337416.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言第四弹---printf和scanf详解

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】 printf和scanf详解 1、printf和scanf详解介绍1.1 printf1.1.1 基本用法1.1.2 占位符1.1.3 占位符列举1.1.4 输出格式1.1.4.1 限定宽度1.1.4.2 总是显示正负号1.1…

第一篇【传奇开心果系列】WeUI开发原生微信小程序:汽车租赁小程序示例

传奇开心果博文系列目录 WeUI开发原生微信小程序示例系列博文目录博文目录一、项目目标二、编程思路三、初步实现汽车租赁微信小程序示例代码四、实现汽车租赁微信小程序的登录注册示例代码五、实现汽车租赁微信小程序的订单管理示例代码六、整合实现比较完整的汽车租赁微信小程…

css绘制下拉框头部三角(分实心/空心)

1:需求图: 手绘下拉框 带三角 2:网上查了一些例子,但都是实心的, 可参考,如图: (原链接: https://blog.csdn.net/qq_33463449/article/details/113375804) 3:简洁版的: a: 实心: <view class"angle"/>.angle{width:0;height:0;border-left: 10px solid t…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--机器人相关、强化学习

专属领域论文订阅 VX 扫吗关注{晓理紫|小李子}&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 分类: 大语言模型LLM视觉模型VLM扩散模型视觉导航具身智能&#xff0c;机器人强化学习开放词汇&#xff0c;检测分割 [晓理紫…

K8s(七)四层代理Service

Service概述 Service在Kubernetes中提供了一种抽象的方式来公开应用程序的网络访问&#xff0c;并提供了负载均衡和服务发现等功能&#xff0c;使得应用程序在集群内外都能够可靠地进行访问。 每个Service都会自动关联一个对应的Endpoint。当创建一个Service时&#xff0c;Ku…

JS-日期对象

日期对象&#xff1a;用来表示时间的对象 作用&#xff1a;可以得到当前系统时间 实例化 在代码中发现了new关键字时&#xff0c;一般将这个操作称为实例化 创建一个时间对象并获取时间 1&#xff09;获得当前时间 const datenew Date() 2)获得指定时间 const datenew D…

Python项目——计算器(PySide6+Pyinstaller)

1、介绍 使用python编写一个计算器&#xff0c;可以实现基本的运算。【注】该项目最终还有一些细小的bug没有完善&#xff0c;例如符号可以一直输入。 2、实现 使用pyCharm创建一个新的项目。 2.1、设计UI 使用Qt designer设计一个UI界面&#xff0c;保存ui文件&#xff0…

基于docker,k8s 搭建服务(单体docker-compose编排)

1、 yum -y install gcc yum -y instacc gcc-c 2、安装yum 工具 yum install -y yum-utils device-mapper-persistent-data lvm2 --skip-broken 3、设置docker镜像仓库 阿里云 yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.…

四种方法将 Docker Registry 迁移至 Harbor

Registry Docker Distribution Docker Distribution 是第一个是实现了打包、发布、存储和镜像分发的工具&#xff0c;起到 docker registry 的作用。&#xff08;目前 Distribution 已经捐赠给了 CNCF&#xff09;。其中 Docker Distribution 中的 spec 规范后来也就成为了 O…

Leetcode 2788. 按分隔符拆分字符串

我们可以先自己模拟一下分隔字符串的过程。如果只是简单的&#xff0c;遇到分隔符&#xff0c;将分隔符前后的子串加入结果的List&#xff0c;那么很显然并没有考虑到一个String中有多个字符串的情况。一种比较容易想到的方法是&#xff1a; 先对List中每个字符串遍历&#xf…

HBase节点故障的容错方案

HBase节点故障的容错方案 1. Master高可用1.1 选主和HA切换逻辑 2. RS高可用2.1 感知RS节点异常2.2 异常DN上的数据处理 4. 疑问和思考5. 参考文档 本文主要探讨hbase集群的高可用容错方案和容错能力的探讨。涉及Master和RS相关组件&#xff0c;在出现单机故障时相关的容错方案…

Node.js Stream.pipeline() Method

Why Stream.pipeline 通过流我们可以将一大块数据拆分为一小部分一点一点的流动起来&#xff0c;而无需一次性全部读入&#xff0c;在 Linux 下我们可以通过 | 符号实现&#xff0c;类似的在 Nodejs 的 Stream 模块中同样也为我们提供了 pipe() 方法来实现。 未使用 Stream p…

实时云渲染服务:流式传输 VR 和 AR 内容

想象一下无需专用的物理计算机&#xff0c;甚至无需实物连接&#xff0c;就能获得高质量的 AR/VR 体验是种什么样的体验&#xff1f; 过去&#xff0c;与 VR 交互需要专用的高端工作站&#xff0c;并且根据头显、壁挂式传感器和专用的物理空间。VR 中的复杂任务会突破传感器范…

快速傅里叶变化检测轻微划痕

像这种轻微划痕,普通算法鲁棒性差,通用性也不是很好,通过一些特殊处理,基本上可以满足客户需求. 图像处理,检测无非这个几个步骤. 预处理----分割----筛选—满足设定条件NG read_image (Image, ‘轻微划痕.bmp’) dev_close_window() get_image_size(Image, Width, Height) dev…

Chatgpt+Comfyui绘图源码线上部署文档

源码仓库&#xff1a; https://gitee.com/BTYY/wailikeji-chatgpt 其他文档地址&#xff1a; ChatgptComfyui绘图源码运营文档 ChatgptComfyui绘图源码说明及本地部署文档 一、云服务器购买 &#xff08;一&#xff09;购买云服务 有两种部署方案&#xff0c;不同方案对服务…

【MongoDB】下载安装、指令操作

目录 1.下载安装 2.指令 2.1.基础操作指令 2.2.增加 2.3.查询 2.4.修改 2.5.删除 前言&#xff1a; 关于MongoDB的核心概念请移步&#xff1a; 【文档数据库】ES和MongoDB的对比-CSDN博客 1.下载安装 本文以安装Windows版本的mongodb为例&#xff0c;Linux版本的其实…

IP劫持的危害分析及应对策略

在当今数字化时代&#xff0c;网络安全问题备受关注&#xff0c;其中IP劫持是一种常见而危险的威胁。本文将深入探讨IP劫持的危害&#xff0c;并提供一些有效的应对策略。 第一部分&#xff1a;IP劫持的定义 IP劫持是指黑客通过各种手段获取并篡改目标IP地址的控制权&#xf…

如何在地图资源下载工具中下载和共享自定义资源

在获取GIS或遥感资源时&#xff0c;有时候可能会有一些您自己的下载资源&#xff01;您也可以在地图资源下载工具增加这些下载资源&#xff01;这样既可以方便以后下载&#xff0c;也可以与其它人分享下载资源&#xff01; 设置方式如下&#xff1a; 下载方式如下&#xff1a;…

【汇编】 13.3 对int iret和栈的深入理解

书中示例 assume cs:codecode segment start:mov ax,csmov ds,axmov si,offset lpmov ax,0mov es,axmov di,200hmov cx,offset end0-offset lpcldrep movsb ;lp到end0的指令传送到0:200处mov ax,0mov es,axmov word ptr es:[7ch*4],200hmov word ptr es:[7ch*42],0 ;设置7c表项…

NeRF - 神经辐射场 原理分析与解释

标题&#xff1a;NeRF&#xff1a;Representing Scenes as Neural Radiance Fields for View Synthesis 顾名思义&#xff1a;通过NeRF 神经辐射场合成新视角来表达场景 这是一篇来源于伯克利大学的论文研究 摘要 论文提出了一种方法&#xff0c;可以通过优化一个连续体积场…