Redisson框架

1. Redisson锁与Redis订阅与发布模式的联系:

Redisson锁中,使用订阅发布模式去通知等待锁的客户端:锁已经释放,可以进行抢锁。

  • publish channel_name message:将消息发送到指定频道
    • 解锁时,在Lua解锁脚本中使用:场景1:锁不存在时,发送消息到解锁频道;场景2:锁重入记数hash等于0时,发送消息到解锁频道。
  • subscribe channel_name ... :客户端订阅一个或多个频道消息
    • 加锁时,没有争抢锁成功的客户端,需要订阅解锁频道 -> RedissonLock#tryLock中订阅频道后,会用await()阻塞等待锁释放。直到获取推送的频道解锁消息,才取消等待。

  • unsubsribe channel_name ... :客户端退订一个或多个频道消息
    • 加锁时,客户端最后都会去退订解锁频道,不管加锁成功与失败。

2. RedissonLock锁:

数据结构:

  • Hash结构:key -> name 、field -> lockName (id+":"+threadId -> UUID:threadId) 、 value -> 可重入次数

加锁伪脚本:

// 如果不存在锁:则新增锁,并设置锁重入计数为1、设置锁过期时间,返回nil结束
exists name == 0                      ->  name:加锁的key名称
hset name lockName  1                 -> lockName:当前客户端标识
pexpire name  internalLockLeaseTime   -> internalLockLeaseTime:锁租期
return nil

// 如果存在锁,且唯一标识也匹配,表示当前锁可重入,重入计数+1,并重新设置锁过期时间,返回nil结束
hexists  name lockName = 1
hincrby  name lockName  1 
pexpire name  internalLockLeaseTime 
return nil

// 如果存在锁,且唯一标识不匹配,表示锁是被其他线程占用,返回剩余时间结束
return pttl name

解锁伪脚本:

// 如果锁不存在:则直接广播解锁消息,返回1,结束
exists name == 0 
publish channelName  0   -> channelName:频道名称 -> redisson_lock__channel:{name}
return 1

// 如果锁存在,但是但唯一标识不匹配:表示锁被其他线程占用,当前线程不允许解锁,返回nil,结束
hexists name lockName == 0 
return nil

// 如果锁存在,并且唯一标识匹配:则先将锁重入计数 -1
counter = hincrby name lockName -1
// 如果锁重入计数-1后,还大于0,重新设置有效期,返回0,结束
pexpire name  internalLockLeaseTime
return 0

// 如果锁重入计数-1后,等于0,直接删除key,并广播解锁消息(即唤醒其它争抢锁的被阻塞的线程),返回1,结束
del name 
publish channelName  0
return 1

参考文档:https://www.cnblogs.com/huangwentian/p/14622441.html

3. RedissonFairLock锁:

数据结构:

  • Hash结构:key -> name 、field -> lockName (UUID:threadId)、 value -> 可重入次数
  • List结构:key -> redisson_lock_queue:{lockName} 、vaule -> lockName (UUID:threadId)
    • 作用:实现公平锁,按顺序记录线程争抢锁的顺序。
  • Zset结构:key -> redisson_lock_timeout:{lockName}、 score -> timeout(过期时间,它是时间戳) member -> lockName (id+":"+threadId)
    • 作用1:利用Zset分值的功能,清除等待队列中,等待加锁,但是已经过期的线程。
    • 作用2:利用Zset分值的功能,在加锁不成功后,计算需要等待的时间(ttl)和更新Zset分值(timeout)

加锁伪脚本:

/ 删除列表中第一个位置是过期的线程
// 死循环
while true do 
  // list中获取第一元素,如果firstThreadId2为空,直接结束循环
  firstThreadId2 = lindex  redisson_lock_queue:{lockName}  0
  if firstThreadId2 == false
      break;
  // 如果firstThreadId2不为空,从Zset中获取分值timeout
  timeout = zscore redisson_lock_timeout:{lockName} firstThreadId2
  // 如果 timeout <= 当前时间戳,从Zset中删除该成员,从List弹出该元素
  if timeout <= currentTime
      zrem redisson_lock_timeout:{lockName}  firstThreadId2
      lpop  redisson_lock_queue:{lockName} 
  // 如果 timeout > 当前时间戳,结束循环
      break;
      
