• Redis Lua 脚本


    简介:
    redis脚本使用lua解释器来执行脚本。
     
    常用命令:
    1.eval
    2.evalsha
    3.script_load
    4.script_exists
    5.script_flush
    6.script_kill
     
    命令详解:
    1.eval
    可用版本: >=2.6.0
    时间复杂度: 寻找要被执行脚本的复杂度为O(1)
    命令格式: eval script numkeys key [key ...] arg [arg ...]
    命令介绍:
    redis2.6.0版本之后,通过内置的lua内置器,可以使用eval命令对lua脚本进行求值。
    script参数是一段lua5.1脚本程序,它会运行在redis服务器上下文中,这脚本不必(也不应该)定位为一个lua函数。
    number参数用来指定键名参数的个数。
    键名参数key [key ...]从eval的第三个参数开始,表示在脚本中用到的redis-key,这些参数可以在lua脚本中通过全局变量keys数组获取,用以1开始的下标访问keys[1]、keys[2]。
    在命令的最后,那些不是键名参数的附加参数arg [arg ...],可以在Lua中通过全局变量ARGV数组访问,访问形式与KEYS变量类似。
    > eval "return {KEYS[1],KEYS[2],ARGV[1],ARGV[2]}" 2 key1 key2 first second
    1) "key1"
    2) "key2"
    3) "first"
    4) "second"
    Lua脚本中执行redis:
    在Lua脚本中,可以使用两个不同的函数来执行redis命令(redis.call()/redis.pcall())。
    这两个函数唯一的不同在于对于错误信息的处理。
    > eval "return redis.call('set',KEYS[1],'bar')" 1 foo
    OK
    数据转换:
    当 Lua 通过 call() 或 pcall() 函数执行 Redis 命令的时候,命令的返回值会被转换成 Lua 数据结构。同样地,当 Lua 脚本在 Redis 内置的解释器里运行时,Lua 脚本的返回值也会被转换成 Redis 协议(protocol),然后由 EVAL 将值返回给客户端。
    数据类型之间的转换遵循这样一个设计原则:如果将一个 Redis 值转换成 Lua 值,之后再将转换所得的 Lua 值转换回 Redis 值,那么这个转换所得的 Redis 值应该和最初时的 Redis 值一样。
    换句话说, Lua 类型和 Redis 类型之间存在着一一对应的转换关系。
    脚本原子性:
    Redis 使用单个 Lua 解释器去运行所有脚本,并且, Redis 也保证脚本会以原子性(atomic)的方式执行:当某个脚本正在运行的时候,不会有其他脚本或 Redis 命令被执行。这和使用 MULTI / EXEC 包围的事务很类似。在其他别的客户端看来,脚本的效果(effect)要么是不可见的(not visible),要么就是已完成的(already completed)。
    另一方面,这也意味着,执行一个运行缓慢的脚本并不是一个好主意。写一个跑得很快很顺溜的脚本并不难,因为脚本的运行开销(overhead)非常少,但是当你不得不使用一些跑得比较慢的脚本时,请小心,因为当这些蜗牛脚本在慢吞吞地运行的时候,其他客户端会因为服务器正忙而无法执行命令。
    错误处理:
    当 redis.call() 在执行命令的过程中发生错误时,脚本会停止执行,并返回一个脚本错误,错误的输出信息会说明错误造成的原因:
    redis> lpush foo a
    (integer) 1
    redis> eval "return redis.call('get', 'foo')" 0
    (error) ERR Error running script (call to f_282297a0228f48cd3fc6a55de6316f31422f5d17): ERR Operation against a key holding the wrong kind of value
    redis.pcall() 出错时并不引发(raise)错误,而是返回一个带 err 域的 Lua 表(table),用于表示错误:
    redis 127.0.0.1:6379> EVAL "return redis.pcall('get', 'foo')" 0
    (error) ERR Operation against a key holding the wrong kind of value
    带宽与evalsha:
    EVAL 命令要求你在每次执行脚本的时候都发送一次脚本主体(script body)。Redis 有一个内部的缓存机制,因此它不会每次都重新编译脚本,不过在很多场合,付出无谓的带宽来传送脚本主体并不是最佳选择。
    为了减少带宽的消耗, Redis 实现了 EVALSHA 命令,它的作用和 EVAL 一样,都用于对脚本求值,但它接受的第一个参数不是脚本,而是脚本的 SHA1 校验和(sum)。
    EVALSHA 命令的表现如下:
    如果服务器还记得给定的 SHA1 校验和所指定的脚本,那么执行这个脚本
    如果服务器不记得给定的 SHA1 校验和所指定的脚本,那么它返回一个特殊的错误,提醒用户使用 EVAL 代替 EVALSHA
    > set foo bar
    OK
    > eval "return redis.call('get','foo')" 0
    "bar"
    > evalsha 6b1bf486c81ceb7edf3c093f4c48582e38c0e791 0
    "bar"
    > evalsha ffffffffffffffffffffffffffffffffffffffff 0
    (error) `NOSCRIPT` No matching script. Please use [EVAL](/commands/eval).
    脚本缓存:
    Redis 保证所有被运行过的脚本都会被永久保存在脚本缓存当中,这意味着,当 EVAL 命令在一个 Redis 实例上成功执行某个脚本之后,随后针对这个脚本的所有 EVALSHA 命令都会成功执行。
    刷新脚本缓存的唯一办法是显式地调用 SCRIPT FLUSH 命令,这个命令会清空运行过的所有脚本的缓存。通常只有在云计算环境中,Redis 实例被改作其他客户或者别的应用程序的实例时,才会执行这个命令。
    缓存可以长时间储存而不产生内存问题的原因是,它们的体积非常小,而且数量也非常少,即使脚本在概念上类似于实现一个新命令,即使在一个大规模的程序里有成百上千的脚本,即使这些脚本会经常修改,即便如此,储存这些脚本的内存仍然是微不足道的。
    事实上,用户会发现 Redis 不移除缓存中的脚本实际上是一个好主意。比如说,对于一个和 Redis 保持持久化链接(persistent connection)的程序来说,它可以确信,执行过一次的脚本会一直保留在内存当中,因此它可以在流水线中使用 EVALSHA 命令而不必担心因为找不到所需的脚本而产生错误(稍候我们会看到在流水线中执行脚本的相关问题)。
    script命令:
    Redis 提供了以下几个 SCRIPT 命令,用于对脚本子系统(scripting subsystem)进行控制:
    1)script flush:清除所有脚本缓存
    2)script exists sha1 [sha1 ...]:根据给定的脚本校验和,检测指定的脚本是否存在与脚本缓存中
    3)script load script:将一个脚本写入脚本缓存,但是不运行它
    4)script kill:杀死当前正在运行的脚本
    redis日志:
    在Lua脚本中,可以通过使用redis.log函数来写redis的日志:
    redis.log(loglevel, message)
    其中,message参数是一个字符串,而loglevel参数可以是:
    1)redis.LOG_DEBUG
    2)redis.LOG_VERBOSE
    3)redis.LOG_NOTICE
    4)redis.LOG_WARNING
    沙箱与最大执行时间:
    脚本应该仅仅用于传递参数和对 Redis 数据进行处理,它不应该尝试去访问外部系统(比如文件系统),或者执行任何系统调用。
    除此之外,脚本还有一个最大执行时间限制,它的默认值是 5 秒钟,一般正常运作的脚本通常可以在几分之几毫秒之内完成,花不了那么多时间,这个限制主要是为了防止因编程错误而造成的无限循环而设置的。
    最大执行时间的长短由 lua-time-limit 选项来控制(以毫秒为单位),可以通过编辑 redis.conf 文件或者使用 CONFIG GET parameter 和 CONFIG SET parameter value 命令来修改它。
    当一个脚本达到最大执行时间的时候,它并不会自动被 Redis 结束,因为 Redis 必须保证脚本执行的原子性,而中途停止脚本的运行意味着可能会留下未处理完的数据在数据集(data set)里面。
    因此,当脚本运行的时间超过最大执行时间后,以下动作会被执行:
    1)Redis 记录一个脚本正在超时运行
    2)Redis 开始重新接受其他客户端的命令请求,但是只有 SCRIPT KILL 和 SHUTDOWN NOSAVE 两个命令会被处理,对于其他命令请求, Redis 服务器只是简单地返回 BUSY 错误。
    3)可以使用 SCRIPT KILL 命令将一个仅执行只读命令的脚本杀死,因为只读命令并不修改数据,因此杀死这个脚本并不破坏数据的完整性
    4)如果脚本已经执行过写命令,那么唯一允许执行的操作就是 SHUTDOWN NOSAVE ,它通过停止服务器来阻止当前数据集写入磁盘
     
     
    2.evalsha
    可用版本: >=2.6.0
    时间复杂度: 根据脚本的复杂度而定
    命令格式: evalsha sha1 numkeys key [key ...] arg [arg ...]
    作用:
    根据给定的sha1校验码,对缓存在服务器中的脚本进行求值。
    参数传入方式与eval一致。
    返回值:
    脚本执行的返回值。
     
    3.script_load
    可用版本: >=2.6.0
    时间复杂度: O(N) , N 为脚本的长度(以字节为单位)
    命令格式: script load script
    作用:
    将脚本script添加到脚本缓存中,但并不执行这个脚本。
    eval 命令也会将脚本添加到脚本缓存中,但是会立刻执行这个脚本。
    如果给定的脚本已经在缓存里面了,那么不做动作。
    返回值:
    给script的SHA1校验和。
     
    4.script_exists
    可用版本: >=2.6.0
    时间复杂度: O(N) , N 为给定的 SHA1 校验和的数量
    命令格式: script exists sha1 [sha1 ...]
    作用:
    给定一个或者多个脚本的SHA1校验和,返回一个包含0/1列表,表示校验和所指定的脚本是否已经被保存在缓存当中。
    返回值:
    一个列表,包含 0 和 1 ,前者表示脚本不存在于缓存,后者表示脚本已经在缓存里面了。 列表中的元素和给定的 SHA1 校验和保持对应关系,比如列表的第三个元素的值就表示第三个 SHA1 校验和所指定的脚本在缓存中的状态。
     
    5.script_flush
    可用版本: >=2.6.0
    时间复杂度: O(N) , N 为缓存中脚本的数量
    命令格式: script flush
    作用:
    清除所有Lua脚本缓存。
    返回值:
    总是返回OK。
     
    6.script_kill
    可用版本: >=2.6.0
    时间复杂度: O(1)
    命令格式: script kill
    作用:
    杀死当前正在运行的 Lua 脚本,当且仅当这个脚本没有执行过任何写操作时,这个命令才生效。
    这个命令主要用于终止运行时间过长的脚本,比如一个因为 BUG 而发生无限 loop 的脚本,诸如此类。
    返回值:
    执行成功返回 OK ,否则返回一个错误。
    作者:红雨
    出处:https://www.cnblogs.com/52why
    微信公众号: 红雨python
  • 相关阅读:
    mySql基础
    ECSHOP模糊分词搜索和商品列表关键字飘红功能
    smarty在循环的时候计数来显示这是第几次循环的功能
    PHP Warning: 的解决方法
    ECSHOP生成缩略图模糊
    ECSHOP商品描述和文章里不加水印,只在商品图片和商品相册加水印
    ECSHOP_百度收录网址后面有?from=rss
    在ecshop中添加页面,并且实现后台管理
    windows 2003子目录权限丢失及子目录权限无法继承更改的解决方法
    Newtonsoft.Json初探
  • 原文地址:https://www.cnblogs.com/52why/p/14356738.html
Copyright © 2020-2023  润新知