服务器之家:专注于服务器技术及软件下载分享
分类导航

Mysql|Sql Server|Oracle|Redis|MongoDB|PostgreSQL|Sqlite|DB2|mariadb|Access|数据库技术|

服务器之家 - 数据库 - Redis - redis中lua脚本使用教程

redis中lua脚本使用教程

2021-11-22 18:18huan1993 Redis

在使用redis的过程中,发现有些时候需要原子性去操作redis命令,而redis的lua脚本正好可以实现这一功能。这篇文章主要介绍了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>

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-- 输出用户传递进来的参数
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、执行命令

?
1
2
3
4
5
6
7
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脚本使用内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://www.cnblogs.com/huan1993/p/15472920.html

延伸 · 阅读

精彩推荐
  • Redis如何使用Redis锁处理并发问题详解

    如何使用Redis锁处理并发问题详解

    这篇文章主要给大家介绍了关于如何使用Redis锁处理并发问题的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用Redis具有一定的参考学习...

    haofly4522019-11-26
  • Redisredis缓存存储Session原理机制

    redis缓存存储Session原理机制

    这篇文章主要为大家介绍了redis缓存存储Session原理机制详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪...

    程序媛张小妍9252021-11-25
  • RedisRedis Template实现分布式锁的实例代码

    Redis Template实现分布式锁的实例代码

    这篇文章主要介绍了Redis Template实现分布式锁,需要的朋友可以参考下 ...

    晴天小哥哥2592019-11-18
  • RedisRedis集群的5种使用方式,各自优缺点分析

    Redis集群的5种使用方式,各自优缺点分析

    Redis 多副本,采用主从(replication)部署结构,相较于单副本而言最大的特点就是主从实例间数据实时同步,并且提供数据持久化和备份策略。...

    优知学院4082021-08-10
  • Redis详解三分钟快速搭建分布式高可用的Redis集群

    详解三分钟快速搭建分布式高可用的Redis集群

    这篇文章主要介绍了详解三分钟快速搭建分布式高可用的Redis集群,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,...

    万猫学社4502021-07-25
  • Redis关于Redis数据库入门详细介绍

    关于Redis数据库入门详细介绍

    大家好,本篇文章主要讲的是关于Redis数据库入门详细介绍,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览...

    沃尔码6982022-01-24
  • RedisRedis 6.X Cluster 集群搭建

    Redis 6.X Cluster 集群搭建

    码哥带大家完成在 CentOS 7 中安装 Redis 6.x 教程。在学习 Redis Cluster 集群之前,我们需要先搭建一套集群环境。机器有限,实现目标是一台机器上搭建 6 个节...

    码哥字节15752021-04-07
  • Redis《面试八股文》之 Redis十六卷

    《面试八股文》之 Redis十六卷

    redis 作为我们最常用的内存数据库,很多地方你都能够发现它的身影,比如说登录信息的存储,分布式锁的使用,其经常被我们当做缓存去使用。...

    moon聊技术8182021-07-26