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 14 Redis
在K8s上部署Redis集群的方法步骤
Apr 27 Redis
浅谈redis缓存在项目中的使用
May 20 Redis
浅谈Redis主从复制以及主从复制原理
May 29 Redis
比较几种Redis集群方案
Jun 21 Redis
在redisCluster中模糊获取key方式
Jul 09 Redis
Redis 持久化 RDB 与 AOF的执行过程
Nov 07 Redis
关于SpringBoot 使用 Redis 分布式锁解决并发问题
Nov 17 Redis
Window server中安装Redis的超详细教程
Nov 17 Redis
关于使用Redisson订阅数问题
Jan 18 Redis
Redis官方可视化工具RedisInsight安装使用教程
Apr 19 Redis
windows安装 redis 6.2.6最新步骤详解
Apr 26 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
php利用反射实现插件机制的方法
2015/03/14 PHP
PHP 实现文件压缩解压操作的方法
2019/06/14 PHP
js中查找最近的共有祖先元素的实现代码
2010/12/30 Javascript
Jquery倒数计时按钮setTimeout的实例代码
2013/07/04 Javascript
JavaScript验证图片类型(扩展名)的函数分享
2014/05/05 Javascript
jQuery.lazyload+masonry改良图片瀑布流代码
2014/06/20 Javascript
jQuery统计上传文件大小的方法
2015/01/24 Javascript
JavaScript实现获得所有兄弟节点的方法
2015/07/23 Javascript
JS鼠标拖拽实例分析
2015/11/23 Javascript
javascript实现标签切换代码示例
2016/05/22 Javascript
Bootstrap页面缩小变形的快速解决办法
2017/02/03 Javascript
js实现五星评价功能
2017/03/08 Javascript
web3.js增加eth.getRawTransactionByHash(txhash)方法步骤
2018/03/15 Javascript
详解vue路由篇(动态路由、路由嵌套)
2019/01/27 Javascript
史上最为详细的javascript继承(推荐)
2019/05/18 Javascript
JS实现轮播图效果
2020/01/11 Javascript
JS如何生成随机验证码
2020/03/02 Javascript
js实现无刷新监听URL的变化示例代码详解
2020/06/03 Javascript
Python验证码识别处理实例
2015/12/28 Python
Python代码解决RenderView窗口not found问题
2016/08/28 Python
Python数据结构与算法之图结构(Graph)实例分析
2017/09/05 Python
python编写分类决策树的代码
2017/12/21 Python
Python序列循环移位的3种方法推荐
2018/04/09 Python
Python二叉搜索树与双向链表转换算法示例
2019/03/02 Python
win10下安装Anaconda的教程(python环境+jupyter_notebook)
2019/10/23 Python
python之MSE、MAE、RMSE的使用
2020/02/24 Python
Python多个装饰器的调用顺序实例解析
2020/05/22 Python
纯CSS3实现Material Design效果
2017/03/09 HTML / CSS
Elemental Herbology官网:英国美容品牌
2019/04/27 全球购物
经典演讲稿开场白
2014/08/25 职场文书
家长通知书家长意见
2014/12/30 职场文书
市场总监岗位职责
2015/02/11 职场文书
活动费用申请报告
2015/05/15 职场文书
运动会通讯稿600字
2015/07/20 职场文书
JavaScript 对象创建的3种方法
2021/11/17 Javascript
Java使用HttpClient实现文件下载
2022/08/14 Java/Android