Java+Redis+Lua
Redis在2.6推出了脚本功能,允许开发者使用Lua语言编写脚本传到Redis中执行。使用脚本的好处如下:
1.减少网络开销:本来5次网络请求的操作,可以用一个请求完成,原先5次请求的逻辑放在redis服务器上完成。使用脚本,减少了网络往返时延。
2.原子操作:Redis会将整个脚本作为一个整体执行,中间不会被其他命令插入。
3.复用:客户端发送的脚本会永久存储在Redis中,意味着其他客户端可以复用这一脚本而不需要使用代码完成同样的逻辑。
高并发 原子 交互
1 redis执行可以保证lua脚本的执行的原子性
2 减少网络延迟
/**
* [times description]
* @type {[type]}
下标从1开始
*/
local times = redis.call('incr',KEYS[1])
if times == 1 then
redis.call('expire',KEYS[1], ARGV[1])
end
if times > tonumber(ARGV[2]) then
return 0
end
return 1
redis-cli --eval ratelimiting.lua rate.limitingl:127.0.0.1 , 10 3
KEYS ARGV
--eval参数是告诉redis-cli读取并运行后面的Lua脚本,ratelimiting.lua是脚本的位置,
后面跟着是传给Lua脚本的参数。其中","前的rate.limiting:127.0.0.1是要操作的键,可以再脚本中用KEYS[1]获取,
","后面的10和3是参数,在脚本中能够使用ARGV[1]和ARGV[2]获得。注:","两边的空格不能省略,否则会出错。
*********************************分布式限流器*******************************************************
local tokens_key = KEYS[1]
local timestamp_key = KEYS[2]
local rate = tonumber(ARGV[1])
local capacity = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
local requested = tonumber(ARGV[4])
local fill_time = capacity/rate
local ttl = math.floor(fill_time*2)
local last_tokens = tonumber(redis.call("get", tokens_key))
if last_tokens == nil then
last_tokens = capacity
end
local last_refreshed = tonumber(redis.call("get", timestamp_key))
if last_refreshed == nil then
last_refreshed = 0
end
local delta = math.max(0, now-last_refreshed)
local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
local allowed = filled_tokens >= requested
local new_tokens = filled_tokens
local allowed_num = 0
if allowed then
new_tokens = filled_tokens - requested
allowed_num = 1
end
redis.call("setex", tokens_key, ttl, new_tokens)
redis.call("setex", timestamp_key, ttl, now)
return { allowed_num, new_tokens }
String prefix = "rate_limiter.{" + key;
String tokenKey = prefix + "}.tokens";
String timestampKey = prefix + "}.timestamp";
return Arrays.asList(tokenKey, timestampKey);
int burstCapacity = replenishRate;
List<String> keys = getKeys(key);
List<String> scriptArgs = Arrays.asList(String.valueOf(replenishRate), String.valueOf(burstCapacity),
String.valueOf(System.currentTimeMillis() / 1000L), "1");
Object reply = jedisCluster.eval(rateLimiterScript, keys, scriptArgs);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Redis Response=" + reply);
}
if (reply != null && List.class.isAssignableFrom(reply.getClass())) {
List list = List.class.cast(reply);
if (CollectionUtils.isNotEmpty(list)) {
Object item;
return NumberUtils.toInt((item = list.get(0)) != null ? item.toString() : null, -1) > 0;
}
}
***********************************************************************************************************************************************