在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
Redis自2.6.0加入了Lua脚本相关的命令,EVAL, EVALSHA, SCRIPT EXISTS, SCRIPT FLUSH, SCRIPT KILL, SCRIPT LOAD,自3.2.0加入了Lua脚本的调试功能和命令。 Lua脚本可以运行在任何平台上,也可以嵌入到大多数语言中,来扩展其功能。Lua脚本是用C语言写的,体积很小,运行速度很快。 使用Redis Lua脚本功能,用户可以向服务器发送Lua脚本来执行自定义动作,获取脚本的相应数据。Redis服务器会单线程原子性执行Lua脚本,保证Lua脚本在执行过程中不会被任意其他请求打断。
生产环境中,推荐使用EVALSHA,相较于EVAL的每次发送脚本主体、占用带宽,EVALSHA会更高效。 使用Lua脚本的好处: 1) 减少网络开销:将脚本发送到服务端,在服务端进行计算,并将结果返回客户端,避免了传递大量数据。 2) 原子性的操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入,因此在编写脚本的过程中,无需使用事物 3) 代码复用 使用Lua脚本需注意的问题: 1) 单线程执行。所有Lua命令都在同一个Lua解释器中执行,当一个脚本执行时,其他脚本或Redis命令都不能执行。如果脚本执行慢,会比较麻烦。 2) 写纯函数脚本 3) Redis集群模式要求单个Lua脚本操作的Key必须在同一节点上,但是Cluster会将数据自动分布到不同的节点(虚拟的16384个slot)。阿里云集群版官网也有说明:在Redis集群版实例中,事务、脚本等命令要求的key必须在同一slot中,否则会返回错误信息:command keys must in same slot。 2 Redis调用Lua脚本2.1 EVAL指令Eval语法: eval script numkeys key [key ...] arg [arg ...] script: lua脚本 numkeys:表示有几个Key,分别是KEYS[1],KEYS[2],….,从第numkeys+1开始是参数值,ARGV[1],ARGV[2],…… 注意:EVAL命令根据参数numkeys来将后面的所有参数分别存入脚本中KEYS和ARGV两个table类型的全局变量。当脚本不需要任何参数时,也不能省略这个参数,应设为0。 示例:
2.2 EVALSHA和SCRIPT LOAD指令EVAL可以将脚本内容传递到服务端执行,可是如果脚本内容很长,而且客户端频繁执行的话,会浪费带宽。Redis提供了SCRIPT LOAD和EVALSHA指令来解决这个问题
SCRIPT LOAD 指令用于将客户端提供的 lua 脚本传递到服务器而不执行,但是会得到脚本的唯一ID(脚本的SHA1校验和),这个唯一ID 是用来唯一标识服务器缓存的这段 lua 脚本,它是由 Redis 使用 sha1 算法揉捏脚本内容而得到的一个很长的字符串。有了这个唯一 ID,后面客户端就可以通过 EVALSHA 指令反复执行这个脚本了。通过SCRIPT LOAD上传的脚本会一直存在缓存中,除非调用了清除脚本命令SCRIPT FLUSH。 SCRIPT LOAD指令: script load luascript 其中luascript为脚本内容,方法返回脚本内容的SHA1值。 EVALSHA指令: eval scriptsha1 numkeys key [key ...] arg [arg ...] EVALSHA命令与EVAL命令一致,但是第一个参数是Lua脚本的SHA1值。 示例:
2.3 执行Lua脚本文件在命令行编写复杂的Lua 脚本不方便,可以将脚本存储为lua文件,然后运行redis-cli –eval命令来执行脚本。 Redis-cli –h xfraud1 –p 6379 –eval scriptpath key1 key2, arg1 arg2 其中scriptpath为Lua脚本存放的路径,脚本后面传入的是参数,通过“,”分隔为两组,前面是KEYS,后面是ARGV。参数之间要用空格分隔。 示例:
3 Lua脚本中调用Redis命令在Lua脚本中,可以通过两种方式调用Redis命令。
两个函数的作用类似,区别在于错误处理机制不同。在脚本运行出现错误时,Redis会保护主线程不会因为脚本的错误导致服务器崩溃,近似于在脚本的外围有一个很大的try catch语句包裹。call调用只会向上抛出异常,客户端会输出服务器返回的通用错误消息。Lua原生没有提供try catch语句,但是lua内置了pcall(f)函数,pcall的意思是protected call ,它会让f函数运行在保护模式下,f如果出现了错误,pcall调用会返回false和错误信息。 Redis.call函数调用产生错误,脚本的返回信息如下图所示。
客户端输出的是一个通用的错误信息,而不是incr调用本应该返回的WRONGTYPE类型的错误消息。Redis内部在处理redis.call遇到错误时是向上抛出异常。Pcall调用捕获到脚本异常时会向客户端回复错误信息。如果我们将上面的call改成pcall,结果如下。
注意:在Lua脚本执行的过程中遇到了错误,同Redis的事务一样,那些通过redis.call函数已经执行过的指令对服务器状态产生的影响是无法撤销的,在编写Lua脚本时一定要小心,避免没有考虑到的判断条件导致脚本没有完全执行。
4 Lua脚本中记录日志redis.log(loglevel,message) loglevel 如下:
message仅仅接收String类型 举例: redis.log(redis.LOG_WARNING,"Something is wrong with this script.") 可以查看redis的conf文件,查看Redis服务日志存放位置。当脚本中输出日志的级别等于或大于conf中设置的级别时,才会输出日志。
5 Redis与Lua数据结构对应关系
注意:
6 Lua Debugger可以使用ldb对Lua脚本进行调试。
调试时,加上参数—ldb即可,示例: redis-cli --ldb -h xfraud1 -p 6379 --eval /CFCA/luaScript/amountsum.lua testamount
7 其他约定
8 脚本示例function string.split(input, delimiter) -- 脚本里所有的键都应该由KEYS数组来传递,因为所有的redis命令,在执行之前都会被分析以确定命令会对哪些键进行操作 --从有序集合中获取所有元素,每个元素的格式为string-number,以“-”分割,number部分表示数值,代码实现的功能是求取所有数值的和 end 9 常见问题1) 纯函数 在Lua脚本中加入redis.call("TIME"),使用的时候会报错,这是因为Redis默认情况复制Lua脚本到备机和持久化中,如果脚本是一个非纯函数,备库中执行的时候或者宕机恢复的时候可能产生不一致的情况。Redis在3.2版本中加入了redis.replicate_commands函数来解决这个问题,在脚本第一行执行这个函数,Redis会将修改数据的命令收集起来,然后用MULTI/EXEC包裹起来,这种方式称为script effects replication,这个类似于mysql中的基于行的复制模式,将非纯函数的值计算出来,用来持久化和主从复制。 2) @user_script:1: @user_script: 1: Lua script attempted to access a non local key in a cluster node Redis集群中,会将key分配到不同的slot,然后分配到对应的机器上,当Redis执行脚本的节点与Key存放的节点不同时,会返回错误: @user_script:1: @user_script: 1: Lua script attempted to access a non local key in a cluster node。Redis集群模式要求单个Lua脚本操作的Key必须在同一个节点上,阿里云集群版官网也有说明:在Redis集群版实例中,事务、脚本等命令要求的key必须在同一个slot中,如果不在同一个slot将返回错误信息:command keys must in same slot. 3) @enable_strict_lua:8: user_script:2: Script attempted to create global variable '***' Lua默认变量是全局的,在脚本中应使用local变量,避免在持久化、复制的时候产生各种问题。应使用 local *** 定义局部变量 |
请发表评论