// 如果不存在name的key,并且不存队列或者队列第一个元素是lockName时,当前线程可以获得锁
if exists name == 0  && (exists redisson_lock_timeout:{lockName} == 0  ||   lindex  redisson_lock_queue:{lockName}  0 ==  lockName)  
    lpop redisson_lock_queue:{lockName}  
    zrem redisson_lock_timeout:{lockName}  lockName
    hset name  lockName  1
    pexpire name internalLockLeaseTime
    return nil
    
// 如果存在name的key,并且hash结构的field也是lockname时,表示当前线程已经获得锁,此时处理锁重入
if hexists name lockName == 1
    hincrby name lockName 1
    pexpire name internalLockLeaseTime
    return nil

//  获取列表第一个元素,计算当前的ttl,然后再重新计算timeout赋值给Zset中,Zset添加成功后,再rpush到队列,返回ttl
firstThreadId = lindex  redisson_lock_queue:{lockName}  0
ttl
// 如果列表第一元素不为空,并且不是lockName时,从Zset中获取lockName的timeout,计算ttl
if firstThreadId ~= false and firstThreadId ~= lockName
    ttl = (zscore redisson_lock_timeout:{lockName} lockName) - currentTime
    // 列表第一元素是lockName时,获取ttl
    ttl = pttl name
    // 计算 timeout
    timeout = ttl + (currentTime + threadWaitTime);
    // 如果 zadd 添加成功,则 rpush
    if (zadd  redisson_lock_timeout:{lockName}  timeout lockName) == 1
        rpush redisson_lock_queue:{lockName}  
    return ttl          

解锁伪脚本:

// 删除列表中第一个位置是过期的线程
... ...
// 1. 如果不存name时,获取列表第一个元素,并且第一元素不为空,向解锁频道发送通知
if exists name == 0
     nextThreadId = lindex  redisson_lock_queue:{lockName}  0
     if nextThreadId ~= false 
         publish channel_name .. ':' .. nextThreadId 0
     return 1
     
// 2. 如果 hexists name lockName 不存在时,直接返回nil
if hexists name lockName == 0
    return nil
    
// 3. 如果 hexists name lockName 存在时,处理锁可重入,或删除key,通知下一个元素
counter = hincrby  name  lockName  -1
// 如果counter大于0,设置过期时间
if counter > 0
    pexpire name internalLockLeaseTime
return 0
// 如果counter小于等于0,删除key
del name
// 获取列表第一个元素,如果不为空,发送解锁通知
nextThreadId = lindex  redisson_lock_queue:{lockName}  0
if nextThreadId ~= false 
    publish channel_name .. ':' .. nextThreadId 0
return 1   

4. 延迟队列:

两个API概念:

  • RBlockingQueue 就是目标队列
  • RDelayedQueue 就是中转队列

  • 我们实际操作的是RBlockingQueue队列,并不是RDelayedQueue队列,RDelayedQueue主要是提供中间转发的一个队列。
  • 我们都是基于RBlockingQueue目标队列在进行消费,而RDelayedQueue就是会把过期的消息放入到我们的目标队列中。
  • 我们只要从RBlockingQueue队列中取数据即可。

数据结构:

  • ZSet结构:key -> redisson_delay_queue_timeout:{name}、score -> System.currentTimeMillis() + delayInMs、member ->数据使用 struct.pack()包装,包含 'dLc0'、randomId、string.len(数据)、数据
    • 作用:该队列实现了获取延迟数据的功能。
  • List结构:key-> redisson_delay_queue:{name} 、value -> 数据使用 struct.pack()包装,包含 'dLc0'、randomId、string.len(数据)、数据
    • 作用:该队列记录了所有数据的加入顺序。
  • List结构:key -> 出入阻塞队列的名称,上边的{name}、value -> 数据

处理流程(展示一条主线):

