|
@@ -1,6 +1,7 @@
|
|
package cn.com.qmth.examcloud.web.interceptor;
|
|
package cn.com.qmth.examcloud.web.interceptor;
|
|
|
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
|
+import java.util.Map.Entry;
|
|
import java.util.Properties;
|
|
import java.util.Properties;
|
|
|
|
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
@@ -52,6 +53,10 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
|
|
|
|
|
|
private static Properties props = new Properties();
|
|
private static Properties props = new Properties();
|
|
|
|
|
|
|
|
+ private static Map<String, RateLimiter> limiterMap = Maps.newConcurrentMap();
|
|
|
|
+
|
|
|
|
+ private static Map<String, Long> permitsPerSecondMap = Maps.newConcurrentMap();
|
|
|
|
+
|
|
static {
|
|
static {
|
|
|
|
|
|
double permitsPerSecond = PropertyHolder.getInt("examcloud.api.permitsPerSecond", 100000);
|
|
double permitsPerSecond = PropertyHolder.getInt("examcloud.api.permitsPerSecond", 100000);
|
|
@@ -61,28 +66,102 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
|
|
allowedRate = PropertyHolder.getInt("examcloud.api.flowLimited.allowedRate", 5);
|
|
allowedRate = PropertyHolder.getInt("examcloud.api.flowLimited.allowedRate", 5);
|
|
minCallRate = PropertyHolder.getInt("examcloud.api.flowLimited.minCallRate", 10);
|
|
minCallRate = PropertyHolder.getInt("examcloud.api.flowLimited.minCallRate", 10);
|
|
|
|
|
|
|
|
+ refreshConfig();
|
|
|
|
+
|
|
|
|
+ for (Entry<Object, Object> entry : props.entrySet()) {
|
|
|
|
+ String key = (String) entry.getKey();
|
|
|
|
+ String value = (String) entry.getValue();
|
|
|
|
+
|
|
|
|
+ if (key.endsWith("[S]")) {
|
|
|
|
+ Long curPermitsPerSecond = null;
|
|
|
|
+ try {
|
|
|
|
+ curPermitsPerSecond = Long.parseLong(value);
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
+ LOG.error("error value. key= " + key, e);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (curPermitsPerSecond < 0) {
|
|
|
|
+ LOG.error("value is less than 0. key= " + key);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (curPermitsPerSecond > 10000) {
|
|
|
|
+ LOG.error("value is more than 10000. key= " + key);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ RateLimiter curRateLimiter = RateLimiter.create(curPermitsPerSecond);
|
|
|
|
+ limiterMap.put(key, curRateLimiter);
|
|
|
|
+ permitsPerSecondMap.put(key, curPermitsPerSecond);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
new Thread(new Runnable() {
|
|
new Thread(new Runnable() {
|
|
@Override
|
|
@Override
|
|
public void run() {
|
|
public void run() {
|
|
|
|
|
|
while (true) {
|
|
while (true) {
|
|
- try {
|
|
|
|
- Properties newProps = new Properties();
|
|
|
|
|
|
+ refreshConfig();
|
|
|
|
+
|
|
|
|
+ for (Entry<String, Long> entry : permitsPerSecondMap.entrySet()) {
|
|
|
|
+ String key = entry.getKey();
|
|
|
|
+ Long value = entry.getValue();
|
|
|
|
+
|
|
|
|
+ String curValue = (String) props.get(key);
|
|
|
|
+ Long curPermitsPerSecond = null;
|
|
|
|
+ try {
|
|
|
|
+ curPermitsPerSecond = Long.parseLong(curValue);
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
+ LOG.error("error value. key= " + key, e);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (curPermitsPerSecond < 0) {
|
|
|
|
+ LOG.error("value is less than 0. key= " + key);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (curPermitsPerSecond > 10000) {
|
|
|
|
+ LOG.error("value is more than 10000. key= " + key);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!value.equals(curPermitsPerSecond)) {
|
|
|
|
+ RateLimiter curRateLimiter = limiterMap.get(key);
|
|
|
|
+ curRateLimiter.setRate(curPermitsPerSecond);
|
|
|
|
+ }
|
|
|
|
|
|
- PropertiesUtil.loadFromResource("limited.properties", newProps);
|
|
|
|
-
|
|
|
|
- props = newProps;
|
|
|
|
-
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- LOG.error("fail to refresh API config.", e);
|
|
|
|
}
|
|
}
|
|
- Util.sleep(10);
|
|
|
|
|
|
+
|
|
|
|
+ Util.sleep(30);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+
|
|
}).start();
|
|
}).start();
|
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ private static void refreshConfig() {
|
|
|
|
+ try {
|
|
|
|
+ Properties newProps = new Properties();
|
|
|
|
+
|
|
|
|
+ Properties nextProps = new Properties();
|
|
|
|
+
|
|
|
|
+ PropertiesUtil.loadFromResource("limited.properties", newProps);
|
|
|
|
+ for (Entry<Object, Object> entry : newProps.entrySet()) {
|
|
|
|
+ String key = (String) entry.getKey();
|
|
|
|
+ String value = (String) entry.getValue();
|
|
|
|
+ if (StringUtils.isBlank(key) || StringUtils.isBlank(value)) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ nextProps.put(key.trim(), value.trim());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ props = nextProps;
|
|
|
|
+
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ LOG.error("fail to refresh API config.", e);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
@Override
|
|
@Override
|
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
|
public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
|
Object handler) throws Exception {
|
|
Object handler) throws Exception {
|
|
@@ -121,11 +200,29 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
|
|
return true;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
+ String rateLimiterKey = apiInfo.getMapping() + "[S]";
|
|
|
|
+ RateLimiter limiter = limiterMap.get(rateLimiterKey);
|
|
|
|
+
|
|
|
|
+ if (null != limiter) {
|
|
|
|
+ acquired = limiter.tryAcquire();
|
|
|
|
+ if (!acquired) {
|
|
|
|
+
|
|
|
|
+ if (INTERFACE_LOG.isDebugEnabled()) {
|
|
|
|
+ INTERFACE_LOG.debug("[Limited]. ");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
|
|
|
|
+ ServletUtil.returnJson(new StatusResponse("503", "limited"), response);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
String expression = null;
|
|
String expression = null;
|
|
|
|
+ String expressionKey = apiInfo.getMapping() + "[E]";
|
|
try {
|
|
try {
|
|
- expression = (String) props.get(apiInfo.getMapping());
|
|
|
|
|
|
+ expression = (String) props.get(expressionKey);
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
- LOG.error("error value. key= " + apiInfo.getMapping(), e);
|
|
|
|
|
|
+ LOG.error("error value. key= " + expressionKey, e);
|
|
}
|
|
}
|
|
if (StringUtils.isBlank(expression)) {
|
|
if (StringUtils.isBlank(expression)) {
|
|
return true;
|
|
return true;
|
|
@@ -157,14 +254,14 @@ public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
|
|
double oneMinuteRate = apiStatusInfo.getOneMinuteRate();
|
|
double oneMinuteRate = apiStatusInfo.getOneMinuteRate();
|
|
|
|
|
|
int curMinCallRate = minCallRate;
|
|
int curMinCallRate = minCallRate;
|
|
- String key = apiInfo.getMapping() + "[R]";
|
|
|
|
|
|
+ String minCallRateKey = apiInfo.getMapping() + "[R]";
|
|
try {
|
|
try {
|
|
- String value = (String) props.get(key);
|
|
|
|
|
|
+ String value = (String) props.get(minCallRateKey);
|
|
if (StringUtils.isNotBlank(value)) {
|
|
if (StringUtils.isNotBlank(value)) {
|
|
curMinCallRate = Integer.parseInt(value.trim());
|
|
curMinCallRate = Integer.parseInt(value.trim());
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
- LOG.error("error value. key= " + key, e);
|
|
|
|
|
|
+ LOG.error("error value. key= " + minCallRateKey, e);
|
|
}
|
|
}
|
|
|
|
|
|
if (LOG.isDebugEnabled()) {
|
|
if (LOG.isDebugEnabled()) {
|