赞
踩
Lua是一个高效的轻量级脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能
1. 减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行
2. 原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心会出现竞态条件
3. 复用性,客户端发送的脚本会永远存储在redis中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑
#下载 curl -R -O http://www.lua.org/ftp/lua-5.3.0.tar.gz #解压 tar zxf lua-5.3.0.tar.gz #进入解压的目录 cd lua-5.3.0 #linux环境下编译 make linux test #安装 make install #如果报错,说找不到readline/readline.h, 可以通过yum命令安装 yum install readline-devel #安装完以后再 make linux / make install #进入lua控制台 lua
查看菜鸟教程 链接
在Lua脚本中调用Redis命令,可以使用redis.call函数调用。比如我们调用string类型的命令
eval "return redis.call('set','hello','world')" 0
redis.call 函数的返回值就是redis命令的执行结果。前面我们介绍过redis的5中类型的数据返回的值的类型也都不一样。redis.call函数会将这5种类型的返回值转化对应的Lua的数据类型
在很多情况下我们都需要脚本可以有返回值,在脚本中可以使用return 语句将值返回给redis客户端,通过return语句来执行,如果没有执行return,默认返回为nil。
Redis提供了EVAL命令可以使开发者像调用其他Redis内置命令一样调用脚本。
[EVAL] [脚本内容] [key参数的数量] [key …] [arg …]
可以通过key和arg这两个参数向脚本中传递数据,他们的值可以在脚本中分别使用__KEYS__和__ARGV__ 这两个类型的全局变量访问。比如我们通过脚本实现一个set命令,通过在redis客户端中调用,那么执行的语句是:
#参数传递 KEYS和ARGV必须大写
eval "return redis.call('set',KEYS[1],ARGV[1])" 1 hello world
EVAL命令是根据 key参数的数量-也就是上面例子中的1来将后面所有参数分别存入脚本中KEYS和ARGV两个表类型的全局变量。当脚本不需要任何参数时也不能省略这个参数。如果没有参数则为0
eval "return redis.call('get','hello')" 0
考虑到我们通过eval执行lua脚本,脚本比较长的情况下,每次调用脚本都需要把整个脚本传给redis,比较占用带宽。为了解决这个问题,redis提供了EVALSHA命令允许开发者通过脚本内容的SHA1摘要来执行脚本。该命令的用法和EVAL一样,只不过是将脚本内容替换成脚本内容的SHA1摘要
1. Redis在执行EVAL命令时会计算脚本的SHA1摘要并记录在脚本缓存中
2. 执行EVALSHA命令时Redis会根据提供的摘要从脚本缓存中查找对应的脚本内容,如果找到了就执行脚本,否则返回“NOSCRIPT No matching script,Please use EVAL”
通过以下案例来演示EVALSHA命令的效果
script load “return redis.call(‘get’,‘hello’)” 将脚本加入缓存并生成sha1命令
evalsha “a5a402e90df3eaeca2ff03d56d99982e05cf6574” 0
我们在调用eval命令之前,先执行evalsha命令,如果提示脚本不存在,则再调用eval命令
实现一个针对某个手机号的访问频次, 以下是lua脚本,保存为phone_limit.lua
local num=redis.call('incr',KEYS[1])
if tonumber(num)==1 then
redis.call('expire',KEYS[1],ARGV[1])
return 1
elseif tonumber(num)>tonumber(ARGV[2]) then
return 0
else
return 1
end
#通过如下命令调用
./redis-cli --eval phone_limit.lua rate.limiting:13700000000 , 10 3
语法为 ./redis-cli –eval [lua脚本] [key…]空格,空格[args…]
public static void main(String[] args) { Jedis jedis = RedisManger.getJedis(); String lua="local num=redis.call('incr',KEYS[1])\n" + "if tonumber(num)==1 then\n" + " redis.call('expire',KEYS[1],ARGV[1])\n" + " return 1\n" + "elseif tonumber(num)>tonumber(ARGV[2]) then\n" + " return 0\n" + "else\n" + " return 1\n" + "end"; List<String> keys = new ArrayList<>(); keys.add("ip:limit:127.0.0.1"); List<String> argss = new ArrayList<>(); argss.add("6000"); argss.add("10"); // Object obj = jedis.eval(lua, keys, argss); Object obj = jedis.evalsha(jedis.scriptLoad(lua), keys, argss);//把脚本缓存起来 System.out.println(obj); }
redis的脚本执行是原子的,即脚本执行期间Redis不会执行其他命令。所有的命令必须等待脚本执行完以后才能执行。为了防止某个脚本执行时间过程导致Redis无法提供服务。Redis提供了lua-time-limit参数限制脚本的最长运行时间。默认是5秒钟。
当脚本运行时间超过这个限制后,Redis将开始接受其他命令但不会执行(以确保脚本的原子性),而是返回BUSY的错误
打开两个客户端窗口
在第一个窗口中执行lua脚本的死循环
eval “while true do end” 0
在第二个窗口中运行get hello
最后第二个窗口的运行结果是Busy, 可以通过script kill命令终止正在执行的脚本。如果当前执行的lua脚本对redis的数据进行了修改,比如(set)操作,那么script kill命令没办法终止脚本的运行,因为要保证lua脚本的原子性。如果执行一部分终止了,就违背了这一个原则
在这种情况下,只能通过 __shutdown nosave__命令强行终止
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。