Redisson延迟队列剖析_why redissondelayedqueue use list-CSDN博客

  • 实例化RedissonDelayedQueue时:
  1. 设置频道名称(redisson_delay_queue_channel:{name})、队列名称(redisson_delay_queue:{name})、延迟队列名称(redisson_delay_queue_timeout:{name})
  2. 设一个“将到期的任务推送阻塞队列”的任务(i.获取到期数据,到阻塞队列的Lua脚本;ii.设置发布订阅的主题)
  3. 启动该任务,并且添加相关监听(i.添加监听订阅发布的事件 ->pushTask() ;ii.添加监听消息的事件->scheduleTask(startTime))
  • 添加消息时:
  1. 向timeoutSet延迟队列添加数据
  2. 向queue队列添加数据
  3. 获取timeoutSet延迟队列的队头数据,如果队头数据等于刚加入的数据,则向频道发送消息
  • 触发频道监听事件->scheduleTask(startTime):
  1. 计算延迟时间 -> delay = startTime - System.currentTimeMillis()
  2. 如果延迟大于10,启动延迟任务
  3. 否则立即执行启动 -> pushTask()
  • 将到期的任务推送到阻塞队列 -> pushTask():
  1. 调用实例化时,定义的pushTaskAsync
  2. 添加监听操作完成的事件,在执行成功后,判断返回值是否为空,不为空调用scheduleTask(future.getNow())

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Python: PyCharm 2023.1 调试

1.设断点 &#xff08;行号对应的红色点&#xff0c;在需要设置断点的代码行与行号之间鼠标点击即可以设置&#xff09; 2.shiftF9,或 altshiftf9 选择文件 或是在菜单 Run->debug "main" 或是在菜单Run->debug 选择文件 &#xff08;鼠标光标放在设置第一个…

Linux下安装RocketMQ:从零开始的消息中间件之旅

感谢您阅读本文&#xff0c;欢迎“一键三连”。作者定会不负众望&#xff0c;按时按量创作出更优质的内容。 ❤️ 1. 毕业设计专栏&#xff0c;毕业季咱们不慌&#xff0c;上千款毕业设计等你来选。 RocketMQ是一款分布式消息中间件&#xff0c;具有高吞吐量、低延迟、高可用性…

环回接口处理 IP 数据报的过程及 Loopback 接口的主要作用

环回接口处理 IP 数据报的过程 IPv4 中 传给环回地址&#xff08;127.0.0.1&#xff09;的任何数据均作为 IP 输入&#xff0c;直接送到环回接口&#xff08;环回&#xff1a;IP 输入队列&#xff09;。 传给广播地址或多播地址的数据报&#xff0c;会复制一份传给环回接口&…

GPT-5:AI新时代的领航者与我们的未来

一、引言&#xff1a;GPT-5的崭新时代 在科技的浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;领域正迎来一个崭新的纪元。GPT-5&#xff0c;作为OpenAI的最新成果&#xff0c;无疑将成为这个新时代的领航者。从GPT-1到GPT-4&#xff0c;我们见证了AI在自然语言处理领域…

科普文:贝叶斯

概叙 贝叶斯推断&#xff08;Bayesian inference&#xff09;是一种统计学方法&#xff0c;用来估计统计量的某种性质。 它是贝叶斯定理&#xff08;Bayes theorem&#xff09;的应用。英国数学家托马斯贝叶斯&#xff08;Thomas Bayes&#xff09;在1763年发表的一篇论文中&am…

FreeBSD虚拟化解决之道:高效、安全、灵活的虚拟解决方案全览

FreeBSD下的虚拟化技术 虚拟化软件可让一台计算机同时运行多个操作系统。这种用于个人电脑的系统软件通常涉及一个运行虚拟化软件的宿主机&#xff08;host&#xff09;操作系统&#xff0c;并支持任何数量的客户机&#xff08;guest&#xff09;操作系统。 FreeBSD下的虚拟解…

在高并发场景下,怎样避免 PostgreSQL 的死锁问题?

文章目录 &#xff08;一&#xff09;不当的事务设计&#xff08;二&#xff09;不正确的锁使用&#xff08;三&#xff09;并发操作冲突&#xff08;一&#xff09;优化事务设计&#xff08;二&#xff09;正确使用锁&#xff08;三&#xff09;调整数据库参数&#xff08;四&…

热管的原理和棒芯的加工

当热管的一端受热时&#xff0c;毛细芯中的液体蒸发汽化&#xff0c;蒸汽在微小的压差下流向另一端&#xff0c;放出热量凝结成液体&#xff0c;液体再靠毛细力&#xff08;或重力&#xff09;的作用&#xff0c;沿多孔材料流回蒸发段。如此循环不已&#xff0c;热量便从一端传…

Nginx 通过代理服务器访问目标接口

需求&#xff1a;某Nginx需要通过一个http代理服务来访问目标URL。 如果下图所示&#xff1a; 服务器B仅提供一个代理出来&#xff0c;不允许在该服务器上安装和进行任何其他配置操作。 在这种需求场景下&#xff0c;网络请求过程和域名解析的工作流程大致如下&#xff1a; 1…

