Redis Lua脚本注意事项
先看一下Redis官方对Lua脚本的解释:https://redis.io/commands/eval
Atomicity of scripts
Redis uses the same Lua interpreter to run all the commands. Also Redis guarantees that a script is executed in an atomic way: no other script or Redis command will be executed while a script is being executed. This semantic is similar to the one of MULTI / EXEC. From the point of view of all the other clients the effects of a script are either still not visible or already completed.
However this also means that executing slow scripts is not a good idea. It is not hard to create fast scripts, as the script overhead is very low, but if you are going to use slow scripts you should be aware that while the script is running no other client can execute commands.
大致翻译:
脚本原子性
Redis使用相同的Lua解释器来运行所有的命令。Redis还保证脚本以原子方式执行:在执行脚本时,不会执行其他脚本或Redis命令。这个语义类似于MULTI/EXEC。从所有其他客户端的角度来看,脚本的效果要么仍然不可见,要么已经完成。”
Lua脚本原子性
官方文档上说了 使用redis EVAL执行lua脚本 语义上与MULTI / EXEC 相同,redis会将lua脚本当作一个整体去执行 并且执行期间redis不会执行其它命令。
单个 Redis 命令的执行是原子性的,但 Redis 没有在事务上增加任何维持原子性的机制,所以 Redis 事务的执行并不是原子性的。
如果一个事务队列中的所有命令都被成功地执行,那么称这个事务执行成功。
另一方面,如果 Redis 服务器进程在执行事务的过程中被停止 —— 比如接到 KILL 信号、宿主机器停机,等等,那么事务执行失败。
当事务失败时,Redis 也不会进行任何的重试或者回滚动作。
温馨提示: 需要注意的是redis.call()函数出错时会中断脚本的执行,此时会出现一部分命令执行了,一部分没有执行。
我们可以验证一下: 准备数据
127.0.0.1:6379> set haha haha
OK
127.0.0.1:6379> set key1 1
OK
127.0.0.1:6379> set key2 2
OK
执行一段中间调用redis.call()会出错的Lua脚本,错误原因haha的key类型是字符串,执行HGET命令会出错的:
127.0.0.1:6379> eval "redis.call('SET',KEYS[1],ARGV[1]);redis.call('HGET','haha',ARGV[1]);redis.call('SET',KEYS[2],ARGV[2]);return 1;" 2 key1 key2 11 22
(error) ERR Error running script (call to f_cf874476bb7116d92b3b0cbbf8b399ba853907d4): @user_script:1: WRONGTYPE Operation against a key holding the wrong kind of value
127.0.0.1:6379> get key1
"11"
127.0.0.1:6379> get key2
"2"
127.0.0.1:6379> get haha
"haha"
127.0.0.1:6379>
可以看到key1被设置成了11,但是key2没有变成22,因为中间一步redis.call('HGET','haha',ARGV[1]);出错中断了脚本的执行。
redis.pcall()函数不会中断Lua脚本的执行:
127.0.0.1:6379> eval "redis.pcall('SET',KEYS[1],ARGV[1]);redis.pcall('HGET','haha',ARGV[1]);redis.pcall('SET',KEYS[2],ARGV[2]);return 1;" 2 key1 key2 1 22
(integer) 1
127.0.0.1:6379> get haha
"haha"
127.0.0.1:6379> get key1
"1"
127.0.0.1:6379> get key2
"22"
127.0.0.1:6379>
所以为了保证脚本的原子性,要谨慎使用redis.call()函数,如使用一定要确保这个函数的正确性。