Redis-Lua脚本详解

如果我们想逻辑性的一起执行多条指令,在执行过程不被别的请求打断,那么Redis提供了Lua脚本,Redis服务器会单线程原子性执行lua脚本,保证了在执行过程中不会被其他请求打断。

通过Lua脚本执行多个指令

if redis.call("get",KEYS[1]) == ARGV[1] then

    return redis.call("del",KEYS[1])

else

    return 0

end

解析:如果通过key获取的数据与第一个入参相等,则将该key删除,并且返回,否则返回0。

将上面的脚本转换成Redis可以识别的格式,采用EVAL指令,如下

127.0.0.1:6379> set name dog

OK

127.0.0.1:6379> eval 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end' 1 name dog

(integer) 1

127.0.0.1:6379> eval 'if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end' 1 name dog

(integer) 0

解析:第二次执行返回0的原因是第一次已经把它删除了。

例如Redission中实现分布式锁就是利用执行Lua脚本,所以保证了设置key+过期时间 原子性,如下所示:

"if (redis.call('exists', KEYS[1]) == 0) then " +  "redis.call('hset', KEYS[1], ARGV[2], 1); " +            "redis.call('pexpire', KEYS[1], ARGV[1]); " + "return nil; " +  "end; " + "if        (redis.call('hexists', KEYS[1], ARGV[2]) == 1) then " +  "redis.call('hincrby', KEYS[1], ARGV[2], 1); " +  "redis.call('pexpire', KEYS[1], ARGV[1]); " +  "return nil; " + "end; " +   "return redis.call('pttl', KEYS[1]);"

SCRIPT LOAD 和 EVALSHA指令

上述lua脚本可能存在一个风险,如果某个lua脚本的内容长,而且客户端频繁操作,如果每次都需要传递那么大的脚本,必然存在浪费网络流量,所以Redis提供SCRIPT LOAD 和 EVALSHA指令

1) SCRIPT LOAD 指令用于将客户端提供的 lua 脚本上传到服务器但不执行,上传后会返回脚本的唯一ID,这个唯一ID是用来获取服务器缓存的这段lua脚本,如下:

127.0.0.1:6379> script load 'local curVal = redis.call("get", KEYS[1]); if curVal == false then curVal = 0 else curVal = tonumber(curVal) end; curVal = curVal * tonumber(ARGV[1]); redis.call("set", KEYS[1], curVal); return curVal'"be4f93d8a5379e5e5b768a74e77c8a4eb0434441"

将得到一个ID be4f93d8a5379e5e5b768a74e77c8a4eb0434441

2)EVALSHA指令用来执行该Lua脚本,例如:

127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 notexistskey 5

(integer) 0

127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 notexistskey 5

(integer) 0

127.0.0.1:6379> set foo 1

OK

127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 foo 5

(integer) 5

127.0.0.1:6379> evalsha be4f93d8a5379e5e5b768a74e77c8a4eb0434441 1 foo 5

(integer) 25

解析:前俩句由于redis不存在 key notexistskey 值为5的情况,所有返回0。

错误处理函数pcall()与call()

1)pcall(f)函数运行在保护模式下,如果f出现错误,调用pcall()返回false和错误消息。

2)call(f)函数,如果f出现错误时只会向上抛出异常。

脚本死循环

从上面可知Redis能够执行脚本,但是如果该脚本存在一点,导致发生了死循环或者是执行时间特别特别长,那么会导致后续的请求无法被处理。Redis为了解决这个问题,它提供了script kill 指令用于动态结束一个执行时间超时的 lua 脚本。但是 script kill 的执行有一个重要的前提条件,那就是当前正在执行的脚本没有对 Redis 的内部数据状态进行修改,因为 Redis 不允许 script kill 破坏脚本执行的原子性。例如脚本内部使用了redis.call("set", key, value) 修改了内部的数据,那么 script kill 执行时服务器会返回错误。

Script Kill 的原理

Lua 脚本引擎功能太强大了,它提供了各种钩子函数,它允许在内部虚拟机执行指令时运行钩子代码。比如每执行 N 条指令执行一次某个钩子函数,Redis 正是使用了这个钩子函数。如下图36

 Redis在钩子函数里会去处理客户端的请求,并且只有在发现 Lua 脚本执行超时之后才会去处理请求,这个超时时间默认是5秒。所以当脚本卡死后,执行kill命令,会出现执行时间比较长的现象。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值