【软件测试】白盒测试(知识点 + 习题 + 答案)

《 软件测试基础持续更新中》 最近大家总是催更……&#xff0c;我也是百忙之中给大家详细总结了白盒测试的重点内容&#xff01; 知识点题型答案&#xff0c;让你用最短的时间&#xff0c;学到最高效的知识&#xff01; 整理不易&#xff0c;求个三连 ₍ᐢ..ᐢ₎ ♡ 目录 一、…

pp 学习一 生产模块主数据

生产成本&#xff1a;原材料是什么&#xff0c;价格多少&#xff0c;人工耗费时间&#xff0c;以及其他的费用 离散制造&#xff1a;有生产订单。工序是分开的&#xff08;可以停&#xff09; 重复制造&#xff1a;没有生产订单&#xff08;可能有客户下达的任务单或者计划订…

Unity扩展编辑器功能的特性

1.添加分组标题 用于在Unity的Inspector视图中为属性或变量组创建一个自定义的标题或头部&#xff0c;有助于在Inspector中组织和分类不同的属性&#xff0c;使其更易于阅读和管理。 [Header("Common Properties")] public float MouseSensitivity 5; public float…

STM32自己从零开始实操07:电机电路原理图

一、LC滤波电路 其实以下的滤波都可以叫低通滤波器。 1.1倒 “L” 型 LC 滤波电路 1.1.1定性分析 1.1.2仿真实验 电感&#xff1a;通低频阻高频的。仿真中高频信号通过电感&#xff0c;因为电感会阻止电流发生变化&#xff0c;故说阻止高频信号 电容&#xff1a;隔直通交。…

汽车电子工程师入门系列——AUTOSAR通信服务框架(中)

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

vue使用scrollreveal和animejs实现页面滑动到指定位置后再开始执行动画效果

效果图 效果链接&#xff1a;http://website.livequeen.top 介绍 一、Scrollreveal ScrollReveal 是一个 JavaScript 库&#xff0c;用于在元素进入/离开视口时轻松实现动画效果。 ScrollReveal 官网链接&#xff1a;ScrollReveal 二、animejs animejs是一个好用的动画库…

python课程设计作业-TCP客户端-服务端通信

说明文档 目录 小组成员分工 作品功能介绍 使用的工具和方法 设计的步骤 课程设计中遇到的问题 结论 1. 小组成员分工 本次课程设计由以下小组成员完成&#xff1a; xxx 2. 作品功能介绍 本次课程设计的作品是一个简单的基于 TCP 协议的客户端-服务端通信示例。通过这个示…

什么是等级保护2.0?

等保的全称是信息安全等级保护&#xff0c;是《网络安全法》规定的必须强制执行的&#xff0c;保障公民、社会、国家利益的重要工作。 官方定义&#xff1a;等级保护是对信息和信息载体按照重要性等级分级别进行保护的一种工作&#xff0c;指对国家重要信息、法人和其他组织及公…

GuLi商城-商品服务-API-三级分类-删除-逻辑删除

注意&#xff1a;官方文档说logic配置可以省略&#xff0c;代码中直观些&#xff0c;配上吧 逻辑删除注解&#xff1a; 实体类字段上加逻辑删除注解&#xff1a; 启动nacos&#xff1a; 启动商品服务&#xff1a; postman测试&#xff1a; 数据库字段值改成了0&#xff0c;说明…

全球海洋平均质量变化的时间序海洋、冰和水文等效水高数据集

Tellus Level-4 Antarctica Mass Anomaly Time Series from JPL GRACE/GRACE-FO Mascon CRI Filtered Release 06.1 version 03 从 JPL GRACE/GRACE-FO Mascon CRI 过滤发布的 Tellus Level-4 南极洲质量异常时间序列 06.1 版本 03 简介 该数据集是全球海洋平均质量变化的时…

Linux线程同步【拿命推荐版】

目录 &#x1f6a9;引言 &#x1f6a9;听故事&#xff0c;引概念 &#x1f6a9;生产者消费者模型 &#x1f680;再次理解生产消费模型 &#x1f680;挖掘特点 &#x1f6a9;条件变量 &#x1f680;条件变量常用接口 &#x1f680;条件变量的原理 &#x1f6a9;引言 上一篇…