您的位置:首页 > 数据库 > Redis

redis中使用lua脚本

2018-10-13 19:52 155 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/CharJay_Lin/article/details/83041552

1.lua脚本

1.1 简介

Lua是一个高效的轻量级脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能

1.2 使用脚本的好处

1. 减少网络开销,在Lua脚本中可以把多个命令放在同一个脚本中运行
2. 原子操作,redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。换句话说,编写脚本的过程中无需担心会出现竞态条件
3. 复用性,客户端发送的脚本会永远存储在redis中,这意味着其他客户端可以复用这一脚本来完成同样的逻辑

2.安装

#下载
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

3.lua基础教程

查看菜鸟教程 链接

4.Redis与Lua

在Lua脚本中调用Redis命令,可以使用redis.call函数调用。比如我们调用string类型的命令

eval "return redis.call('set','hello','world')" 0

redis.call 函数的返回值就是redis命令的执行结果。前面我们介绍过redis的5中类型的数据返回的值的类型也都不一样。redis.call函数会将这5种类型的返回值转化对应的Lua的数据类型

4.1__从Lua脚本中获得返回值__

在很多情况下我们都需要脚本可以有返回值,在脚本中可以使用return 语句将值返回给redis客户端,通过return语句来执行,如果没有执行return,默认返回为nil。

4.2__如何在redis中执行lua脚本__

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

4.3__EVALSHA命令__

考虑到我们通过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命令

4.4__lua脚本实战__

4.4.1 linux上调用lua脚本

实现一个针对某个手机号的访问频次, 以下是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…]

4.4.2 java使用redis调用lua

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);

}

4.5__脚本的原子性__

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__命令强行终止

阅读更多
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: