Redisson中分布式锁的实现原理

redisson版本:3.27.2

简介

锁归根结底就是对同一资源的竞争抢夺,不管是在单体的应用亦或者集群的服务中,上锁都是对同一资源进行修改的操作。
至于分布式锁,那就是多个服务器或资源,同时抢占某一单体应用的同个资源了。在本篇文章中,抢占的资源就是Redis中的某个Key了。

原理

上锁

RLock lock = RedissonClient.getLock("test-lock");
lock.lock();

执行lock.lock()后,最终会在Redis中执行一段lua脚本。来判断锁是否已经被占用:

if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) thenredis.call('hincrby', KEYS[1], ARGV[2], 1);redis.call('pexpire', KEYS[1], ARGV[1]);return nil;
end ;
return redis.call('pttl', KEYS[1]);

参数:

名称内容
KEY[1]锁名称
ARGV[1]锁过期时间,毫秒
ARGV[2]锁对象ID+当前线程ID

注意,在lua脚本中,上锁时并非设置一个key-value,而是使用了hash结构。

redis.call(‘hincrby’, KEYS[1], ARGV[2], 1);

这样做的目的是Redisson不光实现了分布式锁,还增加了一个特性:可重入。因为单独的键值对无法存储上锁次数,就使用了hash结构。


上锁时redis日志:

10:40:50.064 [0 192.168.65.1:34743] "EVAL" "if ((redis.call('exists', KEYS[1]) == 0) or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then redis.call('hincrby', KEYS[1], ARGV[2], 1); redis.call('pexpire', KEYS[1], ARGV[1]); return nil; end; return redis.call('pttl', KEYS[1]);" "1" "test-lock" "30000" "fcce544e-09e1-48bb-9c90-2c77c75d673f:1"
10:40:50.064 [0 lua] "exists" "test-lock"
10:40:50.064 [0 lua] "hincrby" "test-lock" "fcce544e-09e1-48bb-9c90-2c77c75d673f:1" "1"
10:40:50.064 [0 lua] "pexpire" "test-lock" "30000"

可以很清晰的从日志分析出来,Redisson在给分布式锁上锁时所做的操作。

判断锁是否被占->没有被占,抢占并设置过期时间

那么在第一次抢占不到锁时,Redisson在等待时,会不会做些其他事情呢?
的确,Redisson在等待锁时,还会做一些其他事情,免得在傻傻等待。

等待锁释放

在抢不到锁的时候,Redisson会监听redisson_lock__channel开头的Channel
锁释放时,抢占锁的应用会向这个Channel发布一个消息(消息内容为:0)。向正在等待锁释放的应用通知此时锁已经释放了,可以尝试抢占锁了。
在上面的这个例子中,对应的Channel名称为:redisson_lock__channel:{test-method},消息内容为:0。

解锁

在抢到锁并且本地逻辑运行完成后,此时就需要解锁让其他应用运行下去了。

RLock lock = RedissonClient.getLock("test-lock");
lock.lock();

执行的lua脚本

local val = redis.call('get', KEYS[3]); 
if val ~= false then return tonumber(val);
end;
-- 看hash中是否还存在这个键(RLock对象的名称以及线程名称)
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;
end;
-- counter:hash中减一后的值
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); redis.call('set', KEYS[3], 0, 'px', ARGV[5]); return 0; 
-- 为0了,说明重入锁的次数都删掉了
else -- 删除锁对应的redis hash表redis.call('del', KEYS[1]);-- 发布当前锁释放的通知redis.call(ARGV[4], KEYS[2], ARGV[1]); -- 设置对象对应的 值为1 redis.call('set', KEYS[3], 1, 'px', ARGV[5]); return 1; 
end;

参数:

参数名说明
KEY[1]分布式锁名称test-lock
KEY[2]redis pub/sub 通道名称redisson_lock__channel:{test-lock}
KEY[3]正在解锁操作标识redisson_unlock_latch:{test-lock}:96ca7c366fa0a6bda6d39931f2092eb1
ARGV[1]pub/sub 通道值-解锁消息(0)
ARGV[2]锁过期时间
ARGV[3]Lock对象对应的锁名称d5804b0b-50e4-4d61-a91a-319c2ddb5b1d:1
ARGV[4]PUBLISH
ARGV[5]正在解锁操作标识KEY对应过期时间

