前言
最近在看redis的lua,看了官网资料和网上一些文章,整理了lua的相关内容,希望对大家有帮助。
目录
0. redis中运行lua的流程的正常流程
1.redis中的lua概要信息
1.1 lua中调用redis命令
1.2 redis数据结构与lua数据结构对应关系
1.3 EVAL和EVALSHA
1.4 脚本缓存
1.5 脚本命令
1.6 其他约定
1.6.1 全局变量保护
1.6.2 Select 使用
1.6.3 redis中lua脚本内置的lib
2.lua的脚本复制
2.1 whole scripts replication(仅脚本复制)
2.2 script effects replication(脚本影响复制Redis 3.2以后支持)
2.3 lua脚本中的可选复制命令
3. redis中lua脚本的debug
3.1 lua脚本中记录日志
3.2 Lua debugger 简要说明( Redis 3.2 提供)
3.2.1 LDB简介
3.2.2 快速入门
4. lua脚本执行超时处理
4.1 设置超时时间
4.2 脚本执行超时后处理方式
5. RedisTemplate调用lua脚本示例
6. 参考资料
0. redis中运行lua的流程的正常流程
1.redis中的lua概要信息
1.1lua中调用redis命令
在lua脚本中以2种方式调用redis的命令
lua中调用redis的方式
对异常处理的方式
redis.call
遇到异常将抛出lua error
redis.pcall
会将错误信息进行包装,以lua的table类型返回。
2个工具函数
redis.error_reply() 返回一个仅包含err元素的table
redis.status_reply()返回一个仅包含ok元素的table
1.2redis数据结构与lua数据结构对应关系
Redis数据结构
lua数据结构
integer
number
bulk
String
multi bulk
table
status
lua的table中有一个ok做对应
error
lua的table中有一个err做对应
Nil bulk, Nil multi bulk
lua的boolean的false
注意:
Lua boolean true 会变为Redis 中的integer 1.
Lua中的所有number类型的数据,均会变成redis中的integer,采用截取的方式。如果需要lua返回float类型,请使用string作为返回值。
Redis中没有对nil进行转换的简单方法,如果lua的table中的元素有nil,redis无法进行转换。
举例说明:
1.3.EVAL和EVALSHA
redis中2个命令执行lua脚本,EVAL和EVALSHA 。
EVAL 命令要求每次都发送脚本,带宽占用大。
EVALSHA命令为了减少带宽占用,提高效率而出现
EVALSHA 基本与EVAL命令一致,但是第一个参数是lua脚本的sha1值。
如果redis没有该sha1值对应的脚本,会抛出异常。
注意:pipeline中的EVALSHA
需要注意在pipeline中,建议如下:
使用EVAL,保证不会出现脚本不存在的情况
如果一定要使用EVALSHA,请先判断脚本是否存在,调用 SCRIPT EXISTS判断,不存在使用 SCRIPT LOAD 在pipeline中开头时使用。
1.4脚本缓存
redis会缓存执行过的脚本,如果1个redis实例执行EVAL命令成功,所有后续的EVALSHA命令也会成功。
lua脚本相对Redis的数据来说,相对较小,可以忽略其内存占用。
SCRIPT FLUSH可以将redis缓存的脚本都移除。
一个持久化的redis和一个持久化的redis的连接,可以保证lua脚本发送过一次后,始终存在于内存中。
后续的EVALSHA执行都会成功。
1.5脚本命令
SCRIPT FLUSH
唯一可以让redis刷新脚本缓存的命令,一般用于云环境或者测试脚本时。
SCRIPT EXISTS sha1 sha2 ... shaN
判断给的的SHA1对应的脚本是否存在,返回一个列表按顺序对应之前的sha1值,列表元素1表示存在,0表示不存在。
SCRIPT LOAD script
向redis服务器注册lua脚本,确保EVALSHA使用正常。
SCRIPT KILL
没有修改数据的情况下,可以用这个命令中断脚本运行。
1.6其他约定
1.6.1 全局变量保护
Redis的lua脚本不允许声明全局变量,防止lua脚本泄漏数据,并保证AOF和同步从服务器能够正确运行,
如果脚本需要维持状态,可以将状态写入redis中。
Lua脚本可以使用2个全局变量KEYS和ARGV,这两个全局变量用于接收传递的KEY和args。
1.6.2 Select 使用
Redis的2.8.12版本后,select命令仅仅影响当前的脚本执行。
1.6.3 redis中lua脚本内置的lib
base lib table lib string lib.
math lib struct lib cjson lib.
cmsgpack lib bitop lib redis.sha1hex function.
redis.breakpoint redis.debug (用于debug)
2.lua的脚本复制
2.1whole scripts replication(仅脚本复制,默认方式)
redis会将lua脚本复制到从服务器和持久化AOF文件中,因为发送脚本比发送一堆命令更高效。这种模式成为whole scripts replication(仅脚本复制)。
该模式的缺点:
Lua不导出命令来访问系统时间或其他外部状态
RANDOMKEY, SRANDMEMBER, TIME这几个函数在修改数据的脚本中不能使用,只能用于只读数据的脚本中。
注意:
SINTER;SUNION;SDIFF
SMEMEBERS;HKEYS;HVALS
KEYS
这几个命令具有不确定性因为redis的存储是乱序的,但是redis实现了默认按字典排序,保证每次lua脚本访问一致。
2.2script effects replication(脚本影响复制Redis 3.2以后支持)
Redis将lua脚本中的对数据的变更记录后,生成MULTI / EXEC 的事务发送到从服务器和AOF文件中。
使用redis.replicate_commands() 进行开启,返回true,表示脚本影响复制开启,否则表示未开启。
使用范围:
脚本运行缓慢,但是脚本的修改较少。可以快速记录到从服务器和AOF文件中。
当脚本影响复制开启后,非确定性脚本控制会关闭,可以随意使用 RANDOMKEY, TIME。
2.3lua脚本中的可选复制命令
该特性不推荐使用,具备一定风险
需要开启script effects replication,
redis.set_repl(redis.REPL_ALL) -- AOF和从服务器.
redis.set_repl(redis.REPL_AOF) -- 仅AOF.
redis.set_repl(redis.REPL_SLAVE) --仅从服务器.
redis.set_repl(redis.REPL_NONE) -- 不进行任何复制。
3. redis中lua脚本的debug
3.1lua脚本中记录日志
redis.log(loglevel,message)
loglevel 如下:
redis.LOG_DEBUG
redis.LOG_VERBOSE
redis.LOG_NOTICE
redis.LOG_WARNING
message仅仅接收String类型
举例:
redis.log(redis.LOG_WARNING,"Something is wrong with this script.")
3.2 Lua debugger 简要说明( Redis 3.2 提供)
3.2.1LDB简介
LDB使用的是 server-client模式,所以它是一个远程调试器.
Redis server 作为一个调试服务器,默认调试client端是redis-cli,其他client端可以根据redis的协议进行扩展。
默认情况下,每一个调试进程都是一个单独的进程。这意味着在调试一个Lua脚本的同时,Redis不会阻塞,可以进行开发或者并行调试其他脚本。这也意味着调试进程中的所有更改均会回退(roll back),这保证使用同一份数据多次调试lua脚本不会存在问题。
redis也提供了同步模式,该模式下产生的变化将会保留,并会阻塞其他请求。
3.2.3快速入门
注意: 请不要再生环境产的Redis上进行lua脚本调试,请使用开发环境的Redis进行调试。注意同步调试(非默认)会阻塞所有的请求,可以使用redis.breakpoint()的方式动态设置断点。
异步调试方式:
./redis-cli --ldb --eval /tmp/script.lua mykey somekey , arg1 arg2
同步模式:
./redis-cli --ldb-sync-mode --eval /tmp/script.lua
会接手如下3个参数
quit --停止debug,返回redis-cli
restart --调试进程重启,重新加载被调试的脚步
help --显示帮助信息
默认情况下是步进模式。
help显示列表如下
调试命令[缩写]
注释
[h]elp
显示这个列表
[s]tep
一步一步的进行调试,步进模式
[n]ext
下一步.
[c]continue
跳转到下一个断点
[l]list
显示当前行
[l]list [line]
显示第几行代码.line = 0 显示当前行.
[l]list [line] [ctx]
显示当前行,上下指定[ctx]的行数的代码
[w]hole
显示所有代码.
[p]rint
打印所有局部变量.
[p]rint
打印指定变量,也可打印全局的KEYS和ARGV.
[b]reak
显示所有断点.
[b]reak
在指定行添加断点.
[b]reak -
移除指定行的断点.
[b]reak 0
移除所有断点.
[t]race
显示调用栈.
[e]eval
执行一些Lua的代码(在不同的调用框架中).
[r]edis
执行一个redis命令.
[m]axlen [len]
设置记录Redis响应与Lua变量dumps 的长度设置为0表示没有限制
[a]abort
停止脚步.同步模式下改变的数据将保留
debug示例:
我自己写了一个redislock的lua脚本,仅仅为了进行调试。
1.进入redis的src目录后执行如下语句,这里我们采用默认的调试方式(可以运行的前提是,redis版本3.2+,/usr/luascript中存在lock.lua脚本)
./redis-cli --ldb --eval /usr/luascript/lock.lua
会出现如下图的信息:
2.之后 我们输入whole或w线上脚本的所有语句
这个脚本比较简单就4句话,
3.之后我们在 第一行和第三行加入断点,数据b 1 3,之后入下图所示行号前加入了#号表示有断点
4.之后我们运行到下一个断点,输入c
这里显示参数必须是strings或者integers,实际上是我之前5.调试脚本的时候没有输入参数操作的,没有参数是KEYS和ARGV全局变量的值是nil,所以会报错误。
5. 我们退出调试进程,调试的语句为:
./redis-cli --ldb --eval /usr/luascript/lock.lua WWW , 12 1
注意 ,前后有空格,并重复2,3步后,
6.进行跳转到下一个断点,输入c
7.在这里我们看下KEYS和ARGV的值,输入p KEYS 和p ARGV
8.之后重复按c直到最后一步,会显示运行结果
4.lua脚本超时处理
4.1设置超时时间
在redis.conf中设置lua-time-limit 参数来自定义lua脚本的超时时间,单位是毫秒,默认是5000ms,不建议修改改值,目前5s的默认值已经非常大了,理论上一个lua脚本的执行时间应该是毫秒级别的。
4.2脚本执行超时后处理方式
当一个脚本超过时间现在,redis不会终止lua脚本,会进行如下操作:
日志中记录该脚本执行时间过长
对数据进行更改的lua脚本仅能被SHUTDOWN NOSAVE命令处理,该命令会丢失最近一次持久化后的数据。
没有对数据进行更改的lua脚本可以被 SCRIPT KILL处理。
开始接受其他命令,但是对于普通命令均会返回一个busy的error,仅仅会处理 SCRIPT KILL and SHUTDOWN NOSAVE 命令。
5.RedisTemplate如何调用lua脚本示例
1.先将脚本写入XXX.lua文件中,
2.之后将XXX.lua文件放入src/main/resources/lua中,如下图
RedisTemplate调用execute方法,第一个参数是脚本对象,第二个参数是个列表对应乱脚本中的KEYS,之后的可变参数对应lua脚本中的ARGV。具体操作如下:
@Service
public class LuaService {
@Autowired
StringRedisTemplate template;
public void runLua() throws IOException{
//1,创建默认脚本对象
DefaultRedisScript script=new DefaultRedisScript();
//2.设置默认脚本数据源
ScriptSource scriptSource = new ResourceScriptSource(
new ClassPathResource("/lua/lock1.lua"));
script.setScriptSource(scriptSource);
//3.设置Lua脚本中的KEYS对象
Listkeys=new ArrayList();
keys.add("WWWW");
template.execute(script, keys, "1200","1");
}
}
6.参考资料