人生格言:不敢冒险,才是风险!
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
- 1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。
- 2.原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。
- 3.复用:客户端发送的脚本会永久存储在Redis中,意味着其他客户端可以复用这一脚本而不需要使用代码完成同样的逻辑。
实现一个访问频率控制,某个ip在短时间内频繁访问页面,需要记录并检测出来,就可以通过Lua脚本高效的实现
在redis客户端机器上,新建一个文件ip_limit.lua,内容如下
--限流 对某个ip的访问频率进行限制,1分钟内访问十次
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客户端机器上,如何测试这个脚本呢?如下:
./redis-cli --eval "ip_limit.lua" ip:limit:127.0.0.1 , 6000 10
--eval参数是告诉redis-cli读取并运行后面的Lua脚本,ip_limit.lua是脚本的位置,后面跟着是传给Lua脚本的参数。其中","前的ip:limit:127.0.0.1是要操作的键,可以再脚本中用KEYS[1]获取,","后面的6000和10是参数,在脚本中能够使用ARGV[1]和ARGV[2]获得。注:","两边的空格不能省略,否则会出错
结合脚本的内容可知这行命令的作用是将访问频率限制为每10秒最多3次,所以在终端中不断的运行此命令会发现当访问频率在10秒内小于或等于3次时返回1,否则返回0。
测试运行如下:
image.png在Java上演示:
通过RedisManager获取到Jedis
public class RedisManager {
private static JedisPool jedisPool;
static{
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(20);
jedisPoolConfig.setMaxIdle(10);
jedisPool = new JedisPool(jedisPoolConfig,"127.0.0.1",6379);
}
public static Jedis getJedis(){
if(null != jedisPool){
return jedisPool.getResource();
}
throw new RuntimeException("JedisPool was not init!");
}
}
Redis集合Lua的Java演示代码
public class LuaDemo {
private final static String lua = "local num=redis.call('incr',KEYS[1])\n" +
"if tonumber(num)==1 then\n" +
"\tredis.call('expire',KEYS[1],ARGV[1])\n" +
"\treturn 1\n" +
"elseif tonumber(num)>tonumber(ARGV[2]) then\n" +
"\treturn 0\n" +
"else \n" +
"\treturn 1\n" +
"end";
/**
* 这是将脚本提取到外面为常量,用 jedis.evalsha()加载
*/
public static void main(String[] args) {
Jedis jedis = RedisManager.getJedis();
List<String> keys = new ArrayList<>();
keys.add("ip:limit:127.0.0.1");
List<String> arggs = new ArrayList<>();
arggs.add("6000");
arggs.add("10");
String luaLoad = jedis.scriptLoad(lua);
System.out.println(luaLoad);
Object obj = jedis.evalsha(luaLoad,keys,arggs);
System.out.println(obj);
}
/**
* 这是直接将脚本写死在代码中,用 jedis.eval()
*/
public static void method(){
Jedis jedis = RedisManager.getJedis();
String lua = "local num=redis.call('incr',KEYS[1])\n" +
"if tonumber(num)==1 then\n" +
"\tredis.call('expire',KEYS[1],ARGV[1])\n" +
"\treturn 1\n" +
"elseif tonumber(num)>tonumber(ARGV[2]) then\n" +
"\treturn 0\n" +
"else \n" +
"\treturn 1\n" +
"end";
List<String> keys = new ArrayList<>();
keys.add("ip:limit:127.0.0.1");
List<String> arggs = new ArrayList<>();
arggs.add("6000");
arggs.add("10");
Object obj = jedis.eval(lua,keys,arggs);
System.out.println(obj);
}
}
补充:
现在Lua脚本用在很多游戏上,主要是Lua脚本做到可以嵌入到其他程序中运行,游戏升级的时候,可以直接升级脚本,而不用重新安装游戏。比如游戏的很多关卡,只需要增加lua脚本,在游戏中嵌入Lua解释器,游戏团队线上更新Lua脚本,然后游戏自动下载最新的游戏关卡。例如之前很多的游戏《愤怒的小鸟》就是用Lua语言实现的关卡。
摘自《Redis入门指南》
此文章为本人原创,代码都是笔者手动敲出来的,如和其他大神博客有相同之处还望见谅!
网友评论