【Redis Lua 脚本 可重入分布式锁】
文章目录
- 前言
- 一、最简单的版本:setnx key value
- 获取锁成功
- 获取锁失败
- 释放锁
- 缺点
- 二、升级版本:set key value [ex seconds] [nx]
- 获取锁成功
- 获取锁失败
- 释放锁
- 缺点
- 三、Lua脚本 可重入分布式锁
- 获取锁Lua脚本
- 演示获取锁
- 释放锁Lua脚本
- 演示释放锁
- 总结
前言
使用Redis可以很方便地实现分布式锁。
实现分布式锁不难,难的是要考虑性能及优化加锁解锁机制。
提示:以下是本篇文章正文内容,下面案例可供参考
一、最简单的版本:setnx key value
Setnx(SET if Not eXists) 命令在指定的 key 不存在时,为 key 设置指定的值。
基于setnx命令的特性,我们就可以实现一个最简单的分布式锁了。
key = 竞争资源
value =threadId+count
,线程ID+重入次数
客户端需要自行维护 [线程ID+重入次数]
获取锁成功
redis> setnx lock1 threadId_count
(integer) 1
获取锁失败
客户端自行实现自旋
redis> setnx lock1 threadId_count
(integer) 0
释放锁
# 判断是否自己持有的锁
redis> get lock1
"threadId_count"
# 重入次数减1
redis> set lock1 threadId_count
OK
# 直接释放锁
redis> del lock1
(integer) 1
缺点
- 客户端需要自行维护自旋、超时、[线程ID+重入次数]
- 当释放锁Redis宕机时,会出现锁饥饿现象,永远无法获取到锁,至于锁超时。
二、升级版本:set key value [ex seconds] [nx]
将客户端维护锁超时的工作,交给Redis来做。
Redis Setex 命令为指定的 key 设置值及其过期时间。
如果需要同时拥有Setex跟Setnx的特性,可以使用 set命令 + options
key = 竞争资源
value =threadId+count
,线程ID+重入次数
expire = 锁存活时间
客户端需要自行维护 [线程ID+重入次数]
获取锁成功
redis> set lock1 threadId_count ex expire nx
(integer) 1
获取锁失败
redis> set lock1 threadId_count ex expire nx
(integer) 0
释放锁
# 判断是否自己持有的锁
redis> get lock1
"threadId_count"
# 重入次数减1
redis> setex lock1 threadId_count expire
OK
# 直接释放锁
redis> del lock1
(integer) 1
缺点
- 客户端需要自行维护自旋、[线程ID+重入次数]
三、Lua脚本 可重入分布式锁
前面的Redis原生命令实现的方式,需要多次的网络请求,在锁竞争激烈的情况下,对应用和Redis都有不少的压力,对于客户端也需要自行维护原子性、一致性等并发安全问题。
这里可以使用Lua脚本减少对Redis网络请求,并保证一系列操作的原子性。
获取锁Lua脚本
涉及的Redis命令:
- hincrby :将hash中指定域的值增加给定的数字
- pexpire:设置key的有效时间以毫秒为单位
- hexists:判断field是否存在于hash中
- pttl:获取key的有效毫秒数
- subscribe:订阅一个或多个符合给定的Channel
local key = KEYS[1] -- 锁资源
local waitSet = key .. '_waitSet' -- 该锁的等待线程队列的Key
local timeout = ARGV[1] -- 持有锁的时间
local threadId = ARGV[2] -- 线程唯一标识
-- 判断资源是否在锁
if (redis.call('exists', key) == 0) then-- 锁重入次数=1redis.call('hincrby', key, threadId, 1)redis.call('pexpire', key, timeout)return 'ok'
-- 是否是自己的锁
elseif (redis.call('hexists', key, threadId) == 1) then-- 锁重入次数+1redis.call('hincrby', key, threadId, 1)redis.call('pexpire', key, timeout)return 'ok'
else-- 未获取到锁,返回该锁剩余持有时间return redis.call('pttl', key)
end
获取锁失败后,需要订阅waitSet
Channel,收到通知后再次尝试获取锁。
演示获取锁
能正常获取锁,能正常互斥其他线程
重入次数正常累加
释放锁Lua脚本
涉及Redis命令:
- hincrby :将hash中指定域的值增加给定的数字
- pexpire:设置key的有效时间以毫秒为单位
- hexists:判断field是否存在于hash中
- publish:将信息发送到指定的Channel
local key = KEYS[1] -- 锁资源
local waitSet = key .. '_waitSet' -- 该锁的等待线程队列的Key
local timeout = ARGV[1] -- 持有锁的时间
local threadId = ARGV[2] -- 线程唯一标识
-- 锁不存在or不是自己的锁
if (redis.call('hexists', key, threadId) == 0) thenreturn nil
end
-- 释放锁,重入次数减1
local counter = redis.call('hincrby', key, threadId, -1)
-- 释放锁后,本线程的其它业务代码仍持有锁
if (counter > 0) thenredis.call('pexpire', key, timeout)return 0
-- 释放锁后,可以通知其它等待线程唤醒竞争锁
elseredis.call('del', key);redis.call('publish', waitSet, 'UNLOCK')return 1
end
演示释放锁
能正常锁重入次数递减,能正常释放锁
释放锁后,等待队列能正常监听
总结
以上就是今天要讲的内容,本文仅仅简单介绍了Redis分布式锁的使用,Lua脚本提供了原子性操作。而Redisson提供了大量能使我们快速便捷使用的分布式锁实现。
【Redis Lua 脚本 可重入分布式锁】相关推荐
- 老大吩咐的可重入分布式锁,终于完美的实现了~
重做永远比改造简单 最近在做一个项目,将一个其他公司的实现系统(下文称作旧系统),完整的整合到自己公司的系统(下文称作新系统)中,这其中需要将对方实现的功能完整在自己系统也实现一遍. 旧系统还有一批存 ...
- 精尽 Redisson 源码分析 —— 可重入分布式锁 ReentrantLock
1. 概述 在 Redisson 中,提供了 8 种分布锁的实现,具体我们可以在 <Redisson 文档 -- 分布式锁和同步器> 中看到.绝大数情况下,我们使用可重入锁(Reentra ...
- 视频教程- 19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合-Java
19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合 7年的开发架构经验,曾就职于国内一线互联网公司,开发工程师,现在是某创业公司技术负责人, 擅长语言有nod ...
- Redis Lua脚本的详细介绍以及使用入门
Redis Lua脚本的详细介绍以及使用入门. 文章目录 Redis Lua脚本的引入 开源软件的可扩展性 Redis的扩展性脚本 Redis Lua脚本的基本使用 通过EVAL命令执行Lua脚本 通 ...
- 高并发-【抢红包案例】之四:使用Redis+Lua脚本实现抢红包并异步持久化到数据库
文章目录 导读 概述 实现步骤 注解方式配置 Redis lua脚本和异步持久化功能的开发 Service层添加Redis抢红包的逻辑 Controller层新增路由方法 构造模拟数据,测试 代码 总 ...
- Redis Lua脚本中学教程(下)
在中学教程的上半部分我们介绍了Redis Lua相关的命令,没有看过或者忘记的同学可以步行前往直接使用机票Redis Lua脚本中学教程(上).今天我们来简单学习一下Lua的语法. 在介绍Lua语法之 ...
- 12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁
小陈:呼叫老王...... 老王:来了来了,小陈你准备好了吗?今天我们来讲synchronized的锁重入.锁优化.和锁升级的原理 小陈:早就准备好了,我现在都等不及了 老王:那就好,那我们废话不多说 ...
- Redis Lua脚本大学教程
前面我们已经把Redis Lua相关的基础都介绍过了,如果你可以编写一些简单的Lua脚本,恭喜你已经可以从Lua中学毕业了. 在大学课程中,我们主要学习Lua脚本调试和Redis中Lua执行原理两部分 ...
- 一网打尽Redis Lua脚本并发原子组合操作
1. 前言 Redis 是高性能的 KV 内存数据库,除了做缓存中间件的基本作用外还有很多用途,比如胖哥以前分享的Redis GEO 地理位置信息计算.Redis 提供了丰富的命令来供我们使用以实现一 ...
最新文章
- Java学习----到底调用哪一个方法(多态)
- 数据库中的行锁和表锁
- vs2012编译使用lua 5.2静态库
- NYOJ82-迷宫寻宝1
- ETL之Kettle
- python求解微分方程_python能解微分方程吗
- 数据安全对企业的重要性
- java中if条件中删除此行代码_Java中我如何去除if...else...语句?
- 随机数-random模块
- [转]Kinect for Windows SDK开发入门(七):骨骼追踪基础 下
- Python中使用seek方法来移动文件指针的位置
- 创建个人博客只需五步骤——小白都能看会的详细过程,教你如何白嫖阿里云服务器
- arcgis 实验教程--ModelBuilder与空间建模
- 软件·质量·管理(2)任务排期
- BI神器Power Query(16)-- PQ制作时间维度表(5)
- linux系统中串口驱动的基本实现原理
- java Swing QQ登陆界面
- kaggle数据集、mnist数据集、imdb数据集分享
- python if函数用法_python中if条件中的Contains()函数“in”
- VM10安装Ubuntu14.04在登录窗口循环,无法进入桌面
热门文章
- 编写一个基本账户类。成员变量包括:账号、储户姓名和存款余额,成员函数包括: 存款和取款。编写一个测试程序进行测试。
- java计算机毕业设计宠物救助网站的设计与实现源码+数据库+系统+lw文档+部署
- python 二进制转八进制_Python-八进制、二进制互转
- VR直播很火,但能取代传统电视直播吗?
- ChatGPT目前在哪些国家和地区可用
- vi设计中标准字体的特征
- OpenCV4.0.1/4.0.0/3.4.2 + Contrib + Qt5.9 + CMake3.12.1编译及踩坑笔记、Qt5+OpenCV配置、代码验证、效果图、福利彩蛋
- JS-历史记录练习案例
- 【C语言I博客作业09】
- 听说用这个7岁小孩都能编程-Google开源项目blockly