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 arg21) "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 usernameOK127.0.0.1:6379> get username"zhangsan"

redis命令操作的key是通过KEYS获取的。

2、差的写法

代码如下:

127.0.0.1:6379> eval "return redis.call('set','username','zhangsan')" 0OK127.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) 101127.0.0.1:6379>

2、通过evalsha执行

ef424d378d47e7a8b725259cb717d90a4b12a0de的值为上一步通过 script load加载脚本后获取的。

代码如下:

127.0.0.1:6379> evalsha ef424d378d47e7a8b725259cb717d90a4b12a0de 1 100(integer) 101127.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 ef424d378d47e7a8b725259cb717d90a4b12a0de1) (integer) 1127.0.0.1:6379> script exists not-exists-sha11) (integer) 0127.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 ef424d378d47e7a8b725259cb717d90a4b12a0de1) (integer) 1127.0.0.1:6379> script flushOK127.0.0.1:6379> script exists ef424d378d47e7a8b725259cb717d90a4b12a0de1) (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 Luaconversion table.

Redis integer reply -> Lua numberRedis bulk reply -> Lua stringRedis multi bulk reply -> Lua table (may have other Redis data types nested)Redis status reply -> Lua table with a singleokfield containing the statusRedis error reply -> Lua table with a singleerrfield containing the errorRedis 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 replyLua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)Lua table with a singleokfield -> Redis status replyLua table with a singleerrfield -> Redis error replyLua 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) 3127.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'}" 01) (integer) 12) (integer) 23) "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_DEBUGredis.LOG_VERBOSEredis.LOG_NOTICEredis.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)endfor i, v in pairs(ARGV) do redis.log(redis.LOG_NOTICE, "limit: argv" .. i .. " = " .. v)end-- 限流的keylocal 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-- 未达到访问限制-- 访问次数+1redis.call("incrby", limitKey, "1")if (current == 0) then -- 设置过期时间 redis.call("pexpire", limitKey, expireMs)endreturn { 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 1000limit.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运行结果

七、参考文档https://redis.io/topics/ldbhttps://redis.io/commands/eval

(0)

