redis中lua脚本的简单使用(redisson lua脚本)
mhr18 2024-11-05 10:24 52 浏览 0 评论
一、背景
在使用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运行结果
七、参考文档
- https://redis.io/topics/ldb
- https://redis.io/commands/eval
相关推荐
- 【推荐】一个开源免费、AI 驱动的智能数据管理系统,支持多数据库
-
如果您对源码&技术感兴趣,请点赞+收藏+转发+关注,大家的支持是我分享最大的动力!!!.前言在当今数据驱动的时代,高效、智能地管理数据已成为企业和个人不可或缺的能力。为了满足这一需求,我们推出了这款开...
- Pure Storage推出统一数据管理云平台及新闪存阵列
-
PureStorage公司今日推出企业数据云(EnterpriseDataCloud),称其为组织在混合环境中存储、管理和使用数据方式的全面架构升级。该公司表示,EDC使组织能够在本地、云端和混...
- 对Java学习的10条建议(对java课程的建议)
-
不少Java的初学者一开始都是信心满满准备迎接挑战,但是经过一段时间的学习之后,多少都会碰到各种挫败,以下北风网就总结一些对于初学者非常有用的建议,希望能够给他们解决现实中的问题。Java编程的准备:...
- SQLShift 重大更新:Oracle→PostgreSQL 存储过程转换功能上线!
-
官网:https://sqlshift.cn/6月,SQLShift迎来重大版本更新!作为国内首个支持Oracle->OceanBase存储过程智能转换的工具,SQLShift在过去一...
- JDK21有没有什么稳定、简单又强势的特性?
-
佳未阿里云开发者2025年03月05日08:30浙江阿里妹导读这篇文章主要介绍了Java虚拟线程的发展及其在AJDK中的实现和优化。阅前声明:本文介绍的内容基于AJDK21.0.5[1]以及以上...
- 「松勤软件测试」网站总出现404 bug?总结8个原因,不信解决不了
-
在进行网站测试的时候,有没有碰到过网站崩溃,打不开,出现404错误等各种现象,如果你碰到了,那么恭喜你,你的网站出问题了,是什么原因导致网站出问题呢,根据松勤软件测试的总结如下:01数据库中的表空间不...
- Java面试题及答案最全总结(2025版)
-
大家好,我是Java面试陪考员最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试题及答案。涉及的内容非常全面,包含:Spring、MySQL、JVM、Redis、Linux、Sprin...
- 数据库日常运维工作内容(数据库日常运维 工作内容)
-
#数据库日常运维工作包括哪些内容?#数据库日常运维工作是一个涵盖多个层面的综合性任务,以下是详细的分类和内容说明:一、数据库运维核心工作监控与告警性能监控:实时监控CPU、内存、I/O、连接数、锁等待...
- 分布式之系统底层原理(上)(底层分布式技术)
-
作者:allanpan,腾讯IEG高级后台工程师导言分布式事务是分布式系统必不可少的组成部分,基本上只要实现一个分布式系统就逃不开对分布式事务的支持。本文从分布式事务这个概念切入,尝试对分布式事务...
- oracle 死锁了怎么办?kill 进程 直接上干货
-
1、查看死锁是否存在selectusername,lockwait,status,machine,programfromv$sessionwheresidin(selectsession...
- SpringBoot 各种分页查询方式详解(全网最全)
-
一、分页查询基础概念与原理1.1什么是分页查询分页查询是指将大量数据分割成多个小块(页)进行展示的技术,它是现代Web应用中必不可少的功能。想象一下你去图书馆找书,如果所有书都堆在一张桌子上,你很难...
- 《战场兄弟》全事件攻略 一般事件合同事件红装及隐藏职业攻略
-
《战场兄弟》全事件攻略,一般事件合同事件红装及隐藏职业攻略。《战场兄弟》事件奖励,事件条件。《战场兄弟》是OverhypeStudios制作发行的一款由xcom和桌游为灵感来源,以中世纪、低魔奇幻为...
- LoadRunner(loadrunner录制不到脚本)
-
一、核心组件与工作流程LoadRunner性能测试工具-并发测试-正版软件下载-使用教程-价格-官方代理商的架构围绕三大核心组件构建,形成完整测试闭环:VirtualUserGenerator(...
- Redis数据类型介绍(redis 数据类型)
-
介绍Redis支持五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)及Zset(sortedset:有序集合)。1、字符串类型概述1.1、数据类型Redis支持...
- RMAN备份监控及优化总结(rman备份原理)
-
今天主要介绍一下如何对RMAN备份监控及优化,这里就不讲rman备份的一些原理了,仅供参考。一、监控RMAN备份1、确定备份源与备份设备的最大速度从磁盘读的速度和磁带写的带度、备份的速度不可能超出这两...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- oracle位图索引 (63)
- oracle批量插入数据 (62)
- oracle事务隔离级别 (53)
- oracle 空为0 (50)
- oracle主从同步 (55)
- oracle 乐观锁 (51)
- redis 命令 (78)
- php redis (88)
- redis 存储 (66)
- redis 锁 (69)
- 启动 redis (66)
- redis 时间 (56)
- redis 删除 (67)
- redis内存 (57)
- redis并发 (52)
- redis 主从 (69)
- redis 订阅 (51)
- redis 登录 (54)
- redis 面试 (58)
- 阿里 redis (59)
- redis 搭建 (53)
- redis的缓存 (55)
- lua redis (58)
- redis 连接池 (61)
- redis 限流 (51)