网站优化

网站优化

Products

当前位置:首页 > 网站优化 >

如何用Lua脚本在【📕分布式锁通关指南 03】中确保Redis操作的原子性?

GG网络技术分享 2026-04-16 09:52 1


哎呀, 分布式锁这玩意儿真是让人头大,咱们来聊聊Lua脚本怎么救场

说实话,写代码这么多年,最怕的就是并发问题了。特别是那种多个机器、多个进程一边对同一条数据进行修改的时候,简直是噩梦。你想想,JDK自带的锁在单机还好用,一旦到了分布式环境,直接歇菜。这就是所谓的“分布式锁”应用场景,要求必须是原子性的。啥叫原子性?就是要么全做,要么全不做,中间不能插队,不能有状态依赖,好吧好吧...。

最近我在看那个,里面讲的东西真是让人又爱又恨。爱的是终于找到了解决思路,恨的是自己以前写的代码全是漏洞。文章浏览阅读6.3k次看来跟我一样头秃的人不少啊。这里有两个限定: 多个进程之间的竞争,意味着JDK自带的锁失效; 原子性修改,意味着数据是有状态的,修改前后有依赖。 我比较认同... 本文将先介绍Redis的实现方式,后面笔者会介绍分布式锁的其他实现。

通过Lua脚本保证redis操作的原子性

在学习Redis实现分布式锁的过程中,笔者先说说参考了Redis的官方文档实现Red..._lua做分布式锁会阻塞吗 学习消息历史使用redis+lua脚本实现分布式锁 版权当多个机器会对同一条数据进行修改时,并且要求这个修改是原子性的,希望大家...。

咱们今天不扯那些虚的,直接来点干货。Redis里提供了eval指令, 让用户可以输入Lua脚本并施行,接下来让我们以此来实现一个简单的set指令,如下:

实不相瞒... if == 0) n return 0;endif == ARGV) n return 1;else return 0;end

看到了吗?这段代码逻辑看起来非常的简单, 就是判断一下当前是否存在这把锁,如果存在则加锁失败; 拉倒吧... 如果不存在就set一个锁,并且给一个过期时间。脚本编写好后 就可以用eval来施行了如下:

redis-cli eval "if == 0 n ; ; return 1; else return 0; end" 1 lockName uuid 3000,格局小了。

为什么非要Lua脚本?不用行不行?

肯定有人要问了我不用Lua脚本行不行?我就在Java代码里先判断,再Set,行不行?答案是:不行!绝对不行!除非你想让你的系统在并发高峰时产生脏数据,然后被老板骂得狗血淋头。在高并发场景下redis写入数据时可能会产生脏数据,再不加锁的情况下redis多个操作之间是不能保证其原子性的,这里我们可以通过lua脚本来施行多个操作,解决这个问题。

redis下载安装: lua下载安装 :https://www..._lua 可以解决redis并发写的脏读 关闭学习C 知道 消息历史通过lua脚本保证redis操作在并发场景下的原子性 版权技 累并充实着。 能专栏收录该内容1 篇文章 前言 在高并发场景下redis写入数据时可能会产生脏数据,再不加锁的情况下redis多个操作之间是不能保证其的,这里我们可以通过lua脚本来施行多个操作,解决这个问题。

主要原因是Redis是单线程施行命令的,但是你的客户端不是啊!你在客户端发两条命令,中间可能隔着千山万水,网络延迟一来别的客户端早就把数据改了。 让我们一起... 所以必须把这一堆操作打包,扔给Redis一次性施行。这就是Lua脚本的魅力。

文章浏览阅读808次,点赞5次,收藏3次。锁代码:package com.wjj.application.util;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import org.sprin._redis 分布式锁 操作原子性 怎么实现 java redis分布式互斥锁 于 2021-03-23 21:00:51 首次发布 版权java一边被 3 个专栏收录55 篇文章 spring boot41 篇文章 spring38 篇文章 锁代码: 这里需要明白为什么使用lua脚本,主要原因是redis的lua脚本是,加锁解锁整个过程必须保证原...

