|
@@ -1,23 +1,5 @@
|
|
package cn.com.qmth.examcloud.web.interceptor;
|
|
package cn.com.qmth.examcloud.web.interceptor;
|
|
|
|
|
|
-import java.util.Map;
|
|
|
|
-import java.util.Map.Entry;
|
|
|
|
-import java.util.Properties;
|
|
|
|
-
|
|
|
|
-import javax.servlet.http.HttpServletRequest;
|
|
|
|
-import javax.servlet.http.HttpServletResponse;
|
|
|
|
-
|
|
|
|
-import org.apache.commons.lang.math.RandomUtils;
|
|
|
|
-import org.apache.commons.lang3.StringUtils;
|
|
|
|
-import org.slf4j.Logger;
|
|
|
|
-import org.slf4j.LoggerFactory;
|
|
|
|
-import org.springframework.http.HttpStatus;
|
|
|
|
-import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
|
-
|
|
|
|
-import com.google.common.collect.Maps;
|
|
|
|
-import com.google.common.util.concurrent.RateLimiter;
|
|
|
|
-import com.googlecode.aviator.AviatorEvaluator;
|
|
|
|
-
|
|
|
|
import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
|
|
import cn.com.qmth.examcloud.commons.util.PropertiesUtil;
|
|
import cn.com.qmth.examcloud.commons.util.Util;
|
|
import cn.com.qmth.examcloud.commons.util.Util;
|
|
import cn.com.qmth.examcloud.web.actuator.ApiStatusInfo;
|
|
import cn.com.qmth.examcloud.web.actuator.ApiStatusInfo;
|
|
@@ -27,6 +9,21 @@ import cn.com.qmth.examcloud.web.enums.HttpServletRequestAttribute;
|
|
import cn.com.qmth.examcloud.web.support.ApiInfo;
|
|
import cn.com.qmth.examcloud.web.support.ApiInfo;
|
|
import cn.com.qmth.examcloud.web.support.ServletUtil;
|
|
import cn.com.qmth.examcloud.web.support.ServletUtil;
|
|
import cn.com.qmth.examcloud.web.support.StatusResponse;
|
|
import cn.com.qmth.examcloud.web.support.StatusResponse;
|
|
|
|
+import com.google.common.collect.Maps;
|
|
|
|
+import com.google.common.util.concurrent.RateLimiter;
|
|
|
|
+import com.googlecode.aviator.AviatorEvaluator;
|
|
|
|
+import org.apache.commons.lang.math.RandomUtils;
|
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
|
+import org.slf4j.Logger;
|
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
|
+import org.springframework.http.HttpStatus;
|
|
|
|
+import org.springframework.web.servlet.HandlerInterceptor;
|
|
|
|
+
|
|
|
|
+import javax.servlet.http.HttpServletRequest;
|
|
|
|
+import javax.servlet.http.HttpServletResponse;
|
|
|
|
+import java.util.Map;
|
|
|
|
+import java.util.Map.Entry;
|
|
|
|
+import java.util.Properties;
|
|
|
|
|
|
/**
|
|
/**
|
|
* API 流量限制截器
|
|
* API 流量限制截器
|
|
@@ -37,269 +34,271 @@ import cn.com.qmth.examcloud.web.support.StatusResponse;
|
|
*/
|
|
*/
|
|
public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
|
|
public class ApiFlowLimitedInterceptor implements HandlerInterceptor {
|
|
|
|
|
|
- private static final Logger LOG = LoggerFactory.getLogger(ApiFlowLimitedInterceptor.class);
|
|
|
|
-
|
|
|
|
- private static RateLimiter rateLimiter;
|
|
|
|
|
|
+ private static final Logger LOG = LoggerFactory.getLogger(ApiFlowLimitedInterceptor.class);
|
|
|
|
|
|
- private static boolean enable = true;
|
|
|
|
|
|
+ private static RateLimiter rateLimiter;
|
|
|
|
|
|
- private static int allowedRate = 0;
|
|
|
|
|
|
+ private static boolean enable = true;
|
|
|
|
|
|
- private static int minCallRate = 0;
|
|
|
|
|
|
+ private static int allowedRate = 0;
|
|
|
|
|
|
- private static Properties props = new Properties();
|
|
|
|
|
|
+ private static int minCallRate = 0;
|
|
|
|
|
|
- private static Map<String, RateLimiter> limiterMap = Maps.newConcurrentMap();
|
|
|
|
|
|
+ private static Properties PROPS = new Properties();
|
|
|
|
|
|
- private static Map<String, Integer> permitsPerSecondMap = Maps.newConcurrentMap();
|
|
|
|
|
|
+ private static Map<String, RateLimiter> limiterMap = Maps.newConcurrentMap();
|
|
|
|
|
|
- static {
|
|
|
|
- init();
|
|
|
|
- }
|
|
|
|
|
|
+ private static Map<String, Integer> permitsPerSecondMap = Maps.newConcurrentMap();
|
|
|
|
|
|
- private static void init() {
|
|
|
|
|
|
+ static {
|
|
|
|
+ init();
|
|
|
|
+ }
|
|
|
|
|
|
- int permitsPerSecond = PropertyHolder.getInt("examcloud.api.permitsPerSecond", 100000);
|
|
|
|
- rateLimiter = RateLimiter.create(permitsPerSecond);
|
|
|
|
|
|
+ private static void init() {
|
|
|
|
|
|
- enable = PropertyHolder.getBoolean("examcloud.api.flowLimited.enable", true);
|
|
|
|
- allowedRate = PropertyHolder.getInt("examcloud.api.flowLimited.allowedRate", 5);
|
|
|
|
- minCallRate = PropertyHolder.getInt("examcloud.api.flowLimited.minCallRate", 10);
|
|
|
|
|
|
+ int permitsPerSecond = PropertyHolder.getInt("examcloud.api.permitsPerSecond", 100000);
|
|
|
|
+ rateLimiter = RateLimiter.create(permitsPerSecond);
|
|
|
|
|
|
- refreshConfig();
|
|
|
|
|
|
+ enable = PropertyHolder.getBoolean("examcloud.api.flowLimited.enable", true);
|
|
|
|
+ allowedRate = PropertyHolder.getInt("examcloud.api.flowLimited.allowedRate", 5);
|
|
|
|
+ minCallRate = PropertyHolder.getInt("examcloud.api.flowLimited.minCallRate", 10);
|
|
|
|
|
|
- for (Entry<Object, Object> entry : props.entrySet()) {
|
|
|
|
- String key = (String) entry.getKey();
|
|
|
|
- String value = (String) entry.getValue();
|
|
|
|
|
|
+ refreshConfig();
|
|
|
|
|
|
- if (key.endsWith("[S]")) {
|
|
|
|
- Integer curPermitsPerSecond = null;
|
|
|
|
- try {
|
|
|
|
- curPermitsPerSecond = Integer.parseInt(value);
|
|
|
|
- } catch (NumberFormatException e) {
|
|
|
|
- LOG.error("error value. key= " + key, e);
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ for (Entry<Object, Object> entry : PROPS.entrySet()) {
|
|
|
|
+ String key = (String) entry.getKey();
|
|
|
|
+ String value = (String) entry.getValue();
|
|
|
|
|
|
- if (curPermitsPerSecond < 0) {
|
|
|
|
- LOG.error("value is less than 0. key= " + key);
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ if (key.endsWith("[S]")) {
|
|
|
|
+ Integer curPermitsPerSecond = null;
|
|
|
|
+ try {
|
|
|
|
+ curPermitsPerSecond = Integer.parseInt(value);
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
+ LOG.error("error value. key= " + key, e);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- if (curPermitsPerSecond > 10000) {
|
|
|
|
- LOG.error("value is more than 10000. key= " + key);
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ if (curPermitsPerSecond < 0) {
|
|
|
|
+ LOG.error("value is less than 0. key= " + key);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- RateLimiter curRateLimiter = RateLimiter.create(curPermitsPerSecond);
|
|
|
|
- limiterMap.put(key, curRateLimiter);
|
|
|
|
- permitsPerSecondMap.put(key, curPermitsPerSecond);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ if (curPermitsPerSecond > 10000) {
|
|
|
|
+ LOG.error("value is more than 10000. key= " + key);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- new Thread(new Runnable() {
|
|
|
|
- @Override
|
|
|
|
- public void run() {
|
|
|
|
|
|
+ RateLimiter curRateLimiter = RateLimiter.create(curPermitsPerSecond);
|
|
|
|
+ limiterMap.put(key, curRateLimiter);
|
|
|
|
+ permitsPerSecondMap.put(key, curPermitsPerSecond);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- while (true) {
|
|
|
|
- refreshConfig();
|
|
|
|
|
|
+ new Thread(new Runnable() {
|
|
|
|
+ @Override
|
|
|
|
+ public void run() {
|
|
|
|
|
|
- Map<String, Integer> updated = Maps.newHashMap();
|
|
|
|
|
|
+ while (true) {
|
|
|
|
+ refreshConfig();
|
|
|
|
|
|
- for (Entry<String, Integer> entry : permitsPerSecondMap.entrySet()) {
|
|
|
|
- String key = entry.getKey();
|
|
|
|
- Integer value = entry.getValue();
|
|
|
|
|
|
+ Map<String, Integer> updated = Maps.newHashMap();
|
|
|
|
|
|
- String curValue = (String) props.get(key);
|
|
|
|
- if (StringUtils.isBlank(curValue)) {
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
- Integer curPermitsPerSecond = null;
|
|
|
|
- try {
|
|
|
|
- curPermitsPerSecond = Integer.parseInt(curValue);
|
|
|
|
- } catch (NumberFormatException e) {
|
|
|
|
- LOG.error("error value. key= " + key, e);
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ for (Entry<String, Integer> entry : permitsPerSecondMap.entrySet()) {
|
|
|
|
+ String key = entry.getKey();
|
|
|
|
+ Integer value = entry.getValue();
|
|
|
|
|
|
- if (curPermitsPerSecond < 0) {
|
|
|
|
- LOG.error("value is less than 0. key= " + key);
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ String curValue = (String) PROPS.get(key);
|
|
|
|
+ if (StringUtils.isBlank(curValue)) {
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+ Integer curPermitsPerSecond = null;
|
|
|
|
+ try {
|
|
|
|
+ curPermitsPerSecond = Integer.parseInt(curValue);
|
|
|
|
+ } catch (NumberFormatException e) {
|
|
|
|
+ LOG.error("error value. key= " + key, e);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- if (curPermitsPerSecond > 10000) {
|
|
|
|
- LOG.error("value is more than 10000. key= " + key);
|
|
|
|
- continue;
|
|
|
|
- }
|
|
|
|
|
|
+ if (curPermitsPerSecond < 0) {
|
|
|
|
+ LOG.error("value is less than 0. key= " + key);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
|
|
- if (!value.equals(curPermitsPerSecond)) {
|
|
|
|
- RateLimiter curRateLimiter = limiterMap.get(key);
|
|
|
|
- curRateLimiter.setRate(curPermitsPerSecond);
|
|
|
|
- updated.put(key, curPermitsPerSecond);
|
|
|
|
- }
|
|
|
|
|
|
+ if (curPermitsPerSecond > 10000) {
|
|
|
|
+ LOG.error("value is more than 10000. key= " + key);
|
|
|
|
+ continue;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (!value.equals(curPermitsPerSecond)) {
|
|
|
|
+ RateLimiter curRateLimiter = limiterMap.get(key);
|
|
|
|
+ curRateLimiter.setRate(curPermitsPerSecond);
|
|
|
|
+ updated.put(key, curPermitsPerSecond);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ permitsPerSecondMap.putAll(updated);
|
|
|
|
+
|
|
|
|
+ Util.sleep(10);
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ }).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
|
|
|
|
+ public boolean preHandle(HttpServletRequest request,
|
|
|
|
+ HttpServletResponse response,
|
|
|
|
+ Object handler) throws Exception {
|
|
|
|
+ if (!enable) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ boolean acquired = rateLimiter.tryAcquire();
|
|
|
|
+ if (!acquired) {
|
|
|
|
+ if (LOG.isErrorEnabled()) {
|
|
|
|
+ LOG.error("[Limited]. G.");
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
|
|
|
|
+ ServletUtil.returnJson(new StatusResponse("503", "limited. G"), response);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ApiInfo apiInfo = (ApiInfo) request.getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
|
|
|
|
+ if (null == apiInfo) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ ApiStatusInfo apiStatusInfo = null;
|
|
|
|
+ try {
|
|
|
|
+ apiStatusInfo = ApiStatusInfoHolder.getApiStatusInfo(apiInfo.getMapping());
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ // ignore
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (null == apiStatusInfo) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String rateLimiterKey = apiInfo.getMapping() + "[S]";
|
|
|
|
+ RateLimiter limiter = limiterMap.get(rateLimiterKey);
|
|
|
|
+
|
|
|
|
+ if (null != limiter) {
|
|
|
|
+ acquired = limiter.tryAcquire();
|
|
|
|
+ if (!acquired) {
|
|
|
|
+
|
|
|
|
+ if (LOG.isErrorEnabled()) {
|
|
|
|
+ LOG.error("[Limited]. S. mapping=" + apiInfo.getMapping());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
|
|
|
|
+ ServletUtil.returnJson(new StatusResponse("503", "limited. S"), response);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ String expression = null;
|
|
|
|
+ String expressionKey = apiInfo.getMapping() + "[E]";
|
|
|
|
+ try {
|
|
|
|
+ expression = (String) PROPS.get(expressionKey);
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ LOG.error("error value. key= " + expressionKey, e);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (StringUtils.isBlank(expression)) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ expression = expression.trim();
|
|
|
|
+
|
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
|
+ LOG.debug("expression=" + expression + " ; mapping=" + apiInfo.getMapping());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ Map<String, Object> env = Maps.newHashMap();
|
|
|
|
+ env.put("mean", apiStatusInfo.getMean());
|
|
|
|
+ env.put("max", apiStatusInfo.getMax());
|
|
|
|
+ env.put("min", apiStatusInfo.getMin());
|
|
|
|
+ env.put("meanRate", apiStatusInfo.getMeanRate());
|
|
|
|
+ env.put("oneMinRate", apiStatusInfo.getOneMinuteRate());
|
|
|
|
+ env.put("errPercent", apiStatusInfo.getErrorPercent());
|
|
|
|
+ env.put("errMeanRate", apiStatusInfo.getErrorMeanRate());
|
|
|
|
+
|
|
|
|
+ boolean limited = false;
|
|
|
|
+ try {
|
|
|
|
+ limited = (Boolean) AviatorEvaluator.execute(expression, env, true);
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ LOG.error("fail to get value of expression. expression= " + expression, e);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (limited) {
|
|
|
|
+ int random = RandomUtils.nextInt(100);
|
|
|
|
+ if (random <= allowedRate) {
|
|
|
|
+ double oneMinuteRate = apiStatusInfo.getOneMinuteRate();
|
|
|
|
+
|
|
|
|
+ int curMinCallRate = minCallRate;
|
|
|
|
+ String minCallRateKey = apiInfo.getMapping() + "[R]";
|
|
|
|
+ try {
|
|
|
|
+ String value = (String) PROPS.get(minCallRateKey);
|
|
|
|
+ if (StringUtils.isNotBlank(value)) {
|
|
|
|
+ curMinCallRate = Integer.parseInt(value.trim());
|
|
|
|
+ }
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
+ LOG.error("error value. key= " + minCallRateKey, e);
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (LOG.isDebugEnabled()) {
|
|
|
|
+ LOG.debug(
|
|
|
|
+ "minCallRate=" + curMinCallRate + "; mapping=" + apiInfo.getMapping());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (oneMinuteRate < curMinCallRate) {
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if (LOG.isErrorEnabled()) {
|
|
|
|
+ LOG.error("[Limited]. ER. mapping=" + apiInfo.getMapping());
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
|
|
|
|
+ ServletUtil.returnJson(new StatusResponse("503", "limited. ER"), response);
|
|
|
|
+ return false;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return true;
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ @Override
|
|
|
|
+ public void afterCompletion(HttpServletRequest request,
|
|
|
|
+ HttpServletResponse response,
|
|
|
|
+ Object handler, Exception ex) throws Exception {
|
|
|
|
+
|
|
|
|
+ }
|
|
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- permitsPerSecondMap.putAll(updated);
|
|
|
|
-
|
|
|
|
- Util.sleep(10);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- }).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
|
|
|
|
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response,
|
|
|
|
- Object handler) throws Exception {
|
|
|
|
-
|
|
|
|
- if (!enable) {
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- boolean acquired = rateLimiter.tryAcquire();
|
|
|
|
- if (!acquired) {
|
|
|
|
-
|
|
|
|
- if (LOG.isErrorEnabled()) {
|
|
|
|
- LOG.error("[Limited]. G.");
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
|
|
|
|
- ServletUtil.returnJson(new StatusResponse("503", "limited. G"), response);
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- ApiInfo apiInfo = (ApiInfo) request
|
|
|
|
- .getAttribute(HttpServletRequestAttribute.$_API_INFO.name());
|
|
|
|
-
|
|
|
|
- if (null == apiInfo) {
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- ApiStatusInfo apiStatusInfo = null;
|
|
|
|
- try {
|
|
|
|
- apiStatusInfo = ApiStatusInfoHolder.getApiStatusInfo(apiInfo.getMapping());
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- // ignore
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (null == apiStatusInfo) {
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- String rateLimiterKey = apiInfo.getMapping() + "[S]";
|
|
|
|
- RateLimiter limiter = limiterMap.get(rateLimiterKey);
|
|
|
|
-
|
|
|
|
- if (null != limiter) {
|
|
|
|
- acquired = limiter.tryAcquire();
|
|
|
|
- if (!acquired) {
|
|
|
|
-
|
|
|
|
- if (LOG.isErrorEnabled()) {
|
|
|
|
- LOG.error("[Limited]. S. mapping=" + apiInfo.getMapping());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
|
|
|
|
- ServletUtil.returnJson(new StatusResponse("503", "limited. S"), response);
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- String expression = null;
|
|
|
|
- String expressionKey = apiInfo.getMapping() + "[E]";
|
|
|
|
- try {
|
|
|
|
- expression = (String) props.get(expressionKey);
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- LOG.error("error value. key= " + expressionKey, e);
|
|
|
|
- }
|
|
|
|
- if (StringUtils.isBlank(expression)) {
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
- expression = expression.trim();
|
|
|
|
-
|
|
|
|
- if (LOG.isDebugEnabled()) {
|
|
|
|
- LOG.debug("expression=" + expression + " ; mapping=" + apiInfo.getMapping());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- Map<String, Object> env = Maps.newHashMap();
|
|
|
|
- env.put("mean", apiStatusInfo.getMean());
|
|
|
|
- env.put("max", apiStatusInfo.getMax());
|
|
|
|
- env.put("min", apiStatusInfo.getMin());
|
|
|
|
- env.put("meanRate", apiStatusInfo.getMeanRate());
|
|
|
|
- env.put("oneMinRate", apiStatusInfo.getOneMinuteRate());
|
|
|
|
- env.put("errPercent", apiStatusInfo.getErrorPercent());
|
|
|
|
- env.put("errMeanRate", apiStatusInfo.getErrorMeanRate());
|
|
|
|
- boolean limited = false;
|
|
|
|
- try {
|
|
|
|
- limited = (Boolean) AviatorEvaluator.execute(expression, env, true);
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- LOG.error("fail to get value of expression. expression= " + expression, e);
|
|
|
|
- }
|
|
|
|
- if (limited) {
|
|
|
|
-
|
|
|
|
- int random = RandomUtils.nextInt(100);
|
|
|
|
- if (random <= allowedRate) {
|
|
|
|
- double oneMinuteRate = apiStatusInfo.getOneMinuteRate();
|
|
|
|
-
|
|
|
|
- int curMinCallRate = minCallRate;
|
|
|
|
- String minCallRateKey = apiInfo.getMapping() + "[R]";
|
|
|
|
- try {
|
|
|
|
- String value = (String) props.get(minCallRateKey);
|
|
|
|
- if (StringUtils.isNotBlank(value)) {
|
|
|
|
- curMinCallRate = Integer.parseInt(value.trim());
|
|
|
|
- }
|
|
|
|
- } catch (Exception e) {
|
|
|
|
- LOG.error("error value. key= " + minCallRateKey, e);
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (LOG.isDebugEnabled()) {
|
|
|
|
- LOG.debug(
|
|
|
|
- "minCallRate=" + curMinCallRate + "; mapping=" + apiInfo.getMapping());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (oneMinuteRate < curMinCallRate) {
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- if (LOG.isErrorEnabled()) {
|
|
|
|
- LOG.error("[Limited]. ER. mapping=" + apiInfo.getMapping());
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- response.setStatus(HttpStatus.SERVICE_UNAVAILABLE.value());
|
|
|
|
- ServletUtil.returnJson(new StatusResponse("503", "limited. ER"), response);
|
|
|
|
- return false;
|
|
|
|
- }
|
|
|
|
- return true;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- @Override
|
|
|
|
- public void afterCompletion(HttpServletRequest request, HttpServletResponse response,
|
|
|
|
- Object handler, Exception ex) throws Exception {
|
|
|
|
-
|
|
|
|
- }
|
|
|
|
}
|
|
}
|