当前位置: 首页 > java >正文

#Redis黑马点评#(五)Redisson详解

目录

一 基于Redis的分布式锁优化

二 Redisson

1 实现步骤

2 Redisson可重入锁机制

3 Redisson可重试机制

4 Redisson超时释放机制

5 RedissonMultiLock解决主从一致性

三 Redis优化秒杀


一 基于Redis的分布式锁优化

二 Redisson

Redisson是一个在Redis的基础上实现的Java驻Java内存数据网格。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。

1 实现步骤

1 导入依赖

        <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.13.6</version></dependency>

2 配置Redisson客户端

package com.hmdp.config;import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class RedissonConfig {@Beanpublic RedissonClient redissonClient() {Config config = new Config();config.useSingleServer().setAddress("redis://192.168.100.100:6379").setPassword("tan2179432748");return Redisson.create(config);}
}

3 使用Redisson的分布式锁

2 Redisson可重入锁机制

解决的问题:

  • 当你在一个方法内多次获取同一把锁(比如说递归调用),如果锁不可重入,会出现死锁,线程将自己锁死。

实现原理

  • 可重入锁作用对象指的是同一线程,使用的是Redis当中的Hash数据结构,存储一个计数器,当一个锁进入开始执行的时候,就将计数器加1,如果该锁执行结束就将计数器减1(为0时删除),这样避免了可重入锁之间锁的误删问题。

使用的技术

  • 使用Hash的结构:Key是锁名称,Field是线程唯一标识,Value是重入次数。
  • 通过使用Lua脚本保证操作的原子性。

3 Redisson可重试机制

要解决的问题

当多个线程竞争同一把锁时,失败线程如果直接放弃,可能会导致业务无法正常执行,与设计的业务逻辑出现差错,但是如果无脑循环重试,又会浪费CPU的资源,增加Redis的压力。

解决方案:订阅发布机制+信号量控制+指数退避策略

1 订阅发布机制

  1. 订阅锁释放事件

    • 线程获取锁失败后,订阅 Redis 的特定频道(redisson_lock__channel:{锁名称}),进入等待状态。

    • 不占用 CPU:通过 Redis 的 Pub/Sub 模型 阻塞监听,而非主动轮询。

  2. 锁释放通知

    • 当持有锁的线程释放锁时,向该频道发送解锁消息。

    • 所有订阅该频道的线程会被唤醒,立即重试获取锁

  3. 重试次数控制

    • 默认无限重试(直到成功),可通过trylock(...) 指定最大等待时间。

2 信号量控制

使用Semaphore限制阻塞 -- 同时尝试获取锁的线程数量,避免Redis瞬时压力过大。

3 指数退让策略

每次重试的等待时间按指数增长,减少无效次数

4 Redisson超时释放机制

解决的问题:

  • 如果线程获取锁之后崩溃,没有释放锁,其他线程将永远无法获取锁,出现死锁现象。

核心逻辑:

1 锁续期触发条件:

  1. 当使用lock()不超过指定时间时,Redisson默认启用看门狗机制。
  2. 若手动指定超时时间lock(10,...)则不会启用看门狗,锁到期自动释放。

2 守护线程工作流程:

  1. 初始化:加锁成功后,启动一个后台守护线程。(非业务线程)
  2. 定期检查:每十秒检查一次业务线程是否仍然在执行(通过线程存活状态来判断)
  3. 锁续期:若业务未完成,通过Lua脚本重置锁的超时时间为30秒(维持锁的持有状态)

看门狗机制本质上就是一个续期机制,如果不调用 unlock(),看门狗会无限续期锁,直到持有线程异常终止或者执行unlock(),与业务是否完成无关。锁的续期不依赖业务是否执行完毕,而是依赖显式的 unlock() 调用

后果:线程内不管有没有有业务执行只要没有unlock()释放,就会持续续期,将会在当前线程出现假死锁情况

5 RedissonMultiLock解决主从一致性

解决的问题:

  • 在 Redis 的主从架构中,若主节点加锁后未及时同步到从节点即崩溃(触发哨兵机制),新主节点因无锁记录导致锁状态丢失,其他客户端可重复获取同一把锁,引发数据冲突。

解决方案:

  • 并行加锁:向所有独立主节点发送加锁请求(相同锁名称 + 唯一线程标识)。

解决方案:

Redisson的运作流程

