package com.app.mvc.common;
import com.app.mvc.config.GlobalConfig;
import com.app.mvc.config.GlobalConfigKey;
import com.app.mvc.util.DateTimeUtil;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* Created by jimin on 16/5/6.
*/
@Slf4j
public class QPSLimiter {
private final static String SWITCH_OPEN = "on";
private final static String SWITCH_CLOSED = "off";
private static final Cache<String, Integer> urlQpsCache = CacheBuilder.newBuilder() //
.expireAfterAccess(1, TimeUnit.MINUTES) // 每1分钟更新一次
.build();
/**
* 全局的限流开关是否开启
*
* @return true:开启,false:不开启
*/
public static boolean isGlobalLimitSwitchOpen() {
return GlobalConfig.getStringValue(GlobalConfigKey.GLOBAL_QPS_LIMIT_SWITCH, SWITCH_CLOSED).equals(SWITCH_OPEN);
}
/**
* 尝试请求,如果qps被限制,则等待执行
*
* @param permits
* @param key
*/
public static void acquire(int permits, String key) {
while (canAcquire(permits, key)) {
ThreadHelper.safeSleep(1000);
}
}
/**
* 是否限制qps
*
* @param permits 许可的个数, 基本都为1
* @param key 生成config配置的key, 可以为url等
* @return true:限制,false:不限制
*/
private static boolean canAcquire(int permits, String key) {
String configKey = GlobalConfigKey.QPS_LIMIT_PREFFIX + key + GlobalConfigKey.QPS_LIMIT_SUFFIX;
if (!isGlobalLimitSwitchOpen()) {
return false;
}
if (StringUtils.isEmpty(configKey)) {
return false;
}
// 先检查对应的key的是否有配置
if (StringUtils.isBlank(GlobalConfig.getStringValue(configKey, ""))) {
return false;
}
String time = DateTimeUtil.timeFrom(new Date());
String cacheKey = configKey + "." + time;
int result = acquire(permits, configKey, cacheKey);
// 如果拿到许可, 则为不限制
return !(result == permits);
}
/**
* 获得许可
*
* @param permits 许可的个数, 基本都为1
* @return 拿到许可的个数
*/
private static synchronized int acquire(int permits, String qconfigKey, String cacheKey) {
Integer count = urlQpsCache.getIfPresent(cacheKey);
if (count == null) {
// 缓存中没有,需要按照配置往cache中添加新的key和value
count = createCache(qconfigKey, cacheKey);
}
if (count >= permits) {
decreaseCacheQps(cacheKey, permits);
return permits;
}
return 0;
}
/**
* 根据qconfig中的值创建cache
*/
private static int createCache(String qconfigKey, String cacheKey) {
int qconfigQps = GlobalConfig.getIntValue(qconfigKey, 0);
if (log.isDebugEnabled()) {
log.debug("create cache, key:{}, value:{}", cacheKey, qconfigQps);
}
urlQpsCache.put(cacheKey, qconfigQps);
return qconfigQps;
}
/**
* 从cache缓存的数据中减少拿到的许可数
*
* @param permits 许可的个数, 基本都为1
*/
private static void decreaseCacheQps(String cacheKey, int permits) {
Integer cacheQps = urlQpsCache.getIfPresent(cacheKey);
if (log.isDebugEnabled()) {
log.debug("update cache, key:{}, value:{}", cacheKey, cacheQps - permits);
}
if (cacheQps == null) {
return;
}
urlQpsCache.put(cacheKey, cacheQps - permits);
}
}