Redission分布式锁的使用和原理分析

本文涉及的产品
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
简介: Redission分布式锁的使用和原理分析

分布式锁主要是为了解决高并发场景下的数据一致性问题的。一般就是涉及到多线程资源争抢时通过加锁来保证数据的安全性

场景:
image.png

模拟测试:
首先模拟一个抢购场景:

redis依赖:

image.png

代码:
image.png

在redis数据库存了一个stock值,相当于是库存,value是200
image.png

逻辑:首先去从redis里面取库存,判断当前库存是否大于0,如果大于0则库存减一

问题:没加锁,高并发场景下会出现超卖问题。比如当前有三个线程同时访问这段代码,同时判断当前库存大于0,都减一,然后保存,结果三个线程都减一然后保存,但是stock的值还是199
解决:
image.png

乍一看是没问题了,但是想一下,我们的服务有可能要部署多台,那么synchronized就没用了。因为synchronized是仅限于当前服务所在的jvm虚拟机,也就是单机环境下才有效。另外一个相同的服务启动的时候在另一个jvm里面,synchronized是锁不住的。

比如下面的场景:
image.png

一个服务我部署了两台,前端通过nginx转发到不同的服务去

nginx:
image.png

当我访问根目录,它会转发到redislock 下的两个服务中去。好,现在启动两个服务

下面进行高并发测试:
工具:Jmeter
image.png

首先配置一个线程组:

image.png

然后在线程组下面配置你要并发压测的接口

image.png

然后配置接口:
image.png

配置请求次数及时间

image.png

在执行前还可以打开压测报告:
image.png

开始
image.png

选择压测计划保存位置,此处我选择保存到桌面

image.png

执行完毕:

image.png

查看后台日志:

image.png

image.png

可以看到:两台服务在扣减库存后日志有显示库存相同的数字。这说明当前synchroinzed失效了。一样出现了两台机器扣除库存,库存同时减一的超卖情况。

来到正题了。像这种分布式环境下的问题应该怎么处理呢?

答案就是:使用分布式锁 (使用redis、zookeeper)均可以实现

使用redis实现命令: setnx

image.png

这条命令和set命令的区别:

setnx 就是说如果当前setnx 的key 不存在,就会为key设置value值。否则不做任何操作。

set命令就是为key设置value,如果key不存在则为此key设置value,如果存在则会用新的value覆盖旧的value。

代码实现:
image.png

执行完后删除key

image.png

我讲一下执行逻辑:

假设当前有三个线程通过nginx进来转发到两个服务里面,假设线程一先执行到这段代码,那么它就会判断redis里面key="lockey"存不存在,一看,不存在那么它就会setnx,为key等于lockKey设置value "zhuge"

那执行完肯定返回true啊,接着就走下面的逻辑。其他两个线程一看,key为"lockKey" 已经存在了呀,那么就直接返回false了,就不会执行下面的扣减库存的代码了。

问题:
那么第一个线程拿到锁了,当它要执行下面的代码的时候突然因为你的业务代码抛异常了,那么会出现什么后果?

肯定是这个key就删除不了了嘛,它删除不了那redis里面一直有这个key,其他线程又得不到这个锁,那岂不是一件商品都卖不出去了嘛

那怎么办呢?

有小伙伴会说:搞一个try catch块,把代码放进去,delete key 放到finally里面去不就完了嘛

image.png

这样是可以解决代码异常的问题,但是不是从个本上解决的。还有问题。试想一下,你有个线程加锁进来了,正在执行代码的时候宕机了,或者是运维执行了 kill -9暴力终止程序进程,那怎么办?那你这个key不还是执行不到嘛,数据库里面还是会有这个key。

解决:给key设置超时时间,过了这个时间自动删除 此处为10秒自动删除
image.png

问题:上面这两条设置key和超时时间的代码无法保证原子性,假如刚设置完key,服务挂了,怎么办?
解决:可以一条命令设置key和超时时间一步搞定
image.png

看下代码:

image.png

还有没有问题呢? 答案是有的。

问题:当前代码设置key时间为10s,但是执行业务逻辑的时候业务逻辑执行了15秒,由于key存在时间是10s ,肯定已经不存在了,当前环境有是高并发环境,另外一个线程进来一看,key不存在了,那么它就设置了一个key,设置存活时间为10s, 等到第二个线程又执行5s的时候,第一个线程代码已经走完了,由于finally里面会删除key,因此第一个线程会把key再次删除,此时它删除的是第二个线程刚刚设置的key,此时第三个线程进来了,一看key不存在,就设置key,存活时间依旧是10s,此时第二个线程走完删除key,把第三个线程的key删除...如此循环往复,会发现这个key一直就是被其他线程认为是不存在的key,因此其他线程都能访问此段代码,导致超卖情况依然会发生。
怎么办?

解决:
image.png

我们可以在线程进来的时候为key设置一个唯一的value,然后执行finanny的时候去判断当前value和线程进来的时候设置的value是否一致。若一致则判断当前线程是设置此value的线程,则可以删除key,否则就认定当前线程不是设置key的线程。

那么现在还剩下一个问题:

就是我设置的这个key过期时间如果小于业务代码执行的时间,依然会导致key被删除从而引起其它线程进来,依然会导致超卖问题的发生。那该怎么处理这个问题呢?

