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 限制内存使用大小的实现
May 08 Redis
详解缓存穿透击穿雪崩解决方案
May 28 Redis
浅析Redis Sentinel 与 Redis Cluster
Jun 24 Redis
使用Redis实现实时排行榜功能
Jul 02 Redis
Redis三种集群模式详解
Oct 05 Redis
Window server中安装Redis的超详细教程
Nov 17 Redis
在Centos 8.0中安装Redis服务器的教程详解
Mar 21 Redis
源码分析Redis中 set 和 sorted set 的使用方法
Mar 22 Redis
Redis如何使用乐观锁(CAS)保证数据一致性
Mar 25 Redis
一文教你快速生成MySQL数据库关系图
Jun 28 Redis
基于redis+lua进行限流的方法
Jul 23 Redis
基于Redission的分布式锁实战
Aug 14 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调用MySQL的存储过程的实现代码
2008/08/12 PHP
php下通过IP获取地理位置的代码(小偷程序)
2011/06/09 PHP
使用php统计字符串中中英文字符的个数
2013/06/23 PHP
destoon实现VIP排名一直在前面排序的方法
2014/08/21 PHP
浅谈PHP接收POST数据方式
2015/06/05 PHP
round robin权重轮循算法php实现代码
2016/05/28 PHP
replace()方法查找字符使用示例
2013/10/28 Javascript
tangram框架响应式加载图片方法
2013/11/21 Javascript
js实现日历可获得指定日期周数及星期几示例分享(js获取星期几)
2014/03/14 Javascript
js的回调函数详解
2015/01/05 Javascript
JS修改iframe页面背景颜色的方法
2015/04/01 Javascript
jQuery实现新消息在网页标题闪烁提示
2015/06/23 Javascript
JS实现网页标题栏显示当前时间和日期的完整代码
2015/11/02 Javascript
jQuery设置Easyui校验规则(推荐)
2016/11/21 Javascript
jQuery实现切换隐藏与显示同时切换图标功能
2017/10/29 jQuery
Js面试算法详解
2018/04/08 Javascript
Vue 组件传值几种常用方法【总结】
2018/05/28 Javascript
vuex的数据渲染与修改浅析
2020/11/26 Vue.js
跟老齐学Python之集合的关系
2014/09/24 Python
详细讲解Python中的文件I/O操作
2015/05/24 Python
Python连接mysql数据库的正确姿势
2016/02/03 Python
Python 从列表中取值和取索引的方法
2018/12/25 Python
python远程邮件控制电脑升级版
2019/05/23 Python
python批量爬取下载抖音视频
2019/06/17 Python
使用pip安装python库的多种方式
2019/07/31 Python
对Django 转发和重定向的实例详解
2019/08/06 Python
windows10 pycharm下安装pyltp库和加载模型实现语义角色标注的示例代码
2020/05/07 Python
澳大利亚窗帘商店:Curtain Wonderland
2019/12/01 全球购物
校园报刊亭的创业计划书
2014/01/02 职场文书
高三地理教学反思
2014/01/11 职场文书
房屋出售协议书
2014/04/10 职场文书
党的群众路线教育实践活动领导班子整改措施
2014/09/30 职场文书
英文慰问信
2015/02/14 职场文书
人事任命书范本
2015/09/21 职场文书
linux下导入、导出mysql数据库命令的实现方法
2021/05/26 MySQL
浅谈Redis主从复制以及主从复制原理
2021/05/29 Redis