从redis 2.6.0版本开始,redis内置了Lua解释器,并提供了eval命令来解析Lua脚本求值。
1. 语法格式
语法: eval script numkeys keys args
参数: eval — redis提供解析lua脚本的命令
script — lua脚本
numkeys — 指定键名参数集(keys)的个数
keys — 键名参数集,通过全局变量KEYS数组表示,起始下标为1
args — 键值参数集,通过全局变量ARGV数组表示,起始下标为1
描述: EVAL命令的语义要求字面量不要直接写在lua脚本中,推荐使用变量来定义lua脚本,并将字面量放在键名参数集keys和键值参数集args中,通过全局变量KEYS和ARGV来获取,这样做的好处是可缓存!在lua脚本中,可以使用两个函数来执行redis命令,分别是:redis.call()和redis.pcall()
例子: 使用lua脚本执行 set key val
127.0.0.1:6379:0>eval "return redis.call(‘set‘,KEYS[1],ARGV[1])" 1 key1 val1 "OK"
使用lua脚本执行 get key
127.0.0.1:6379:0>eval "return redis.call(‘get‘,KEYS[1])" 1 key1 "val1"
使用lua脚本执行 mset key1 val1 key2 val2
127.0.0.1:6379:0>eval "return redis.call(‘mset‘,KEYS[1],ARGV[1],KEYS[2],ARGV[2])" 2 key1 key2 val1 val2 "OK"
使用lua脚本执行 mget key1 key2
127.0.0.1:6379:0>eval "return redis.call(‘mget‘,KEYS[1],KEYS[2])" 2 key1 key2 1) "val1" 2) "val2"
错误处理:
上面说过,在lua脚本中可以使用call()和pcall()来执行redis脚本,这两个函数的效果是一模一样的,唯一区别就是它们对于错误处理的不同:
①redis.call()在执行命令中发生错误,脚本会停止执行,返回一个脚本错误,错误的输出信息会说明错误造成的原因:
②redis.pcall()执行命令出错时将捕获错误并返回表示错误的Lua表类型
127.0.0.1:6379:0>eval "return redis.call(‘lrange‘,‘list‘,‘0‘,‘2‘)" 0 1) "3" 2) "2" 3) "1"
127.0.0.1:6379:0>eval "return redis.call(‘get‘,‘list‘)" 0 "ERR Error running script (call to f_ba3d4ebf4203769966ef6de3b9450e9ff4436115): @user_script:1: WRONGTYPE Operation against a key holding the wrong kind of value "
127.0.0.1:6379:0>eval "return redis.pcall(‘get‘,‘list‘)" 0 "WRONGTYPE Operation against a key holding the wrong kind of value"
2.类型转换
当 Lua 通过 call() 或 pcall() 函数执行 Redis 命令的时候,命令的返回值会被转换成 Lua 数据结构;同样地,当Lua脚本在 redis内置解释器里运行时,Lua的返回值也会被转换成Redis类型,然后由EVAL将值返回客户端。
lua类型与redis类型之间存在一一转换的关系:
redis -> lua
redis类型 | lua类型 | 描述 |
redis_integer | lua_number | redis整数转为lua数字 |
redis_bulk | lua_string | redis bulk回复转为lua字符串 |
redis_multi bulk | Lua_table | redis 多条bulk回复转为Lua 表 |
redis_status | lua_table | redis状态回复转为lua表,表内ok域包含状态信息 |
redis_error | lua_table | redis错误回复转为lua表,表内的err域包含错误信息 |
redis_nil、 redis_multi nil |
lua_boolean_false | redis的nil回复和nil多条回复转为lua的布尔值false |
lua -> redis
lua类型 |
redis类型 |
描述 |
lua_number |
redis_integer | lua数字转为redis整数 |
lua_string | redis_bluk | lua字符串转为redis bulk回复 |
lua_table、 lua_array |
redis_multi bulk | lua表(数组)转为redis多条bulk回复 |
lua_table_ok | redis status | 一个带单个ok域的lua表,转为redis
状态回复 |
lua_table_err | redis_error | 一个带单个err域的lua表,转为redis
错误回复 |
lua_boolean_false | redis nil | lua布尔值false转为redis的nil回复 |
lua_boolean_true | redis_integer_1 | lua布尔值true转为redis的1回复 |
3、script和evalsha
script命令:
redis提供了以下几个script命令,用于对于脚本子系统进行控制:
script flush:清除所有的脚本缓存
script load:将脚本装入脚本缓存,不立即运行并返回其校验和
script exists:根据指定脚本校验和,检查脚本是否存在于缓存
script kill:杀死当前正在运行的脚本(防止脚本运行缓存,占用内存)
127.0.0.1:6379:0>script load "return redis.call(‘get‘,KEYS[1])" "fd758d1589d044dd850a6f05d52f2eefd27f033f" 127.0.0.1:6379:0>script exists fd758d1589d044dd850a6f05d52f2eefd27f033f 1) "1" 127.0.0.1:6379:0>script kill "NOTBUSY No scripts in execution right now." 127.0.0.1:6379:0>script flush "OK"
evalsha命令:
redis有一个内部的脚本缓存机制,它不会每次都重新编译脚本,反倒是它会将所有运行过的脚本永久保存在脚本缓存中(因为redis发现脚本体积非常小,即使量很大,甚至经常修改,储存这些脚本的内存也是微不足道的)。清空脚本缓存只有唯一一个方式,就是执行script flush命令。使用eval命令执行脚本时,每次都要发送脚本主体,如果脚本足够复杂,这会付出无谓的网络带宽。redis基于对lua的缓存,它实现了evalsha命令。
evalsha命令和eval命令效果一样,都是解释lua脚本执行,但是evalsha命令的第一个参数不是脚本主体,而是脚本的SHAI校验和,这个校验和可以通过script load命令得到。evalsha命令执行过程分两步:
①如果redis服务器保存了给定SHA1校验和所指定的脚本,就会执行该脚本
②如果redis服务器没保存给定SHA1校验和所指定的脚本,它就会返回一个特殊错误,告知客户端使用eval命令去执行
127.0.0.1:6379:0>script load "return redis.call(‘get‘,KEYS[1])" "fd758d1589d044dd850a6f05d52f2eefd27f033f" 127.0.0.1:6379:0>evalsha fd758d1589d044dd850a6f05d52f2eefd27f033f 1 key1 "val1"
4、脚本原子性
redis使用单个lua解释器去运行所有脚本,并且保证脚本会以原子性的方式去执行,意味着当某个脚本在运行时,不会有其它脚本或者redis命令被执行!所以,如果当前脚本运行很慢,服务器可能会因为正忙而无法执行命令,如:
127.0.0.1:6379> eval "while 1 do end" 0
127.0.0.1:6379> eval "return 1" 0 (error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
每个脚本都有一个最大执行时间限制,默认值是5s。最大执行时间的长短由配置文件redis.conf的lua-time-limit选项来控制,或直接使用config get和config set命令来修改。当一个脚本执行达到最大执行时间,redis不会主动结束它,它会进行下面几个步骤:
①redis记录一个脚本正在超时运行
②redis开始重新接受其它客户端请求,但只接受执行script kill命令和shutdown nosave两个命令,若客户端执行其它命令,redis会返回busy错误。
③如果脚本只执行过读操作,使用script kill命令可以立即停止此脚本;如果脚本执行过写操作,只允许shutdown save/nosave命令,通过停止服务器来阻止当前数据写入磁盘。(此时服务器关闭,数据不会被保存)
redis的lua脚本不允许创建全局变量,如果脚本需要在多次执行之间维持某种状态,可以借助外部redis key来保存状态,每次脚本执行前,获取redis相对应的key赋值给局部变量。在lua脚本中创建或访问一个全局变量,都会引起脚本停止,eval命令会返回一个错误:
127.0.0.1:6379:0>eval "a=10" 0 "ERR Error running script (call to f_933044db579a2f8fd45d8065f04a8d0249383e57): @enable_strict_lua:8: user_script:1: Script attempted to create global variable ‘a‘ "
redis的全局变量保护并不是百分百成功,有时候会在脚本中混入lua全局状态,可能会引发AOF持久化和主从复制都无法得到保证。redis建议不要在脚本中使用全局变量,可以使用local关键字定义脚本中的变量!
127.0.0.1:6379:0>eval "local a=10" 0 null
————————————————
原文:https://blog.csdn.net/sym542569199/article/details/88746776
原文:https://www.cnblogs.com/JerryQTQcjl/p/13359695.html