Redis调用Lua脚本及使用场景快速掌握


Posted in Redis onMarch 16, 2022

Redis调用Lua脚本及使用场景快速掌握

一、阅读本文前置条件

可以遵循这个链接中的方法在操作系统上安装 Redis

如果你对redis命令不熟悉,查看《Redis 命令引用

二、为什么需要Lua脚本

简而言之:Lua脚本带来性能的提升。

很多应用的服务任务包含多步redis操作以及使用多个redis命令,这时你可以使用Redis结合Lua脚本,会为你的应用带来更好的性能。

另外包含在一个Lua脚本里面的redis命令具备原子性,当你面对高并发场景下的redis数据库操作时,可以有效避免多线程操作产生脏数据。

三、学点Lua语法

说了那么多,Lua不会怎么办?不要慌!Lua其实很简单,如果你曾经学习过任何一门编程语言,学习Lua都非常简单。下面给大家举几个例子学习一下:

3.1.一个简单的例子

Lua脚本通过各种语言的redis客户端都可以调用,我们就简单一点使用redis-cli
看下面的redis命令行:

eval "redis.call('set', KEYS[1], ARGV[1])" 1 key:name value

EVAL命令行后面跟着的是Lua脚本:"redis.call('set', KEYS[1], ARGV[1])",放到编程语言里面就是一段字符串,跟在Lua脚本字符串后面的三个参数依次是:

  • redis Lua脚本所需要的KEYS的数量 ,只有一个KEYS[1],所以紧跟脚本之后的参数值是1
  • Lua 脚本需要的参数KEYS[1]的参数值,在我们的例子中值为key:name
  • Lua 脚本需要的参数ARGV[1]的参数值,在我们的例子中值为value

Lua脚本中包括两组参数:KEYS[]和ARGV[],两个数组下标从1开始。一个值得去遵守的最佳实践是:把redis操作所需的key通过KEYS进行参数传递,其他的Lua脚本所需的参数通过ARGV进行传递。

上面的脚本执行完成之后,我们使用下面的Lua脚本来进行验证,如果该脚本的返回值是”value”,与我们之前设置的key:name的值相同,则表示我们的Lua脚本被正确执行了。

eval "return redis.call('get', KEYS[1])" 1 key:name

3.2.仔细看下Lua脚本里的内容

我们的第一个Lua脚本只包含一条语句,调用redis.call

redis.call('set', KEYS[1], ARGV[1])

所以在Lua脚本里面可以通过redis.call执行redis命令,call方法的第一个参数就是redis命令的名称,因为我们调用的是redis 的set命令,所以需要传递key和value两个参数。

我们第二个脚本不只是执行了一个脚本,因为执行get命令还返回了执行结果。注意脚本中有一个return 关键字。

eval "return redis.call('get', KEYS[1])" 1 key:name

当然如果只是上面的这么简单的Lua脚本,还不如直接使用命令行更方便。我们实际使用到的Lua脚本会比上面的复杂,上面的Lua脚本只是一个Hello World。

3.3. 复杂点的例子

我曾使用Lua脚本从一个hash map里面按照一定的顺序获取若干key对应的值。对应的顺序在一个zset排序集合中进行保存,数据设置及排序可以通过下面的完成。

# 设置hkeys为键Hash值
hmset hkeys key:1 value:1 key:2 value:2 key:3 value:3 key:4 value:4 key:5 value:5 key:6 value:6
# 建一个order为键的集合,并给出顺序
zadd order 1 key:3 2 key:1 3 key:2

如果不知道hmset和zadd命令的作用,可以参考hmsetzadd

执行下面的Lua脚本

eval "local order = redis.call('zrange', KEYS[1], 0, -1); return redis.call('hmget',KEYS[2],unpack(order));" 2 order hkeys

你将看到如下的输出结果

“value:3”
“value:1”
“value:2”

  • 通过zrange取出order集合里面的数据,即:[ key:3 , key:1 , key:2]
  • 然后通过unpack函数将[ key:3 , key:1 ,key:2] 转成 key:3 key:1 key:2
  • 最后执行 hmget hkeys key:3 key:1 key:2,所以得到上面的输出结果

四、Lua脚本预加载

Redis可以对Lua脚本进行预加载,可以通过script load命令把Lua脚本预加载到redis里面。

script load "return redis.call('get', KEYS[1])"

预加载完成之后,你会看到下面的一段输出

“4e6d8fc8bb01276962cce5371fa795a7763657ae”

这是一个具有唯一性的hash字符串,这个hash就代表着我们刚刚预加载的Lua脚本,我们可以通过EVALSHA命令执行该脚本。如:

evalsha 4e6d8fc8bb01276962cce5371fa795a7763657ae 1 key:name

执行的结果与下面的是一致的。

eval "return redis.call('get', KEYS[1])" 1 key:name

五、一个修改 JSON数据的例子?

有些开发人员有的时候可能会将JSON数据保存在Redis里面,我们先不说这样做是不是一种好的方式,我们只来谈一下如何通过Lua脚本修改JSON数据。

正常情况下,你需要修改一个JSON Object,你需要把它从redis里面查询回来,解析它,修改key值,然后再将它序列化保存到redis里面。这样做有几个问题:

高并发场景下无法保证原子性,另一个线程可以在当前线程获取和设置Object操作之间更改这个JSON数据。在这种情况下,将丢失更新。

性能问题。如果您经常进行这样的更改并且JSON数据相当大,这可能会成为应用的性能瓶颈。因为你经常性的进行取数据,存数据。

通过在 Lua 中实现上面逻辑,因为redis的Lua脚本是在服务端执行的,一方面可以保证操作的原子性,解决高并发丢失更新的问题,另一方面节省网络传输同时提升性能。