解锁时Redis日志:

10:40:50.073 [0 192.168.65.1:34745] "EVAL" "local val = redis.call('get', KEYS[3]); if val ~= false then return tonumber(val);end; if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;end; local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); redis.call('set', KEYS[3], 0, 'px', ARGV[5]); return 0; else redis.call('del', KEYS[1]); redis.call(ARGV[4], KEYS[2], ARGV[1]); redis.call('set', KEYS[3], 1, 'px', ARGV[5]); return 1; end; " "3" "test-lock" "Redisson_lock__channel:{test-lock}" "Redisson_unlock_latch:{test-lock}:4673449b09ccae99bad2a89c9f0122de" "0" "30000" "fcce544e-09e1-48bb-9c90-2c77c75d673f:1" "PUBLISH" "13500"
10:40:50.073 [0 lua] "get" "Redisson_unlock_latch:{test-lock}:4673449b09ccae99bad2a89c9f0122de"
10:40:50.073 [0 lua] "hexists" "test-lock" "fcce544e-09e1-48bb-9c90-2c77c75d673f:1"
10:40:50.073 [0 lua] "hincrby" "test-lock" "fcce544e-09e1-48bb-9c90-2c77c75d673f:1" "-1"
10:40:50.073 [0 lua] "del" "test-lock"
10:40:50.073 [0 lua] "PUBLISH" "Redisson_lock__channel:{test-lock}" "0"
10:40:50.073 [0 lua] "set" "Redisson_unlock_latch:{test-lock}:4673449b09ccae99bad2a89c9f0122de" "1" "px" "13500"
10:40:50.076 [0 192.168.65.1:34746] "DEL" "Redisson_unlock_latch:{test-lock}:4673449b09ccae99bad2a89c9f0122de"

解锁的lua脚本比上锁时的脚本有太多的逻辑了,不过还是分为了三块:

  1. 判断是否有其他线程在解锁,如果有其他线程在同时释放锁时,忽略本次操作
local val = redis.call('get', KEYS[3]); 
if val ~= false then return tonumber(val);
end;
  1. 判断锁是否已经释放,锁已经释放了,无需操作
-- 看hash中是否还存在这个键(RLock对象的名称以及线程名称)
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;
end;
  1. 给锁的hash结构减一,根据减一后的结果做进一步处理。
-- counter:hash中减一后的值
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 
if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); redis.call('set', KEYS[3], 0, 'px', ARGV[5]); return 0; 
-- 为0了,说明重入锁的次数都删掉了
else -- 删除锁对应的redis hash表redis.call('del', KEYS[1]);-- 发布当前锁释放的通知redis.call(ARGV[4], KEYS[2], ARGV[1]); -- 设置对象对应的 值为1 redis.call('set', KEYS[3], 1, 'px', ARGV[5]); return 1; 
end;

解锁时,会发布一条消息,通知锁已经释放。

10:40:50.073 [0 lua] “PUBLISH” “redisson_lock__channel:{test-lock}” “0”

方便其他正在等待锁的Redisson应用及时唤醒抢占锁。

其他隐藏配置

Redisson在默认上锁时设置的锁过期时间为30S,与其他Java Redis库不设置过期时间的逻辑相反。
由于Redisson显示声明了锁过期时间,那么他一定会在别的地方去一直延长该时间,否则锁在用着用着就被别人抢占了,
于是Redisson中一个特殊机制就出现了:看门狗机制
至于为什么Redisson要这么做,在他对于这个看门狗过期时间配置项可以得知:

This prevents against infinity locked locks due to Redisson client crash or any other reason when lock can’t be released in proper way.
这可以防止由于Redisson客户端崩溃或任何其他原因导致无法以适当的方式解锁而导致的无限锁定。

image.png
看门狗续期脚本:
如果锁还在使用中,那么重置锁的过期时间,否则不做任何操作。