有同学会说:给它设置时间长一点不就完了嘛。那服务宕机了怎么办呢?如果我服务宕机了又重启了,你给key设置60s,那我最起码得等一分钟才能重新设置这个key

解决:其实可以设置一个后台定时任务每隔一段时间判断这个key是否存在。若存在则给它的时间延长一段时间。

最终解决:使用redisson框架。
引入依赖:
image.png

配置代码:
image.png

使用:

引入依赖
image.png

业务代码: 三行代码搞定

image.png

附上一段简单的代码:

public class TestController {
    @Autowired
    private static Redisson redisson;
 
    public static void main(String[] args) {
        String lockKey = "product_01";
        // 获取锁对象
        RLock lock = redisson.getLock(lockKey);
        // 加锁
        lock.lock();
        // 释放锁
        lock.unlock();
    }
}

流程图:
image.png

原理:假设当前有两个线程:线程1 和 线程2 ,这两个线程同时访问业务代码。 假设线程1拿到锁了,那么它就会设置setnx命令,线程2 判断自己没拿到锁,它就会一直等待线程一释放锁然后进行加锁。线程一拿到锁后会在后台启动一个线程用于判断当前key是否存在,若存在则把key的存活时间额外加上当前key设置的存活时间的1/3 。 也就是说如果当前key的存货时间是9s,则在9s的基础上再加上3s 变成 12s。
redisson底层原理:

image.png
image.png
image.png
image.png
image.png
image.png
image.png

这其实就是一个lua脚本。上面那一段代码意思就是判断当前代码是否存在,如果不存在则设置key 和 value 然后 再设置超时时间 ARGV [1] 默认 30s

其实这么写跟上面说的这段代码很像
image.png

但是为什么redisson要这么写呢?没有原子性问题吗?

其实redis会保证这个lua脚本的原子性,他会把这个lua脚本当成一行代码去执行,要么全部成功,要么全部失败。

时间监听:
image.png

点进去就会发现 有个timeTask() 任务

这个脚本的意思是判断当前key是否存在 若存在则进行延时

image.png
image.png

如果执行成功再次调用此方法

image.png

默认时间:

image.png

以上就是redisson帮我们做的加锁的处理。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore     ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库 ECS 实例和一台目标数据库 RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
目录
相关文章
|
2月前
|
设计模式 安全 Java
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
35 0
|
1月前
|
存储 分布式计算 Hadoop
Hadoop【基础知识 01】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)
【4月更文挑战第3天】Hadoop【基础知识 01】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)
43 3
|
1月前
|
存储 分布式计算 监控
Hadoop【基础知识 01+02】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)【分布式计算框架MapReduce核心概念+编程模型+combiner&partitioner+词频统计案例解析与进阶+作业的生命周期】(图片来源于网络)
【4月更文挑战第3天】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)【分布式计算框架MapReduce核心概念+编程模型+combiner&partitioner+词频统计案例解析与进阶+作业的生命周期】(图片来源于网络)
77 2
|
12天前
|
数据采集 存储 运维
如何使用SkyWalking收集分析分布式系统的追踪数据
通过以上步骤,你可以使用 SkyWalking 工具实现对分布式系统的数据采集和可视化。SkyWalking 提供了强大的追踪和度量功能,帮助开发者和运维人员更好地理解系统的性能状况。欢迎关注威哥爱编程,一起学习成长。
|
19天前
|
存储 NoSQL 分布式数据库
【Flink】Flink分布式快照的原理是什么?
【4月更文挑战第21天】【Flink】Flink分布式快照的原理是什么?
|
1月前
|
消息中间件 存储 监控
解析RocketMQ:高性能分布式消息队列的原理与应用
RocketMQ是阿里开源的高性能分布式消息队列,具备低延迟、高吞吐和高可靠性,广泛应用于电商、金融等领域。其核心概念包括Topic、Producer、Consumer、Message和Name Server/Broker。RocketMQ支持异步通信、系统解耦、异步处理和流量削峰。关键特性有分布式架构、顺序消息、高可用性设计和消息事务。提供发布/订阅和点对点模型,以及消息过滤功能。通过集群模式、存储方式、发送和消费方式的选择进行性能优化。RocketMQ易于部署,可与Spring集成,并与Kafka等系统对比各有优势,拥有丰富的生态系统。
149 4
|
2月前
|
缓存 算法 关系型数据库
深度思考:雪花算法snowflake分布式id生成原理详解
雪花算法snowflake是一种优秀的分布式ID生成方案,其优点突出:它能生成全局唯一且递增的ID,确保了数据的一致性和准确性;同时,该算法灵活性强,可自定义各部分bit位,满足不同业务场景的需求;此外,雪花算法生成ID的速度快,效率高,能有效应对高并发场景,是分布式系统中不可或缺的组件。
102 2
深度思考:雪花算法snowflake分布式id生成原理详解
|
2月前
|
存储 负载均衡 NoSQL
【分布式技术架构】「Tomcat技术专题」 探索Tomcat集群架构原理和开发分析指南
【分布式技术架构】「Tomcat技术专题」 探索Tomcat集群架构原理和开发分析指南
54 1
|
2月前
|
存储 Java 应用服务中间件
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
【分布式技术专题】「架构实践于案例分析」盘点互联网应用服务中常用分布式事务(刚性事务和柔性事务)的原理和方案
61 0
|
2月前
|
缓存 应用服务中间件 数据库
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(多级缓存设计分析)
【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(多级缓存设计分析)
43 1