Redis高并发分布锁实战

Redis高并发分布锁实战

问题场景

场景一: 没有捕获异常

// 仅仅加锁
// 读取 stock=15
Boolean ret = stringRedisTemplate.opsForValue().setIfAbsent("lock_key", "1"); // jedis.setnx(k,v)
// TODO 业务代码 stock--
stringRedisTemplate.delete("lock_key");
  • **问题 **
    • 以上场景在代码出现异常的时候,会出现死锁,导致后面的线程无法获取锁,会阻塞所有线程

场景二: 线程间交互删除锁

// 加锁,且设置锁过期时间
// 读取 stock = 15
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("key", "1", 10, TimeUnit.SECONDS);
// TODO 业务代码 stock--
stringRedisTemplate.delete(key);
  • 问题
    • 相对于场景一多了锁的过期时间
    • 假如线程A执行业务代码的时间是15s,而锁的时间是10s,那么锁过期后自动会被删除,此时线程B获取锁,执行业务代码时间为8s,而这个时候线程A刚好执行完业务代码了,就会出现线程A把线程B的锁删除掉
// 加锁,且(给每个线程)设置锁过期时间, 删除锁时判断是否当前线程
// 读取  stock = 15
String uuid = UUID.getUuid; 
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("key", uuid, 10, TimeUnit.SECONDS);
// TODO 业务代码  stock--  15 -> 14
// 判断是否当前线程
if (uuid.equals(stringRedisTemplate.opsForValue().get(key)) {// 极端场景下:(执行时间定格在9.99秒)突然卡顿 10ms or redis服务宕机!!!// 此时刚好锁过期,自动删除// 其他线程获取锁,然后会把上个线程的锁删除,又会出现bugstringRedisTemplate.delete(key);
}
  • 问题
    • 当线程A持有锁,执行完扣减库存后,假设锁过期时间是10s,恰好此时在执行9.99s的时候出现卡顿,等服务器反应过来之间,锁过期自动删除了,这个时候线程B获取锁,然后执行业务代码,此时线程A刚好反应过来,执行锁删除,这样就会把线程B的锁删除,要知道此时线程B是没有执行完业务代码的,锁删除后,线程C又获取锁,此时线程B执行完,又会把线程C的锁删除,依次类推

解决方案

方案: 使用Redisson分布式锁

@Autowire
public Redisson redisson;public void stock () {String key = "key";RLock lock = redisson.getLock(key);try {lock.lock();// TODO: 业务代码 } catch(Exception e) {lock.unlock();}}

优点

  • 自带锁续命功能,默认30s过期时间,可以自行调整过期时间

  • LUA脚本模拟商品减库存

//模拟一个商品减库存的原子操作
//lua脚本命令执行方式:redis-cli --eval /tmp/test.lua , 10
jedis.set("product_stock_10016", "15");  // 初始化商品10016的库存
String script = " local count = redis.call('get', KEYS[1]) " +" local a = tonumber(count) " +" local b = tonumber(ARGV[1]) " +" if a >= b then " +"   redis.call('set', KEYS[1], a-b) " +// 模拟语法报错回滚操作"   bb == 0 " +"   return 1 " +" end " +" return 0 ";
Object obj = jedis.eval(script, Arrays.asList("product_stock_10016"), Arrays.asList("10"));
System.out.println(obj);

Redisson实现

public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {long threadId = Thread.currentThread().getId();Long ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl != null) {RFuture<RedissonLockEntry> future = this.subscribe(threadId);this.commandExecutor.syncSubscription(future);try {while(true) {ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl == null) {return;}if (ttl >= 0L) {this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {this.getEntry(threadId).getLatch().acquire();}}} finally {this.unsubscribe(future, threadId);}}
}
  • LUA脚本适合用于做原子操作,在Redisson分布式锁实现中,就有用到LUA脚本实现创建/获取锁的操作,而Redis的事务机制(multi/exec)非常鸡肋,可以对相同的key通过不同的数据结构做修改,比如事务开启后,将String类型的key,再次使用hset修改,而且还能修改成功,这就意味着事务已失效,而且不支持事务回滚

  • Redisson分布式锁流程

    • 高并发下Lua脚本保证了原子性

    • Schedule定期锁续命

    • 未获取锁的线程先Subscribe channel

    • 自旋,再次尝试获取锁

    • 如果还是未获取锁,则通过Semaphore->tryAcquire(ttl.TimeUnit)阻塞所有进入自旋代码块的线程(这样做的目的是为了不让其他线程因为不停的自旋而给服务器造成压力,所以让其他线程先阻塞一段时间,等阻塞时间结束,再次自旋)

    • 获取锁的线程解锁后,使用Redis的发布功能进行发布消息,订阅消息的线程调用release方法释放阻塞的线程,再次尝试获取锁

    • 如果是调用Redisson的tryAcquire(1000,TimeUnit.SECONDS)方法,那么未获取到锁的线程不用进行自旋,因为时间一到,未获取到锁的线程就会自动往下走进入业务代码块

    • Redisson分布式锁流程.png

总结

  • Redis分布式锁自己去实现可能会出现几个问题
    • 没有在finally显示释放锁,当客户端挂掉了,锁没有被及时删除,这样会导致死锁问题,它这个是需要我们显示的释放锁
    • 假如此时我们设置过期时间,但是我们用的是同一个key,就可能出现下一个线程删除上一个线程的锁,但是上一个线程还没有执行完,它这个需要key是不能重复的
    • 假如我们既设置了过期时间也指定了不同的key,此时可能因为网络延迟出现上一个线程删除下一个线程的锁,也就是说业务执行的时间超过了锁过期的时间,它这个需要一个锁续命的功能
  • 对于Redis它也有事务,但是它的事务非常鸡肋,仅仅只能保证多个指令按照顺序执行,并不能保证原子性,而且key还能被其他指令修改对应的数据结构,所以我们选择Redisson来进行分布式锁的实现,因为它提供了锁续命的功能以及通过lua脚本保证了多个指令的原子操作,主要流程是这样的
    • 当线程抢到了锁,假如业务没执行完,会定时去进行锁续命,而其他线程会订阅这个抢到锁的线程的channel,然后自旋一定时间去尝试获取锁,如果获取锁失败,会被安排进入队列中阻塞,一旦线程释放锁,他们会被通知到,然后继续去自旋一定时间去尝试获取锁,重复此操作

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/491248.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

第三百六十五回

文章目录 1. 概念介绍2. 使用方法3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何获取时间戳"相关的内容&#xff0c;本章回中将介绍dio中的通用请求.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1. 概念介绍 我们在前面章回中介绍了如何使用dio库发起网络…

项目打包提示一堆 ts 类型错误问题解决

问题 vue3 ts 项目在打包的过程中报了一大堆 ts 类型错误提示&#xff0c;如下图所示&#xff1a; 报错&#xff1a;Could not find a declaration file for module … implicitly has an ‘any’ type. 解决方法 查看 package.json 文件&#xff0c;可以看到&#xff0c;默…

物麒平台根据入耳出耳状态使能或禁止触摸按键实现方法

是否需要申请加入数字音频系统研究开发交流答疑群(课题组)?可加我微信hezkz17, 本群提供音频技术答疑服务,+群赠送语音信号处理降噪算法,蓝牙耳机音频,DSP音频项目核心开发资 料, 1 消息发送 2 消息处理 3 宏开关 4 代码 #include "app_main.h" #include &q…

代码随想录算法训练营29期Day61|LeetCode 739,496

文档讲解&#xff1a;每日温度 下一个更大元素I 739.每日温度 题目链接&#xff1a;https://leetcode.cn/problems/daily-temperatures/description/ 思路&#xff1a; 维护一个单调递减的栈就行了。 一次读取一个数组中的元素&#xff0c;将其与栈顶元素比较&#xff0c;如…

Sqli-labs靶场第11关详解[Sqli-labs-less-11]

Sqli-labs-Less-11 前言&#xff1a; SQL注入的三个条件&#xff1a; ①参数可控&#xff1b;&#xff08;从参数输入就知道参数可控&#xff09; ②参数过滤不彻底导致恶意代码被执行&#xff1b;&#xff08;需要在测试过程中判断&#xff09; ③参数带入数据库执行。&…

杀疯啦!yolov9+bytetrack的目标跟踪实现

目录 YOLOV9介绍 bytetrack介绍&#xff1a; yolov9结合bytetrack实现目标跟踪 效果展示 训练与预测 UI设计 其他功能展示 完整代码实现UI界面 此次yolov9bytetrack不论是准确率还是稳定性&#xff0c;都超越了之前的yolobytetrack系列。 YOLOV9介绍 在目标检测…

Jitsi Meet 大型视频会议调优方案

jitsi meet 大型视频会议调优方案 在举办一些大型会议的时候,比如100个人会议,为了节约宽带和节省资源,我们并不会选择传输全部的音视频资源。 举个例子,比如100个人线下会议,如果大家都说话的情况下,大家要么听不清,要么听得是声音最大的那几个人。 视频会议也可以借…

VMware的三种连接模式

桥接模式 就是将主机网卡与虚拟机虚拟的网卡利用虚拟网桥进行通信。在桥接的作用下&#xff0c;类似于把物理主机虚拟为一个交换机&#xff0c;所有桥接设置的虚拟机连接到这个交换机的一个接口上&#xff0c;物理主机也同样插在这个交换机当中&#xff0c;所以所有桥接下的网…

xss靶场实战(xss-labs-master靶场)

xss-labs-master靶场链接&#xff1a;https://pan.baidu.com/s/1X_uZLF3CWw2Cmt3UnZ1bTw?pwdgk9c 提取码&#xff1a;gk9c xss-labs level 1 修改 url 地址中的name<script>alert(1)</script>&#xff0c;便可以通关 level 2 在搜索框中输入的 JS 代码无法执行 …

微服务-微服务Spring Security OAuth 2实战

1. Spring Authorization Server 是什么 Spring Authorization Server 是一个框架&#xff0c;它提供了 OAuth 2.1 和 OpenID Connect 1.0 规范以及其他相关规范的实现。它建立在 Spring Security 之上&#xff0c;为构建 OpenID Connect 1.0 身份提供者和 OAuth2 授权服务器产…

冯诺依曼体系结构 计算机组成的金字塔

01 冯诺依曼体系结构&#xff1a;计算机组成的金字塔 学习计算机组成原理&#xff0c;到底是在学些什么呢&#xff1f;这个事儿&#xff0c;一两句话还真说不清楚。不过没关系&#xff0c;我们先从“装电脑”这个看起来没有什么技术含量的事情说起&#xff0c;来弄清楚计算机到…

链表OJ题第二弹:环形链表和环形链表 II

前言 第一弹的链表题目比较基础&#xff0c;下面两道题目难度升级&#xff0c;可以先自己挑战一下&#xff0c;再来看解析。解析有图示和的文字&#xff0c;有助于你的理解。 1. 环形链表 &#xff08;1&#xff09;题目及示例 给定一个链表的头节点 head &#xff0c;返回…