文章目录

  • 前言
  • 一、最简单的版本: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 脚本 可重入分布式锁】相关推荐

  1. 老大吩咐的可重入分布式锁,终于完美的实现了~

    重做永远比改造简单 最近在做一个项目,将一个其他公司的实现系统(下文称作旧系统),完整的整合到自己公司的系统(下文称作新系统)中,这其中需要将对方实现的功能完整在自己系统也实现一遍. 旧系统还有一批存 ...

  2. 精尽 Redisson 源码分析 —— 可重入分布式锁 ReentrantLock

    1. 概述 在 Redisson 中,提供了 8 种分布锁的实现,具体我们可以在 <Redisson 文档 -- 分布式锁和同步器> 中看到.绝大数情况下,我们使用可重入锁(Reentra ...

  3. 视频教程- 19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合-Java

    19年录制Redis实战教程 高可用秒杀分布式锁布隆过滤器实战 SpringBoot教程整合 7年的开发架构经验,曾就职于国内一线互联网公司,开发工程师,现在是某创业公司技术负责人, 擅长语言有nod ...

  4. Redis Lua脚本的详细介绍以及使用入门

    Redis Lua脚本的详细介绍以及使用入门. 文章目录 Redis Lua脚本的引入 开源软件的可扩展性 Redis的扩展性脚本 Redis Lua脚本的基本使用 通过EVAL命令执行Lua脚本 通 ...

  5. 高并发-【抢红包案例】之四:使用Redis+Lua脚本实现抢红包并异步持久化到数据库

    文章目录 导读 概述 实现步骤 注解方式配置 Redis lua脚本和异步持久化功能的开发 Service层添加Redis抢红包的逻辑 Controller层新增路由方法 构造模拟数据,测试 代码 总 ...

  6. Redis Lua脚本中学教程(下)

    在中学教程的上半部分我们介绍了Redis Lua相关的命令,没有看过或者忘记的同学可以步行前往直接使用机票Redis Lua脚本中学教程(上).今天我们来简单学习一下Lua的语法. 在介绍Lua语法之 ...

  7. 12.synchronized的锁重入、锁消除、锁升级原理?无锁、偏向锁、轻量级锁、自旋、重量级锁

    小陈:呼叫老王...... 老王:来了来了,小陈你准备好了吗?今天我们来讲synchronized的锁重入.锁优化.和锁升级的原理 小陈:早就准备好了,我现在都等不及了 老王:那就好,那我们废话不多说 ...

  8. Redis Lua脚本大学教程

    前面我们已经把Redis Lua相关的基础都介绍过了,如果你可以编写一些简单的Lua脚本,恭喜你已经可以从Lua中学毕业了. 在大学课程中,我们主要学习Lua脚本调试和Redis中Lua执行原理两部分 ...

  9. 一网打尽Redis Lua脚本并发原子组合操作

    1. 前言 Redis 是高性能的 KV 内存数据库,除了做缓存中间件的基本作用外还有很多用途,比如胖哥以前分享的Redis GEO 地理位置信息计算.Redis 提供了丰富的命令来供我们使用以实现一 ...

最新文章

  1. Java学习----到底调用哪一个方法(多态)
  2. 数据库中的行锁和表锁
  3. vs2012编译使用lua 5.2静态库
  4. NYOJ82-迷宫寻宝1
  5. ETL之Kettle
  6. python求解微分方程_python能解微分方程吗
  7. 数据安全对企业的重要性
  8. java中if条件中删除此行代码_Java中我如何去除if...else...语句?
  9. 随机数-random模块
  10. [转]Kinect for Windows SDK开发入门(七):骨骼追踪基础 下
  11. Python中使用seek方法来移动文件指针的位置
  12. 创建个人博客只需五步骤——小白都能看会的详细过程,教你如何白嫖阿里云服务器
  13. arcgis 实验教程--ModelBuilder与空间建模
  14. 软件·质量·管理(2)任务排期
  15. BI神器Power Query(16)-- PQ制作时间维度表(5)
  16. linux系统中串口驱动的基本实现原理
  17. java Swing QQ登陆界面
  18. kaggle数据集、mnist数据集、imdb数据集分享
  19. python if函数用法_python中if条件中的Contains()函数“in”
  20. VM10安装Ubuntu14.04在登录窗口循环,无法进入桌面

热门文章

  1. 编写一个基本账户类。成员变量包括:账号、储户姓名和存款余额,成员函数包括: 存款和取款。编写一个测试程序进行测试。
  2. java计算机毕业设计宠物救助网站的设计与实现源码+数据库+系统+lw文档+部署
  3. python 二进制转八进制_Python-八进制、二进制互转
  4. VR直播很火,但能取代传统电视直播吗?
  5. ChatGPT目前在哪些国家和地区可用
  6. vi设计中标准字体的特征
  7. OpenCV4.0.1/4.0.0/3.4.2 + Contrib + Qt5.9 + CMake3.12.1编译及踩坑笔记、Qt5+OpenCV配置、代码验证、效果图、福利彩蛋
  8. JS-历史记录练习案例
  9. 【C语言I博客作业09】
  10. 听说用这个7岁小孩都能编程-Google开源项目blockly