由于业务需要。需要设计服务限流。规则为按秒(QPS)或者按天(QPD)或者其他规则。现在设计如下:
讲appkey+method作为redis key 。value为限流规则(Hash表类型数据)。存储在reis中
List<BopRateLimitConfig> bopRateLimitConfigs = bopRateLimitConfigService.getAllBopRateLimitConfig();
if(null != bopRateLimitConfigs && bopRateLimitConfigs.size()>0){
Map<String,List<String>> configMap = new HashMap<String,List<String>>();//限流配置对象,相同的appkey+method可能有多种时间限流配置,所以创建hashmap来存储相同的配置,value为配置内容
for (BopRateLimitConfig config : bopRateLimitConfigs) {
// value.set("allRateLimitConfigs:" + config.getAppkey()+ "-" + config.getMethod() + "-" + config.getExpiretime(),config.toString());
String key = config.getAppkey() + "-" + config.getMethod();
if(configMap.containsKey(key)){
configMap.get(key).add(config.toString());//相同的appkey+method有不同的限流配置。需加到同一个key的集合中;
}else{
List<String> configs = new ArrayList<String>();
configs.add(config.toString());
configMap.put(key,configs);//存储配置
}
}
HashOperations hashMapValueOperations = redisTemplate.opsForHash();
hashMapValueOperations.putAll("allRateLimitConfigs",configMap);//入redis,存放hash类型数据
接下来根据配置的规则来判断是否限流:
HashOperations hashMapValueOperations = redisTemplate.opsForHash();
Object o = hashMapValueOperations.get("allRateLimitConfigs",appkey + "-" + method);
if(null == o){
return true;
}
List<String> configStrList = (List)o;
for(String str : configStrList){
BopRateLimitConfig bopRateLimitConfig = JacksonUtil.jsonStrToObject(str,BopRateLimitConfig.class);
if(!rateLimitForLua(bopRateLimitConfig.getAppkey(),bopRateLimitConfig.getMethod(),bopRateLimitConfig.getExpiretime(),bopRateLimitConfig.getThreshhold())){
return false;
}
}
return true;
其中 rateLimitForLua函数是具体调用lua脚本的函数,
try {
DefaultRedisScript<Boolean> redisScript = new DefaultRedisScript<>();
redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("/lua/appkeyAndMethodLimiter.lua")));
redisScript.setResultType(Boolean.class);
//组装key值
String key = getRedisKey(appkey,method,expeireTime,threshhold);
return redisTemplate.execute(redisScript, Collections.singletonList(key), expeireTime.toString(), threshhold.toString());
} catch (Exception e) {
logger.error("ratelimit 出现异常默认放行,appkey:{},method:{}",appkey,method,e);
return true;//出现异常 默认放行
}
至此 结束!
lua脚本代码:
-- key:redis中存放的key,由java程序组装传入, expiretime: 过期时间(单位秒), threshhold :阈值
local key,expiretime,threshhold = KEYS[1], ARGV[1], ARGV[2];
local currentValue=redis.call("incr", key);-- 获取redis中现有的访问次数
redis.log(redis.LOG_NOTICE, "incr "..key);
if(not currentValue or tonumber(currentValue) == 1) then -- 如果是nil 需要先set key 。值为1.再设置过期时间
redis.call("expire", key, tonumber(expiretime));
redis.log(redis.LOG_NOTICE, "expire "..key.." "..tonumber(expiretime))
return true;
else
return tonumber(currentValue) <= tonumber(threshhold);
end