if (redis.call('hexists', KEYS[1], ARGV[2]) == 1) thenredis.call('pexpire', KEYS[1], ARGV[1]);return 1;
end ;
return 0;
名称内容
KEY[1]锁名称test-lock
ARGV[1]锁过期时间,毫秒30000
ARGV[2]锁对象ID+当前线程IDd5804b0b-50e4-4d61-a91a-319c2ddb5b1d:1

引用文章

https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers
https://github.com/redisson/redisson/wiki/2.-Configuration/

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

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

相关文章

什么是Google SEO优化,如何做好谷歌seo排名?2024年谷歌搜索引擎优化(谷歌SEO)3分钟速通教程指南

1 - 什么是SEO? 谷歌排名优化(SEO:Search Engine Optimization)是指当您在谷歌搜索那里输入一个您正在推广的产品或服务的关键词时,如何在使您的站在Google里获得一个较高的排名位置而做的优化过程。谷歌排名优化的意…

六西格玛培训证书攻略2024:一站式解决方案助你快速上手

目前,企业对于员工的专业能力和综合素质要求越来越高。六西格玛作为一种先进的质量管理方法,已经成为众多企业提升运营效率、降低成本的重要手段。张驰咨询针对2024年六西格玛培训证书考取,为广大学员制定了实用的攻略,帮助学员们…

玩转大模型 企业AI着陆新正解 神州问学AI原生赋能平台正式发布

在人工智能技术日新月异的今天,神州数码凭借深厚的行业洞察和技术积累,揭开了AI原生赋能平台——神州问学的神秘面纱。作为企业AI着陆的加速引擎,神州问学致力于通过AI原生场景赋能,为企业开辟一条通往智能未来的坦途。 神州问学—…

vue3使用el-radio-group获取表格数据无法选中问题

这里是引用 今天写项目发现使用el-radio-group无法获取表格中的数据&#xff0c;于是去官网查看了一下&#xff0c;发现写的没啥问题&#xff0c;就是 <el-radio value"1" size"large"> 未知</el-radio>这样的写法&#xff0c;又在网上看了一些…

不写一行代码,使用ChatGpt开发一个射击游戏

1.简介 最近需要开发一个网页应用&#xff0c;想到了使用ChatGpt生成Html页面&#xff0c;生成的效果非常好&#xff0c;说几句话就可以实现复杂的功能。不过需要一步步耐心的引导。然后我就想到可以用ChatGpt生成一个网页游戏。这个游戏包含了人物移动、跳跃、射击、生命值&a…

02Django项目安装和环境变量设置

凯哥英语视频 Django项目安装和环境变量设置 凯哥英语视频1.汉化1.打开PyCharm&#xff0c;点击File&#xff0c;再点击Settings2.然后点击 Plugins&#xff0c;再Marketplace&#xff0c;找到Chinese&#xff08;simplified&#xff09;Language&#xff0c;再点击Install然后…

GPT-4o: 从最难的“大海捞针”基准看起

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在阅读过程中有些知识点存在盲区&#xff0c;可以回到如何优雅的谈论大模型重新阅读。另外斯坦福2024人工智能报告解读为通识性读物。若对于如果…

基于EBAZ4205矿板的图像处理:12二值化图像的膨胀与腐蚀

基于EBAZ4205矿板的图像处理&#xff1a;12二值化图像的膨胀与腐蚀 先看效果 注意&#xff1a;我的项目中的膨胀和腐蚀是对二值化图像中的像素值为255的像素进行处理&#xff0c;而我的图像中255为白色&#xff0c;0为黑色&#xff0c;所以是对颜色为白色的像素点进行的膨胀和…

Google I/O 2024:有关AI的一切已公布|TodayAI

2024年谷歌I/O大会圆满落幕&#xff0c;谷歌在会上发布了一系列更新&#xff0c;涵盖从最新的人工智能技术到Android系统的多项改进。此次大会特别关注于谷歌的Gemini人工智能模型&#xff0c;并详细介绍了这些模型如何被融入到Workspace、Chrome等多个应用程序中&#xff0c;展…

男士内裤哪个牌子质量好又舒服?五款不容错过的男士内裤