来来来 看看Java里怎么写这玩意儿

光有脚本不行,还得在代码里调用。大致说明下 就是施行redis指令,里面的参数分别是具体的指令和key、value的占位符,括号外的1表示1个键,name和cc则分别是key和value的实际值。通过get命令查看后来啊, 如下:,说实话...

扯后腿。 脚本编写完成后我们就需要在代码中进行调用,这里我们对原来的加锁和解锁方法进行改过代码如下:

也是没谁了。 @Override public void lock { while { // 使用Lua脚本进行加锁 String luaScript = "if == 0) n return 1; else return 0; end"; Long result = , , uuid, ); if ) { break; } try { ; } catch { throw new RuntimeException; } } } @Override public void unlock { // 使用Lua脚本进行解锁 String luaScript = "if == 0) n return 0; end if == ARGV) n return 1; else return 0; end"; , , uuid); }

看这代码,是不是有点晕?没事,我也晕。但是只要知道它是在调用那个Lua脚本就行了。逻辑也是相当的简单, 就是先判断锁是否存在如果存在再比较的value的uuid是否一致,如果一致,则删除锁。施行如下:,从一个旁观者的角度看...

如果业务要求精细,我们可以使用lua脚本来进行完美解锁 /*** redis可以保证lua中的键的原子操作 unlock:lock调用完之后需unlock,否则需等待lock自动过期 * *@paramlock * uid 只有线程已经获取了该才能释放它*/publicvoidunlock { Jedis jedis=newJedis;finalString uid=tokenMap.get;if)return;try{finalString script = if redis.call == \\ +uid+ \\ n return redis.call else return 0 end ; jedis.eval; }catch {thrownewRedisException; }finally{if jedis.close; } }...

这还不够,咱们得聊聊可重入锁

不地道。 你以为这就完了?太天真了。在前文中, 我们讲到分布式锁具备的几个特性中有提到可重入性这个特性对于分布式锁的实现至关重要。先说说 我们明确下它的定义-在同一个线程中,同一个锁可以被多次获取而不会发生死锁。假设方法A调用了需要相同锁的方法B或者本身就是递归的, 被阻塞,从而发生死锁。而观察下我们前面写的Lua脚本明摆着不足以支持可重入,所以呢我们就需要改过。

公正地讲... 那么实现可重入的关键就是:获取了多少把锁就得解锁的时候解多少把,这里需要保持到头来一致性。所以我们这里的实现思路就需要用到redis的一种数据结构-hash 代码如下:

这里的实现逻辑也是很简单:先判断锁是否存在如果不存在直接加锁,重入次数设置为1以及加过期时间;如果存在则比较uuid是否是本线程,如果是那么可重入次数+1,并且给锁加一个过期时间,如果不是那么就加锁失败。那么我们的解锁逻辑也就很清晰了 如下:,妥妥的!

if == 0) n return 0;endlocal lockCount = if n return 1;else end

百感交集。 这里的实现逻辑是:先说说判断当前持有锁的线程是不是本线程,不是的话,就不需要释放了。如果是 就对重入次数减1,减1之后判断值是否大于0,如果大于还持有锁,就设置一个新的过期时间,如果不大于0,就可以删除锁了。这里我们施行一下脚本看看效果, 如下:

这里我们测试先加了三次锁,然后施行一次解锁, 查看,可以看到锁只剩两次了至此,可重入锁成功实现。