下面我们向redis里面保存一个测试JSON 字符串:obj

set obj '{"a":"foo","b":"bar"}'

现在,让我们运行我们的脚本:

EVAL 'local obj = redis.call("get",KEYS[1]); local obj2 = string.gsub(obj,"(" .. ARGV[1] .. "\":)([^,}]+)", "%1" .. ARGV[2]);  return redis.call("set",KEYS[1],obj2);' 1 obj b bar2

local obj = redis.call("get",KEYS[1]);

其中KEYS[1]=obj,所以返回值

obj= '{"a":"foo","b":"bar"}'

而 

local obj2 = string.gsub(obj,"(" .. ARGV[1] .. "\":)([^,}]+)", "%1" .. ARGV[2]);

是Lua脚本的字符串连接符号;我们使用 RegEx 模式来匹配密钥并替换其值,如果对表达式不熟悉,自行补课;"%1"表示第一个被匹配的子串,"%1" … ARGV[2] 等于 “b”:“bar2”,并使用gsub进行替换。

最后将结果返回,obj的JSON对象的结果如下:

{"a":"foo","b":"bar2"}

六、总结

我建议只有在你能证明它能带来更好的性能时才使用Lua脚本。如果你只是想要保证redis操作原子性,那么可以使用transactions事务。不一定非要使用Lua脚本。

此外redis Lua脚本不应太长。因为当脚本运行时相当于为被操作对象加锁,其他操作都在等待它完成。如果Lua脚本需要相当长的时间执行,则可能会导致瓶颈而不是提高性能。Lua脚本在达到超时后停止(默认情况下为 5 秒)。

以上就是Redis调用Lua脚本及使用场景快速掌握的详细内容,更多关于Redis调用Lua脚本使用场景的资料请关注三水点靠木其它相关文章!

Redis 相关文章推荐
redis连接被拒绝的解决方案
Apr 12 Redis
深入浅析Redis 集群伸缩原理
May 15 Redis
redis使用不当导致应用卡死bug的过程解析
Jul 01 Redis
Jedis操作Redis实现模拟验证码发送功能
Sep 25 Redis
详解redis在微服务领域的贡献
Oct 16 Redis
SpringBoot集成Redis的思路详解
Oct 16 Redis
Redis高并发防止秒杀超卖实战源码解决方案
Nov 01 Redis
使用RedisTemplat实现简单的分布式锁
Nov 20 Redis
Redis中缓存穿透/击穿/雪崩问题和解决方法
Dec 04 Redis
详解Redis的三种常用的缓存读写策略步骤
May 06 Redis
Redis特殊数据类型HyperLogLog基数统计算法讲解
Jun 01 Redis
基于redis+lua进行限流的方法
Jul 23 Redis
Redis 的查询很快的原因解析及Redis 如何保证查询的高效
Redis 中使用 list,streams,pub/sub 几种方式实现消息队列的问题
Redis中有序集合的内部实现方式的详细介绍
Mar 16 #Redis
面试分析分布式架构Redis热点key大Value解决方案
分布式架构Redis中有哪些数据结构及底层实现原理
Redis之RedisTemplate配置方式(序列和反序列化)
Mar 13 #Redis
浅谈Redis跟MySQL的双写问题解决方案
You might like
JAVA/JSP学习系列之四
2006/10/09 PHP
第九节 绑定 [9]
2006/10/09 PHP
利用ThinkPHP内置的ThinkAjax实现异步传输技术的实现方法
2011/12/19 PHP
如何使用PHP计算上一个月的今天
2013/05/23 PHP
PHP children()函数讲解
2019/02/03 PHP
PHP7引入的"??"和"?:"的区别讲解
2019/04/08 PHP
js定义对象或数组直接量时各浏览器对多余逗号的处理(json)
2011/03/05 Javascript
HTML DOM的nodeType值介绍
2011/03/31 Javascript
用表格输出1-1000之间的数字实现代码(附特效)
2013/04/21 Javascript
jQuery UI 实现email输入提示实例
2013/08/15 Javascript
jQuery中bind()方法用法实例
2015/01/19 Javascript
JS 作用域与作用域链详解
2015/04/07 Javascript
JS版元素周期表实现方法
2015/08/05 Javascript
快速学习jQuery插件 Cookie插件使用方法
2015/12/01 Javascript
JS排序方法(sort,bubble,select,insert)代码汇总
2016/01/30 Javascript
基于vuejs实现一个todolist项目
2017/04/11 Javascript
详解Vue组件实现tips的总结
2017/11/01 Javascript
JS中使用react-tooltip插件实现鼠标悬浮显示框
2019/05/15 Javascript
socket在egg中的使用实例代码详解
2019/05/30 Javascript
JS实现滑动插件
2020/01/15 Javascript
python基础教程之缩进介绍
2014/08/29 Python
编写简单的Python程序来判断文本的语种
2015/04/07 Python
彻底理解Python list切片原理
2017/10/27 Python
人工神经网络算法知识点总结
2019/06/11 Python
Python中遍历列表的方法总结
2019/06/27 Python
Python编译成.so文件进行加密后调用的实现
2019/12/23 Python
Python任务调度模块APScheduler使用
2020/04/15 Python
网络教育毕业生自我鉴定
2013/10/10 职场文书
经典安踏广告词
2014/03/21 职场文书
歌颂党的演讲稿
2014/09/10 职场文书
小学生学习保证书
2015/02/26 职场文书
如何写辞职书
2015/02/26 职场文书
pytorch 梯度NAN异常值的解决方案
2021/06/05 Python
vue+element ui实现锚点定位
2021/06/29 Vue.js
JavaScript 中for/of,for/in 的详细介绍
2021/11/17 Javascript
vue @click.native 绑定原生点击事件
2022/04/22 Vue.js