redis执行lua脚本实战

本文详细介绍了Redis中Lua脚本的使用方法,包括eval命令的参数传递、数据类型转换、原子性执行、redis.call与redis.pcall的区别、脚本缓存机制及超时处理等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 eval调用传递参数

[root@base task]# redis-cli
127.0.0.1:6379> eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
1) "key1"
2) "key2"
3) "first"
4) "second"
127.0.0.1:6379> 

其中2为参数的redis键key的个数,key1,key2为redis键key,first,second为键名的附加参数。在lua脚本中通过KEYS和ARGV数组对脚本执行指定的参数进行访问。

调用redis的set命令(方式一)

127.0.0.1:6379> eval "return redis.call('set','foo','bar')" 0
OK
127.0.0.1:6379> get foo
"bar"
127.0.0.1:6379> 

调用redis的set命令(方式二)

127.0.0.1:6379> eval "return redis.call('set',KEYS[1],'bar')" 1 foo
OK
127.0.0.1:6379> get foo
"bar"
127.0.0.1:6379> 

需要注意的是,上面这段脚本的确实现了将键 foo 的值设为 bar 的目的,但是,它违反了 EVAL 命令的语义,因为脚本里使用的所有键都应该由 KEYS 数组来传递,就像这样:

要求使用正确的形式来传递键(key)是有原因的,因为不仅仅是 EVAL 这个命令,所有的 Redis 命令,在执行之前都会被分析,籍此来确定命令会对哪些键进行操作。

因此,对于 EVAL 命令来说,必须使用正确的形式来传递键,才能确保分析工作正确地执行。除此之外,使用正确的形式来传递键还有很多其他好处,它的一个特别重要的用途就是确保 Redis 集群可以将你的请求发送到正确的集群节点。(对 Redis 集群的工作还在进行当中,但是脚本功能被设计成可以与集群功能保持兼容。)不过,这条规矩并不是强制性的,从而使得用户有机会滥用(abuse) Redis 单实例配置(single instance configuration),代价是这样写出的脚本不能被 Redis 集群所兼容。

 Lua数据类型和Redis数据类型存在的转换关系如下:

以下列出的是详细的转换规则:

从 Redis 转换到 Lua :

  • Redis integer reply -> Lua number / Redis 整数转换成 Lua 数字
  • Redis bulk reply -> Lua string / Redis bulk 回复转换成 Lua 字符串
  • Redis multi bulk reply -> Lua table (may have other Redis data types nested) / Redis 多条 bulk 回复转换成 Lua 表,表内可能有其他别的 Redis 数据类型
  • Redis status reply -> Lua table with a single ok field containing the status / Redis 状态回复转换成 Lua 表,表内的 ok 域包含了状态信息
  • Redis error reply -> Lua table with a single err field containing the error / Redis 错误回复转换成 Lua 表,表内的 err 域包含了错误信息
  • Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type / Redis 的 Nil 回复和 Nil 多条回复转换成 Lua 的布尔值 false

从 Lua 转换到 Redis:

  • Lua number -> Redis integer reply / Lua 数字转换成 Redis 整数
  • Lua string -> Redis bulk reply / Lua 字符串转换成 Redis bulk 回复
  • Lua table (array) -> Redis multi bulk reply / Lua 表(数组)转换成 Redis 多条 bulk 回复
  • Lua table with a single ok field -> Redis status reply / 一个带单个 ok 域的 Lua 表,转换成 Redis 状态回复
  • Lua table with a single err field -> Redis error reply / 一个带单个 err 域的 Lua 表,转换成 Redis 错误回复
  • Lua boolean false -> Redis Nil bulk reply / Lua 的布尔值 false 转换成 Redis 的 Nil bulk 回复

从 Lua 转换到 Redis 有一条额外的规则,这条规则没有和它对应的从 Redis 转换到 Lua 的规则:

  • Lua boolean true -> Redis integer reply with value of 1 / Lua 布尔值 true 转换成 Redis 整数回复中的 1

