limit.lua文件内容如下:
local key = KEYS[1] --限流KEY(一秒一个)
local limit = tonumber(ARGV[1]) --限流大小
local current = tonumber(redis.call('get', key) or "0")
if current + 1 > limit then --如果超出限流大小
return 0
else --请求数+1,并设置2秒过期
redis.call("INCRBY", key, "1")
redis.call("expire", key, "2")
end
return 1
Java代码如下:
public static void main(String[] args) throws IOException {
String luaScript = Files.toString(new File("E:\\limit.lua"), Charset.defaultCharset());
Jedis jedis = new Jedis("*******", 6379);
jedis.auth("**********");
String key = "ip" + System.currentTimeMillis() / 1000;
String limit = "3";
for (int i = 0; i < 5 ;i ++) {
Object eval = jedis.eval(luaScript, Lists.newArrayList(key), Lists.newArrayList(limit));
System.out.println(eval);
}
}
console结果:
1
1
1
0
0
总结:
相比Redis事务来说,Lua脚本有以下优点:
减少网络开销: 不使用 Lua 的代码需要向 Redis 发送多次请求, 而脚本只需一次即可, 减少网络传输;
原子操作: Redis 将整个脚本作为一个原子执行, 无需担心并发, 也就无需事务;
复用: 脚本会永久保存 Redis 中, 其他客户端可继续使用.
分布式限流:
分布式限流最关键的是要将限流服务做成原子化,而解决方案可以使使用redis+lua或者nginx+lua技术进行实现,通过这两种技术可以实现的高并发和高性能。
首先我们来使用redis+lua实现时间窗内某个接口的请求数限流,实现了该功能后可以改造为限流总并发/请求数和限制总资源数。Lua本身就是一种编程语言,也可以使用它实现复杂的令牌桶或漏桶算法。
如下操作因是在一个lua脚本中(相当于原子操作),又因Redis是单线程模型,因此是线程安全的。
另附IP限流Lua脚本:
local key = "rate.limit:" .. KEYS[1]
local limit = tonumber(ARGV[1])
local expire_time = ARGV[2]
local is_exists = redis.call("EXISTS", key)
if is_exists == 1 then
if redis.call("INCR", key) > limit then
return 0
else
return 1
end
else
redis.call("SET", key, 1)
redis.call("EXPIRE", key, expire_time)
return 1
end