Redis与Lua

核心知识点:

1.Redis中执行Lua脚本的两种方法:

  a.eval

  b.evalsha

2.Lua中对redis的访问(redis.call())

3.管理Lua脚本

  a.script load:加载

  b.script exists:判断

  c.script flush:清除

  d.script kill:杀死(如果已经执行过写操作,必须使用shutdown nosave)重启服务

 

 

 

1.在Redis中使用Lua

在Redis中执行Lua脚本有两种方法:eval和evalsha

(1)eval

语法:

eval 脚本内容 key个数 key列表 参数列表

下面例子使用了key列表和参数列表来为Lua脚本提供更多的灵活性:

127.0.0.1:6379> eval 'return "hello" .. KEYS[1]..ARGV[1]' 1 redis world
"helloredisworld"

此时KEYS[1]="redis",ARGV[1]="world",所以最终的返回结果是“helloredisworld”。

如果脚本较长,还可以使用redis-cli--eval直接执行文件。

eval命令和--eval参数本质是一样的,客户端如果想执行Lua脚本,

首先在客户端编写好Lua脚本代码,然后把脚本作为字符串发送给服务端,服务端会将执行结果返回给客户端。

 

 

(2)evalsha

除了使用eval,Redis还提供了evalsha命令来执行脚本。

首先要将Lua脚本加载到Redis服务端,得到该脚本的SHA1校验和,

evalsha命令使用SHA1作为参数可以直接执行对应的Lua脚本,避免每次发送Lua脚本的开销。

这样客户端就不需要每次执行脚本内容,而脚本也会常驻在服务端,脚本功能得到复用。

加载脚本:script load命令可以将脚本内容加载到Redis内存中,例如:

127.0.0.1:6379> script load "return 'hello kebi'"
"8df13960c2be12fec0947206c9e087cd68b2f1b9"

执行脚本:evalsha可以执行脚本:

127.0.0.1:6379> evalsha "8df13960c2be12fec0947206c9e087cd68b2f1b9" 0
"hello kebi"

 

2.Lua的Redis API

Lua可以使用redis.call函数实现对Redis的访问:

127.0.0.1:6379> eval 'return redis.call("get",KEYS[1])' 1 hello
"world"

除此之外Lua还可以使用Redis.pcall函数实现对Redis的调用,redis.call和redis.pcall的不同在于,

如果redis.call执行失败,那么脚本执行结束会直接返回错误,而redis.pcall会忽略错误继续执行脚本,

所在实际开发中要根据具体的应用场景进行函数的选择。

Lua可以使用redis.log函数将Lua脚本的日志输出到Redis的日志文件中,但是一定要控制日志级别。

 

3.Redis如何管理Lua脚本

Redis提供了4个命令实现对Lua脚本的管理,下面分别介绍:

(1)script load

script load script

将Lua脚本加载到Redis内存之中。

 

(2)script exists

scripts exists sha1 [sha1 ...]

判断sha1是否已经加载到Redis内存之中。

127.0.0.1:6379> script load "return 'hello kebi'"
"8df13960c2be12fec0947206c9e087cd68b2f1b9"
127.0.0.1:6379> script exists "8df13960c2be12fec0947206c9e087cd68b2f1b9"
1) (integer) 1

 

(3)script flush

script flush

清除Redis内存中已经加载的所有Lua脚本。

127.0.0.1:6379> script exists "8df13960c2be12fec0947206c9e087cd68b2f1b9"
1) (integer) 1
127.0.0.1:6379> script flush
OK
127.0.0.1:6379> script exists "8df13960c2be12fec0947206c9e087cd68b2f1b9"
1) (integer) 0

 

(4)script kill

script kill

此命令用于杀掉正在执行的Lua脚本。

如果Lua脚本比较耗时,甚至Lua脚本存在问题,那么此时Lua脚本的执行会阻塞Redis,直到脚本执行完毕或外部进行干预将其结束。

下面模拟一个Lua脚本阻塞的情况进行说明。

执行Lua脚本,当前客户端会阻塞:

127.0.0.1:6379> eval 'while 1==1 do end' 0
..卡在这里不动了,因为这个脚本一直做死循环

在另外一个客户端执行一个命令:

127.0.0.1:6379> get world
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.

此时Redis已经阻塞,无法处理正常的调用,可以使用script kill结束当前正在执行的Lua脚本:

127.0.0.1:6379> script kill
OK

可在阻塞的那一端看到(脚本被杀死的信息):

127.0.0.1:6379> eval 'while 1==1 do end' 0
(error) ERR Error running script (call to f_c045d3ae3b3eca855e00c772db40aa560b3a1fc8): @user_script:1: Script killed by user with SCRIPT KILL... 
(38.91s)

注意:如果当前Lua脚本已经执行过写操作,那么script kill将不会生效。例如:

127.0.0.1:6379> eval 'while 1==1 do redis.call("set","k","v") end' 0   --一直执行写操作

在另一个客户端使用script kill杀不掉:

127.0.0.1:6379> script kill
(error) UNKILLABLE Sorry the script already executed write commands against the dataset. You can either wait the script termination or kill the server in a hard way using the SHUTDOWN NOSAVE command.

此时唯一的办法就是重启服务,必须使用shutdown nosave

--不能以这种方法关
[root@Redis ~]# redis-cli shutdown
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
--必须这样
127.0.0.1:6379> shutdown nosave
not connected>
然后再开

补充:

Redis提供了一个lua-time-limit参数,默认是5秒,它是Lua脚本的“超时时间”,但这个超时时间仅仅是当Lua脚本超过lua-time-limit后,向其他命令发送BUSY的信号,但是不会停止掉服务端和客户端的执行脚本。

 

 

4.使用Lua对Redis中的数据进行处理(案例)

Lua脚本功能为Redis开发和运维人员带来如下三个好处:

  • Lua脚本在Redis中是原子执行的,执行过程中间不会插入其他命令;
  • Lua脚本可以帮助开发和运维人员创造出自己定制的命令,并可以将这些命令常驻在Redis内存中,实现复用的的效果;
  • Lua脚本可以将多条命令一次性打包,有效的减少网络开销

下面以一个例子说明·Lua脚本的使用,当前列表记录着热门用户的id,假设这个列表有5个元素,如下所示:

127.0.0.1:6379> lrange hot:user:list 0 -1
1) "user:1:ratio"
2) "user:8:ratio"
3) "user:3:ratio"
4) "user:4:ratio"
5) "user:72:ratio"

user:{id}:ratio代表用户的热度,它本身又是一个字符串类型的键:

127.0.0.1:6379> mget user:1:ratio user:8:ratio user:3:ratio user:4:ratio user:72:ratio
1) "987"
2) "763"
3) "556"
4) "400"
5) "101"

现要求将列表内所有的键对应热度做加1操作,并且保证是原子性执行,此功能可以利用Lua脚本来实现。

脚本内容如下:

--将列表中所有元素取出,赋值给mylist
local mylist = redis.call("lrange",KEYS[1],0,1)
--定义局部变量count=0,这个count就是最后incr的总次数
local count = 0
--遍历mylist中所有元素,每次做完count自增,最后返回count
for index,key in ipairs(mylist)
do
    redis.call("incr",key)
    count = count + 1
end
return count

执行脚本:

[root@Redis script.lua]# redis-cli --eval 3_1.lua hot:user:list
(integer) 5

执行后用户热度自增1:

127.0.0.1:6379> mget user:1:ratio user:8:ratio user:3:ratio user:4:ratio user:72:ratio
1) "987"
2) "763"
3) "557"
4) "401"
5) "101"

 

posted @ 2017-12-25 01:02  明王不动心  阅读(919)  评论(0编辑  收藏  举报