相关推荐

  • redis中lua脚本的简单使用

    一.背景 在使用redis的过程中,发现有些时候需要原子性去操作redis命令,而redis的lua脚本正好可以实现这一功能.比如: 扣减库存操作.限流操作等等. redis的pipelining虽然也可以一次执行一组命令,但是如果在这一组命令的执行过程中,需要根据上一步执行的结果做一些判断,则无法实现. 二.使用lua脚本 Redis中使用的是 Lua 5.1 的脚本规范,同时我们编写的脚本的时候,不需要定义 Lua 函数.同时也不能使用全局变量等等. 1.lua脚本的格式和注意事项 1.格式

  • redis中lua脚本使用教程

    目录 一.背景 二.使用lua脚本 三.lua和redis数据类型转换 四.lua脚本中输出日志 五.一个简单限流的案例 六.lua脚本的debug 七.参考文档 一.背景 在使用redis的过程中,发现有些时候需要原子性去操作redis命令,而redis的lua脚本正好可以实现这一功能.比如: 扣减库存操作.限流操作等等. redis的pipelining虽然也可以一次执行一组命令,但是如果在这一组命令的执行过程中,需要根据上一步执行的结果做一些判断,则无法实现. 二.使用lua脚本 Redi

  • 详解Redis中Lua脚本的应用和实践

    引言 前段时间组内有个投票的产品,上线前考虑欠缺,导致被刷票严重.后来,通过研究,发现可以通过 redis lua 脚本实现限流,这里将 redis lua 脚本相关的知识分享出来,讲的不到位的地方还望斧正. redis lua 脚本相关命令 这一小节的内容是基本命令,可粗略阅读后跳过,等使用的时候再回来查询 redis 自 2.6.0 加入了 lua 脚本相关的命令,EVAL.EVALSHA.SCRIPT EXISTS.SCRIPT FLUSH.SCRIPT KILL.SCRIPT LOAD,

  • Redis中lua脚本实现及其应用场景

    目录 1. Redis Lua脚本概述 2. Redis Lua脚本的优势 3. Redis Lua脚本的应用场景 4. Redis Lua脚本的使用方法 5. java中使用redis的lua脚本 5.1. 添加Redis依赖 在pom.xml中添加以下依赖: 5.2. 配置Redis连接信息 在application.properties中添加以下配置: 5.3. 定义Redis Lua脚本 5.4. 实现RedisService 5.5. 编写Redis Lua脚本 5.6. 测试Redi

  • Redis中Lua脚本的使用和设置超时

    目录 EVAL命令简介 eval格式 特性 执行流程 关于脚本超时 SCRIPT KILL 命令 SHUTDOWN NOSAVE 命令 参考 Redis提供了Lua脚本功能来让用户实现自己的原子命令,但也存在着风险,编写不当的脚本可能阻塞线程导致整个Redis服务不可用. 本文将介绍Redis中Lua脚本的基本用法,以及脚本超时导致的问题和处理方式. EVAL命令简介 eval格式 Redis 提供了命令EVAL来执行Lua脚本,格式如下 EVAL script numkeys key [key

  • Redis调用Lua脚本及使用场景快速掌握

    目录 一.阅读本文前置条件 二.为什么需要Lua脚本 三.学点Lua语法 3.1.一个简单的例子 3.2.仔细看下Lua脚本里的内容 3.3.复杂点的例子 四.Lua脚本预加载 五.一个修改JSON数据的例子? 六.总结 一.阅读本文前置条件 可以遵循这个链接中的方法在操作系统上安装 Redis 如果你对redis命令不熟悉,查看<Redis 命令引用> 二.为什么需要Lua脚本 简而言之:Lua脚本带来性能的提升. 很多应用的服务任务包含多步redis操作以及使用多个redis命令,这时你可

  • Redis执行Lua脚本的好处与示例代码

    前言 Redis从2.6版本开始引入对Lua脚本的支持,通过在服务器中嵌入Lua环境,Redis客户端可以使用Lua脚本,直接在服务端原子的执行多个Redis命令. 其中,使用EVAL命令可以直接对输入的脚本进行求值: redis>EVAL "return 'hello world'" 0 "hello world" 使用脚本的好处如下: 1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成.使用脚本,减少

  • redis执行lua脚本的实现方法

    目录 1. 语法格式 2.类型转换 3.lua脚本 3.1 script命令 3.2 脚本原子性 3.3 脚本缓存和EVALSHA 3.4 全局变量保护 3.5 日志记录 从redis 2.6.0版本开始,redis内置了Lua解释器,并提供了eval命令来解析Lua脚本求值. 1. 语法格式 语法: eval script numkeys keys args 参数: eval - redis提供解析lua脚本的命令          script - lua脚本           numke

  • Redis中五种数据类型简单操作

    Redis中五种数据类型简单操作 提出问题 Redis五种数据类型的简单增删改查命令??? 解决问题 假设你已经安装Redis服务器: 假设你已经打开Redis cli命令行工具: 假设你对Redis有所了解: Redis简单增删改查例子 例一:字符串的增删改查 #增加一个key为ay_key的值 127.0.0.1:6379> set ay_key "ay" OK #查询ay_key的值 127.0.0.1:6379> get ay_key "ay"

  • redis通过lua脚本,获取满足key pattern的所有值方式

    我们知道,redis提供了keys命令去获取所有满足格式的key,如我们键入命令 keys "user*" 将得到所有以user开头的key 然后执行 mget命令可以获取多个key的值,如 但如果满足条件的key过多,我们要将所有key拿到,再用mget去拿到所有值则为相对比较麻烦,因此可以借助xargs redis-cli keys "user*"|xargs redis-cli mget获取到所有key的值 也可以执行lua脚本local keys = red

随机推荐