redis中lua脚本的简单使用
一、背景
在使用redis
的过程中,发现有些时候需要原子性
去操作redis命令,而redis的lua
脚本正好可以实现这一功能。比如: 扣减库存操作、限流操作等等。 redis的pipelining
虽然也可以一次执行一组命令,但是如果在这一组命令的执行过程中,需要根据上一步执行的结果做一些判断,则无法实现。
二、使用lua脚本
Redis中使用的是 Lua 5.1
的脚本规范,同时我们编写的脚本的时候,不需要定义 Lua 函数。同时也不能使用全局变量等等。
1、lua脚本的格式和注意事项
1、格式
> EVAL script numkeys key [key ...] arg [arg ...]
127.0.0.1:6379> eval "return {KEYS[1],ARGV[1],ARGV[2]}" 1 key1 arg1 arg2 1) "key1" 2) "arg1" 3) "arg2" 127.0.0.1:6379>
2、注意事项
Lua
脚本中的redis操作的key最好都是通过 KEYS
来传递,而不要写死。否则在Redis Cluster的情况下可能有问题.
1、好的写法
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],'zhangsan')" 1 username OK 127.0.0.1:6379> get username "zhangsan"
redis命令操作的key是通过KEYS获取的。
2、差的写法
127.0.0.1:6379> eval "return redis.call('set','username','zhangsan')" 0 OK 127.0.0.1:6379> get username "zhangsan"
redis命令操作的key是直接写死的。
2、将脚本加载到redis中
需求: 此处定义一个lua脚本,将输入的参数的值+1返回。
注意:
当我们把 lua脚本
加载到redis中,这个脚本并不会马上执行,而是会缓存起来,并且返回sha1
校验和,后期我们可以通过 EVALSHA
来执行这个脚本。
> 此处我们记住这个脚本加载后返回的hash值,在下一步执行的时候需要用到。
127.0.0.1:6379> script load "return tonumber(KEYS[1])+1" "ef424d378d47e7a8b725259cb717d90a4b12a0de" 127.0.0.1:6379>
3、执行lua脚本
1、通过eval执行
127.0.0.1:6379> eval "return tonumber(KEYS[1]) + 1" 1 100 (integer) 101 127.0.0.1:6379>
2、通过evalsha执行
ef424d378d47e7a8b725259cb717d90a4b12a0de
的值为上一步通过 script load
加载脚本后获取的。
127.0.0.1:6379> evalsha ef424d378d47e7a8b725259cb717d90a4b12a0de 1 100 (integer) 101 127.0.0.1:6379>
通过 evalsha
执行的好处是可以节省带宽。如果我们的lua脚本比较长,程序在执行的时候将lua脚本发送到redis服务器则可能耗费的带宽多,如果发送的是hash值的话,则耗费的带宽少。
4、判断脚本是否在redis服务器缓存中
127.0.0.1:6379> script load "return tonumber(KEYS[1])+1" "ef424d378d47e7a8b725259cb717d90a4b12a0de" 127.0.0.1:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de 1) (integer) 1 127.0.0.1:6379> script exists not-exists-sha1 1) (integer) 0 127.0.0.1:6379>
5、清空服务器上的脚本缓存
注意:
我们无法清除某一个脚本的缓存,只可以清楚所有的缓存,一般情况下没有必要清楚,因为即使有大量的脚本也不会太占用服务器内存。
127.0.0.1:6379> script load "return tonumber(KEYS[1])+1" "ef424d378d47e7a8b725259cb717d90a4b12a0de" 127.0.0.1:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de 1) (integer) 1 127.0.0.1:6379> script flush OK 127.0.0.1:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de 1) (integer) 0
6、杀死正在运行的脚本
127.0.0.1:6379> script kill
注意:
- 该命令只可以杀死正在运行的
只读脚本
。 - 对于修改了数据的脚本,无法使用此命令杀死,只能使用
shutdown nosave
命令。 - 脚本执行的
默认超时时间
为5分钟
,可以通过redis.conf
配置文件的lua-time-limit
配置项修改。 - 脚本即使到达了超时时间,也不会停止执行,因为这违反了lua脚本的原子性。
三、lua和redis数据类型转换
Lua
的数据类型和Redis
的数据类型存在一对一的转换关系,如果将Redis类型转换成Lua类型,然后在转换成Redis类型,那么结果和初试值是一致的。
1、类型转换
Redis to Lua conversion table.
- Redis integer reply -> Lua number
- Redis bulk reply -> Lua string
- Redis multi bulk reply -> Lua table (may have other Redis data types nested)
- Redis status reply -> Lua table with a single
ok
field containing the status - Redis error reply -> Lua table with a single
err
field containing the error - Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type
Lua to Redis conversion table.
- Lua number -> Redis integer reply (the number is converted into an integer)
- Lua string -> Redis bulk reply
- Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
- Lua table with a single
ok
field -> Redis status reply - Lua table with a single
err
field -> Redis error reply - Lua boolean false -> Redis Nil bulk reply.
2、额外的转换规则
- Lua的布尔类型,Lua的True会转换成Redis的1
3、3个重要规则
1. 数字类型
在Lua中,只有一个number
类型,整数和浮点数之间没有区别,如果我们在Lua中返回一个浮点数,实际返回的是一个整数,如果要返回浮点数,需要以字符串的方式返回。
127.0.0.1:6379> eval "return 3.98" 0 (integer) 3 127.0.0.1:6379> eval "return '3.98'" 0 "3.98"
2. lua数组存在nil
当 Redis 将 Lua 数组转换为 Redis 协议时,如果遇到 nil,则转换会停止。即 nil 后的值都不会返回。
127.0.0.1:6379> eval "return {1,2,'data',nil,'can not return value','vv'}" 0 1) (integer) 1 2) (integer) 2 3) "data" 127.0.0.1:6379>
3. Lua的Table类型包含建和值
出现这种情况返回的redis的是一个空数组
127.0.0.1:6379> eval "return {key1 ='value1',key2='value2'}" 0 (empty array) 127.0.0.1:6379>
四、lua脚本中输出日志
这个一般调试我们的脚本的时候比较有用。
redis.log(loglevel,message)
loglevel的取值范围:
redis.LOG_DEBUG
redis.LOG_VERBOSE
redis.LOG_NOTICE
redis.LOG_WARNING
举例:
五、一个简单限流的案例
1、需求
在 1s 之内,方法最大的并发只能是 5。
1s 和 5 当作参数传递。
2、实现步骤
1、编写lua脚本
-- 输出用户传递进来的参数 for i, v in pairs(KEYS) do redis.log(redis.LOG_NOTICE, "limit: key" .. i .. " = " .. v) end for i, v in pairs(ARGV) do redis.log(redis.LOG_NOTICE, "limit: argv" .. i .. " = " .. v) end -- 限流的key local limitKey = tostring(KEYS[1]) -- 限流的次数 local limit = tonumber(ARGV[1]) -- 多长时间过期 local expireMs = tonumber(ARGV[2]) -- 当前已经执行的次数 local current = tonumber(redis.call('get', limitKey) or '0') -- 设置一个断点 redis.breakpoint() redis.log(redis.LOG_NOTICE, "limit key: " .. tostring(limitKey) .. " 在[" .. tostring(expireMs) .. "]ms内已经访问了 " .. tostring(current) .. " 次,最多可以访问: " .. limit .. " 次") -- 限流了 if (current + 1 > limit) then return { true } end -- 未达到访问限制 -- 访问次数+1 redis.call("incrby", limitKey, "1") if (current == 0) then -- 设置过期时间 redis.call("pexpire", limitKey, expireMs) end return { false }
2、程序中执行lua脚本
完整代码: https://gitee.com/huan1993/spring-cloud-parent/tree/master/springboot/springboot-redis-lua
六、lua脚本的debug
当我们编写好了lua脚本后,如果在执行的过程中发生了错误,那么我们如何该如何解决呢?此处我们来了解下如何debug lua 脚本。
1、lua脚本中的几个小命令
在 脚本中打一个断点
redis.breakpoint()
2、断点调试
1、执行命令
redis-cli --ldb --eval limit.lua invoked , 1 1000 limit.lua 需要debug的lua文件 invoked 为传递到 lua 脚本中 KEYS 的值 1 和 1000 为传递到 lua 脚本中 ARGV 的值 , 分割 出 KEYS 和 ARGV 的值
2、一些debug指令
help
: 列出可用的debug指令s
或n
: 运行到当前行并停止 (此时当前行还未执行)c
:运行到下个断点,即运行到lua脚本中存在redis.breakpoint()
方法的地方list
:列出当前行周围的一些源码p
:打印出所有的 local 变量的值p <var>
:打印具体的某个 local 变量的值r
:执行 redis 命令
-- eg: r set key value r get key
3、debug运行结果
七、参考文档
</var>

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
私域管理平台,LinkWechat V1.5 版本发布
LinkWeChat 基于企业微信开放能力,不仅集成了企微强大的后台管理及基础的客户管理功能,而且提供了多种渠道、多个方式连接微信客户。并通过客情维系、聊天增强等灵活高效的客户运营模块,让客户与企业之间建立强链接,从而进一步通过多元化的营销工具,帮助企业提高客户运营效率,强化营销能力,拓展盈利空间。 此次LinkWechat V1.5版本更新内容如下: 1、系统界面全新设计,更加简洁好用; 2、客户管理再升级,一键查看重复客户; 3、新增客户画像侧边栏,精准运营客户; 3、优化社群运营,新客拉群、老客建群、关键词群、群 SOP 玩转社群营销; 预告:2.0版本预计11月份更新,2.0功能规划
- 下一篇
Java版流媒体编解码和图像处理(JavaCPP+FFmpeg)
欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java、Docker、Kubernetes、DevOPS等; FFmpeg、JavaCPP、JavaCV的关系 先简单的梳理一下FFmpeg、JavaCPP、JavaCV的关系: FFmpeg、OpenCV可以理解成C语言版的本地库(Native library),Java应用无法直接使用 JavaCPP将FFmpeg、OpenCV这些常用库做了包装(wrapper),使得Java应用也能使用这些Native API(JavaCPP的底层实现是JNI) 这些JavaCPP包装后的API,被JavaCV封装成了工具类(utility classes),这些工具类比原生API更简单易用 简单的说如下图所示,JavaCPP是Native API转Java API,JavaCV是Java API封装成工具类,这些工具类更加简单易用: 学习目的 欣宸的目标是学习和掌握JavaCV,而深入JavaCV内部去了解它用到的JavaCPP,就相当于打好基础,今后...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Linux系统CentOS6、CentOS7手动修改IP地址
- 设置Eclipse缩进为4个空格,增强代码规范
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Red5直播服务器,属于Java语言的直播服务器
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS6,CentOS7官方镜像安装Oracle11G