redis中lua脚本使用教程


Posted in Redis onNovember 01, 2021

一、背景

在使用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>

redis中lua脚本使用教程

2、注意事项

Lua脚本中的redis操作的key最好都是通过 KEYS来传递,而不要写死。否则在Redis Cluster的情况下可能有问题.

redis中lua脚本使用教程

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

redis中lua脚本使用教程

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 Luaconversion 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 Redisconversion 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、额外的转换规则

  1. 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

举例:

redis中lua脚本使用教程

五、一个简单限流的案例

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脚本

redis中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指令
  • sn: 运行到当前行并停止 (此时当前行还未执行)
  • c:运行到下个断点,即运行到lua脚本中存在 redis.breakpoint()方法的地方
  • list:列出当前行周围的一些源码
  • p:打印出所有的 local 变量的值
  • p <var>:打印具体的某个 local 变量的值
  • r:执行 redis 命令

-- eg:
r set key value
r get key

3、debug运行结果

redis中lua脚本使用教程

七、参考文档

https://redis.io/topics/ldb

https://redis.io/commands/eval

到此这篇关于redis中lua脚本的简单使用的文章就介绍到这了,更多相关redis中lua脚本使用内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木!

Redis 相关文章推荐
在K8s上部署Redis集群的方法步骤
Apr 27 Redis
Redis5之后版本的高可用集群搭建的实现
Apr 27 Redis
使用Redis实现秒杀功能的简单方法
May 08 Redis
Redis Cluster 集群搭建你会吗
Aug 04 Redis
Redis入门教程详解
Aug 30 Redis
Redis命令处理过程源码解析
Feb 12 Redis
在Centos 8.0中安装Redis服务器的教程详解
Mar 21 Redis
Redis 操作多个数据库的配置的方法实现
Mar 23 Redis
Redis中key的过期删除策略和内存淘汰机制
Apr 12 Redis
详解Redis的三种常用的缓存读写策略步骤
May 06 Redis
Redis基本数据类型Zset有序集合常用操作
Jun 01 Redis
Redis实战之Lettuce的使用技巧详解
Dec 24 Redis
Redis高并发防止秒杀超卖实战源码解决方案
Redis的字符串是如何实现的
SpringBoot集成Redis的思路详解
详解redis在微服务领域的贡献
详解Redis在SpringBoot工程中的综合应用
Oct 16 #Redis
Redis三种集群模式详解
浅谈Redis的keys命令到底有多慢
You might like
使用php判断服务器是否支持Gzip压缩功能
2013/09/24 PHP
php中memcache 基本操作实例
2015/05/17 PHP
PHP 读取文本文件内容并分页显示
2016/01/02 PHP
PHP PDOStatement::fetch讲解
2019/01/31 PHP
Avengerls vs Newbee BO3 第一场2.18
2021/03/10 DOTA
常用的javascript function代码
2008/05/23 Javascript
jQuery ajax 路由和过滤器使用说明
2011/08/02 Javascript
比较新旧两个数组值得增加和删除的JS代码
2013/10/30 Javascript
JavaScript设计模式之适配器模式介绍
2014/12/28 Javascript
jQuery实现按键盘方向键翻页特效
2015/03/18 Javascript
10个很棒的jQuery代码片段
2015/09/24 Javascript
JSP基于Bootstrap分页显示实例解析
2016/06/12 Javascript
Javascript实现页面滚动时导航智能定位
2017/05/06 Javascript
layui实现根据table数据判断按钮显示情况的方法
2019/09/26 Javascript
详解Vue串联过滤器的使用场景
2020/04/30 Javascript
js实现幻灯片轮播图
2020/08/14 Javascript
JavaScript实现网页下拉菜单效果
2020/11/20 Javascript
基于Python实现一个简单的银行转账操作
2016/03/06 Python
分享一下如何编写高效且优雅的 Python 代码
2017/09/07 Python
django定期执行任务(实例讲解)
2017/11/03 Python
Python解决走迷宫问题算法示例
2018/07/27 Python
对python GUI实现完美进度条的示例详解
2018/12/13 Python
python实现二维数组的对角线遍历
2019/03/02 Python
Django REST Framework序列化外键获取外键的值方法
2019/07/26 Python
Python BeautifulReport可视化报告代码实例
2020/04/13 Python
美国和加拿大房车出售在线分类广告:RVT.com
2018/04/23 全球购物
泰国最新活动和优惠:Megatix
2020/05/07 全球购物
《自然之道》教学反思
2014/02/11 职场文书
房地产公司见习自我鉴定
2014/04/28 职场文书
初婚未育证明样本
2015/06/18 职场文书
重阳节主题班会
2015/08/17 职场文书
少先队大队委竞选口号
2015/12/25 职场文书
公文格式,规则明细(新手收藏)
2019/07/23 职场文书
python中使用 unittest.TestCase单元测试的用例详解
2021/08/30 Python
Vue提供的三种调试方式你知道吗
2022/01/18 Vue.js
Apache Kafka 分区重分配的实现原理解析
2022/07/15 Servers