文章浏览阅读1.5k次,点赞2次,收藏3次。 ?php/** * Created by PhpStorm. * User: Hank * Date: 2019/3/25 0025 * Time: 下午 15:14 */namespace App\\Lib\\Redis;use Predis\\ClientInterface;/** * redis锁 * 平安和可靠性保证 卷不动了。 在描述我们的设计之前,我们想先..._php redis互斥锁 php redis分布式互斥锁 于 2019-04-10 09:22:18 首次发布 版权php专栏收录该内容2 篇文章 这里需要明白为什么使用lua脚本,主要原因是redis的lua脚本是,加锁解锁整个过程必须保证原子性,具体的原理可以参考: 原子性的用Redis构建分布式锁关注点赞 踩 收藏 觉得还...

随便插个表格, 看看市面上这些玩意儿

写累了咱们看个表格放松一下。现在市面上做分布式锁的中间件也不少, 咱们随便对比一下虽然跟Lua脚本没直接关系, 勇敢一点... 但你知道一下总没坏处。

中间件/方案 实现原理 性能 可靠性 推荐指数
Redis 单机Redis + 过期时间 ⭐⭐⭐⭐
Zookeeper 临时顺序节点 ⭐⭐⭐
Database 版本号CAS ⭐⭐
Redisson 封装了Redis + Watchdog ⭐⭐⭐⭐⭐

看到了吧, Redis虽然可靠性不是最高的,但是性能是真的猛。而且配合Lua脚本,可靠性也能提升不少。上面我们介绍了基于Redis的锁的理论,这里有一个用Node.js写的开源模块Warlock,通过npm可以获取。它使用了redis脚本来创建/释放锁,用于为缓存,数据库,任务队列和其他需要并发的地方提供分布式的锁服务,详见,牛逼。。

Github原文中还缺少一个释放锁的脚本,如果一直依赖TTL来释放锁,效率会很低。Redis的SET操作文档就提供了一个释放锁的脚本: 应用程序中只要加锁的时候指定一个随机数或特定的value作为key的值,解锁的时候用这个value去解锁就可以了。当然,每次加锁时的value必须要保证是唯一的,嗯,就这么回事儿。。

还有个坑,集群环境怎么办?

你以为用了Lua就万事大吉了?天真!这里使用Lua脚本的方式,尽量保证原子性。 使用set key value 命令 看上去很OK,其实吧在Redis集群的时候也会出现问题,比如说A客户端在Redis的master节点上拿到了锁,但是这个加锁的key还没有同步到slave节点,master故障,发生故障转移,一个slave节点升级为master节点,B客户端也可以获取同个key的锁,但客户端A也已经拿到锁了,这就导致多个客户端都拿到锁。 所以针对Redis集群这种情况,还有其他方案 4. Redlock算法 与Redisson实现 Redis作者 antirez基于分布式环境下提出了一种更高级的分布式锁的实现Redlock,原理如下: 下面参考文章...,说实话...

这也就是为什么会有Redlock这种东西。不过Redlock也有争议, 咱们今天先不深究,毕竟标题是“指南03”,咱们还是专注于Lua脚本怎么保证原子性,另起炉灶。。

脚本缓存, 别每次都传那么长的字符串

上面通过eval指令可以实现输入并施行lua脚本,那么如果相同的脚本每次都要重新输入就很麻烦, 捡漏。 所以呢,我们可以通过script load指令来将脚本缓存下来如下:

施行后它会返回一个校验和,我们就把这个校验和当做脚本的id吧,然后我们就可以通过evalsha命令通过这个校验和来施行对应的脚本命令,如下:,麻了...

官方介绍它是一种轻量小巧的脚本语言,设计的目的是为了嵌入应用程序,从而为应用程序提供灵活的 性和定制功能。redis支持嵌入Lua脚本, 瞎扯。 所以呢可以很方便地使用。安装过程很简单,本文不做赘述,直接移步官网下载即可,如遇到问题,善用一下你的搜索引擎即可。

script kill 杀死正在施行的Lua脚本。 Redis提供了一个lua-time-limit参数,默认5秒,它是Lua脚本的超时时间,如果Lua脚本超时,其他施行正常命令的客户端会收到 Busy 可以。 Redis is busy running a script 错误,但是不会停止脚本运行,此时可以... 以上方案是很多客户端实现的方式,建立和释放锁,并保证绝对的平安,是这个锁的设计比较棘手的地方。

有两个潜在的陷阱: 1.应用程序通过网络和redis交互,这 稳了! 意味着从应用程序发出命令到redis后来啊返回之间会有延迟。

再来个表格, 看看Redis Lua相关的命令

为了显得咱们专业一点,再列个表,把常用的Redis Lua命令列出来以后面试的时候能吹一吹。

命令 作用 常用场景
EVAL script numkeys key arg 施行Lua脚本 一次性施行复杂逻辑, 如分布式锁
EVALSHA sha1 numkeys key arg 施行缓存的Lua脚本 减少网络传输,提升性能
SCRIPT LOAD script 将脚本加载到缓存 配合EVALSHA使用
SCRIPT EXISTS sha1 检查脚本是否存在 调试或管理
SCRIPT FLUSH 清空脚本缓存 清理内存

文章浏览阅读1w次,点赞10次,收藏50次。Lua脚本是高并发、高性能的必备脚本语言,大部分的开源框架中的分布式锁组件,都是用纯lua脚本实现的。 学习C 知道 消息历史基于 Redis + Lua 脚本实 我们一起... 现分布式锁,确保操作的原子性 https://blog.csdn.net/weixin_44259720/article/details/121968837版权为了保证数据的争用平安,通常要采用锁机制控制。

如果是单应用部署,直接通过synchronized关键字修改方法,就能解决,但是如果是分布式的部署 该方法就不能解决这个问题啦,此时就引出了一个的概念。

再说说一下 别嫌我啰嗦

在02篇的小结中,为大家指出了我们处理锁误删的代码中存在的问题,但其实只要使用redis来做分布式锁,如果你不能把操作一步完成,不管什么场景可能或多或少都会出问题。所以引出了本篇的内容。在03篇中,我会为大家讲解如何通过Lua脚本来保持redis指令的原子性,从而避免并发问题。友情提示:本篇其实算番外篇, 如果你对Lua脚本不感兴趣可以直接跳过毕竟在实际业务里应该没多少人会自己去手撸,后续篇章中会讲解到其他中间件更为成熟可靠的方案,栓Q了...。

它的使用也是非常简单,把脚本声明之后直接通过execute方法进行传参调用即可。通过Lua脚本的加解锁主要是将原本使用redis的多步操作合并成了一步来保证了操作的原子性。

别纠结... 我们并不讲解Lua的基础语法, 能看这篇文章的肯定都是起码掌握一门甚至多门编程语言的人,所有我们直接上手实战,看看它是如何在redis中使用的。

既然我们已经学会了Lua脚本的基础用法, 现在让我们用Lua脚本来分别实现加锁和解锁的逻辑,这里可以创建一个文件来进行编写,CPU你。。

文章浏览阅读3k次。lua脚本保证Redis多条命令原子性Redis能施行lua脚本,一段lua脚本可以作为一个整体,这样将多条Redis命令写入lua,即可以实现事务的原子性,下面演示了jedis和redisTemplate是如何调用lua脚本的Jedis调用lua脚本# 完成的功能是 set my_key1 my_value1 EX 15 NX, LUA 我可是吃过亏的。 脚本很神奇不需要加分号和换行符# 其实该功能在J..._redis lua 实现多个查询判断 lua脚本保证Redis多条命令原子性 石头wang最新推荐文章于 2024-09-19 07:15:00 发布 阅读量3k收藏7点赞数 版权传统数据库/Mybatis/Redis专栏收录该内容27 篇文章3 订阅 lua脚本保证Redis多条命令原子性 Red...

本期带领大家简单学习了如何通过Lua脚本来保证锁的原子性,进而保证了我们锁的平安性。在下期的04篇中将会给大家继续完善我们的redis分布式锁, 闹笑话。 主要原因是目前看似很完美,但其实还存在细节问题,那么我们下一期见。


提交需求或反馈

Demand feedback