diff --git a/gateway-support-lib/pom.xml b/gateway-support-lib/pom.xml index 0465afa..d66586a 100644 --- a/gateway-support-lib/pom.xml +++ b/gateway-support-lib/pom.xml @@ -45,6 +45,11 @@ + + com.alibaba.csp + sentinel-core + 1.8.0 + \ No newline at end of file diff --git a/gateway-support-lib/src/main/java/cn/axzo/foundation/gateway/support/plugin/impl/RequestFilterHook.java b/gateway-support-lib/src/main/java/cn/axzo/foundation/gateway/support/plugin/impl/RequestFilterHook.java index 20aafdc..719175b 100644 --- a/gateway-support-lib/src/main/java/cn/axzo/foundation/gateway/support/plugin/impl/RequestFilterHook.java +++ b/gateway-support-lib/src/main/java/cn/axzo/foundation/gateway/support/plugin/impl/RequestFilterHook.java @@ -1,5 +1,6 @@ package cn.axzo.foundation.gateway.support.plugin.impl; +import cn.axzo.foundation.exception.BusinessException; import cn.axzo.foundation.gateway.support.entity.GateResponse; import cn.axzo.foundation.gateway.support.entity.ProxyContext; import cn.axzo.foundation.gateway.support.entity.RequestContext; @@ -8,6 +9,13 @@ import cn.axzo.foundation.gateway.support.plugin.impl.filters.*; import cn.axzo.foundation.util.FastjsonUtils; import cn.axzo.foundation.web.support.rpc.RequestParams; import cn.axzo.foundation.web.support.rpc.RpcClient; +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.Tracer; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.google.common.base.Preconditions; @@ -15,6 +23,7 @@ import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; import lombok.*; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -74,6 +83,17 @@ public class RequestFilterHook implements ProxyHook { .put("requestParamCheckFilter", new RequestParamCheckFilter()) .put("moveInputFieldFilter", new MoveInputFieldFilter()) .put("keyNoQueryFilter", new KeyNoQueryFilter()) + .put("emptyFilter", new RequestFilter() { + @Override + public JSON filterIn(RequestContext reqContext, JSON params, JSONObject config) { + return RequestFilter.super.filterIn(reqContext, params, config); + } + + @Override + public JSON filterOut(RequestContext reqContext, JSON response, JSONObject config) { + return RequestFilter.super.filterOut(reqContext, response, config); + } + }) .build(); @Builder @@ -103,9 +123,32 @@ public class RequestFilterHook implements ProxyHook { for (FilterBean bean : beans) { RequestFilter requestFilter = filterBeanResolver.apply(bean.getName()); Preconditions.checkState(requestFilter != null, bean.getName() + " 没在系统注册"); - requestBody = requestFilter.filterIn(reqContext, requestBody, bean.getConfig()); - } + FallBackConfig fallBackConfig = bean.getFallBackConfig(); + Entry entry = null; + try { + if (fallBackConfig != null) { + String resourceName = bean.getResourceName(reqContext.getRequestURI()); + fallBackConfig.registerRule(resourceName); + entry = SphU.entry(resourceName, EntryType.IN); + } + requestBody = requestFilter.filterIn(reqContext, requestBody, bean.getConfig()); + } catch (BlockException ex) { + //降级处理 + requestBody = filterBeanResolver.apply(fallBackConfig.getFallBackFilter()) + .filterIn(reqContext, requestBody, fallBackConfig.getConfig()); + } catch (Exception ex) { + //非业务异常记录trace + if (!BusinessException.class.isAssignableFrom(ex.getClass())) { + Tracer.traceEntry(ex, entry); + } + throw ex; + } finally { + if (entry != null) { + entry.exit(); + } + } + } // 只支持 json 格式 return ((RequestParams.BodyParams) postParams).toBuilder().content(requestBody).build(); } @@ -133,7 +176,31 @@ public class RequestFilterHook implements ProxyHook { for (FilterBean bean : beans) { RequestFilter requestFilter = filterBeanResolver.apply(bean.getName()); Preconditions.checkState(requestFilter != null, bean.getName() + " 没在系统注册"); - responseBody = requestFilter.filterOut(reqContext, responseBody, bean.getConfig()); + FallBackConfig fallBackConfig = bean.getFallBackConfig(); + + Entry entry = null; + try { + if (fallBackConfig != null) { + String resourceName = bean.getResourceName(reqContext.getRequestURI()); + fallBackConfig.registerRule(resourceName); + entry = SphU.entry(resourceName, EntryType.OUT); + } + responseBody = requestFilter.filterOut(reqContext, responseBody, bean.getConfig()); + } catch (BlockException ex) { + //降级处理 + responseBody = filterBeanResolver.apply(fallBackConfig.getFallBackFilter()) + .filterIn(reqContext, responseBody, fallBackConfig.getConfig()); + } catch (Exception ex) { + //非业务异常记录trace + if (!BusinessException.class.isAssignableFrom(ex.getClass())) { + Tracer.traceEntry(ex, entry); + } + throw ex; + } finally { + if (entry != null) { + entry.exit(); + } + } } GateResponse res = response.toBuilder().build(); @@ -195,5 +262,49 @@ public class RequestFilterHook implements ProxyHook { private static class FilterBean { private String name; private JSONObject config; + + public FallBackConfig getFallBackConfig() { + return Optional.ofNullable(config) + .flatMap(e -> Optional.ofNullable(e.getJSONObject("fallBack")) + .map(fallBack -> fallBack.toJavaObject(FallBackConfig.class))) + .orElse(null); + } + + public String getResourceName(String requestUrl) { + return "RequestFilterHook:" + name + "@" + requestUrl; + } + } + + @Data + @Builder + @NoArgsConstructor + @AllArgsConstructor + private static class FallBackConfig { + String fallBackFilter = "emptyFilter"; + JSONObject config; + /** 策略, 支持FLOW_GRADE_QPS慢查询比例, DEGRADE_GRADE_EXCEPTION_RATIO异常比例, DEGRADE_GRADE_EXCEPTION_COUNT异常数 */ + Integer grade = 2; + /** 默认10s内请求大于5个, 同时错误超过一半则降级30s */ + Double count = 0.5D; + Integer timeWindow = 30; + Integer minRequestAmount = 5; + /** 慢比例有效 */ + Double slowRatioThreshold = 1.0; + Integer statIntervalMs = 10000; + + public void registerRule(String resourceName) { + if (DegradeRuleManager.hasConfig(resourceName)) { + return; + } + DegradeRule degradeRule = new DegradeRule(resourceName); + degradeRule.setGrade(grade); + degradeRule.setCount(count); + degradeRule.setTimeWindow(timeWindow); + degradeRule.setMinRequestAmount(minRequestAmount); + degradeRule.setSlowRatioThreshold(slowRatioThreshold); + degradeRule.setStatIntervalMs(statIntervalMs); + + DegradeRuleManager.setRulesForResource(resourceName, ImmutableSet.of(degradeRule)); + } } } diff --git a/gateway-support-lib/src/test/java/cn/axzo/foundation/gateway/support/plugin/impl/RequestFilterHookTest.java b/gateway-support-lib/src/test/java/cn/axzo/foundation/gateway/support/plugin/impl/RequestFilterHookTest.java new file mode 100644 index 0000000..64cb226 --- /dev/null +++ b/gateway-support-lib/src/test/java/cn/axzo/foundation/gateway/support/plugin/impl/RequestFilterHookTest.java @@ -0,0 +1,57 @@ +package cn.axzo.foundation.gateway.support.plugin.impl; + + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.Tracer; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.google.common.collect.ImmutableSet; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +class RequestFilterHookTest { + + + public static void main(String[] args) { + + String resourceName = "aaaa"; + + DegradeRule degradeRule = new DegradeRule(resourceName); + degradeRule.setGrade(2); + degradeRule.setCount(0.5); + degradeRule.setTimeWindow(30); + degradeRule.setMinRequestAmount(5); + degradeRule.setStatIntervalMs(10); + + DegradeRuleManager.setRulesForResource(resourceName, ImmutableSet.of(degradeRule)); + + List blocked = IntStream.range(0, 20).mapToObj(e -> { + Entry entry = null; + try { + entry = SphU.entry(resourceName, EntryType.IN); + if (e % 2 == 0) { + throw new RuntimeException(); + } + return e; + } catch (BlockException ex) { + return -1; + } catch (Exception ex) { + Tracer.traceEntry(ex, entry); + return -2; + } finally { + if (entry != null) { + entry.exit(); + } + } + }) + .collect(Collectors.toList()); + + System.out.printf(blocked.toString()); + } + +} \ No newline at end of file