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

这篇具有很好参考价值的文章主要介绍了分布式锁实现(mysql,以及redis)以及分布式的概念。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

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

那么就正式进入正题:

分布式概念

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

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

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

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

这个是网图,但是我认为这个是可以将现在大部分的分布式知识做一个较为全面的总结的
分布式锁实现(mysql,以及redis)以及分布式的概念,中间件,GoLong,分布式,mysql,redis,golang

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

  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版本号是否对应,对应的上就允许跟新,诺是不相同,就会导致冲突,此时就更新失败。

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

分布式锁实现(mysql,以及redis)以及分布式的概念,中间件,GoLong,分布式,mysql,redis,golang
分布式锁实现(mysql,以及redis)以及分布式的概念,中间件,GoLong,分布式,mysql,redis,golang

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

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

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

续:今天补上了:请看这篇文章文章来源地址https://www.toymoban.com/news/detail-811651.html

到了这里,关于分布式锁实现(mysql,以及redis)以及分布式的概念的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Java程序员面试专栏 分布式中间件】Redis 核心面试指引

    关于Redis部分的核心知识进行一网打尽,包括Redis的基本概念,基本架构,工作流程,存储机制等,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 明确redis的特性、应用场景和数据结构 Redis是一个 开源的、内存中的数据结构存储系统

    2024年02月20日
    浏览(46)
  • Redis实战案例14-分布式锁的基本原理、不同实现方法对比以及基于Redis进行实现思路

    基于数据库的分布式锁:这种方式使用数据库的特性来实现分布式锁。具体流程如下: 获取锁:当一个节点需要获得锁时,它尝试在数据库中插入一个特定的唯一键值(如唯一约束的主键),如果插入成功,则表示获得了锁。 释放锁:当节点完成任务后,通过删除该唯一键

    2024年02月13日
    浏览(52)
  • MySQL、Redis 和 Zookeeper 实现分布式锁方法及优缺点

    MySQL、Redis 和 Zookeeper 都可以用来实现分布式锁,每种技术都有其特定的实现方法以及各自的优缺点。 MySQL 分布式锁 实现方法 在 MySQL 中实现分布式锁通常涉及到使用数据库表。可以创建一个专用的锁表,并利用行的唯一性(例如利用唯一索引)来实现锁机制。 使用基于事务

    2024年04月11日
    浏览(42)
  • XXL-JOB中间件【实现分布式任务调度】

    目录 1:XXL-JOB介绍 2:搭建XXL-JOB 2.1:调度中心 2.2:执行器 2.3:执行任务 3:分片广播 XXL-JOB是一个轻量级分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线,开箱即用。 官网:https://www.xuxueli.com/xxl-

    2024年02月03日
    浏览(110)
  • SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】

    上一篇实现了单体应用下如何上锁,这一篇主要说明如何在分布式场景下上锁 上一篇地址:加锁 需要注意的点是: 在上锁和释放锁的过程中要保证 原子性操作 核心是上锁和解锁的过程 关于解锁使用脚本参考:SET key value [EX seconds] [PX milliseconds] [NX|XX] 3.1 一个服务按照多个端口同时

    2023年04月10日
    浏览(52)
  • 分别使用Redis、MySQL、ZooKeeper构建分布式锁

    本文使用Java构建三种中间件的分布式锁,下面介绍下三种分布式锁的优缺点, 使用MySQL构建分布式锁 ,因为数据库数据存储在磁盘中,所以IO速率相对较慢,因此构建出来的分布式锁不适合用在高并发场景,对于一些对并发要求不高的系统中可以使用,进一步提高系统的安全

    2024年02月06日
    浏览(46)
  • Redis——》实现分布式锁

    推荐链接:     总结——》【Java】     总结——》【Mysql】     总结——》【Redis】     总结——》【Kafka】     总结——》【Spring】     总结——》【SpringBoot】     总结——》【MyBatis、MyBatis-Plus】     总结——》【Linux】     总结——》【MongoDB】    

    2024年02月10日
    浏览(61)
  • redis实现分布式延时队列

    延时队列是一种特殊的消息队列,它允许将消息在一定的延迟时间后再进行消费。延时队列的主要特点是可以延迟消息的处理时间,以满足定时任务或者定时事件的需求。 总之,延时队列通过延迟消息的消费时间,提供了一种方便、可靠的方式来处理定时任务和定时事件。它

    2024年02月08日
    浏览(45)
  • 分布式锁之redis实现

    需要挂在的data和redis.conf自行创建即可 不要忘记开放端口6379 修改redis.conf配置文件,设置 requirepass xxxxx 如果直接使用RedisTemplate使用的序列化器是jdk的,存的是二进制,使用StringRedisTemplate默认初始化序列化器就是String类型 执行票数存入redis指令  编写代码演示超卖问题  500

    2024年02月10日
    浏览(47)
  • 使用redis实现分布式锁

    在一个分布式系统中,也会涉及多个节点访问同一个公共资源的情况,此时就需要通过锁来做互斥控制,避免出现类似于“线程安全”的问题,而java的synchronized这样的锁只能在当前进程中生效,在分布式的这种多个进程多个主机的场景无能为力,此时就需要分布式锁。 例如

    2024年02月07日
    浏览(56)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包