男士内裤&#xff0c;作为男士日常穿着的重要贴身衣物&#xff0c;其舒适度和透气性至关重要。尽管有些男士可能习惯长时间穿着同一条内裤&#xff0c;但为了确保健康和舒适&#xff0c;建议每3-6个月更换一次内裤。长时间不更换内裤会导致其舒适性和透气性下降&#xff0c;同时…

性价比王者HUSB237,极简PD Sink的“瘦身秘籍”

在小型化、高集成的要求下&#xff0c;慧能泰取电芯片进行技术升级后“瘦身成功”&#xff0c;推出最新一代极具性价比的最简PD Sink取电芯片——HUSB237。 图1&#xff1a;HUSB237 demo及封装图 HUSB237 是一款极具性价比的最简PD Sink取电芯片&#xff0c;支持PD3.1协议包含…

算法课程笔记——蓝桥云课第11次直播

算法课程笔记——蓝桥云课第11次直播

收藏与品鉴:精酿啤酒的艺术之旅

啤酒&#xff0c;这一古老的酒精饮品&#xff0c;不仅是人们生活中的日常饮品&#xff0c;更是一种艺术和文化的载体。对于Fendi club啤酒而言&#xff0c;收藏与品鉴更是一门深入骨髓的艺术之旅。 Fendi club啤酒的收藏&#xff0c;不仅仅是简单的存放和保管&#xff0c;而是一…

交换机组网最常见的8大故障及解决方式

有朋友多次提到网络故障&#xff0c;其中在交换机组网时常见的故障比较多&#xff0c;为了便于大家排除这些故障&#xff0c;在此介绍一些常见的典型故障案例及处理思路。 故障1&#xff1a;交换机刚加电时网络无法通信 【故障现象】 交换机刚刚开启的时候无法连接至其他网络…

k8s StatefulSet

Statefulset 一个 Statefulset 创建的每个pod都有一个从零开始的顺序索引&#xff0c;这个会体现在 pod 的名称和主机名上&#xff0c;同样还会体现在 pod 对应的固定存储上。这些 pod 的名称是可预知的&#xff0c;它是由 Statefulset 的名称加该实例的顺序索引值组成的。不同…

现货黄金在线交易有哪些优势_EE trade

现货黄金在线交易拥有几项独特优势&#xff0c;使其成为广受投资者青睐的贵金属投资方式&#xff1a; 1. 全天候交易 现货黄金市场几乎可以实现24小时不间断交易&#xff0c;投资者可以根据全球市场的变动随时参与交易&#xff0c;这提供了极大的灵活性和即时反应市场变化的能…

Nginx+Keepalived高可用集群

NginxKeepalived高可用集群 服务器准备 服务器名IP软件包主从n1RIP 192.168.99.111 VIP 192.168.99.200nginx keepaliveMASTERn2RIP192.168.99.122 VIP 192.168.99.200nginx keepalivedh1RIP 192.168.99.133 VIP 192.168.99.200httpdh2RIP 192.168.32.144 VIP 192.168.99.200h…

从入门到精通:.gitlab-ci.yml文件的完整指南

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 从入门到精通&#xff1a;.gitlab-ci.yml文件的完整指南 前言.gitlab-ci.yml文件概述stagesimagesbefore_script和after_scripttagsonly和exceptonly关键字except关键字 artifacts使用方式产物路径其他…

写一个类ChatGPT应用,前后端数据交互有哪几种

❝ 对世界的态度&#xff0c;本质都是对自己的态度 ❞ 大家好&#xff0c;我是「柒八九」。一个「专注于前端开发技术/Rust及AI应用知识分享」的Coder 前言 最近&#xff0c;公司有一个AI项目&#xff0c;要做一个文档问答的AI产品。前端部分呢&#xff0c;还是「友好借鉴」Cha…

16.ABA问题

文章目录 ABA问题1.什么是ABA问题&#xff1f;2.ABA问题解决方案2.1.使用AtomicStampedReference解决ABA问题2.2.使用AtomicMarkableReference解决ABA问题 ABA问题 因为CAS操作的原子性能高&#xff0c;在JUC中广泛被应用&#xff0c;但是如果使用的不合理&#xff0c;CAS操作就…