当客户端线程调用tryLock()方法申请加锁时(支持传入waitTime等待时长、leaseTime自动释放时间及TimeUnit时间单位)我们就会去获取锁,返回值为Boolean值,,通过Redis的EXISTS命令检测锁Key,若不存在则表示当前线程为首个锁持有者,采用Redis Hash结构存储锁元数据,通过key-field-value三元组实现可重入计数。若锁Key已存在,通过对比Hash字段中的客户端UUID+线程ID验证线程身份​​​​​​​,若匹配则执行重入操作,将重入计数器值递增,并刷新锁过期时间为默认30秒(可通过leaseTime配置,若非当前线程,那就会通过PTTL命令获取锁剩余存活时间,与用户设定的waitTime进行超时判定,若未超期,订阅锁释放的Redis频道,结合JUC.Semaphore实现线程阻塞控制​​​​​​​,实现线程的等待,当执行的线程释放之后再去唤醒等待线程,如果超过了就直接但会获取锁失败。还有超时释放机制:看门狗机制本质上就是一个续期机制,如果不调用 unlock(),看门狗会无限续期锁,直到持有线程异常终止或者执行unlock(),与业务是否完成无关。锁的续期不依赖业务是否执行完毕,而是依赖显式的 unlock() 调用。

三 Redis优化秒杀

大体思路:判断库存与判断用户是否下过单在Redis的Lua脚本当中进行判断,再异步开启一个线程,来操作Mysql数据库。

需求:

  • 1. 新增秒杀优惠券的同时,将优惠券信息保存到Redis中  
    @Override@Transactionalpublic void addSeckillVoucher(Voucher voucher) {// 保存优惠券save(voucher);// 保存秒杀信息SeckillVoucher seckillVoucher = new SeckillVoucher();seckillVoucher.setVoucherId(voucher.getId());seckillVoucher.setStock(voucher.getStock());seckillVoucher.setBeginTime(voucher.getBeginTime());seckillVoucher.setEndTime(voucher.getEndTime());seckillVoucherService.save(seckillVoucher);//保存秒杀库存到Redis中stringRedisTemplate.opsForValue().set(RedisConstants.SECKILL_STOCK_KEY + voucher.getId(), voucher.getStock().toString());}
  • 2. 基于Lua脚本,判断秒杀库存、一人一单,决定用户是否抢购成功  
--1参数列表
--1.1优惠券id
local voucherId = ARGV[1]
--1.2用户id
local userId = ARGV[2]
--2数据key
--2.1库存key
local stockKey = 'seckill:stock:' .. voucherId
--2.2订单key
local orderKey = 'seckill:order:' .. userId
--3 脚本业务
--3.1判断库存是否充足 get stockKey
if (tonumber(redis.call('get', stockKey)) <= 0) then--库存不足,返回1return 1
end
--3.2判断用户是否重复下单 sismember orderKey userId
if (redis.call('sismember', orderKey, userId) == 1) then--用户重复下单,返回2return 2
end
--3.3扣减库存 incrby stockKey -1
redis.call('incrby', stockKey, -1)
--3.4下单保存用户 sadd orderKey userId
redis.call('sadd', orderKey, userId)
return 0
  • 3. 如果抢购成功,将优惠券id和用户id封装后存入阻塞队列  
  • 4. 开启线程任务,不断从阻塞队列中获取信息,实现异步下单功能  

http://www.xdnf.cn/news/5433.html

相关文章:

  • 并发笔记-条件变量(三)
  • 第二十一周:项目开发中遇到的相关问题(二)
  • 使用Visual Studio将C#程序发布为.exe文件
  • java加强 -Collection集合
  • Java基础语法之循环结构
  • immutable.js介绍
  • 【Diffusion】在华为云ModelArts上运行MindSpore扩散模型教程
  • 深入浅出之STL源码分析2_stl与标准库,编译器的关系
  • 解决VirtualBox中虚拟机(ubuntu)与主机(windows)之间互相复制粘贴(文本)
  • 文件批量重命名工具,简单高效一键完成更名
  • 【常用算法:排序篇】4.高效堆排序:线性建堆法与蚂蚁问题的降维打击
  • kubectl系列(十二):查询pod的resource 配置
  • Java定时任务
  • Cribl 利用CSV 对IP->hostname 的转换
  • tokenizer.encode_plus,BERT类模型 和 Sentence-BERT 他们之间的区别与联系
  • 数据结构练习:顺序表题目
  • terraform云上实战(一):执行阿里云云助手命令
  • C++ string初始化、string赋值操作、string拼接操作
  • Celery 在分布式任务调度中的实现原理及 MQ 系统对比
  • GIF图像技术介绍
  • 隐马尔可夫模型(HMM)在彩票预测中的Java实现
  • OpenCV进阶操作:指纹验证、识别
  • 复现MAET的环境问题(自用)
  • Javascript基础语法
  • 【STM32开发】-单片机开发基础(以STM32F407为例)
  • SEO长尾关键词布局优化法则
  • 虚拟内存笔记(三)虚拟内存替换策略与机制
  • 前端项目打包部署流程j
  • 北大闰凯博士:热辐射输运问题蒙特卡罗模拟中的全局最优参考场方法
  • HTOL集成电路老化测试学习总结-20250510