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 相关文章推荐
Redis5之后版本的高可用集群搭建的实现
Apr 27 Redis
分布式锁为什么要选择Zookeeper而不是Redis?看完这篇你就明白了
May 21 Redis
Redis高级数据类型Hyperloglog、Bitmap的使用
May 24 Redis
redis哨兵常用命令和监控示例详解
May 27 Redis
浅析Redis Sentinel 与 Redis Cluster
Jun 24 Redis
Redis源码阅读:Redis字符串SDS详解
Jul 15 Redis
详解Redis在SpringBoot工程中的综合应用
Oct 16 Redis
分布式Redis Cluster集群搭建与Redis基本用法
Feb 24 Redis
分布式架构Redis中有哪些数据结构及底层实现原理
Mar 13 Redis
redis数据结构之压缩列表
Mar 21 Redis
Grafana可视化监控系统结合SpringBoot使用
Apr 19 Redis
Redis实战之Lettuce的使用技巧详解
Dec 24 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
域名查询代码公布
2006/10/09 PHP
最省空间的计数器
2006/10/09 PHP
dede3.1分页文字采集过滤规则详说(图文教程)
2007/04/03 PHP
使用PHP备份MYSQL数据的多种方法
2014/01/15 PHP
php用户登录之cookie信息安全分析
2016/05/13 PHP
Yii2主题(Theme)用法详解
2016/07/23 PHP
php读取XML的常见方法实例总结
2017/04/25 PHP
jQuery 隔行换色 支持键盘上下键,按Enter选定值
2009/08/02 Javascript
jquery 无限级联菜单案例分享
2013/03/26 Javascript
select标签模拟/美化方法采用JS外挂式插件
2013/04/01 Javascript
JS实现仿Windows7风格的网页右键菜单效果代码
2015/09/11 Javascript
JS非Alert实现网页右下角“未读信息”效果弹窗
2015/09/26 Javascript
详解JavaScript时间格式化
2015/12/23 Javascript
学习javascript面向对象 实例讲解面向对象选项卡
2016/01/04 Javascript
jQuery中fadein与fadeout方法用法示例
2016/09/16 Javascript
如何使用jquery实现文字上下滚动效果
2016/10/12 Javascript
vue图片加载与显示默认图片实例代码
2017/03/16 Javascript
jQuery取得元素标签名称小结(附代码)
2017/08/16 jQuery
Element-Ui组件 NavMenu 导航菜单的具体使用
2019/10/24 Javascript
详解小程序如何动态绑定点击的执行方法
2019/11/26 Javascript
在vue中使用vant TreeSelect分类选择组件操作
2020/11/02 Javascript
Python实现把utf-8格式的文件转换成gbk格式的文件
2015/01/22 Python
Python中为什么要用self探讨
2015/04/14 Python
详解python调度框架APScheduler使用
2017/03/28 Python
python+selenium实现京东自动登录及秒杀功能
2017/11/18 Python
实例讲解Python中浮点型的基本内容
2019/02/11 Python
pyqt实现.ui文件批量转换为对应.py文件脚本
2019/06/19 Python
Python使用matplotlib绘制圆形代码实例
2020/05/27 Python
python rolling regression. 使用 Python 实现滚动回归操作
2020/06/08 Python
财务总监管理职责范文
2014/03/09 职场文书
升旗仪式主持词
2014/03/19 职场文书
《槐乡五月》教学反思
2014/04/25 职场文书
班级文化建设标语
2014/06/23 职场文书
网吧七夕活动策划方案
2014/08/31 职场文书
幼儿园教学工作总结2015
2015/05/12 职场文书
浪漫的婚礼主持词
2015/06/30 职场文书