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

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容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
目录
相关文章
|
6月前
|
存储 分布式计算 Hadoop
Hadoop【基础知识 01】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)
【4月更文挑战第3天】Hadoop【基础知识 01】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)
229 3
|
9天前
|
存储 Dubbo Java
分布式 RPC 底层原理详解,看这篇就够了!
本文详解分布式RPC的底层原理与系统设计,大厂面试高频,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式 RPC 底层原理详解,看这篇就够了!
|
26天前
|
程序员
后端|一个分布式锁「失效」的案例分析
小猿最近很苦恼:明明加了分布式锁,为什么并发还是会出问题呢?
31 2
|
1月前
|
分布式计算 Hadoop 网络安全
Hadoop-08-HDFS集群 基础知识 命令行上机实操 hadoop fs 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
Hadoop-08-HDFS集群 基础知识 命令行上机实操 hadoop fs 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
37 1
|
1月前
|
存储 机器学习/深度学习 缓存
Hadoop-07-HDFS集群 基础知识 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
Hadoop-07-HDFS集群 基础知识 分布式文件系统 读写原理 读流程与写流程 基本语法上传下载拷贝移动文件
45 1
|
1月前
|
存储 缓存 数据处理
深度解析:Hologres分布式存储引擎设计原理及其优化策略
【10月更文挑战第9天】在大数据时代,数据的规模和复杂性不断增加,这对数据库系统提出了更高的要求。传统的单机数据库难以应对海量数据处理的需求,而分布式数据库通过水平扩展提供了更好的解决方案。阿里云推出的Hologres是一个实时交互式分析服务,它结合了OLAP(在线分析处理)与OLTP(在线事务处理)的优势,能够在大规模数据集上提供低延迟的数据查询能力。本文将深入探讨Hologres分布式存储引擎的设计原理,并介绍一些关键的优化策略。
101 0
|
6月前
|
存储 分布式计算 监控
Hadoop【基础知识 01+02】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)【分布式计算框架MapReduce核心概念+编程模型+combiner&partitioner+词频统计案例解析与进阶+作业的生命周期】(图片来源于网络)
【4月更文挑战第3天】【分布式文件系统HDFS设计原理+特点+存储原理】(部分图片来源于网络)【分布式计算框架MapReduce核心概念+编程模型+combiner&partitioner+词频统计案例解析与进阶+作业的生命周期】(图片来源于网络)
308 2
|
2月前
|
网络协议 安全 Java
分布式(基础)-RMI的原理
分布式(基础)-RMI的原理
|
3月前
|
运维 安全
基于simulink的分布式发电系统自动重合闸的建模与仿真分析
本课题研究配电系统中分布式电源接入后的自动重合闸问题,着重分析非同期重合闸带来的冲击电流及其影响。通过Simulink搭建模型,仿真不同位置及容量的分布式电源对冲击电流的影响,并对比突发性和永久性故障情况。利用MATLAB2022a进行参数设置与仿真运行,结果显示非同期重合闸对系统安全构成挑战,需通过优化参数提升系统性能。
|
4月前
|
NoSQL Java Redis
jedis 与 redission 实现分布式锁
jedis 与 redission 实现分布式锁
99 4

热门文章

最新文章

下一篇
无影云桌面