Lua数据类型和Redis数据类型转换 

127.0.0.1:6379> eval "return {1,{1,'L','U','A'},0}" 0
1) (integer) 1
2) 1) (integer) 1
   2) "L"
   3) "U"
   4) "A"
3) (integer) 0

脚本原子性的执行描述如下:

Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。

另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难,因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心,因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。

redis.call()和redis.pcall()的区别如下:

当 redis.call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因

和 redis.call() 不同, redis.pcall() 出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误

执行lua脚本时,可以通过evalsha命令代替eval,按照正确的格式来传递参数,从而使脚本主体不变直接通过evalsha命令对脚本进行复用:

客户端库的底层实现可以一直乐观地使用 EVALSHA 来代替 EVAL ,并期望着要使用的脚本已经保存在服务器上了,只有当 NOSCRIPT 错误发生时,才使用 EVAL 命令重新发送脚本,这样就可以最大限度地节省带宽。

这也说明了执行 EVAL 命令时,使用正确的格式来传递键名参数和附加参数的重要性:因为如果将参数硬写在脚本中,那么每次当参数改变的时候,都要重新发送脚本,即使脚本的主体并没有改变,相反,通过使用正确的格式来传递键名参数和附加参数,就可以在脚本主体不变的情况下,直接使用 EVALSHA 命令对脚本进行复用,免去了无谓的带宽消耗。

Redis提供的脚本缓存:

Redis 保证所有被运行过的脚本都会被永久保存在脚本缓存当中,这意味着,当 EVAL 命令在一个 Redis 实例上成功执行某个脚本之后,随后针对这个脚本的所有 EVALSHA 命令都会成功执行。

刷新脚本缓存的唯一办法是显式地调用 SCRIPT FLUSH 命令,这个命令会清空运行过的所有脚本的缓存。通常只有在云计算环境中,Redis 实例被改作其他客户或者别的应用程序的实例时,才会执行这个命令。

缓存可以长时间储存而不产生内存问题的原因是,它们的体积非常小,而且数量也非常少,即使脚本在概念上类似于实现一个新命令,即使在一个大规模的程序里有成百上千的脚本,即使这些脚本会经常修改,即便如此,储存这些脚本的内存仍然是微不足道的。

执行脚本时禁止使用Lua全局变量,避免变量数据泄漏到Lua执行环境中,从而使redis的AOF持久化和复制得不到保证。(注:脚本的执行的结果应该是只对本次执行有效,如果脚本中引入Lua的全局变量,脚本多次执行或者并行执行时则会出现问题)

避免引入全局变量的一个诀窍是:将脚本中用到的所有变量都使用 local 关键字定义为局部变量。

执行超时脚本如下观察redis返回忙等待提示 

127.0.0.1:6379> eval "while (true) do redis.call('incr',KEYS[1]) end" 1 count
[root@base task]# redis-cli monitor
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

如果是在执行脚本前执行redis-cli -r -1 -i 1 get count(间隔从redis获取count的值)时,如下的结果显示了当默认配置5s过后,redis返回的BUSY忙等待响应 

root@base redis-3.2.1]# redis-cli -r -1 -i 1 get count
"524"
"524"
"524"
"524"
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

redis.conf默认配置lua-time-limit

#################       LUA SCRIPTING    ########################

# Max execution time of a Lua script in milliseconds.
#
# If the maximum execution time is reached Redis will log that a script is
# still in execution after the maximum allowed time and will start to
# reply to queries with an error.
#
# When a long running script exceeds the maximum execution time only the
# SCRIPT KILL and SHUTDOWN NOSAVE commands are available. The first can be
# used to stop a script that did not yet called write commands. The second
# is the only way to shut down the server in the case a write command was
# already issued by the script but the user doesn't want to wait for the natural
# termination of the script.
#
# Set it to 0 or a negative value for unlimited execution without warnings.
lua-time-limit 5000

 

参考资料:

http://redisdoc.com/script/eval.html

转载于:https://my.oschina.net/u/914290/blog/789912

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值