redis2.6以后可以在redis 中使用lua语言。
1、用lua的好处:
1)一个脚本包含多个操作,减少访问次数从而减少网络开销
2)原子操作 redis 对lua脚本是原子化执行方案
3)复用性 复用 lua脚本的逻辑
2、lua脚本安装:
1)下载安装包 并解压
tar-zvxf lua-5.3.5.tar.gz
2)执行 make linux 编译
编译过程报错 lua.c:82:31: 致命错误:readline/readline.h:没有那个文件或目录
百度一下得知,需要安装依赖
执行 如下命令安装依赖:
yum install libtermcap-devel ncurses-devel libevent-devel readline-devel
重新解压编译
[root@192 lua-5.3.5]# make linux test
cd src && make linux
make[1]: 进入目录“/usr/apps/lua/lua-5.3.5/src”
make all SYSCFLAGS="-DLUA_USE_LINUX" SYSLIBS="-Wl,-E -ldl -lreadline"
make[2]: 进入目录“/usr/apps/lua/lua-5.3.5/src”
gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_LINUX -c -o lapi.o lapi.c
gcc -std=gnu99 -O2 -Wall -Wextra -DLUA_COMPAT_5_2 -DLUA_USE_LINUX -c -o lcode.o lcode.c
然后 运行 lua 就启动进入了 lua 如下:
[root@192 ~]# lua
Lua 5.1.4 Copyright (C) 1994-2008 Lua.org, PUC-Rio
>
3、初步使用
lua特点 动态类型的语言,和js类似
变量 分为 全局变量 和局部变量
单行注释 –
多行注释 --[[ ]]
全局变量 a=1
局部变量 local b=1
逻辑表达式 ±*/
测试代码如下 编辑脚本 luatest :
vi testlua 输入内容 (lua testlua 执行脚本)
a=1
local b=2
print(a+b)
执行 lua luatest 结果
关系运算符
a==b 比较两个值是否相等
~= 比较两个值是否不等
a=1
local b=2
--print(a+b)
print (a==b)
print (a~=b)
print(a=='1')
执行 lua luatest 结果
逻辑运算符
and
or
not
测试脚本:
true
false
[root@192 lua-5.3.5]# vi luatest
print((a==b)and(a==1
a=1
local b=2
--[[print(a+b)
print (a==b)
print (a~=b)
print(a=='1')
]]
print((a==b)and(a==1) )
print((a==b)or(a==1) )
print(not(a==b))
执行 lua luatest 结果
字符串 操作
… 拼接 a…b 拼接字符串 a和b 同 java 的a+b
#计算字符串的长度 #a 字符串a 的长度
测试代码
str1='hello'
str2='world'
print(str1..str2)
print(#str1)
条件判断 如下
a=1
if( a == 1) then
print(‘a=1’)
elseif( b == 2) then
print(‘b=2’)
else
print(‘111’)
end
执行结果:
a=1
循环
a=1
while(a<10) do
print(a)
a=a+1
end
测试结果:
for i=1,5 do
print(i)
end
测试
遍历数组
local arr={‘a’,‘b’,‘c’}
for i,v in ipairs(arr) do
print(i…‘=’…v)
end
[root@192 lua-5.3.5]# lua luatest
1=a
2=b
3=c
[root@192 lua-5.3.5]#
测试结果如下
local function add(a,b)
return a+b;
end
print(add(1,2))
"luatest" 46L, 474C written
[root@192 lua-5.3.5]# lua luatest
3
其他内置操作:Sting 操作字符串 Table 操作数组
4、redis 整合 lua
lua 提供了redis 的操作 如:redis.call(‘set‘,‘name’,‘zhang’)
但是直接运行该段脚本提示错误 如下
> redis.call('set','name','zhang');
stdin:1: attempt to index global 'redis' (a nil value)
stack traceback:
stdin:1: in main chunk
[C]:
原因是直接在lua环境中运行该脚本,没有依赖的redis引擎。所以报错。
因为redis中提供了这个引擎,所以在redis中运行该段脚本就没有这个问题了。
在redis-cli 中运行该段脚本的方式: eval “脚本” keynumbers key… arg…[参数(没有参数写0)]
如下:
127.0.0.1:6379> eval "redis.call('set','name','zhang')" 0
(nil)
127.0.0.1:6379> get name
"zhang"
127.0.0.1:6379>
有参数的测试:
通过 1(传入几个键) ‘name’ ‘lisi’ 出入参数
在脚本中 通过KEYS[1] 和 ARGV[1] 获取使用参数如下(下标从1开始):
127.0.0.1:6379> eval "return redis.call('set',KEYS[1],ARGV[1])" 1 'name' 'lisi'
OK
127.0.0.1:6379> get name
"lisi"
5、lua实现访问ip频率限制
编写脚本
local num = redis.call('incr',KEYS[1])//每次ip访问的时候都对这个ip为键的值加1
if (tonumber(num)==1) then//如果是新插入的 ip (键在redis中不存在 自增 后返回1),那么为这个键设置一个过期时间 (过期时间由参数传入 比如下方测试中传入 10)
redis.call('expire',KEYS[1],ARGV[1]) //设置过期时间
return 1 // 返回1 表示没有 超限 (第一次访问肯定不超限)
elseif (tonumber(num)>tonumber(ARGV[2])) then //如果返回的结果不是1 证明这个ip 的键 在redis中已经存在(不是第一次访问) 判断是否大于最大限制值,如果大于返回0 表示已经超限。
return 0
else//否则,返回1 表示不超限
return 1
end
然后执行脚本 10秒内 第11次返回为0 (表示超限了,不允许访问了) 10秒后(因为过了限制的时间)返回为1,表示不超限了
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 , 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 1
[root@192 bin]# ./redis-cli --eval "iplimitlua.lua" ip1 , 10 10
(integer) 0
遇到的问题:
执行lua脚本时 要 ./redis-cli --eval “iplimitlua.lua” ip1 , 10 10 这样来执行 (ip1 为ip地址key ,10 10 表示 10 秒内访问不能大于10次 并且逗号前后要有空格)
而不能 先执行 ./redis-cli
然后执行 -eval “iplimitlua.lua” ip1 , 10 10 这样会报错
6、lua脚本原子性验证
一个redis-cli 中执行一个死循环 如下:
[root@192 bin]# ./redis-cli
127.0.0.1:6379> eval "while true do print(1) end" 0
另一个客户端也链接 redis 执行redis操作 提示 redis is buzy .不能执行任何操作。
[root@192 bin]# ./redis-cli
127.0.0.1:6379> get name
(error) BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.
只有杀死redis服务重启后才能正常访问redis
7、jedis 执行lua脚本
两种方式 一种是直接执行脚本
一种是 将脚本缓存在服务端,以摘要的方式执行,避免每次都传递完整脚本到服务端。具体脚本如下:
public class TestLUA {
public static String 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";
public static String sha =null;
public static String loadScript() throws Exception{
//
Jedis jedis=RedisManager.getJedis();
//将脚本缓存到服务端并返回摘要信息
String sha = jedis.scriptLoad(lua);
System.out.println("sha===>"+sha);
return sha;
}
public static Object testLua1() throws Exception{
if(null==sha){
System.out.println("加载脚本");
sha=loadScript();
}
Jedis jedis=RedisManager.getJedis();
//String sha = jedis.scriptLoad(lua);
List<String> keys = new ArrayList<String>();
keys.add("ip:limit:127.0.0.1");
List<String> args = new ArrayList<String>();
args.add("30");
args.add("10");
//以摘要方式执行脚本
return jedis.eval(sha, keys, args);
}
public static Object testLua2() throws Exception{
Jedis jedis=RedisManager.getJedis();
List<String> keys = new ArrayList<String>();
keys.add("ip:limit:127.0.0.1");
List<String> args = new ArrayList<String>();
args.add("30");
args.add("10");
return jedis.eval(lua, keys, args); //这样每次都传递脚本到服务端增加网络消耗
}
public static void main(String[] args) throws Exception {
for(int i=0;i<13;i++){
System.out.println(testLua1());
//System.out.println(testLua2());
}
}
}