• 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"
  • 相关阅读:
    hashmap的一些基础原理
    关于uuid
    读锁跟写锁的区别
    栈为什么效率比堆高
    MySQL行级锁、表级锁、页级锁详细介绍
    MYSQL MyISAM与InnoDB对比
    MYSQL锁表问题解决
    mysql查询锁表语句
    三种排序方法
    正则表达式
  • 原文地址:https://www.cnblogs.com/yangmingxianshen/p/8100250.html
Copyright © 2020-2023  润新知