diff --git a/.gitignore b/.gitignore index 84c94f6903..44f7205303 100755 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,9 @@ out gen +# Visual Studio Code +.history/ + # Maven target/ pom.xml.tag diff --git a/.travis.yml b/.travis.yml index 0f940c4d27..158f71de31 100755 --- a/.travis.yml +++ b/.travis.yml @@ -9,11 +9,17 @@ matrix: env: BUILD_JDK=ORACLE_JDK_8 - jdk: oraclejdk11 env: BUILD_JDK=ORACLE_JDK_11 + - arch: arm64 allow_failures: - env: BUILD_JDK=ORACLE_JDK_11 # https://docs.travis-ci.com/user/languages/java/#maven-dependency-management install: + - if [ "${TRAVIS_CPU_ARCH}" == "arm64" ]; then + sudo apt-get install -y maven openjdk-11-jdk; + export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-arm64; + export PATH=$JAVA_HOME/bin:$PATH; + fi - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -DminimumPriority=1 after_success: diff --git a/README.md b/README.md index d635a97be9..2c31657374 100755 --- a/README.md +++ b/README.md @@ -11,13 +11,15 @@ ## Introduction As distributed systems become increasingly popular, the reliability between services is becoming more important than ever before. -Sentinel takes "flow" as breakthrough point, and works on multiple fields including **flow control**, **circuit breaking** and **system adaptive protection**, to guarantee reliability of microservices. +Sentinel takes "flow" as breakthrough point, and works on multiple fields including **flow control**, +**traffic shaping**, **circuit breaking** and **system adaptive protection**, to guarantee reliability and resilience for microservices. Sentinel has the following features: - **Rich applicable scenarios**: Sentinel has been wildly used in Alibaba, and has covered almost all the core-scenarios in Double-11 (11.11) Shopping Festivals in the past 10 years, such as “Second Kill” which needs to limit burst flow traffic to meet the system capacity, message peak clipping and valley fills, circuit breaking for unreliable downstream services, cluster flow control, etc. - **Real-time monitoring**: Sentinel also provides real-time monitoring ability. You can see the runtime information of a single machine in real-time, and the aggregated runtime info of a cluster with less than 500 nodes. - **Widespread open-source ecosystem**: Sentinel provides out-of-box integrations with commonly-used frameworks and libraries such as Spring Cloud, Dubbo and gRPC. You can easily use Sentinel by simply add the adapter dependency to your services. +- **Polyglot support**: Sentinel has provided native support for Java, [Go](https://github.com/alibaba/sentinel-golang) and [C++](https://github.com/alibaba/sentinel-cpp). - **Various SPI extensions**: Sentinel provides easy-to-use SPI extension interfaces that allow you to quickly customize your logic, for example, custom rule management, adapting data sources, and so on. Features overview: @@ -47,16 +49,16 @@ Below is a simple demo that guides new users to use Sentinel in just 3 steps. It ### 1. Add Dependency -**Note:** Sentinel requires Java 7 or later. +**Note:** Sentinel Core requires Java 7 or later. -If your application is build in Maven, just add the following dependency in `pom.xml`. +If your're using Maven, just add the following dependency in `pom.xml`. ```xml com.alibaba.csp sentinel-core - 1.7.1 + 1.8.0 ``` @@ -78,7 +80,7 @@ try (Entry entry = SphU.entry("HelloWorld")) { // try-with-resources auto exit ``` -So far the code modification is done. We also provide [annotation support module](https://github.com/alibaba/Sentinel/blob/master/sentinel-extension/sentinel-annotation-aspectj/README.md) to define resource easier. +So far the code modification is done. We've also provided [annotation support module](https://github.com/alibaba/Sentinel/blob/master/sentinel-extension/sentinel-annotation-aspectj/README.md) to define resource easier. ### 3. Define Rules @@ -124,6 +126,8 @@ Samples can be found in the [sentinel-demo](https://github.com/alibaba/Sentinel/ ### 5. Start Dashboard +> Note: Java 8 is required for building or running the dashboard. + Sentinel also provides a simple dashboard application, on which you can monitor the clients and configure the rules in real time. ![dashboard](https://user-images.githubusercontent.com/9434884/55449295-84866d80-55fd-11e9-94e5-d3441f4a2b63.png) @@ -139,11 +143,11 @@ All the information can be found in [logs](https://github.com/alibaba/Sentinel/w For bug report, questions and discussions please submit [GitHub Issues](https://github.com/alibaba/sentinel/issues). -Contact us: sentinel@linux.alibaba.com +Contact us via [Gitter](https://gitter.im/alibaba/Sentinel) or [Email](mailto:sentinel@linux.alibaba.com). ## Contributing -Contributions are always welcomed! Please see [CONTRIBUTING](./CONTRIBUTING.md) for detailed guidelines. +Contributions are always welcomed! Please refer to [CONTRIBUTING](./CONTRIBUTING.md) for detailed guidelines. You can start with the issues labeled with [`good first issue`](https://github.com/alibaba/Sentinel/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22). @@ -155,18 +159,21 @@ And thanks for all [contributors](https://github.com/alibaba/Sentinel/graphs/con ## Who is using -These are only part of the companies using Sentinel, for reference only. If you are using Sentinel, please [add your company here](https://github.com/alibaba/Sentinel/issues/18) to tell us your scenario to make Sentinel better :) +These are only part of the companies using Sentinel, for reference only. +If you are using Sentinel, please [add your company here](https://github.com/alibaba/Sentinel/issues/18) to tell us your scenario to make Sentinel better :) ![Alibaba Group](https://docs.alibabagroup.com/assets2/images/en/global/logo_header.png) +![AntFin](https://user-images.githubusercontent.com/9434884/90598732-30961c00-e226-11ea-8c86-0b1d7f7875c7.png) ![Taiping Renshou](http://www.cntaiping.com/tplresource/cms/www/taiping/img/home_new/tp_logo_img.png) +![拼多多](http://cdn.pinduoduo.com/assets/img/pdd_logo_v3.png) +![爱奇艺](https://user-images.githubusercontent.com/9434884/90598445-a51c8b00-e225-11ea-9327-3543525f3f2a.png) ![Shunfeng Technology](https://user-images.githubusercontent.com/9434884/48463502-2f48eb80-e817-11e8-984f-2f9b1b789e2d.png) -![Mandao](https://user-images.githubusercontent.com/9434884/48463559-6cad7900-e817-11e8-87e4-42952b074837.png) -![每日优鲜](https://home.missfresh.cn/statics/img/logo.png) ![二维火](https://user-images.githubusercontent.com/9434884/49358468-bc43de00-f70d-11e8-97fe-0bf05865f29f.png) +![Mandao](https://user-images.githubusercontent.com/9434884/48463559-6cad7900-e817-11e8-87e4-42952b074837.png) ![文轩在线](http://static.winxuancdn.com/css/v2/images/logo.png) ![客如云](https://www.keruyun.com/static/krynew/images/logo.png) ![亲宝宝](https://stlib.qbb6.com/wclt/img/home_hd/version1/title_logo.png) ![杭州光云科技](https://www.raycloud.com/images/logo.png) ![金汇金融](https://res.jinhui365.com/r/images/logo2.png?v=1.527) ![闪电购](http://cdn.52shangou.com/shandianbang/official-source/3.1.1/build/images/logo.png) -![拼多多](http://cdn.pinduoduo.com/assets/img/pdd_logo_v3.png) + diff --git a/doc/image/sentinel-opensource-eco-landscape-en.png b/doc/image/sentinel-opensource-eco-landscape-en.png index fbf0314e1d..a00ec3edaa 100644 Binary files a/doc/image/sentinel-opensource-eco-landscape-en.png and b/doc/image/sentinel-opensource-eco-landscape-en.png differ diff --git a/pom.xml b/pom.xml index 2d80de934b..6a6cc820d6 100755 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.alibaba.csp sentinel-parent - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT pom ${project.artifactId} @@ -41,11 +41,12 @@ - 1.2.62 + 1.2.71 + 1.3.2 4.12 - 2.21.0 + 2.28.2 3.12.1 3.1.5 2.0.0 @@ -71,11 +72,14 @@ sentinel-extension sentinel-transport sentinel-adapter + sentinel-cluster + sentinel-logging + sentinel-dashboard sentinel-demo sentinel-benchmark - sentinel-cluster + @@ -95,6 +99,11 @@ sentinel-annotation-aspectj ${project.version} + + com.alibaba.csp + sentinel-annotation-cdi-interceptor + ${project.version} + com.alibaba.csp sentinel-parameter-flow-control @@ -263,6 +272,7 @@ @{argLine} -Xms1024m -Xmx2048m + -Dfile.encoding=UTF-8 false diff --git a/sentinel-adapter/pom.xml b/sentinel-adapter/pom.xml index d1bdc9e63c..2b35586da5 100755 --- a/sentinel-adapter/pom.xml +++ b/sentinel-adapter/pom.xml @@ -7,7 +7,7 @@ com.alibaba.csp sentinel-parent - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT sentinel-adapter pom @@ -18,6 +18,7 @@ sentinel-web-servlet sentinel-dubbo-adapter sentinel-apache-dubbo-adapter + sentinel-apache-httpclient-adapter sentinel-sofa-rpc-adapter sentinel-grpc-adapter sentinel-zuul-adapter @@ -26,6 +27,10 @@ sentinel-api-gateway-adapter-common sentinel-spring-cloud-gateway-adapter sentinel-spring-webmvc-adapter + sentinel-zuul2-adapter + sentinel-okhttp-adapter + sentinel-jax-rs-adapter + sentinel-quarkus-adapter diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/README.md b/sentinel-adapter/sentinel-apache-dubbo-adapter/README.md index fe97e9f48b..9d232bef03 100755 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/README.md +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/README.md @@ -1,4 +1,4 @@ -# Sentinel Apache Dubbo Adapter +# Sentinel Apache Dubbo Adapter (for 2.7.x+) > Note: 中文文档请见[此处](https://github.com/alibaba/Sentinel/wiki/主流框架的适配#dubbo)。 @@ -21,7 +21,7 @@ To use Sentinel Dubbo Adapter, you can simply add the following dependency to yo The Sentinel filters are **enabled by default**. Once you add the dependency, the Dubbo services and methods will become protected resources in Sentinel, which can leverage Sentinel's flow control and guard ability when rules are configured. -Demos can be found in [sentinel-demo-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-dubbo). +Demos can be found in [sentinel-demo-apache-dubbo](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-apache-dubbo). If you don't want the filters enabled, you can manually disable them. For example: @@ -37,8 +37,8 @@ For more details of Dubbo filter, see [here](http://dubbo.apache.org/en-us/docs/ The resource for Dubbo services has two granularities: service interface and service method. -- Service interface:resourceName format is `interfaceName`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService` -- Service method:resourceName format is `interfaceName:methodSignature`,e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)` +- Service interface: resourceName format is `interfaceName`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService` +- Service method: resourceName format is `interfaceName:methodSignature`, e.g. `com.alibaba.csp.sentinel.demo.dubbo.FooService:sayHello(java.lang.String)` ## Flow control based on caller @@ -52,17 +52,21 @@ If `limitApp` of flow rules is not configured (`default`), flow control will tak If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller. > Note: Dubbo consumer does not provide its Dubbo application name when doing RPC, -so developers should manually put the application name into *attachment* at consumer side, -then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`) -where consumer can carry application name information to provider automatically. -If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller, developers can manually put the application name into attachment with the key `dubboApplication`. +> so developers should manually put the application name into *attachment* at consumer side, +> then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`) +> where consumer can carry application name information to provider automatically. +> If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller, +> developers can manually put the application name into attachment with the key `dubboApplication`. +> +> Since 1.8.0, the adapter provides support for customizing origin parsing logic. You may register your own `DubboOriginParser` +> implementation to `DubboAdapterGlobalConfig`. ## Global fallback Sentinel Dubbo Adapter supports global fallback configuration. The global fallback will handle exceptions and give replacement result when blocked by flow control, degrade or system load protection. You can implement your own `DubboFallback` interface -and then register to `DubboFallbackRegistry`. If no fallback is configured, Sentinel will wrap the `BlockException` -then directly throw it out. +and then register to `DubboAdapterGlobalConfig`. +If no fallback is configured, Sentinel will wrap the `BlockException` as the fallback result. Besides, we can also leverage [Dubbo mock mechanism](http://dubbo.apache.org/en-us/docs/user/demos/local-mock.html) to provide fallback implementation of degraded Dubbo services. \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml b/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml index 44f799db5e..cc7d379969 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml @@ -5,7 +5,7 @@ sentinel-adapter com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/BaseSentinelDubboFilter.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/BaseSentinelDubboFilter.java index bc2101ffb2..8d4009f23a 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/BaseSentinelDubboFilter.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/BaseSentinelDubboFilter.java @@ -16,66 +16,34 @@ package com.alibaba.csp.sentinel.adapter.dubbo; -import com.alibaba.csp.sentinel.Entry; -import com.alibaba.csp.sentinel.Tracer; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; -import com.alibaba.csp.sentinel.context.ContextUtil; -import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.constants.CommonConstants; -import org.apache.dubbo.rpc.*; +import org.apache.dubbo.rpc.Filter; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; /** - * Base Class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}. + * Base class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}. * * @author Zechao Zheng */ +public abstract class BaseSentinelDubboFilter implements Filter { -public abstract class BaseSentinelDubboFilter extends ListenableFilter { - public BaseSentinelDubboFilter() { - this.listener = new SentinelDubboListener(); - } - static class SentinelDubboListener implements Listener { + /** + * Get method name of dubbo rpc + * + * @param invoker + * @param invocation + * @return + */ + abstract String getMethodName(Invoker invoker, Invocation invocation, String prefix); - public void onResponse(Result appResponse, Invoker invoker, Invocation invocation) { - onSuccess(appResponse, invoker); - } + /** + * Get interface name of dubbo rpc + * + * @param invoker + * @return + */ + abstract String getInterfaceName(Invoker invoker, String prefix); - //for compatible dubbo 2.7.5 rename onResponse to onMessage - public void onMessage(Result appResponse, Invoker invoker, Invocation invocation) { - onSuccess(appResponse, invoker); - } - private void onSuccess(Result appResponse, Invoker invoker) { - if (DubboConfig.getDubboBizExceptionTraceEnabled()) { - traceAndExit(appResponse.getException(), invoker.getUrl()); - } else { - traceAndExit(null, invoker.getUrl()); - } - } - - @Override - public void onError(Throwable t, Invoker invoker, Invocation invocation) { - traceAndExit(t, invoker.getUrl()); - } - - } - - static void traceAndExit(Throwable throwable, URL url) { - Entry interfaceEntry = (Entry) RpcContext.getContext().get(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY); - Entry methodEntry = (Entry) RpcContext.getContext().get(DubboUtils.DUBBO_METHOD_ENTRY_KEY); - if (methodEntry != null) { - Tracer.traceEntry(throwable, methodEntry); - methodEntry.exit(); - RpcContext.getContext().remove(DubboUtils.DUBBO_METHOD_ENTRY_KEY); - } - if (interfaceEntry != null) { - Tracer.traceEntry(throwable, interfaceEntry); - interfaceEntry.exit(); - RpcContext.getContext().remove(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY); - } - if (CommonConstants.PROVIDER_SIDE.equals(url.getParameter(CommonConstants.SIDE_KEY))) { - ContextUtil.exit(); - } - } } diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java index d2c325fafd..f9da92a507 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtils.java @@ -15,7 +15,7 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.util.StringUtil; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; @@ -26,8 +26,6 @@ public final class DubboUtils { public static final String SENTINEL_DUBBO_APPLICATION_KEY = "dubboApplication"; - public static final String DUBBO_METHOD_ENTRY_KEY = "dubboMethodEntry"; - public static final String DUBBO_INTERFACE_ENTRY_KEY = "dubboInterfaceEntry"; public static String getApplication(Invocation invocation, String defaultValue) { if (invocation == null || invocation.getAttachments() == null) { @@ -36,11 +34,11 @@ public static String getApplication(Invocation invocation, String defaultValue) return invocation.getAttachment(SENTINEL_DUBBO_APPLICATION_KEY, defaultValue); } - public static String getResourceName(Invoker invoker, Invocation invocation){ - return getResourceName(invoker, invocation, false); + public static String getMethodResourceName(Invoker invoker, Invocation invocation){ + return getMethodResourceName(invoker, invocation, false); } - public static String getResourceName(Invoker invoker, Invocation invocation, Boolean useGroupAndVersion) { + public static String getMethodResourceName(Invoker invoker, Invocation invocation, Boolean useGroupAndVersion) { StringBuilder buf = new StringBuilder(64); String interfaceResource = useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName(); buf.append(interfaceResource) @@ -59,16 +57,39 @@ public static String getResourceName(Invoker invoker, Invocation invocation, return buf.toString(); } - public static String getResourceName(Invoker invoker, Invocation invocation, String prefix) { + public static String getMethodResourceName(Invoker invoker, Invocation invocation, String prefix) { if (StringUtil.isNotBlank(prefix)) { return new StringBuilder(64) .append(prefix) - .append(getResourceName(invoker, invocation, DubboConfig.getDubboInterfaceGroupAndVersionEnabled())) + .append(getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled())) .toString(); } else { - return getResourceName(invoker, invocation, DubboConfig.getDubboInterfaceGroupAndVersionEnabled()); + return getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()); } } + + + public static String getInterfaceName(Invoker invoker) { + return getInterfaceName(invoker, false); + } + + public static String getInterfaceName(Invoker invoker, Boolean useGroupAndVersion) { + StringBuilder buf = new StringBuilder(64); + return useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName(); + } + + public static String getInterfaceName(Invoker invoker, String prefix) { + if (StringUtil.isNotBlank(prefix)) { + return new StringBuilder(64) + .append(prefix) + .append(getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled())) + .toString(); + } else { + return getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboInterfaceGroupAndVersionEnabled()); + } + } + + private DubboUtils() { } } diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java index ffa9fba9cf..02b8c5ad91 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java @@ -15,23 +15,19 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo; -import com.alibaba.csp.sentinel.Entry; -import com.alibaba.csp.sentinel.EntryType; -import com.alibaba.csp.sentinel.ResourceTypeConstants; -import com.alibaba.csp.sentinel.SphU; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; -import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry; +import com.alibaba.csp.sentinel.*; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; + import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.InvokeMode; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.RpcContext; -import org.apache.dubbo.rpc.RpcException; +import org.apache.dubbo.rpc.*; import org.apache.dubbo.rpc.support.RpcUtils; +import java.util.LinkedList; +import java.util.Optional; +import java.util.function.BiConsumer; + import static org.apache.dubbo.common.constants.CommonConstants.CONSUMER; /** @@ -44,6 +40,7 @@ * * @author Carpenter Lee * @author Eric Zhao + * @author Lin Liang */ @Activate(group = CONSUMER) public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter { @@ -52,33 +49,106 @@ public SentinelDubboConsumerFilter() { RecordLog.info("Sentinel Apache Dubbo consumer filter initialized"); } + @Override + String getMethodName(Invoker invoker, Invocation invocation, String prefix) { + return DubboUtils.getMethodResourceName(invoker, invocation, prefix); + } + + @Override + String getInterfaceName(Invoker invoker, String prefix) { + return DubboUtils.getInterfaceName(invoker, prefix); + } + @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { + InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); + if (InvokeMode.SYNC == invokeMode) { + return syncInvoke(invoker, invocation); + } else { + return asyncInvoke(invoker, invocation); + } + } + + private Result syncInvoke(Invoker invoker, Invocation invocation) { Entry interfaceEntry = null; Entry methodEntry = null; - RpcContext rpcContext = RpcContext.getContext(); + String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); + String interfaceResourceName = getInterfaceName(invoker, prefix); + String methodResourceName = getMethodName(invoker, invocation, prefix); try { - String methodResourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix()); - String interfaceResourceName = DubboConfig.getDubboInterfaceGroupAndVersionEnabled() ? invoker.getUrl().getColonSeparatedKey() - : invoker.getInterface().getName(); - InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); - - if (InvokeMode.SYNC == invokeMode) { - interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); - rpcContext.set(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY, interfaceEntry); - methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, invocation.getArguments()); - } else { - // should generate the AsyncEntry when the invoke model in future or async - interfaceEntry = SphU.asyncEntry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); - rpcContext.set(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY, interfaceEntry); - methodEntry = SphU.asyncEntry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, 1, invocation.getArguments()); + interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); + methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT, + invocation.getArguments()); + Result result = invoker.invoke(invocation); + if (result.hasException()) { + Tracer.traceEntry(result.getException(), interfaceEntry); + Tracer.traceEntry(result.getException(), methodEntry); + } + return result; + } catch (BlockException e) { + return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); + } catch (RpcException e) { + Tracer.traceEntry(e, interfaceEntry); + Tracer.traceEntry(e, methodEntry); + throw e; + } finally { + if (methodEntry != null) { + methodEntry.exit(1, invocation.getArguments()); } - rpcContext.set(DubboUtils.DUBBO_METHOD_ENTRY_KEY, methodEntry); - return invoker.invoke(invocation); + if (interfaceEntry != null) { + interfaceEntry.exit(); + } + } + } + + private Result asyncInvoke(Invoker invoker, Invocation invocation) { + LinkedList queue = new LinkedList<>(); + String prefix = DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey(); + String interfaceResourceName = getInterfaceName(invoker, prefix); + String methodResourceName = getMethodName(invoker, invocation, prefix); + try { + queue.push(new EntryHolder( + SphU.asyncEntry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT), null)); + queue.push(new EntryHolder( + SphU.asyncEntry(methodResourceName, ResourceTypeConstants.COMMON_RPC, + EntryType.OUT, 1, invocation.getArguments()), invocation.getArguments())); + Result result = invoker.invoke(invocation); + result.whenCompleteWithContext((r, throwable) -> { + Throwable error = throwable; + if (error == null) { + error = Optional.ofNullable(r).map(Result::getException).orElse(null); + } + while (!queue.isEmpty()) { + EntryHolder holder = queue.pop(); + Tracer.traceEntry(error, holder.entry); + exitEntry(holder); + } + }); + return result; } catch (BlockException e) { - return DubboFallbackRegistry.getConsumerFallback().handle(invoker, invocation, e); + while (!queue.isEmpty()) { + exitEntry(queue.pop()); + } + return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); } } -} + static class EntryHolder { + final private Entry entry; + final private Object[] params; + + public EntryHolder(Entry entry, Object[] params) { + this.entry = entry; + this.params = params; + } + } + + private void exitEntry(EntryHolder holder) { + if (holder.params != null) { + holder.entry.exit(1, holder.params); + } else { + holder.entry.exit(); + } + } +} diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java index c919978694..867073e147 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java @@ -15,20 +15,16 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo; -import com.alibaba.csp.sentinel.Entry; -import com.alibaba.csp.sentinel.EntryType; -import com.alibaba.csp.sentinel.ResourceTypeConstants; -import com.alibaba.csp.sentinel.SphU; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; -import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry; +import com.alibaba.csp.sentinel.*; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; + import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.RpcException; import static org.apache.dubbo.common.constants.CommonConstants.PROVIDER; @@ -52,30 +48,57 @@ public SentinelDubboProviderFilter() { RecordLog.info("Sentinel Apache Dubbo provider filter initialized"); } + @Override + String getMethodName(Invoker invoker, Invocation invocation, String prefix) { + return DubboUtils.getMethodResourceName(invoker, invocation, prefix); + } + + @Override + String getInterfaceName(Invoker invoker, String prefix) { + return DubboUtils.getInterfaceName(invoker, prefix); + } + @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { // Get origin caller. - String application = DubboUtils.getApplication(invocation, ""); - RpcContext rpcContext = RpcContext.getContext(); + String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation); + if (null == origin) { + origin = ""; + } Entry interfaceEntry = null; Entry methodEntry = null; + String prefix = DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey(); + String interfaceResourceName = getInterfaceName(invoker, prefix); + String methodResourceName = getMethodName(invoker, invocation, prefix); try { - String methodResourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix()); - String interfaceResourceName = DubboConfig.getDubboInterfaceGroupAndVersionEnabled() ? invoker.getUrl().getColonSeparatedKey() - : invoker.getInterface().getName(); // Only need to create entrance context at provider side, as context will take effect // at entrance of invocation chain only (for inbound traffic). - ContextUtil.enter(methodResourceName, application); + ContextUtil.enter(methodResourceName, origin); interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); - rpcContext.set(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY, interfaceEntry); - methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments()); - rpcContext.set(DubboUtils.DUBBO_METHOD_ENTRY_KEY, methodEntry); - return invoker.invoke(invocation); + methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, + invocation.getArguments()); + Result result = invoker.invoke(invocation); + if (result.hasException()) { + Tracer.traceEntry(result.getException(), interfaceEntry); + Tracer.traceEntry(result.getException(), methodEntry); + } + return result; } catch (BlockException e) { - return DubboFallbackRegistry.getProviderFallback().handle(invoker, invocation, e); + return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e); + } catch (RpcException e) { + Tracer.traceEntry(e, interfaceEntry); + Tracer.traceEntry(e, methodEntry); + throw e; + } finally { + if (methodEntry != null) { + methodEntry.exit(1, invocation.getArguments()); + } + if (interfaceEntry != null) { + interfaceEntry.exit(); + } + ContextUtil.exit(); } } - } diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboAdapterGlobalConfig.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboAdapterGlobalConfig.java new file mode 100644 index 0000000000..43664f0a38 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboAdapterGlobalConfig.java @@ -0,0 +1,116 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.dubbo.config; + +import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback; +import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback; +import com.alibaba.csp.sentinel.adapter.dubbo.origin.DefaultDubboOriginParser; +import com.alibaba.csp.sentinel.adapter.dubbo.origin.DubboOriginParser; +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; + +/** + *

+ * Responsible for dubbo service provider, consumer attribute configuration + *

+ * + * @author lianglin + * @since 1.7.0 + */ +public final class DubboAdapterGlobalConfig { + + private static final String TRUE_STR = "true"; + + public static final String DUBBO_RES_NAME_WITH_PREFIX_KEY = "csp.sentinel.dubbo.resource.use.prefix"; + public static final String DUBBO_PROVIDER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.provider.prefix"; + public static final String DUBBO_CONSUMER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.consumer.prefix"; + + private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:"; + private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:"; + + public static final String DUBBO_INTERFACE_GROUP_VERSION_ENABLED = "csp.sentinel.dubbo.interface.group.version.enabled"; + + private static volatile DubboFallback consumerFallback = new DefaultDubboFallback(); + private static volatile DubboFallback providerFallback = new DefaultDubboFallback(); + private static volatile DubboOriginParser originParser = new DefaultDubboOriginParser(); + + public static boolean isUsePrefix() { + return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_RES_NAME_WITH_PREFIX_KEY)); + } + + public static String getDubboProviderResNamePrefixKey() { + if (isUsePrefix()) { + String config = SentinelConfig.getConfig(DUBBO_PROVIDER_RES_NAME_PREFIX_KEY); + return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX; + } + return null; + } + + public static String getDubboConsumerResNamePrefixKey() { + if (isUsePrefix()) { + String config = SentinelConfig.getConfig(DUBBO_CONSUMER_RES_NAME_PREFIX_KEY); + return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX; + } + return null; + } + + public static Boolean getDubboInterfaceGroupAndVersionEnabled() { + return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED)); + } + + public static DubboFallback getConsumerFallback() { + return consumerFallback; + } + + public static void setConsumerFallback(DubboFallback consumerFallback) { + AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); + DubboAdapterGlobalConfig.consumerFallback = consumerFallback; + } + + public static DubboFallback getProviderFallback() { + return providerFallback; + } + + public static void setProviderFallback(DubboFallback providerFallback) { + AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); + DubboAdapterGlobalConfig.providerFallback = providerFallback; + } + + /** + * Get the origin parser of Dubbo adapter. + * + * @return the origin parser + * @since 1.8.0 + */ + public static DubboOriginParser getOriginParser() { + return originParser; + } + + /** + * Set the origin parser of Dubbo adapter. + * + * @param originParser the origin parser + * @since 1.8.0 + */ + public static void setOriginParser(DubboOriginParser originParser) { + AssertUtil.notNull(originParser, "originParser cannot be null"); + DubboAdapterGlobalConfig.originParser = originParser; + } + + private DubboAdapterGlobalConfig() {} + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java deleted file mode 100644 index 9b2b021828..0000000000 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.csp.sentinel.adapter.dubbo.config; - -import com.alibaba.csp.sentinel.config.SentinelConfig; -import com.alibaba.csp.sentinel.util.StringUtil; - -/** - *

- * Responsible for dubbo service provider, consumer attribute configuration - *

- * - * @author lianglin - * @since 1.7.0 - */ -public final class DubboConfig { - - public static final String DUBBO_USE_PREFIX = "csp.sentinel.dubbo.resource.use.prefix"; - private static final String TRUE_STR = "true"; - - public static final String DUBBO_PROVIDER_PREFIX = "csp.sentinel.dubbo.resource.provider.prefix"; - public static final String DUBBO_CONSUMER_PREFIX = "csp.sentinel.dubbo.resource.consumer.prefix"; - - private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:"; - private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:"; - - public static final String DUBBO_INTERFACE_GROUP_VERSION_ENABLED = "csp.sentinel.dubbo.interface.group.version.enabled"; - - public static final String TRACE_BIZ_EXCEPTION_ENABLED = "csp.sentinel.dubbo.trace.biz.exception.enabled"; - - - public static boolean isUsePrefix() { - return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_USE_PREFIX)); - } - - public static String getDubboProviderPrefix() { - if (isUsePrefix()) { - String config = SentinelConfig.getConfig(DUBBO_PROVIDER_PREFIX); - return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX; - } - return null; - } - - public static String getDubboConsumerPrefix() { - if (isUsePrefix()) { - String config = SentinelConfig.getConfig(DUBBO_CONSUMER_PREFIX); - return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX; - } - return null; - } - - public static Boolean getDubboInterfaceGroupAndVersionEnabled() { - return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED)); - } - - public static Boolean getDubboBizExceptionTraceEnabled() { - String traceBizExceptionEnabled = SentinelConfig.getConfig(TRACE_BIZ_EXCEPTION_ENABLED); - if (StringUtil.isNotBlank(traceBizExceptionEnabled)) { - return TRUE_STR.equalsIgnoreCase(traceBizExceptionEnabled); - } - return true; - } - -} diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java index 8a4659f7be..d5099b1d6a 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java @@ -16,8 +16,8 @@ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; +import org.apache.dubbo.rpc.AsyncRpcResult; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.Result; @@ -29,7 +29,7 @@ public class DefaultDubboFallback implements DubboFallback { @Override public Result handle(Invoker invoker, Invocation invocation, BlockException ex) { - // Just wrap and throw the exception. - throw new SentinelRpcException(ex); + // Just wrap the exception. + return AsyncRpcResult.newDefaultAsyncResult(ex.toRuntimeException(), invocation); } } diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java index 78c3a0b191..a9fb1f5a44 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java @@ -15,39 +15,31 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; -import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; /** *

Global fallback registry for Dubbo.

* - *

- * Note: Circuit breaking is mainly designed for consumer. The provider should not - * give fallback result in most circumstances. - *

- * * @author Eric Zhao + * @deprecated use {@link DubboAdapterGlobalConfig} instead since 1.8.0. */ +@Deprecated public final class DubboFallbackRegistry { - private static volatile DubboFallback consumerFallback = new DefaultDubboFallback(); - private static volatile DubboFallback providerFallback = new DefaultDubboFallback(); - public static DubboFallback getConsumerFallback() { - return consumerFallback; + return DubboAdapterGlobalConfig.getConsumerFallback(); } public static void setConsumerFallback(DubboFallback consumerFallback) { - AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); - DubboFallbackRegistry.consumerFallback = consumerFallback; + DubboAdapterGlobalConfig.setConsumerFallback(consumerFallback); } public static DubboFallback getProviderFallback() { - return providerFallback; + return DubboAdapterGlobalConfig.getProviderFallback(); } public static void setProviderFallback(DubboFallback providerFallback) { - AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); - DubboFallbackRegistry.providerFallback = providerFallback; + DubboAdapterGlobalConfig.setProviderFallback(providerFallback); } private DubboFallbackRegistry() {} diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DefaultDubboOriginParser.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DefaultDubboOriginParser.java new file mode 100644 index 0000000000..5a84fc189e --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DefaultDubboOriginParser.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.dubbo.origin; + +import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; + +/** + * Default Dubbo origin parser. + * + * @author jingzian + */ +public class DefaultDubboOriginParser implements DubboOriginParser { + + @Override + public String parse(Invoker invoker, Invocation invocation) { + return DubboUtils.getApplication(invocation, ""); + } + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginParser.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginParser.java new file mode 100644 index 0000000000..234ce1c3f0 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginParser.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.dubbo.origin; + +import com.alibaba.csp.sentinel.context.Context; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; + +/** + * Customized origin parser for Dubbo provider filter.{@link Context#getOrigin()} + * + * @author jingzian + */ +public interface DubboOriginParser { + + /** + * Parses the origin (caller) from Dubbo invocation. + * + * @param invoker Dubbo invoker + * @param invocation Dubbo invocation + * @return the parsed origin + */ + String parse(Invoker invoker, Invocation invocation); + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java index 7793a6567e..db78c3666a 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/BaseTest.java @@ -15,26 +15,19 @@ */ package com.alibaba.csp.sentinel; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; -import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; +import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; -import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.constants.CommonConstants; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.RpcContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - /** * Base test class, provide common methods for subClass * The package is same as CtSph, to call CtSph.resetChainMap() method for test @@ -42,46 +35,41 @@ * Note: Only for test. DO NOT USE IN PRODUCTION! * * @author cdfive + * @author lianglin */ public class BaseTest { - protected Invoker invoker; - protected Invocation invocation; - - public void constructInvokerAndInvocation() { - invoker = mock(Invoker.class); - URL url = URL.valueOf("dubbo://127.0.0.1:2181") - .addParameter(CommonConstants.VERSION_KEY, "1.0.0") - .addParameter(CommonConstants.GROUP_KEY, "grp1") - .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); - when(invoker.getUrl()).thenReturn(url); - when(invoker.getInterface()).thenReturn(DemoService.class); - - invocation = mock(Invocation.class); - Method method = DemoService.class.getMethods()[0]; - when(invocation.getMethodName()).thenReturn(method.getName()); - when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); - - } - /** * Clean up resources for context, clusterNodeMap, processorSlotChainMap */ - protected static void cleanUpAll() { + public void cleanUpAll() { try { - RpcContext.removeContext(); - ClusterBuilderSlot.getClusterNodeMap().clear(); - CtSph.resetChainMap(); - Method method = ContextUtil.class.getDeclaredMethod("resetContextMap"); - method.setAccessible(true); - method.invoke(null, null); - ContextUtil.exit(); - SentinelConfig.setConfig(DubboConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "true"); - FlowRuleManager.loadRules(new ArrayList<>()); - DegradeRuleManager.loadRules(new ArrayList<>()); + clearDubboContext(); + cleanUpCstContext(); } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } + + private void cleanUpCstContext() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { + ClusterBuilderSlot.getClusterNodeMap().clear(); + CtSph.resetChainMap(); + Method method = ContextUtil.class.getDeclaredMethod("resetContextMap"); + method.setAccessible(true); + method.invoke(null, null); + ContextUtil.exit(); + FlowRuleManager.loadRules(new ArrayList<>()); + DegradeRuleManager.loadRules(new ArrayList<>()); + } + + private void clearDubboContext() { + SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); + DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); + RpcContext.removeContext(); + + } } \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/DubboTestUtil.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/DubboTestUtil.java new file mode 100644 index 0000000000..326f216b24 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/DubboTestUtil.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel; + +import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; + +import java.lang.reflect.Method; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * @author lianglin + */ +public class DubboTestUtil { + + + public static Class DEFAULT_TEST_SERVICE = DemoService.class; + public static Method DEFAULT_TEST_METHOD_ONE = DEFAULT_TEST_SERVICE.getMethods()[0]; + public static Method DEFAULT_TEST_METHOD_TWO = DEFAULT_TEST_SERVICE.getMethods()[1]; + + public static Invoker getMockInvoker(URL url, Class cls) { + Invoker invoker = mock(Invoker.class); + when(invoker.getUrl()).thenReturn(url); + when(invoker.getInterface()).thenReturn(cls); + return invoker; + } + + public static Invoker getDefaultMockInvoker() { + return getMockInvoker(getDefaultTestURL(), DEFAULT_TEST_SERVICE); + } + + public static Invocation getMockInvocation(Method method) { + Invocation invocation = mock(Invocation.class); + when(invocation.getMethodName()).thenReturn(method.getName()); + when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); + return invocation; + } + + public static Invocation getDefaultMockInvocationOne() { + Invocation invocation = mock(Invocation.class); + when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_ONE.getName()); + when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_ONE.getParameterTypes()); + return invocation; + } + + public static Invocation getDefaultMockInvocationTwo() { + Invocation invocation = mock(Invocation.class); + when(invocation.getMethodName()).thenReturn(DEFAULT_TEST_METHOD_TWO.getName()); + when(invocation.getParameterTypes()).thenReturn(DEFAULT_TEST_METHOD_TWO.getParameterTypes()); + return invocation; + } + + public static URL getDefaultTestURL() { + URL url = URL.valueOf("dubbo://127.0.0.1:2181") + .addParameter(CommonConstants.VERSION_KEY, "1.0.0") + .addParameter(CommonConstants.GROUP_KEY, "grp1") + .addParameter(CommonConstants.INTERFACE_KEY, DEFAULT_TEST_SERVICE.getName()); + return url; + } + + +} diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java index ece46fde3d..4b067c04bf 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboUtilsTest.java @@ -15,7 +15,7 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; import com.alibaba.csp.sentinel.config.SentinelConfig; import org.apache.dubbo.common.URL; @@ -29,11 +29,10 @@ import java.lang.reflect.Method; import java.util.HashMap; +import static com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.mockito.Mockito.*; /** * @author cdfive @@ -43,18 +42,18 @@ public class DubboUtilsTest { @Before public void setUp() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "true"); - SentinelConfig.setConfig(DubboConfig.DUBBO_PROVIDER_PREFIX, ""); - SentinelConfig.setConfig(DubboConfig.DUBBO_CONSUMER_PREFIX, ""); - SentinelConfig.setConfig(DubboConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); } @After public void tearDown() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); - SentinelConfig.setConfig(DubboConfig.DUBBO_PROVIDER_PREFIX, ""); - SentinelConfig.setConfig(DubboConfig.DUBBO_CONSUMER_PREFIX, ""); - SentinelConfig.setConfig(DubboConfig.DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); } @@ -93,7 +92,7 @@ public void testGetResourceName() throws NoSuchMethodException { when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); - String resourceName = DubboUtils.getResourceName(invoker, invocation); + String resourceName = DubboUtils.getMethodResourceName(invoker, invocation); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); @@ -114,7 +113,7 @@ public void testGetResourceNameWithGroupAndVersion() throws NoSuchMethodExceptio when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); - String resourceNameUseGroupAndVersion = DubboUtils.getResourceName(invoker, invocation, true); + String resourceNameUseGroupAndVersion = DubboUtils.getMethodResourceName(invoker, invocation, true); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:1.0.0:grp1:sayHello(java.lang.String,int)", resourceNameUseGroupAndVersion); } @@ -131,19 +130,78 @@ public void testGetResourceNameWithPrefix() throws NoSuchMethodException { when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); //test with default prefix - String resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix()); + String resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); - resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix()); + resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); //test with custom prefix - SentinelConfig.setConfig(DubboConfig.DUBBO_PROVIDER_PREFIX, "my:dubbo:provider:"); - SentinelConfig.setConfig(DubboConfig.DUBBO_CONSUMER_PREFIX, "my:dubbo:consumer:"); - resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix()); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); + resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); - resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix()); + resourceName = DubboUtils.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); } + + @Test + public void testGetInterfaceName() { + + URL url = URL.valueOf("dubbo://127.0.0.1:2181") + .addParameter(CommonConstants.VERSION_KEY, "1.0.0") + .addParameter(CommonConstants.GROUP_KEY, "grp1") + .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); + Invoker invoker = mock(Invoker.class); + when(invoker.getUrl()).thenReturn(url); + when(invoker.getInterface()).thenReturn(DemoService.class); + + SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "false"); + assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", DubboUtils.getInterfaceName(invoker)); + + } + + @Test + public void testGetInterfaceNameWithGroupAndVersion() throws NoSuchMethodException { + + URL url = URL.valueOf("dubbo://127.0.0.1:2181") + .addParameter(CommonConstants.VERSION_KEY, "1.0.0") + .addParameter(CommonConstants.GROUP_KEY, "grp1") + .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); + Invoker invoker = mock(Invoker.class); + when(invoker.getUrl()).thenReturn(url); + when(invoker.getInterface()).thenReturn(DemoService.class); + + SentinelConfig.setConfig(DUBBO_INTERFACE_GROUP_VERSION_ENABLED, "true"); + assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:1.0.0:grp1", DubboUtils.getInterfaceName(invoker, true)); + } + + @Test + public void testGetInterfaceNameWithPrefix() throws NoSuchMethodException { + URL url = URL.valueOf("dubbo://127.0.0.1:2181") + .addParameter(CommonConstants.VERSION_KEY, "1.0.0") + .addParameter(CommonConstants.GROUP_KEY, "grp1") + .addParameter(CommonConstants.INTERFACE_KEY, DemoService.class.getName()); + Invoker invoker = mock(Invoker.class); + when(invoker.getUrl()).thenReturn(url); + when(invoker.getInterface()).thenReturn(DemoService.class); + + + //test with default prefix + String resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); + assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName); + resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); + assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName); + + + //test with custom prefix + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); + resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboProviderResNamePrefixKey()); + assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName); + resourceName = DubboUtils.getInterfaceName(invoker, DubboAdapterGlobalConfig.getDubboConsumerResNamePrefixKey()); + assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService", resourceName); + + } } \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java index ea69ab8bdb..a350a47de1 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java @@ -16,13 +16,12 @@ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.BaseTest; +import com.alibaba.csp.sentinel.DubboTestUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; -import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback; import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry; -import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.ClusterNode; @@ -36,125 +35,88 @@ import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; -import org.apache.dubbo.rpc.AsyncRpcResult; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.Invoker; -import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.RpcContext; + +import org.apache.dubbo.rpc.*; import org.apache.dubbo.rpc.support.RpcUtils; import org.junit.After; import org.junit.Before; import org.junit.Test; -import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.Set; +import java.util.*; import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO; import static org.apache.dubbo.rpc.Constants.ASYNC_KEY; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; /** * @author cdfive + * @author lianglin */ public class SentinelDubboConsumerFilterTest extends BaseTest { - private SentinelDubboConsumerFilter filter = new SentinelDubboConsumerFilter(); - + private final SentinelDubboConsumerFilter consumerFilter = new SentinelDubboConsumerFilter(); @Before public void setUp() { cleanUpAll(); initFallback(); - constructInvokerAndInvocation(); } @After - public void cleanUp() { + public void destroy() { cleanUpAll(); - DubboFallbackRegistry.setConsumerFallback(new DefaultDubboFallback()); - } - - public void initFlowRule(String resource) { - FlowRule flowRule = new FlowRule(resource); - flowRule.setCount(1); - flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); - List flowRules = new ArrayList<>(); - flowRules.add(flowRule); - FlowRuleManager.loadRules(flowRules); - } - - public void initDegradeRule(String resource) { - DegradeRule degradeRule = new DegradeRule(resource) - .setCount(0.5) - .setGrade(DEGRADE_GRADE_EXCEPTION_RATIO); - List degradeRules = new ArrayList<>(); - degradeRules.add(degradeRule); - degradeRule.setTimeWindow(1); - DegradeRuleManager.loadRules(degradeRules); - } - - - public void initFallback() { - DubboFallbackRegistry.setConsumerFallback(new DubboFallback() { - @Override - public Result handle(Invoker invoker, Invocation invocation, BlockException ex) { - boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation); - Result fallbackResult = null; - fallbackResult = AsyncRpcResult.newDefaultAsyncResult("fallback", invocation); - return fallbackResult; - } - }); } @Test public void testInterfaceLevelFollowControlAsync() throws InterruptedException { + + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); - initFlowRule(invoker.getUrl().getColonSeparatedKey()); - Result result1 = responseBack(requestGo(false, invocation)); + initFlowRule(DubboUtils.getInterfaceName(invoker)); + + Result result1 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result1.getValue()); + // should fallback because the qps > 1 - Result result2 = responseBack(requestGo(false, invocation)); + Result result2 = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", result2.getValue()); + // sleeping 1000 ms to reset qps Thread.sleep(1000); - Result result3 = responseBack(requestGo(false, invocation)); + Result result3 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result3.getValue()); - verifyInvocationStructureForCallFinish(); + verifyInvocationStructureForCallFinish(invoker, invocation); } @Test public void testDegradeAsync() throws InterruptedException { + + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); + initDegradeRule(DubboUtils.getInterfaceName(invoker)); - initDegradeRule(invoker.getUrl().getColonSeparatedKey()); - Result result = requestGo(false, invocation); - verifyInvocationStructureForAsyncCall(invoker, invocation); - responseBack(result); + Result result = invokeDubboRpc(false, invoker, invocation); + verifyInvocationStructureForCallFinish(invoker, invocation); assertEquals("normal", result.getValue()); + // inc the clusterNode's exception to trigger the fallback for (int i = 0; i < 5; i++) { - responseBack(requestGo(true, invocation)); - verifyInvocationStructureForCallFinish(); + invokeDubboRpc(true, invoker, invocation); + verifyInvocationStructureForCallFinish(invoker, invocation); } - Result result2 = responseBack(requestGo(false, invocation)); + + Result result2 = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", result2.getValue()); + // sleeping 1000 ms to reset exception Thread.sleep(1000); - - Result result3 = responseBack(requestGo(false, invocation)); + Result result3 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result3.getValue()); Context context = ContextUtil.getContext(); @@ -164,87 +126,69 @@ public void testDegradeAsync() throws InterruptedException { @Test public void testDegradeSync() throws InterruptedException { - initDegradeRule(invoker.getUrl().getColonSeparatedKey()); - Result result = requestGo(false, invocation); - verifyInvocationStructure(invoker, invocation); - responseBack(result); + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + initDegradeRule(DubboUtils.getInterfaceName(invoker)); + + Result result = invokeDubboRpc(false, invoker, invocation); + verifyInvocationStructureForCallFinish(invoker, invocation); assertEquals("normal", result.getValue()); + // inc the clusterNode's exception to trigger the fallback for (int i = 0; i < 5; i++) { - responseBack(requestGo(true, invocation)); - verifyInvocationStructureForCallFinish(); + invokeDubboRpc(true, invoker, invocation); + verifyInvocationStructureForCallFinish(invoker, invocation); } - Result result2 = responseBack(requestGo(false, invocation)); + + Result result2 = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", result2.getValue()); + // sleeping 1000 ms to reset exception Thread.sleep(1000); - - Result result3 = responseBack(requestGo(false, invocation)); + Result result3 = invokeDubboRpc(false, invoker, invocation); assertEquals("normal", result3.getValue()); Context context = ContextUtil.getContext(); assertNull(context); } - @Test public void testMethodFlowControlAsync() { - when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); - initFlowRule(DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix())); - responseBack(requestGo(false, invocation)); - responseBack(requestGo(false, invocation)); + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); - Invocation invocation2 = mock(Invocation.class); - Method method = DemoService.class.getMethods()[1]; - when(invocation2.getMethodName()).thenReturn(method.getName()); - when(invocation2.getParameterTypes()).thenReturn(method.getParameterTypes()); - Result result2 = responseBack(requestGo(false, invocation2)); - verifyInvocationStructureForCallFinish(); + when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); + initFlowRule(consumerFilter.getMethodName(invoker, invocation, null)); + invokeDubboRpc(false, invoker, invocation); + invokeDubboRpc(false, invoker, invocation); + + Invocation invocation2 = DubboTestUtil.getDefaultMockInvocationTwo(); + Result result2 = invokeDubboRpc(false, invoker, invocation2); + verifyInvocationStructureForCallFinish(invoker, invocation2); assertEquals("normal", result2.getValue()); // the method of invocation should be blocked - Result fallback = requestGo(false, invocation); - assertNotNull(RpcContext.getContext().get(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY)); - assertNull(RpcContext.getContext().get(DubboUtils.DUBBO_METHOD_ENTRY_KEY)); - responseBack(fallback); + Result fallback = invokeDubboRpc(false, invoker, invocation); assertEquals("fallback", fallback.getValue()); - verifyInvocationStructureForCallFinish(); - - - } - - public Result requestGo(boolean exception, Invocation currentInvocation) { - AsyncRpcResult result = null; - - if (exception) { - result = AsyncRpcResult.newDefaultAsyncResult(new Exception("error"), currentInvocation); - } else { - result = AsyncRpcResult.newDefaultAsyncResult("normal", currentInvocation); - } - when(invoker.invoke(currentInvocation)).thenReturn(result); - return filter.invoke(invoker, currentInvocation); - } + verifyInvocationStructureForCallFinish(invoker, invocation); - public Result responseBack(Result result) { - filter.listener().onMessage(result, invoker, invocation); - return result; } - @Test - public void testInvokeAsync() throws InterruptedException { + public void testInvokeAsync() { - when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); final Result result = mock(Result.class); when(result.hasException()).thenReturn(false); when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { verifyInvocationStructureForAsyncCall(invoker, invocation); return result; }); - - filter.invoke(invoker, invocation); + consumerFilter.invoke(invoker, invocation); verify(invoker).invoke(invocation); Context context = ContextUtil.getContext(); @@ -254,6 +198,9 @@ public void testInvokeAsync() throws InterruptedException { @Test public void testInvokeSync() { + Invocation invocation = DubboTestUtil.getDefaultMockInvocationOne(); + Invoker invoker = DubboTestUtil.getDefaultMockInvoker(); + final Result result = mock(Result.class); when(result.hasException()).thenReturn(false); when(result.getException()).thenReturn(new Exception()); @@ -262,10 +209,9 @@ public void testInvokeSync() { return result; }); - filter.invoke(invoker, invocation); + consumerFilter.invoke(invoker, invocation); verify(invoker).invoke(invocation); - filter.listener().onMessage(result, invoker, invocation); Context context = ContextUtil.getContext(); assertNull(context); } @@ -280,9 +226,10 @@ private void verifyInvocationStructure(Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context - // In actual project, a consumer is usually also a provider, the context will be created by SentinelDubboProviderFilter + // In actual project, a consumer is usually also a provider, the context will be created by + //SentinelDubboProviderFilter // If consumer is on the top of Dubbo RPC invocation chain, use default context - String resourceName = DubboUtils.getResourceName(invoker, invocation, true); + String resourceName = consumerFilter.getMethodName(invoker, invocation, null); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); @@ -295,10 +242,10 @@ private void verifyInvocationStructure(Invoker invoker, Invocation invocation) { // As SphU.entry(interfaceName, EntryType.OUT); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); - DefaultNode interfaceNode = getNode(invoker.getUrl().getColonSeparatedKey(), entranceNode); + DefaultNode interfaceNode = getNode(DubboUtils.getInterfaceName(invoker), entranceNode); ResourceWrapper interfaceResource = interfaceNode.getId(); - assertEquals(invoker.getUrl().getColonSeparatedKey(), interfaceResource.getName()); + assertEquals(DubboUtils.getInterfaceName(invoker), interfaceResource.getName()); assertSame(EntryType.OUT, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.OUT); @@ -318,7 +265,8 @@ private void verifyInvocationStructure(Invoker invoker, Invocation invocation) { // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); - assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode + assertNotSame(methodClusterNode, + interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); @@ -333,9 +281,10 @@ private void verifyInvocationStructureForAsyncCall(Invoker invoker, Invocation i assertNotNull(context); // As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context - // In actual project, a consumer is usually also a provider, the context will be created by SentinelDubboProviderFilter + // In actual project, a consumer is usually also a provider, the context will be created by + //SentinelDubboProviderFilter // If consumer is on the top of Dubbo RPC invocation chain, use default context - String resourceName = DubboUtils.getResourceName(invoker, invocation, true); + String resourceName = consumerFilter.getMethodName(invoker, invocation, null); assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); @@ -347,9 +296,9 @@ private void verifyInvocationStructureForAsyncCall(Invoker invoker, Invocation i // As SphU.entry(interfaceName, EntryType.OUT); Set childList = entranceNode.getChildList(); assertEquals(2, childList.size()); - DefaultNode interfaceNode = getNode(invoker.getUrl().getColonSeparatedKey(), entranceNode); + DefaultNode interfaceNode = getNode(DubboUtils.getInterfaceName(invoker), entranceNode); ResourceWrapper interfaceResource = interfaceNode.getId(); - assertEquals(invoker.getUrl().getColonSeparatedKey(), interfaceResource.getName()); + assertEquals(DubboUtils.getInterfaceName(invoker), interfaceResource.getName()); assertSame(EntryType.OUT, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.OUT); @@ -368,7 +317,8 @@ private void verifyInvocationStructureForAsyncCall(Invoker invoker, Invocation i // Verify clusterNode ClusterNode methodClusterNode = methodNode.getClusterNode(); ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); - assertNotSame(methodClusterNode, interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode + assertNotSame(methodClusterNode, + interfaceClusterNode);// Different resource->Different ProcessorSlot->Different ClusterNode // As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode Map methodOriginCountMap = methodClusterNode.getOriginCountMap(); @@ -378,18 +328,15 @@ private void verifyInvocationStructureForAsyncCall(Invoker invoker, Invocation i assertEquals(0, interfaceOriginCountMap.size()); } - - private void verifyInvocationStructureForCallFinish() { + private void verifyInvocationStructureForCallFinish(Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNull(context); - Entry interfaceEntry = (Entry) RpcContext.getContext().get(DubboUtils.DUBBO_INTERFACE_ENTRY_KEY); - Entry methodEntry = (Entry) RpcContext.getContext().get(DubboUtils.DUBBO_METHOD_ENTRY_KEY); - assertNull(interfaceEntry); - assertNull(methodEntry); + String methodResourceName = consumerFilter.getMethodName(invoker, invocation, null); + Entry[] entries = (Entry[]) RpcContext.getContext().get(methodResourceName); + assertNull(entries); } - - public DefaultNode getNode(String resourceName, DefaultNode root) { + private DefaultNode getNode(String resourceName, DefaultNode root) { Queue queue = new LinkedList<>(); queue.offer(root); @@ -405,4 +352,43 @@ public DefaultNode getNode(String resourceName, DefaultNode root) { return null; } + private void initFlowRule(String resource) { + FlowRule flowRule = new FlowRule(resource); + flowRule.setCount(1); + flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); + List flowRules = new ArrayList<>(); + flowRules.add(flowRule); + FlowRuleManager.loadRules(flowRules); + } + + private void initDegradeRule(String resource) { + DegradeRule degradeRule = new DegradeRule(resource) + .setCount(0.5) + .setGrade(DEGRADE_GRADE_EXCEPTION_RATIO); + List degradeRules = new ArrayList<>(); + degradeRules.add(degradeRule); + degradeRule.setTimeWindow(1); + DegradeRuleManager.loadRules(degradeRules); + } + + private void initFallback() { + DubboAdapterGlobalConfig.setConsumerFallback((invoker, invocation, ex) -> { + // boolean async = RpcUtils.isAsync(invoker.getUrl(), invocation); + return AsyncRpcResult.newDefaultAsyncResult("fallback", invocation); + }); + } + + private Result invokeDubboRpc(boolean exception, Invoker invoker, Invocation invocation) { + Result result = null; + InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); + if (InvokeMode.SYNC == invokeMode) { + result = exception ? new AppResponse(new Exception("error")) : new AppResponse("normal"); + } else { + result = exception ? AsyncRpcResult.newDefaultAsyncResult(new Exception("error"), invocation) + : AsyncRpcResult.newDefaultAsyncResult("normal", invocation); + } + when(invoker.invoke(invocation)).thenReturn(result); + return consumerFilter.invoke(invoker, invocation); + } + } diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java index ed97800a41..741dae6da6 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java @@ -16,6 +16,7 @@ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.BaseTest; +import com.alibaba.csp.sentinel.DubboTestUtil; import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.EntryType; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; @@ -35,52 +36,49 @@ import org.junit.Before; import org.junit.Test; -import java.lang.reflect.Method; import java.util.Map; import java.util.Set; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNotSame; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertSame; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import static org.junit.Assert.*; +import static org.mockito.Mockito.*; /** * @author cdfive + * @author lianglin */ public class SentinelDubboProviderFilterTest extends BaseTest { + private SentinelDubboProviderFilter filter = new SentinelDubboProviderFilter(); + @Before public void setUp() { - constructInvokerAndInvocation(); cleanUpAll(); } @After - public void cleanUp() { + public void destroy() { cleanUpAll(); } @Test public void testInvoke() { + final String originApplication = "consumerA"; - URL url = invoker.getUrl() - .addParameter(CommonConstants.SIDE_KEY, CommonConstants.PROVIDER_SIDE); - when(invoker.getUrl()).thenReturn(url); + URL url = DubboTestUtil.getDefaultTestURL(); + url = url.addParameter(CommonConstants.SIDE_KEY, CommonConstants.PROVIDER_SIDE); + Invoker invoker = DubboTestUtil.getMockInvoker(url, DemoService.class); + Invocation invocation = DubboTestUtil.getMockInvocation(DemoService.class.getMethods()[0]); when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) .thenReturn(originApplication); final Result result = mock(Result.class); when(result.hasException()).thenReturn(false); when(result.getException()).thenReturn(new Exception()); + when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { verifyInvocationStructure(originApplication, invoker, invocation); return result; @@ -89,29 +87,28 @@ public void testInvoke() { filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); - filter.listener().onMessage(result, invoker, invocation); Context context = ContextUtil.getContext(); assertNull(context); } /** * Simply verify invocation structure in memory: - * EntranceNode(resourceName) + * EntranceNode(methodResourceName) * --InterfaceNode(interfaceName) - * ----MethodNode(resourceName) + * ----MethodNode(methodResourceName) */ private void verifyInvocationStructure(String originApplication, Invoker invoker, Invocation invocation) { Context context = ContextUtil.getContext(); assertNotNull(context); // As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter - String resourceName = DubboUtils.getResourceName(invoker, invocation, true); - assertEquals(resourceName, context.getName()); + String methodResourceName = filter.getMethodName(invoker, invocation, null); + assertEquals(methodResourceName, context.getName()); assertEquals(originApplication, context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); - assertEquals(resourceName, entranceResource.getName()); + assertEquals(methodResourceName, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.IN); @@ -120,7 +117,7 @@ private void verifyInvocationStructure(String originApplication, Invoker invoker DefaultNode interfaceNode = (DefaultNode) childList.iterator().next(); ResourceWrapper interfaceResource = interfaceNode.getId(); - assertEquals(invoker.getUrl().getColonSeparatedKey(), interfaceResource.getName()); + assertEquals(filter.getInterfaceName(invoker, null), interfaceResource.getName()); assertSame(EntryType.IN, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments()); @@ -128,7 +125,7 @@ private void verifyInvocationStructure(String originApplication, Invoker invoker assertEquals(1, childList.size()); DefaultNode methodNode = (DefaultNode) childList.iterator().next(); ResourceWrapper methodResource = methodNode.getId(); - assertEquals(resourceName, methodResource.getName()); + assertEquals(methodResourceName, methodResource.getName()); assertSame(EntryType.IN, methodResource.getEntryType()); // Verify curEntry @@ -151,4 +148,6 @@ private void verifyInvocationStructure(String originApplication, Invoker invoker assertEquals(1, interfaceOriginCountMap.size()); assertTrue(interfaceOriginCountMap.containsKey(originApplication)); } + + } diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java index e1f199b729..6c2f427c96 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java @@ -15,9 +15,10 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; + import org.apache.dubbo.rpc.AsyncRpcResult; import org.apache.dubbo.rpc.Result; import org.junit.After; @@ -32,28 +33,31 @@ public class DubboFallbackRegistryTest { @Before public void setUp() { - DubboFallbackRegistry.setConsumerFallback(new DefaultDubboFallback()); + DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); } @After public void tearDown() { - DubboFallbackRegistry.setConsumerFallback(new DefaultDubboFallback()); + DubboAdapterGlobalConfig.setConsumerFallback(new DefaultDubboFallback()); } - @Test(expected = SentinelRpcException.class) + @Test public void testDefaultFallback() { - // Test for default. + // Test for default fallback. BlockException ex = new FlowException("xxx"); - DubboFallbackRegistry.getConsumerFallback() - .handle(null, null, ex); + Result result = new DefaultDubboFallback().handle(null, null, ex); + Assert.assertTrue("The result should carry exception", result.hasException()); + Assert.assertTrue(BlockException.isBlockException(result.getException())); + Assert.assertTrue(result.getException().getMessage().contains(ex.getClass().getSimpleName())); } @Test public void testCustomFallback() { BlockException ex = new FlowException("xxx"); - DubboFallbackRegistry.setConsumerFallback( - (invoker, invocation, e) -> AsyncRpcResult.newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation)); - Result result = DubboFallbackRegistry.getConsumerFallback() + DubboAdapterGlobalConfig.setConsumerFallback( + (invoker, invocation, e) -> AsyncRpcResult + .newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation)); + Result result = DubboAdapterGlobalConfig.getConsumerFallback() .handle(null, null, ex); Assert.assertFalse("The invocation should not fail", result.hasException()); Assert.assertEquals("Error: " + ex.getClass().getName(), result.getValue()); diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginRegistryTest.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginRegistryTest.java new file mode 100644 index 0000000000..8143a43bc7 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginRegistryTest.java @@ -0,0 +1,75 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.dubbo.origin; + +import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboAdapterGlobalConfig; +import com.alibaba.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author tiecheng + */ +public class DubboOriginRegistryTest { + + @After + public void cleanUp() { + DubboAdapterGlobalConfig.setOriginParser(new DefaultDubboOriginParser()); + } + + @Test(expected = IllegalArgumentException.class) + public void testDefaultOriginParserFail() { + DubboAdapterGlobalConfig.getOriginParser().parse(null, null); + } + + @Test + public void testDefaultOriginParserSuccess() { + RpcInvocation invocation = new RpcInvocation(); + String dubboName = "sentinel"; + invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName); + String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals(dubboName, origin); + } + + @Test + public void testCustomOriginParser() { + DubboAdapterGlobalConfig.setOriginParser(new DubboOriginParser() { + @Override + public String parse(Invoker invoker, Invocation invocation) { + return invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "default") + "_" + invocation + .getMethodName(); + } + }); + + RpcInvocation invocation = new RpcInvocation(); + String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals("default_null", origin); + + String dubboName = "sentinel"; + invocation.setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, dubboName); + origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals(dubboName + "_null", origin); + + invocation.setMethodName("hello"); + origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals(dubboName + "_hello", origin); + } + +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/README.md b/sentinel-adapter/sentinel-apache-httpclient-adapter/README.md new file mode 100755 index 0000000000..3ec548d2f6 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/README.md @@ -0,0 +1,75 @@ +# Sentinel Apache Httpclient Adapter + +## Introduction + +Sentinel provides integration for OkHttp client to enable flow control for web requests. + +Add the following dependency in `pom.xml` (if you are using Maven): + +```xml + + com.alibaba.csp + sentinel-apache-httpclient-adapter + x.y.z + +``` + +We can use the `SentinelApacheHttpClientBuilder` when `CloseableHttpClient` at initialization, for example: + +```java +CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder().build(); +``` + +If we want to add some additional configurations, we can refer to the following code + +```java +HttpClientBuilder builder = new SentinelApacheHttpClientBuilder(); +//builder Other Definitions +CloseableHttpClient httpclient = builder.build(); +``` + +## Configuration + +- `SentinelApacheHttpClientConfig` configuration: + +| name | description | type | default value | +|------|------------|------|-------| +| prefix | customize resource prefix | `String` | `httpclient:` | +| extractor | customize resource extractor | `ApacheHttpClientResourceExtractor` | `DefaultApacheHttpClientResourceExtractor` | +| fallback | handle request when it is blocked | `ApacheHttpClientFallback` | `DefaultApacheHttpClientFallback` | + +### extractor (resource extractor) + +We can define `ApacheHttpClientResourceExtractor` to customize resource extractor replace `DefaultApacheHttpClientResourceExtractor` at `SentinelApacheHttpClientBuilder` default config, for example: httpclient:GET:/httpclient/back/1 ==> httpclient:GET:/httpclient/back/{id} + +```java +SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); +config.setExtractor(new ApacheHttpClientResourceExtractor() { + + @Override + public String extractor(HttpRequestWrapper request) { + String contains = "/httpclient/back/"; + String uri = request.getRequestLine().getUri(); + if (uri.startsWith(contains)) { + uri = uri.substring(0, uri.indexOf(contains) + contains.length()) + "{id}"; + } + return request.getMethod() + ":" + uri; + } +}); +CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder(config).build(); +``` + +### fallback (Block handling) + +We can define `ApacheHttpClientFallback` at `SentinelApacheHttpClientBuilder` default config, to handle request is blocked according to the actual scenario, for example: + +```java +public class DefaultApacheHttpClientFallback implements ApacheHttpClientFallback { + + @Override + public CloseableHttpResponse handle(HttpRequestWrapper request, BlockException e) { + // Just wrap and throw the exception. + throw new SentinelRpcException(e); + } +} +``` \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/pom.xml b/sentinel-adapter/sentinel-apache-httpclient-adapter/pom.xml new file mode 100644 index 0000000000..ae7864d4d9 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/pom.xml @@ -0,0 +1,69 @@ + + + + sentinel-adapter + com.alibaba.csp + 1.8.1-SNAPSHOT + + 4.0.0 + + sentinel-apache-httpclient-adapter + jar + + + 4.5.6 + 2.1.3.RELEASE + 5.1.5.RELEASE + + + + + com.alibaba.csp + sentinel-core + + + org.apache.httpcomponents + httpclient + ${apache.httpclient.version} + provided + + + + junit + junit + test + + + + org.mockito + mockito-core + test + + + com.alibaba + fastjson + test + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + test + + + org.springframework.boot + spring-boot-test + ${spring.boot.version} + test + + + org.springframework + spring-test + ${spring-test.version} + test + + + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/SentinelApacheHttpClientBuilder.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/SentinelApacheHttpClientBuilder.java new file mode 100644 index 0000000000..4b6aad3789 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/SentinelApacheHttpClientBuilder.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient; + +import com.alibaba.csp.sentinel.*; +import com.alibaba.csp.sentinel.adapter.apache.httpclient.config.SentinelApacheHttpClientConfig; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.util.StringUtil; +import org.apache.http.HttpException; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpExecutionAware; +import org.apache.http.client.methods.HttpRequestWrapper; +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.impl.execchain.ClientExecChain; + +import java.io.IOException; + +/** + * @author zhaoyuguang + */ +public class SentinelApacheHttpClientBuilder extends HttpClientBuilder { + + private final SentinelApacheHttpClientConfig config; + + public SentinelApacheHttpClientBuilder(){ + this.config = new SentinelApacheHttpClientConfig(); + } + + public SentinelApacheHttpClientBuilder(SentinelApacheHttpClientConfig config){ + this.config = config; + } + + @Override + protected ClientExecChain decorateMainExec(final ClientExecChain mainExec) { + return new ClientExecChain() { + @Override + public CloseableHttpResponse execute(HttpRoute route, HttpRequestWrapper request, + HttpClientContext clientContext, HttpExecutionAware execAware) + throws IOException, HttpException { + Entry entry = null; + try { + String name = config.getExtractor().extractor(request); + if (!StringUtil.isEmpty(config.getPrefix())) { + name = config.getPrefix() + name; + } + entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); + return mainExec.execute(route, request, clientContext, execAware); + } catch (BlockException e) { + return config.getFallback().handle(request, e); + } catch (Throwable t) { + Tracer.traceEntry(t, entry); + throw t; + } finally { + if (entry != null) { + entry.exit(); + } + } + } + }; + } +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/config/SentinelApacheHttpClientConfig.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/config/SentinelApacheHttpClientConfig.java new file mode 100644 index 0000000000..9dbc1520ff --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/config/SentinelApacheHttpClientConfig.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient.config; + +import com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor.ApacheHttpClientResourceExtractor; +import com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor.DefaultApacheHttpClientResourceExtractor; +import com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback.ApacheHttpClientFallback; +import com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback.DefaultApacheHttpClientFallback; +import com.alibaba.csp.sentinel.util.AssertUtil; + +/** + * @author zhaoyuguang + */ +public class SentinelApacheHttpClientConfig { + + private String prefix = "httpclient:"; + private ApacheHttpClientResourceExtractor extractor = new DefaultApacheHttpClientResourceExtractor(); + private ApacheHttpClientFallback fallback = new DefaultApacheHttpClientFallback(); + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + AssertUtil.notNull(prefix, "prefix cannot be null"); + this.prefix = prefix; + } + + public ApacheHttpClientResourceExtractor getExtractor() { + return extractor; + } + + public void setExtractor(ApacheHttpClientResourceExtractor extractor) { + AssertUtil.notNull(extractor, "extractor cannot be null"); + this.extractor = extractor; + } + + public ApacheHttpClientFallback getFallback() { + return fallback; + } + + public void setFallback(ApacheHttpClientFallback fallback) { + AssertUtil.notNull(fallback, "fallback cannot be null"); + this.fallback = fallback; + } +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/extractor/ApacheHttpClientResourceExtractor.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/extractor/ApacheHttpClientResourceExtractor.java new file mode 100644 index 0000000000..ab494d67d3 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/extractor/ApacheHttpClientResourceExtractor.java @@ -0,0 +1,26 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor; + +import org.apache.http.client.methods.HttpRequestWrapper; + +/** + * @author zhaoyuguang + */ +public interface ApacheHttpClientResourceExtractor { + + String extractor(HttpRequestWrapper request); +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/extractor/DefaultApacheHttpClientResourceExtractor.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/extractor/DefaultApacheHttpClientResourceExtractor.java new file mode 100644 index 0000000000..02ccf697a8 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/extractor/DefaultApacheHttpClientResourceExtractor.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor; + +import org.apache.http.client.methods.HttpRequestWrapper; + +/** + * @author zhaoyuguang + */ +public class DefaultApacheHttpClientResourceExtractor implements ApacheHttpClientResourceExtractor { + + @Override + public String extractor(HttpRequestWrapper request) { + return request.getRequestLine().getUri(); + } +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/ApacheHttpClientFallback.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/ApacheHttpClientFallback.java new file mode 100644 index 0000000000..f670eca8a5 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/ApacheHttpClientFallback.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpRequestWrapper; +import org.apache.http.protocol.HttpContext; + +import java.io.IOException; + +/** + * @author zhaoyuguang + */ +public interface ApacheHttpClientFallback { + + CloseableHttpResponse handle(HttpRequestWrapper request, BlockException e); +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/DefaultApacheHttpClientFallback.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/DefaultApacheHttpClientFallback.java new file mode 100644 index 0000000000..81994bfc00 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/DefaultApacheHttpClientFallback.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpRequestWrapper; +import org.apache.http.protocol.HttpContext; + +import java.io.IOException; + +/** + * @author zhaoyuguang + */ +public class DefaultApacheHttpClientFallback implements ApacheHttpClientFallback { + + @Override + public CloseableHttpResponse handle(HttpRequestWrapper request, BlockException e) { + // Just wrap and throw the exception. + throw new SentinelRpcException(e); + } +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/SentinelApacheHttpClientTest.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/SentinelApacheHttpClientTest.java new file mode 100644 index 0000000000..882edafe0c --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/SentinelApacheHttpClientTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient; + +import com.alibaba.csp.sentinel.Constants; +import com.alibaba.csp.sentinel.adapter.apache.httpclient.app.TestApplication; +import com.alibaba.csp.sentinel.adapter.apache.httpclient.config.SentinelApacheHttpClientConfig; +import com.alibaba.csp.sentinel.adapter.apache.httpclient.extractor.ApacheHttpClientResourceExtractor; +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpRequestWrapper; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.protocol.BasicHttpContext; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; + +import static org.junit.Assert.assertNotNull; + +/** + * @author zhaoyuguang + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + properties = { + "server.port=8084" + }) +public class SentinelApacheHttpClientTest { + + @Value("${server.port}") + private Integer port; + + @Test + public void testSentinelOkHttpInterceptor0() throws Exception { + + CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder().build(); + + HttpGet httpGet = new HttpGet("http://localhost:" + port + "/httpclient/back"); + System.out.println(getRemoteString(httpclient, httpGet)); + ClusterNode cn = ClusterBuilderSlot.getClusterNode("httpclient:/httpclient/back"); + assertNotNull(cn); + Constants.ROOT.removeChildList(); + ClusterBuilderSlot.getClusterNodeMap().clear(); + } + + @Test + public void testSentinelOkHttpInterceptor1() throws Exception { + SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); + config.setExtractor(new ApacheHttpClientResourceExtractor() { + + @Override + public String extractor(HttpRequestWrapper request) { + String contains = "/httpclient/back/"; + String uri = request.getRequestLine().getUri(); + if (uri.startsWith(contains)) { + uri = uri.substring(0, uri.indexOf(contains) + contains.length()) + "{id}"; + } + return request.getMethod() + ":" + uri; + } + }); + CloseableHttpClient httpclient = new SentinelApacheHttpClientBuilder(config).build(); + + HttpGet httpGet = new HttpGet("http://localhost:" + port + "/httpclient/back/1"); + System.out.println(getRemoteString(httpclient, httpGet)); + ClusterNode cn = ClusterBuilderSlot.getClusterNode("httpclient:GET:/httpclient/back/{id}"); + assertNotNull(cn); + Constants.ROOT.removeChildList(); + ClusterBuilderSlot.getClusterNodeMap().clear(); + } + + private String getRemoteString(CloseableHttpClient httpclient, HttpGet httpGet) throws IOException { + String result; + HttpContext context = new BasicHttpContext(); + CloseableHttpResponse response; + response = httpclient.execute(httpGet, context); + try { + HttpEntity entity = response.getEntity(); + result = EntityUtils.toString(entity, "utf-8"); + EntityUtils.consume(entity); + } finally { + response.close(); + } + httpclient.close(); + return result; + } +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/app/TestApplication.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/app/TestApplication.java new file mode 100644 index 0000000000..e073dda5ed --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/app/TestApplication.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author zhaoyuguang + */ +@SpringBootApplication +public class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class); + } +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/app/controller/TestController.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/app/controller/TestController.java new file mode 100644 index 0000000000..2ae4275f05 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/app/controller/TestController.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient.app.controller; + +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author zhaoyuguang + */ +@RestController +public class TestController { + + @RequestMapping("/httpclient/back") + public String back() { + return "Welcome Back!"; + } + + @RequestMapping("/httpclient/back/{id}") + public String back(@PathVariable String id) { + return "Welcome Back! " + id; + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/config/SentinelApacheHttpClientConfigTest.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/config/SentinelApacheHttpClientConfigTest.java new file mode 100644 index 0000000000..414322b79f --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/config/SentinelApacheHttpClientConfigTest.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient.config; + +import org.junit.Test; + +/** + * @author zhaoyuguang + */ +public class SentinelApacheHttpClientConfigTest { + + @Test(expected = IllegalArgumentException.class) + public void testConfigSetPrefix() { + SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); + config.setPrefix(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testConfigSetCleaner() { + SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); + config.setExtractor(null); + } + + @Test(expected = IllegalArgumentException.class) + public void testConfigSetFallback() { + SentinelApacheHttpClientConfig config = new SentinelApacheHttpClientConfig(); + config.setFallback(null); + } +} diff --git a/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/ApacheHttpClientFallbackTest.java b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/ApacheHttpClientFallbackTest.java new file mode 100644 index 0000000000..56865918c2 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-httpclient-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/apache/httpclient/fallback/ApacheHttpClientFallbackTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.apache.httpclient.fallback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; +import com.alibaba.csp.sentinel.slots.block.flow.FlowException; +import org.junit.Test; + +/** + * @author zhaoyuguang + */ +public class ApacheHttpClientFallbackTest { + + @Test(expected = SentinelRpcException.class) + public void testDefaultOkHttpFallback() { + BlockException e = new FlowException("xxx"); + ApacheHttpClientFallback fallback = new DefaultApacheHttpClientFallback(); + fallback.handle(null, e); + } +} diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml b/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml index 6b2d57dcd3..19b37b91e6 100644 --- a/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/pom.xml @@ -5,7 +5,7 @@ sentinel-adapter com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/GatewayApiDefinitionManager.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/GatewayApiDefinitionManager.java index 3985e7d970..8f24803c69 100644 --- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/GatewayApiDefinitionManager.java +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/api/GatewayApiDefinitionManager.java @@ -62,8 +62,8 @@ private static void initializeApiChangeObserverSpi() { List listeners = SpiLoader.loadInstanceList(ApiDefinitionChangeObserver.class); for (ApiDefinitionChangeObserver e : listeners) { API_CHANGE_OBSERVERS.put(e.getClass().getCanonicalName(), e); - RecordLog.info("[GatewayApiDefinitionManager] ApiDefinitionChangeObserver added: {0}", - e.getClass().getCanonicalName()); + RecordLog.info("[GatewayApiDefinitionManager] ApiDefinitionChangeObserver added: {}" + , e.getClass().getCanonicalName()); } } @@ -103,13 +103,13 @@ private static final class ApiDefinitionPropertyListener implements PropertyList @Override public void configUpdate(Set set) { applyApiUpdateInternal(set); - RecordLog.info("[GatewayApiDefinitionManager] Api definition updated: " + API_MAP); + RecordLog.info("[GatewayApiDefinitionManager] Api definition updated: {}", API_MAP); } @Override public void configLoad(Set set) { applyApiUpdateInternal(set); - RecordLog.info("[GatewayApiDefinitionManager] Api definition loaded: " + API_MAP); + RecordLog.info("[GatewayApiDefinitionManager] Api definition loaded: {}", API_MAP); } private static synchronized void applyApiUpdateInternal(Set set) { diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayApiDefinitionGroupCommandHandler.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayApiDefinitionGroupCommandHandler.java index d480512c28..96b8a454ce 100644 --- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayApiDefinitionGroupCommandHandler.java +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayApiDefinitionGroupCommandHandler.java @@ -56,7 +56,7 @@ public CommandResponse handle(CommandRequest request) { return CommandResponse.ofFailure(e, "decode gateway API definition data error"); } - RecordLog.info("[API Server] Receiving data change (type: gateway API definition): {0}", data); + RecordLog.info("[API Server] Receiving data change (type: gateway API definition): {}", data); String result = SUCCESS_MSG; diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayRuleCommandHandler.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayRuleCommandHandler.java index b79b78f9cf..a5e6d0f830 100644 --- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayRuleCommandHandler.java +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/command/UpdateGatewayRuleCommandHandler.java @@ -51,7 +51,7 @@ public CommandResponse handle(CommandRequest request) { return CommandResponse.ofFailure(e, "decode gateway rule data error"); } - RecordLog.info(String.format("[API Server] Receiving rule change (type: gateway rule): %s", data)); + RecordLog.info("[API Server] Receiving rule change (type: gateway rule): {}", data); String result = SUCCESS_MSG; Set flowRules = JSON.parseObject(data, new TypeReference>() { @@ -93,4 +93,4 @@ public synchronized static void setWritableDataSource(WritableDataSource> CONVERTED_PARAM_RULE_MAP = new ConcurrentHashMap<>(); private static final GatewayRulePropertyListener LISTENER = new GatewayRulePropertyListener(); + private static final Set FIELD_REQUIRED_SET = new HashSet<>( + Arrays.asList(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM, + SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER, + SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE) + ); private static SentinelProperty> currentProperty = new DynamicSentinelProperty<>(); static { currentProperty.addListener(LISTENER); } + private GatewayRuleManager() { + } + public static void register2Property(SentinelProperty> property) { AssertUtil.notNull(property, "property cannot be null"); synchronized (LISTENER) { @@ -111,18 +114,48 @@ public static List getConvertedParamRules(String resourceName) { return CONVERTED_PARAM_RULE_MAP.get(resourceName); } + public static boolean isValidRule(GatewayFlowRule rule) { + if (rule == null || StringUtil.isBlank(rule.getResource()) || rule.getResourceMode() < 0 + || rule.getGrade() < 0 || rule.getCount() < 0 || rule.getBurst() < 0 || rule.getControlBehavior() < 0) { + return false; + } + if (rule.getGrade() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER + && rule.getMaxQueueingTimeoutMs() < 0) { + return false; + } + if (rule.getIntervalSec() <= 0) { + return false; + } + GatewayParamFlowItem item = rule.getParamItem(); + if (item != null) { + return isValidParamItem(item); + } + return true; + } + + static boolean isValidParamItem(/*@NonNull*/ GatewayParamFlowItem item) { + if (item.getParseStrategy() < 0) { + return false; + } + // Check required field name for item types. + if (FIELD_REQUIRED_SET.contains(item.getParseStrategy()) && StringUtil.isBlank(item.getFieldName())) { + return false; + } + return StringUtil.isEmpty(item.getPattern()) || item.getMatchStrategy() >= 0; + } + private static final class GatewayRulePropertyListener implements PropertyListener> { @Override public void configUpdate(Set conf) { applyGatewayRuleInternal(conf); - RecordLog.info("[GatewayRuleManager] Gateway flow rules received: " + GATEWAY_RULE_MAP); + RecordLog.info("[GatewayRuleManager] Gateway flow rules received: {}", GATEWAY_RULE_MAP); } @Override public void configLoad(Set conf) { applyGatewayRuleInternal(conf); - RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: " + GATEWAY_RULE_MAP); + RecordLog.info("[GatewayRuleManager] Gateway flow rules loaded: {}", GATEWAY_RULE_MAP); } private int getIdxInternal(Map idxMap, String resourceName) { @@ -136,7 +169,7 @@ private int getIdxInternal(Map idxMap, String resourceName) { private void cacheRegexPattern(/*@NonNull*/ GatewayParamFlowItem item) { String pattern = item.getPattern(); if (StringUtil.isNotEmpty(pattern) && - item.getMatchStrategy() == SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX) { + item.getMatchStrategy() == SentinelGatewayConstants.PARAM_MATCH_STRATEGY_REGEX) { if (GatewayRegexCache.getRegexPattern(pattern) == null) { GatewayRegexCache.addRegexPattern(pattern); } @@ -205,7 +238,7 @@ private synchronized void applyGatewayRuleInternal(Set conf) { private void applyToConvertedParamMap(Set paramFlowRules) { Map> newRuleMap = ParamFlowRuleUtil.buildParamRuleMap( - new ArrayList<>(paramFlowRules)); + new ArrayList<>(paramFlowRules)); if (newRuleMap == null || newRuleMap.isEmpty()) { // No parameter flow rules, so clear all the metrics. for (String resource : CONVERTED_PARAM_RULE_MAP.keySet()) { @@ -217,10 +250,20 @@ private void applyToConvertedParamMap(Set paramFlowRules) { } // Clear unused parameter metrics. - Set previousResources = CONVERTED_PARAM_RULE_MAP.keySet(); - for (String resource : previousResources) { + for (Map.Entry> entry : CONVERTED_PARAM_RULE_MAP.entrySet()) { + String resource = entry.getKey(); if (!newRuleMap.containsKey(resource)) { ParameterMetricStorage.clearParamMetricForResource(resource); + continue; + } + List newRuleList = newRuleMap.get(resource); + List oldRuleList = new ArrayList<>(entry.getValue()); + oldRuleList.removeAll(newRuleList); + for (ParamFlowRule rule : oldRuleList) { + ParameterMetric metric = ParameterMetricStorage.getParamMetricForResource(resource); + if (null != metric) { + metric.clearForRule(rule); + } } } @@ -228,45 +271,7 @@ private void applyToConvertedParamMap(Set paramFlowRules) { CONVERTED_PARAM_RULE_MAP.clear(); CONVERTED_PARAM_RULE_MAP.putAll(newRuleMap); - RecordLog.info("[GatewayRuleManager] Converted internal param rules: " + CONVERTED_PARAM_RULE_MAP); + RecordLog.info("[GatewayRuleManager] Converted internal param rules: {}", CONVERTED_PARAM_RULE_MAP); } } - - public static boolean isValidRule(GatewayFlowRule rule) { - if (rule == null || StringUtil.isBlank(rule.getResource()) || rule.getResourceMode() < 0 - || rule.getGrade() < 0 || rule.getCount() < 0 || rule.getBurst() < 0 || rule.getControlBehavior() < 0) { - return false; - } - if (rule.getGrade() == RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER - && rule.getMaxQueueingTimeoutMs() < 0) { - return false; - } - if (rule.getIntervalSec() <= 0) { - return false; - } - GatewayParamFlowItem item = rule.getParamItem(); - if (item != null) { - return isValidParamItem(item); - } - return true; - } - - static boolean isValidParamItem(/*@NonNull*/ GatewayParamFlowItem item) { - if (item.getParseStrategy() < 0) { - return false; - } - // Check required field name for item types. - if (FIELD_REQUIRED_SET.contains(item.getParseStrategy()) && StringUtil.isBlank(item.getFieldName())) { - return false; - } - return StringUtil.isEmpty(item.getPattern()) || item.getMatchStrategy() >= 0; - } - - private static final Set FIELD_REQUIRED_SET = new HashSet<>( - Arrays.asList(SentinelGatewayConstants.PARAM_PARSE_STRATEGY_URL_PARAM, - SentinelGatewayConstants.PARAM_PARSE_STRATEGY_HEADER, - SentinelGatewayConstants.PARAM_PARSE_STRATEGY_COOKIE) - ); - - private GatewayRuleManager() {} } diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java index 15151729d6..b0932b7c99 100644 --- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewayFlowSlot.java @@ -27,11 +27,13 @@ import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParameterMetricStorage; +import com.alibaba.csp.sentinel.spi.SpiOrder; /** * @author Eric Zhao * @since 1.6.1 */ +@SpiOrder(-4000) public class GatewayFlowSlot extends AbstractLinkedProcessorSlot { @Override diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java index 383e465326..204b46198f 100644 --- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/common/slot/GatewaySlotChainBuilder.java @@ -15,43 +15,18 @@ */ package com.alibaba.csp.sentinel.adapter.gateway.common.slot; -import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain; -import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; -import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; -import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; -import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; -import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowSlot; -import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; -import com.alibaba.csp.sentinel.slots.logger.LogSlot; -import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; -import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; -import com.alibaba.csp.sentinel.slots.system.SystemSlot; +import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; /** * @author Eric Zhao * @since 1.6.1 + * + * @deprecated since 1.7.2, we can use @SpiOrder(-4000) to adjust the order of {@link GatewayFlowSlot}, + * this class is reserved for compatibility with older versions. + * @see GatewayFlowSlot + * @see DefaultSlotChainBuilder */ -public class GatewaySlotChainBuilder implements SlotChainBuilder { - - @Override - public ProcessorSlotChain build() { - ProcessorSlotChain chain = new DefaultProcessorSlotChain(); - // Prepare slot - chain.addLast(new NodeSelectorSlot()); - chain.addLast(new ClusterBuilderSlot()); - // Stat slot - chain.addLast(new LogSlot()); - chain.addLast(new StatisticSlot()); - // Rule checking slot - chain.addLast(new AuthoritySlot()); - chain.addLast(new SystemSlot()); - chain.addLast(new GatewayFlowSlot()); - - chain.addLast(new ParamFlowSlot()); - chain.addLast(new FlowSlot()); - chain.addLast(new DegradeSlot()); +@Deprecated +public class GatewaySlotChainBuilder extends DefaultSlotChainBuilder { - return chain; - } } diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot new file mode 100644 index 0000000000..190da2e814 --- /dev/null +++ b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewayFlowSlot \ No newline at end of file diff --git a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder b/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder deleted file mode 100644 index 5f9fde567e..0000000000 --- a/sentinel-adapter/sentinel-api-gateway-adapter-common/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.SlotChainBuilder +++ /dev/null @@ -1 +0,0 @@ -com.alibaba.csp.sentinel.adapter.gateway.common.slot.GatewaySlotChainBuilder \ No newline at end of file diff --git a/sentinel-adapter/sentinel-dubbo-adapter/README.md b/sentinel-adapter/sentinel-dubbo-adapter/README.md index cbeb0e2e98..29dc61b79a 100755 --- a/sentinel-adapter/sentinel-dubbo-adapter/README.md +++ b/sentinel-adapter/sentinel-dubbo-adapter/README.md @@ -1,6 +1,6 @@ # Sentinel Dubbo Adapter -> Note: 中文文档请见[此处](https://github.com/alibaba/Sentinel/wiki/%E4%B8%BB%E6%B5%81%E6%A1%86%E6%9E%B6%E7%9A%84%E9%80%82%E9%85%8D#dubbo)。 +> Note: 中文文档请见[此处](https://github.com/alibaba/Sentinel/wiki/主流框架的适配#dubbo)。 Sentinel Dubbo Adapter provides service consumer filter and provider filter for [Dubbo](https://dubbo.apache.org/en-us/) services. @@ -52,17 +52,21 @@ If `limitApp` of flow rules is not configured (`default`), flow control will tak If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller. > Note: Dubbo consumer does not provide its Dubbo application name when doing RPC, -so developers should manually put the application name into *attachment* at consumer side, -then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`) -where consumer can carry application name information to provider automatically. -If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller, developers can manually put the application name into attachment with the key `dubboApplication`. +> so developers should manually put the application name into *attachment* at consumer side, +> then extract it at provider side. Sentinel Dubbo Adapter has implemented a filter (`DubboAppContextFilter`) +> where consumer can carry application name information to provider automatically. +> If the consumer does not use Sentinel Dubbo Adapter but requires flow control based on caller, +> developers can manually put the application name into attachment with the key `dubboApplication`. +> +> Since 1.8.0, the adapter provides support for customizing origin parsing logic. You may register your own `DubboOriginParser` +> implementation to `DubboAdapterGlobalConfig`. ## Global fallback Sentinel Dubbo Adapter supports global fallback configuration. The global fallback will handle exceptions and give replacement result when blocked by flow control, degrade or system load protection. You can implement your own `DubboFallback` interface -and then register to `DubboFallbackRegistry`. If no fallback is configured, Sentinel will wrap the `BlockException` -then directly throw it out. +and then register to `DubboAdapterGlobalConfig`. +If no fallback is configured, Sentinel will wrap the `BlockException` as the fallback result. Besides, we can also leverage [Dubbo mock mechanism](http://dubbo.apache.org/en-us/docs/user/demos/local-mock.html) to provide fallback implementation of degraded Dubbo services. \ No newline at end of file diff --git a/sentinel-adapter/sentinel-dubbo-adapter/pom.xml b/sentinel-adapter/sentinel-dubbo-adapter/pom.xml index 732c8c6caa..eba2c16ad9 100755 --- a/sentinel-adapter/sentinel-dubbo-adapter/pom.xml +++ b/sentinel-adapter/sentinel-dubbo-adapter/pom.xml @@ -6,7 +6,7 @@ com.alibaba.csp sentinel-adapter - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 sentinel-dubbo-adapter diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilter.java b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilter.java index aa8248a524..8939e4a39e 100755 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilter.java +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilter.java @@ -25,12 +25,12 @@ */ abstract class AbstractDubboFilter implements Filter { - protected String getResourceName(Invoker invoker, Invocation invocation) { + protected String getMethodResourceName(Invoker invoker, Invocation invocation) { StringBuilder buf = new StringBuilder(64); buf.append(invoker.getInterface().getName()) - .append(":") - .append(invocation.getMethodName()) - .append("("); + .append(":") + .append(invocation.getMethodName()) + .append("("); boolean isFirst = true; for (Class clazz : invocation.getParameterTypes()) { if (!isFirst) { @@ -43,15 +43,24 @@ protected String getResourceName(Invoker invoker, Invocation invocation) { return buf.toString(); } - protected String getResourceName(Invoker invoker, Invocation invocation, String prefix) { + protected String getMethodResourceName(Invoker invoker, Invocation invocation, String prefix) { if (StringUtil.isBlank(prefix)) { - return getResourceName(invoker, invocation); + return getMethodResourceName(invoker, invocation); } StringBuilder buf = new StringBuilder(64); return buf.append(prefix) - .append(getResourceName(invoker, invocation)) - .toString(); + .append(getMethodResourceName(invoker, invocation)) + .toString(); + } + protected String getInterfaceName(Invoker invoker) { + return invoker.getInterface().getName(); + } + protected String getInterfaceName(Invoker invoker, String prefix) { + if (StringUtil.isBlank(prefix)) { + return getInterfaceName(invoker); + } + return prefix + getInterfaceName(invoker); } } diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAdapterGlobalConfig.java b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAdapterGlobalConfig.java new file mode 100644 index 0000000000..e7ec688bca --- /dev/null +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAdapterGlobalConfig.java @@ -0,0 +1,108 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.dubbo; + +import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DefaultDubboFallback; +import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallback; +import com.alibaba.csp.sentinel.adapter.dubbo.origin.DefaultDubboOriginParser; +import com.alibaba.csp.sentinel.adapter.dubbo.origin.DubboOriginParser; +import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; + +/** + *

Global config and callback registry of Dubbo legacy adapter.

+ * + * @author lianglin + * @author Eric Zhao + * @since 1.7.0 + */ +public final class DubboAdapterGlobalConfig { + + private static final String TRUE_STR = "true"; + + public static final String DUBBO_RES_NAME_WITH_PREFIX_KEY = "csp.sentinel.dubbo.resource.use.prefix"; + public static final String DUBBO_PROVIDER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.provider.prefix"; + public static final String DUBBO_CONSUMER_RES_NAME_PREFIX_KEY = "csp.sentinel.dubbo.resource.consumer.prefix"; + + private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:"; + private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:"; + + private static volatile DubboFallback consumerFallback = new DefaultDubboFallback(); + private static volatile DubboFallback providerFallback = new DefaultDubboFallback(); + private static volatile DubboOriginParser originParser = new DefaultDubboOriginParser(); + + public static boolean isUsePrefix() { + return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_RES_NAME_WITH_PREFIX_KEY)); + } + + public static String getDubboProviderPrefix() { + if (isUsePrefix()) { + String config = SentinelConfig.getConfig(DUBBO_PROVIDER_RES_NAME_PREFIX_KEY); + return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX; + } + return null; + } + + public static String getDubboConsumerPrefix() { + if (isUsePrefix()) { + String config = SentinelConfig.getConfig(DUBBO_CONSUMER_RES_NAME_PREFIX_KEY); + return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX; + } + return null; + } + + public static DubboFallback getConsumerFallback() { + return consumerFallback; + } + + public static void setConsumerFallback(DubboFallback consumerFallback) { + AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null"); + DubboAdapterGlobalConfig.consumerFallback = consumerFallback; + } + + public static DubboFallback getProviderFallback() { + return providerFallback; + } + + public static void setProviderFallback(DubboFallback providerFallback) { + AssertUtil.notNull(providerFallback, "providerFallback cannot be null"); + DubboAdapterGlobalConfig.providerFallback = providerFallback; + } + + /** + * Get the origin parser of Dubbo adapter. + * + * @return the origin parser + * @since 1.8.0 + */ + public static DubboOriginParser getOriginParser() { + return originParser; + } + + /** + * Set the origin parser of Dubbo adapter. + * + * @param originParser the origin parser + * @since 1.8.0 + */ + public static void setOriginParser(DubboOriginParser originParser) { + AssertUtil.notNull(originParser, "originParser cannot be null"); + DubboAdapterGlobalConfig.originParser = originParser; + } + + private DubboAdapterGlobalConfig() {} +} diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java index e7c36af92e..72dfd281e4 100755 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilter.java @@ -20,8 +20,6 @@ import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; -import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.dubbo.common.extension.Activate; @@ -56,10 +54,12 @@ public Result invoke(Invoker invoker, Invocation invocation) throws RpcExcept Entry interfaceEntry = null; Entry methodEntry = null; try { - String resourceName = getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix()); - interfaceEntry = SphU.entry(invoker.getInterface().getName(), ResourceTypeConstants.COMMON_RPC, - EntryType.OUT); - methodEntry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); + String prefix = DubboAdapterGlobalConfig.getDubboConsumerPrefix(); + String interfaceResourceName = getInterfaceName(invoker, prefix); + String methodResourceName = getMethodResourceName(invoker, invocation, prefix); + interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT); + methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, + EntryType.OUT, invocation.getArguments()); Result result = invoker.invoke(invocation); if (result.hasException()) { @@ -70,14 +70,14 @@ public Result invoke(Invoker invoker, Invocation invocation) throws RpcExcept } return result; } catch (BlockException e) { - return DubboFallbackRegistry.getConsumerFallback().handle(invoker, invocation, e); + return DubboAdapterGlobalConfig.getConsumerFallback().handle(invoker, invocation, e); } catch (RpcException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); throw e; } finally { if (methodEntry != null) { - methodEntry.exit(); + methodEntry.exit(1, invocation.getArguments()); } if (interfaceEntry != null) { interfaceEntry.exit(); diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java index 85d566ad99..405706e0a2 100755 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilter.java @@ -20,8 +20,6 @@ import com.alibaba.csp.sentinel.ResourceTypeConstants; import com.alibaba.csp.sentinel.SphU; import com.alibaba.csp.sentinel.Tracer; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; -import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; @@ -55,16 +53,20 @@ public SentinelDubboProviderFilter() { @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { // Get origin caller. - String application = DubboUtils.getApplication(invocation, ""); + String origin = DubboAdapterGlobalConfig.getOriginParser().parse(invoker, invocation); + if (null == origin) { + origin = ""; + } Entry interfaceEntry = null; Entry methodEntry = null; try { - String resourceName = getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix()); - String interfaceName = invoker.getInterface().getName(); - ContextUtil.enter(resourceName, application); + String prefix = DubboAdapterGlobalConfig.getDubboProviderPrefix(); + String methodResourceName = getMethodResourceName(invoker, invocation, prefix); + String interfaceName = getInterfaceName(invoker, prefix); + ContextUtil.enter(methodResourceName, origin); interfaceEntry = SphU.entry(interfaceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); - methodEntry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_RPC, + methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN, invocation.getArguments()); Result result = invoker.invoke(invocation); @@ -76,7 +78,7 @@ public Result invoke(Invoker invoker, Invocation invocation) throws RpcExcept } return result; } catch (BlockException e) { - return DubboFallbackRegistry.getProviderFallback().handle(invoker, invocation, e); + return DubboAdapterGlobalConfig.getProviderFallback().handle(invoker, invocation, e); } catch (RpcException e) { Tracer.traceEntry(e, interfaceEntry); Tracer.traceEntry(e, methodEntry); diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java deleted file mode 100644 index b7658d29a4..0000000000 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.csp.sentinel.adapter.dubbo.config; - -import com.alibaba.csp.sentinel.config.SentinelConfig; -import com.alibaba.csp.sentinel.util.StringUtil; - -/** - *

- * Responsible for dubbo service provider, consumer attribute configuration - *

- * - * @author lianglin - * @since 1.7.0 - */ -public final class DubboConfig { - - public static final String DUBBO_USE_PREFIX = "csp.sentinel.dubbo.resource.use.prefix"; - private static final String TRUE_STR = "true"; - - public static final String DUBBO_PROVIDER_PREFIX = "csp.sentinel.dubbo.resource.provider.prefix"; - public static final String DUBBO_CONSUMER_PREFIX = "csp.sentinel.dubbo.resource.consumer.prefix"; - - private static final String DEFAULT_DUBBO_PROVIDER_PREFIX = "dubbo:provider:"; - private static final String DEFAULT_DUBBO_CONSUMER_PREFIX = "dubbo:consumer:"; - - public static boolean isUsePrefix() { - return TRUE_STR.equalsIgnoreCase(SentinelConfig.getConfig(DUBBO_USE_PREFIX)); - } - - - public static String getDubboProviderPrefix() { - if (isUsePrefix()) { - String config = SentinelConfig.getConfig(DUBBO_PROVIDER_PREFIX); - return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_PROVIDER_PREFIX; - } - return null; - } - - public static String getDubboConsumerPrefix() { - if (isUsePrefix()) { - String config = SentinelConfig.getConfig(DUBBO_CONSUMER_PREFIX); - return StringUtil.isNotBlank(config) ? config : DEFAULT_DUBBO_CONSUMER_PREFIX; - } - return null; - } - - -} diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java index b97f703036..b893a55851 100644 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DefaultDubboFallback.java @@ -20,6 +20,7 @@ import com.alibaba.dubbo.rpc.Invocation; import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; +import com.alibaba.dubbo.rpc.RpcResult; /** * @author Eric Zhao @@ -28,7 +29,9 @@ public class DefaultDubboFallback implements DubboFallback { @Override public Result handle(Invoker invoker, Invocation invocation, BlockException ex) { - // Just wrap and throw the exception. - throw new SentinelRpcException(ex); + // Just wrap the exception. edit by wzg923 2020/9/23 + RpcResult result = new RpcResult(); + result.setException(new SentinelRpcException(ex.toRuntimeException())); + return result; } } diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java index fe06741bbd..2fb80a3cd2 100644 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistry.java @@ -15,33 +15,31 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; +import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig; + /** - * Global fallback registry for Dubbo. - * - * Note: Degrading is mainly designed for consumer. The provider should not - * give fallback result in most circumstances. + *

Global fallback registry for Dubbo.

* * @author Eric Zhao + * @deprecated use {@link DubboAdapterGlobalConfig} instead. */ +@Deprecated public final class DubboFallbackRegistry { - private static volatile DubboFallback consumerFallback = new DefaultDubboFallback(); - private static volatile DubboFallback providerFallback = new DefaultDubboFallback(); - public static DubboFallback getConsumerFallback() { - return consumerFallback; + return DubboAdapterGlobalConfig.getConsumerFallback(); } public static void setConsumerFallback(DubboFallback consumerFallback) { - DubboFallbackRegistry.consumerFallback = consumerFallback; + DubboAdapterGlobalConfig.setConsumerFallback(consumerFallback); } public static DubboFallback getProviderFallback() { - return providerFallback; + return DubboAdapterGlobalConfig.getProviderFallback(); } public static void setProviderFallback(DubboFallback providerFallback) { - DubboFallbackRegistry.providerFallback = providerFallback; + DubboAdapterGlobalConfig.setProviderFallback(providerFallback); } private DubboFallbackRegistry() {} diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DefaultDubboOriginParser.java b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DefaultDubboOriginParser.java new file mode 100644 index 0000000000..8276079343 --- /dev/null +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DefaultDubboOriginParser.java @@ -0,0 +1,35 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.dubbo.origin; + +import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; + +/** + * Default Dubbo origin parser. + * + * @author tiecheng + * @since 1.8.0 + */ +public class DefaultDubboOriginParser implements DubboOriginParser { + + @Override + public String parse(Invoker invoker, Invocation invocation) { + return DubboUtils.getApplication(invocation, ""); + } + +} diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginParser.java b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginParser.java new file mode 100644 index 0000000000..13e476a1f7 --- /dev/null +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginParser.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.dubbo.origin; + +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; + +/** + * Customized origin parser for Dubbo provider filter. + * + * @author tiecheng + * @since 1.8.0 + */ +public interface DubboOriginParser { + + /** + * Parses the origin (caller) from Dubbo invocation. + * + * @param invoker Dubbo invoker + * @param invocation Dubbo invocation + * @return the parsed origin + */ + String parse(Invoker invoker, Invocation invocation); + +} diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilterTest.java b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilterTest.java index cb9da8c413..d1003fd27a 100644 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilterTest.java +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/AbstractDubboFilterTest.java @@ -1,6 +1,5 @@ package com.alibaba.csp.sentinel.adapter.dubbo; -import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; import com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService; import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.dubbo.rpc.Invocation; @@ -26,15 +25,15 @@ public class AbstractDubboFilterTest { @Before public void setUp() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "true"); - SentinelConfig.setConfig(DubboConfig.DUBBO_PROVIDER_PREFIX, ""); - SentinelConfig.setConfig(DubboConfig.DUBBO_CONSUMER_PREFIX, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); } @After public void tearDown() { SentinelConfig.setConfig("csp.sentinel.dubbo.resource.use.prefix", "false"); - SentinelConfig.setConfig(DubboConfig.DUBBO_PROVIDER_PREFIX, ""); - SentinelConfig.setConfig(DubboConfig.DUBBO_CONSUMER_PREFIX, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, ""); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, ""); } private AbstractDubboFilter filter = new AbstractDubboFilter() { @@ -55,7 +54,7 @@ public void testGetResourceName() { when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); - String resourceName = filter.getResourceName(invoker, invocation); + String resourceName = filter.getMethodResourceName(invoker, invocation); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); } @@ -71,19 +70,19 @@ public void testGetResourceNameWithPrefix() { when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); //test with default prefix - String resourceName = filter.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix()); + String resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderPrefix()); System.out.println("resourceName = " + resourceName); assertEquals("dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); - resourceName = filter.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix()); + resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerPrefix()); assertEquals("dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); //test with custom prefix - SentinelConfig.setConfig(DubboConfig.DUBBO_PROVIDER_PREFIX, "my:dubbo:provider:"); - SentinelConfig.setConfig(DubboConfig.DUBBO_CONSUMER_PREFIX, "my:dubbo:consumer:"); - resourceName = filter.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix()); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_PROVIDER_RES_NAME_PREFIX_KEY, "my:dubbo:provider:"); + SentinelConfig.setConfig(DubboAdapterGlobalConfig.DUBBO_CONSUMER_RES_NAME_PREFIX_KEY, "my:dubbo:consumer:"); + resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboProviderPrefix()); assertEquals("my:dubbo:provider:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); - resourceName = filter.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix()); + resourceName = filter.getMethodResourceName(invoker, invocation, DubboAdapterGlobalConfig.getDubboConsumerPrefix()); assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); } diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java index 58de29d64f..ee7bfccc91 100644 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboConsumerFilterTest.java @@ -85,7 +85,7 @@ private void verifyInvocationStructure(Invoker invoker, Invocation invocation) { // As not call ContextUtil.enter(resourceName, application) in SentinelDubboConsumerFilter, use default context // In actual project, a consumer is usually also a provider, the context will be created by SentinelDubboProviderFilter // If consumer is on the top of Dubbo RPC invocation chain, use default context - String resourceName = filter.getResourceName(invoker, invocation); + String resourceName = filter.getMethodResourceName(invoker, invocation); assertEquals(Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java index 8f1f850f2f..bcd986e31d 100644 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/SentinelDubboProviderFilterTest.java @@ -85,7 +85,7 @@ private void verifyInvocationStructure(String originApplication, Invoker invoker assertNotNull(context); // As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter - String resourceName = filter.getResourceName(invoker, invocation); + String resourceName = filter.getMethodResourceName(invoker, invocation); assertEquals(resourceName, context.getName()); assertEquals(originApplication, context.getOrigin()); diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java index f75e0c34e8..edcd2a017b 100644 --- a/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/fallback/DubboFallbackRegistryTest.java @@ -15,6 +15,7 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo.fallback; +import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; import com.alibaba.csp.sentinel.slots.block.flow.FlowException; @@ -22,7 +23,6 @@ import com.alibaba.dubbo.rpc.Invoker; import com.alibaba.dubbo.rpc.Result; import com.alibaba.dubbo.rpc.RpcResult; - import org.junit.Assert; import org.junit.Test; @@ -31,24 +31,25 @@ */ public class DubboFallbackRegistryTest { - @Test(expected = SentinelRpcException.class) + @Test public void testDefaultFallback() { - // Test for default. + // Test for default fallback. BlockException ex = new FlowException("xxx"); - DubboFallbackRegistry.getConsumerFallback() - .handle(null, null, ex); + Result result = new DefaultDubboFallback().handle(null, null, ex); + Assert.assertTrue(result.hasException()); + Assert.assertEquals(SentinelRpcException.class, result.getException().getClass()); } @Test public void testCustomFallback() { BlockException ex = new FlowException("xxx"); - DubboFallbackRegistry.setConsumerFallback(new DubboFallback() { + DubboAdapterGlobalConfig.setConsumerFallback(new DubboFallback() { @Override public Result handle(Invoker invoker, Invocation invocation, BlockException e) { return new RpcResult("Error: " + e.getClass().getName()); } }); - Result result = DubboFallbackRegistry.getConsumerFallback() + Result result = DubboAdapterGlobalConfig.getConsumerFallback() .handle(null, null, ex); Assert.assertFalse("The invocation should not fail", result.hasException()); Assert.assertEquals("Error: " + ex.getClass().getName(), result.getValue()); diff --git a/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginRegistryTest.java b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginRegistryTest.java new file mode 100644 index 0000000000..ab253c267f --- /dev/null +++ b/sentinel-adapter/sentinel-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/origin/DubboOriginRegistryTest.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.dubbo.origin; + +import com.alibaba.csp.sentinel.adapter.dubbo.DubboAdapterGlobalConfig; +import com.alibaba.csp.sentinel.adapter.dubbo.DubboUtils; +import com.alibaba.dubbo.rpc.Invocation; +import com.alibaba.dubbo.rpc.Invoker; +import com.alibaba.dubbo.rpc.RpcInvocation; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author tiecheng + */ +public class DubboOriginRegistryTest { + + @After + public void cleanUp() { + DubboAdapterGlobalConfig.setOriginParser(new DefaultDubboOriginParser()); + } + + @Test(expected = IllegalArgumentException.class) + public void testDefaultOriginParserFail() { + DubboAdapterGlobalConfig.getOriginParser().parse(null, null); + } + + @Test + public void testDefaultOriginParserSuccess() { + RpcInvocation invocation = new RpcInvocation(); + String dubboName = "sentinel"; + invocation.setAttachment(DubboUtils.DUBBO_APPLICATION_KEY, dubboName); + String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals(dubboName, origin); + } + + @Test + public void testCustomOriginParser() { + DubboAdapterGlobalConfig.setOriginParser(new DubboOriginParser() { + @Override + public String parse(Invoker invoker, Invocation invocation) { + return invocation.getAttachment(DubboUtils.DUBBO_APPLICATION_KEY, "default") + "_" + invocation + .getMethodName(); + } + }); + + RpcInvocation invocation = new RpcInvocation(); + String origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals("default_null", origin); + + String dubboName = "sentinel"; + invocation.setAttachment(DubboUtils.DUBBO_APPLICATION_KEY, dubboName); + origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals(dubboName + "_null", origin); + + invocation.setMethodName("hello"); + origin = DubboAdapterGlobalConfig.getOriginParser().parse(null, invocation); + Assert.assertEquals(dubboName + "_hello", origin); + } + +} diff --git a/sentinel-adapter/sentinel-grpc-adapter/pom.xml b/sentinel-adapter/sentinel-grpc-adapter/pom.xml index 7f4f9f1378..909e397a2f 100755 --- a/sentinel-adapter/sentinel-grpc-adapter/pom.xml +++ b/sentinel-adapter/sentinel-grpc-adapter/pom.xml @@ -5,14 +5,14 @@ sentinel-adapter com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 sentinel-grpc-adapter jar - 1.13.1 + 1.30.2 @@ -43,7 +43,7 @@ javax.annotation javax.annotation-api - 1.2 + ${javax.annotation-api.version} @@ -95,4 +95,4 @@ - \ No newline at end of file + diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/README.md b/sentinel-adapter/sentinel-jax-rs-adapter/README.md new file mode 100755 index 0000000000..50ac623d01 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/README.md @@ -0,0 +1,83 @@ +# Sentinel adapter for JAX-RS + +Sentinel provides integration to enable fault-tolerance and flow control for JAX-RS web requests. +Add the following dependency in `pom.xml` (if you are using Maven): + +```xml + + com.alibaba.csp + sentinel-jax-rs-adapter + x.y.z + +``` + +## SentinelJaxRsProviderFilter + +The `SentinelJaxRsProviderFilter` is auto activated in pure JAX-RS application. + +For Spring web applications you can configure with Spring bean: + +```java +@Configuration +public class FilterConfig { + + @Bean + public SentinelJaxRsProviderFilter sentinelJaxRsProviderFilter() { + return new SentinelJaxRsProviderFilter(); + } +} +``` + +## DefaultExceptionMapper + +Sentinel provides DefaultExceptionMapper to map Throwable to Response (with Status.INTERNAL_SERVER_ERROR), +in order to let SentinelJaxRsProviderFilter to be called and exit the Sentinel entry. + +According to `3.3.4 Exceptions` of [jaxrs-2_1-final-spec](https://download.oracle.com/otn-pub/jcp/jaxrs-2_1-final-eval-spec/jaxrs-2_1-final-spec.pdf): + +> Checked exceptions and throwables that have not been mapped and cannot be thrown directly MUST be wrapped in a container-specific exception that is then thrown and allowed to propagate to the underlying container. + +If WebApplicationException or its subclasses are thrown, they'll be automatically converted to `Response` and can enter response filter. +If other kind of exceptions were thrown, and not matched by custom exception mapper, then the response filter cannot be called. +For this case, a default exception mapper maybe introduced. + +According to `4.4 Exception Mapping Providers` of [jaxrs-2_1-final-spec](https://download.oracle.com/otn-pub/jcp/jaxrs-2_1-final-eval-spec/jaxrs-2_1-final-spec.pdf): + +> When choosing an exception mapping provider to map an exception, an implementation MUST use the provider whose generic type is the nearest superclass of the exception. If two or more exception providers are applicable, the one with the highest priority MUST be chosen as described in Section 4.1.3. + +If user also provides customized exception mapper of `Throwable`, then user has the responsibility to convert it to response and then the response filter can be called. + +As describe in `6.7.1 exceptions` of [jaxrs-2_1-final-spec](https://download.oracle.com/otn-pub/jcp/jaxrs-2_1-final-eval-spec/jaxrs-2_1-final-spec.pdf): + +> A response mapped from an exception MUST be processed using the ContainerResponse filter chain and the WriteTo interceptor chain (if an entity is present in the mapped response). + +## SentinelJaxRsClientTemplate + +For jax-rs client, we provide `SentinelJaxRsClientTemplate` you can use it like this: + +``` +Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { + @Override + public Response get() { + return client.target(host).path(url).request() + .get(); + } +}); +``` + +or executeAsync like this: + +``` +Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { + @Override + public Future get() { + return client.target(host).path(url).request() + .async() + .get(); + } +}); +``` + +When a request is blocked, Sentinel JAX-RS filter will return Response with status of `TOO_MANY_REQUESTS` indicating the request is rejected. + +You can customize it by implement your own `SentinelJaxRsFallback` and register to `SentinelJaxRsConfig`. diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/pom.xml b/sentinel-adapter/sentinel-jax-rs-adapter/pom.xml new file mode 100755 index 0000000000..7b1a9b6074 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/pom.xml @@ -0,0 +1,74 @@ + + + 4.0.0 + + + com.alibaba.csp + sentinel-adapter + 1.8.1-SNAPSHOT + + + sentinel-jax-rs-adapter + jar + + + 2.1.1 + + + + + com.alibaba.csp + sentinel-core + + + + javax.ws.rs + javax.ws.rs-api + ${javax.ws.rs-api.version} + provided + + + + junit + junit + test + + + org.springframework.boot + spring-boot-starter-web + 2.2.6.RELEASE + test + + + org.springframework.boot + spring-boot-starter-test + 2.2.6.RELEASE + test + + + io.rest-assured + rest-assured + 4.3.0 + test + + + org.jboss.resteasy + resteasy-spring-boot-starter + 4.5.1.Final + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + always + + + + + diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsClientTemplate.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsClientTemplate.java new file mode 100644 index 0000000000..07abea3d77 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsClientTemplate.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs; + +import com.alibaba.csp.sentinel.*; +import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; +import com.alibaba.csp.sentinel.adapter.jaxrs.future.FutureWrapper; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.util.function.Supplier; + +import javax.ws.rs.core.Response; +import java.util.concurrent.Future; + + +/** + * wrap jax-rs client execution with sentinel + *
+ *         Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() {
+ *
+ *             @Override
+ *             public Response get() {
+ *                 return client.target(host).path(url).request()
+ *                         .get();
+ *             }
+ *         });
+ * 
+ * @author sea + */ +public class SentinelJaxRsClientTemplate { + + /** + * execute supplier with sentinel + * @param resourceName + * @param supplier + * @return + */ + public static Response execute(String resourceName, Supplier supplier) { + Entry entry = null; + try { + entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); + return supplier.get(); + } catch (BlockException ex) { + return SentinelJaxRsConfig.getJaxRsFallback().fallbackResponse(resourceName, ex); + } finally { + System.out.println("entry exit"); + if (entry != null) { + entry.exit(); + } + } + } + + /** + * execute supplier with sentinel + * @param resourceName + * @param supplier + * @return + */ + public static Future executeAsync(String resourceName, Supplier> supplier) { + try { + AsyncEntry entry = SphU.asyncEntry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); + return new FutureWrapper<>(entry, supplier.get()); + } catch (BlockException ex) { + return SentinelJaxRsConfig.getJaxRsFallback().fallbackFutureResponse(resourceName, ex); + } + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsProviderFilter.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsProviderFilter.java new file mode 100644 index 0000000000..ffe4edb2f1 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/SentinelJaxRsProviderFilter.java @@ -0,0 +1,88 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs; + +import com.alibaba.csp.sentinel.*; +import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.util.StringUtil; + +import javax.ws.rs.container.*; +import javax.ws.rs.core.Context; +import javax.ws.rs.ext.Provider; +import java.io.IOException; + +/** + * @author sea + */ +@Provider +public class SentinelJaxRsProviderFilter implements ContainerRequestFilter, ContainerResponseFilter { + + private static final String SENTINEL_JAX_RS_PROVIDER_CONTEXT_NAME = "sentinel_jax_rs_provider_context"; + + + private static final String SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY = "sentinel_jax_rs_provider_entry_property"; + + @Context + private ResourceInfo resourceInfo; + + @Override + public void filter(ContainerRequestContext containerRequestContext) throws IOException { + + try { + String resourceName = getResourceName(containerRequestContext, resourceInfo); + + if (StringUtil.isNotEmpty(resourceName)) { + // Parse the request origin using registered origin parser. + String origin = parseOrigin(containerRequestContext); + String contextName = getContextName(containerRequestContext); + ContextUtil.enter(contextName, origin); + Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); + + containerRequestContext.setProperty(SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY, entry); + } + } catch (BlockException e) { + try { + containerRequestContext.abortWith(SentinelJaxRsConfig.getJaxRsFallback().fallbackResponse(containerRequestContext.getUriInfo().getPath(), e)); + } finally { + ContextUtil.exit(); + } + } + } + + @Override + public void filter(ContainerRequestContext containerRequestContext, ContainerResponseContext containerResponseContext) throws IOException { + Entry entry = (Entry) containerRequestContext.getProperty(SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY); + if (entry != null) { + entry.exit(); + } + containerRequestContext.removeProperty(SENTINEL_JAX_RS_PROVIDER_ENTRY_PROPERTY); + ContextUtil.exit(); + } + + public String getResourceName(ContainerRequestContext containerRequestContext, ResourceInfo resourceInfo) { + return SentinelJaxRsConfig.getResourceNameParser().parse(containerRequestContext, resourceInfo); + } + + protected String getContextName(ContainerRequestContext request) { + return SENTINEL_JAX_RS_PROVIDER_CONTEXT_NAME; + } + + protected String parseOrigin(ContainerRequestContext request) { + return SentinelJaxRsConfig.getRequestOriginParser().parseOrigin(request); + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/config/SentinelJaxRsConfig.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/config/SentinelJaxRsConfig.java new file mode 100644 index 0000000000..a2684ed2ae --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/config/SentinelJaxRsConfig.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs.config; + +import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.DefaultSentinelJaxRsFallback; +import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback; +import com.alibaba.csp.sentinel.adapter.jaxrs.request.DefaultRequestOriginParser; +import com.alibaba.csp.sentinel.adapter.jaxrs.request.DefaultResourceNameParser; +import com.alibaba.csp.sentinel.adapter.jaxrs.request.RequestOriginParser; +import com.alibaba.csp.sentinel.adapter.jaxrs.request.ResourceNameParser; + +/** + * @author sea + */ +public class SentinelJaxRsConfig { + + private static volatile ResourceNameParser resourceNameParser = new DefaultResourceNameParser(); + + private static volatile RequestOriginParser requestOriginParser = new DefaultRequestOriginParser(); + + private static volatile SentinelJaxRsFallback jaxRsFallback = new DefaultSentinelJaxRsFallback(); + + public static ResourceNameParser getResourceNameParser() { + return resourceNameParser; + } + + public static void setResourceNameParser(ResourceNameParser resourceNameParser) { + SentinelJaxRsConfig.resourceNameParser = resourceNameParser; + } + + public static RequestOriginParser getRequestOriginParser() { + return requestOriginParser; + } + + public static void setRequestOriginParser(RequestOriginParser originParser) { + SentinelJaxRsConfig.requestOriginParser = originParser; + } + + public static SentinelJaxRsFallback getJaxRsFallback() { + return jaxRsFallback; + } + + public static void setJaxRsFallback(SentinelJaxRsFallback jaxRsFallback) { + SentinelJaxRsConfig.jaxRsFallback = jaxRsFallback; + } + + private SentinelJaxRsConfig() { + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/exception/DefaultExceptionMapper.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/exception/DefaultExceptionMapper.java new file mode 100644 index 0000000000..a441204a47 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/exception/DefaultExceptionMapper.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs.exception; + + +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ExceptionMapper; +import javax.ws.rs.ext.Provider; + +/** + * sentinel jax-rs adapter provide this exception mapper + * in case of user throw exception which is not {@link javax.ws.rs.WebApplicationException} and not matched by any ExceptionMapper + * this exception mapper convert exception to Response let ContainerResponseFilter to be called to exit sentinel entry + * user can add custom ExceptionMapper and config with {@link javax.annotation.Priority} with lower value + * @author sea + */ +@Provider +public class DefaultExceptionMapper implements ExceptionMapper { + + @Override + public Response toResponse(Throwable exception) { + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(exception.getMessage()) + .build(); + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/DefaultSentinelJaxRsFallback.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/DefaultSentinelJaxRsFallback.java new file mode 100644 index 0000000000..d1f937728b --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/DefaultSentinelJaxRsFallback.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs.fallback; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; + +/** + * @author sea + */ +public class DefaultSentinelJaxRsFallback implements SentinelJaxRsFallback { + @Override + public Response fallbackResponse(String route, Throwable cause) { + return Response.status(Response.Status.TOO_MANY_REQUESTS) + .entity("Blocked by Sentinel (flow limiting)") + .type(MediaType.APPLICATION_JSON_TYPE) + .build(); + } + + @Override + public Future fallbackFutureResponse(final String route, final Throwable cause) { + return new FutureTask<>(new Callable() { + @Override + public Response call() throws Exception { + return fallbackResponse(route, cause); + } + }); + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/SentinelJaxRsFallback.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/SentinelJaxRsFallback.java new file mode 100644 index 0000000000..b5cf611525 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/fallback/SentinelJaxRsFallback.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs.fallback; + +import javax.ws.rs.core.Response; +import java.util.concurrent.Future; + +/** + * @author sea + */ +public interface SentinelJaxRsFallback { + + /** + * Provides a fallback response based on the cause of the failed execution. + * + * @param route The route the fallback is for + * @param cause cause of the main method failure, may be null + * @return the fallback response + */ + Response fallbackResponse(String route, Throwable cause); + + /** + * Provides a fallback response future based on the cause of the failed execution. + * + * @param route The route the fallback is for + * @param cause cause of the main method failure, may be null + * @return the fallback response future + */ + Future fallbackFutureResponse(String route, Throwable cause); +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/future/FutureWrapper.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/future/FutureWrapper.java new file mode 100644 index 0000000000..bc332114e4 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/future/FutureWrapper.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs.future; + +import com.alibaba.csp.sentinel.AsyncEntry; + +import java.util.concurrent.*; + +/** + * wrap Future to ensure entry exit + * @author sea + */ +public class FutureWrapper implements Future { + + AsyncEntry entry; + + Future future; + + public FutureWrapper(AsyncEntry entry, Future future) { + this.entry = entry; + this.future = future; + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + try { + return future.cancel(mayInterruptIfRunning); + } finally { + exitEntry(); + } + + } + + @Override + public boolean isCancelled() { + return future.isCancelled(); + } + + @Override + public boolean isDone() { + return future.isDone(); + } + + @Override + public V get() throws InterruptedException, ExecutionException { + try { + return future.get(); + } finally { + exitEntry(); + } + } + + @Override + public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + try { + return future.get(timeout, unit); + } finally { + exitEntry(); + } + } + + private void exitEntry() { + if (entry != null) { + entry.exit(); + } + } + +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultRequestOriginParser.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultRequestOriginParser.java new file mode 100644 index 0000000000..b8ab0f9cdc --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultRequestOriginParser.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs.request; + +import javax.ws.rs.container.ContainerRequestContext; + +/** + * @author sea + */ +public class DefaultRequestOriginParser implements RequestOriginParser { + + private static final String EMPTY_ORIGIN = ""; + + @Override + public String parseOrigin(ContainerRequestContext request) { + return EMPTY_ORIGIN; + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultResourceNameParser.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultResourceNameParser.java new file mode 100644 index 0000000000..efc8c7ed67 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/DefaultResourceNameParser.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs.request; + +import javax.ws.rs.Path; +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ResourceInfo; + +/** + * @author sea + */ +public class DefaultResourceNameParser implements ResourceNameParser { + @Override + public String parse(ContainerRequestContext containerRequestContext, ResourceInfo resourceInfo) { + Path classPath = resourceInfo.getResourceClass().getAnnotation(Path.class); + Path methodPath = resourceInfo.getResourceMethod().getAnnotation(Path.class); + return containerRequestContext.getRequest().getMethod() + ":" + (classPath != null ? classPath.value() : "") + (methodPath != null ? methodPath.value() : ""); + } + +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/RequestOriginParser.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/RequestOriginParser.java new file mode 100644 index 0000000000..0eff671f32 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/RequestOriginParser.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs.request; + +import javax.ws.rs.container.ContainerRequestContext; + +/** + * The origin parser parses request origin (e.g. IP, user, appName) from HTTP request. + * + * @author sea + */ +public interface RequestOriginParser { + + /** + * Parse the origin from given HTTP request. + * + * @param request HTTP request + * @return parsed origin + */ + String parseOrigin(ContainerRequestContext request); +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/ResourceNameParser.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/ResourceNameParser.java new file mode 100644 index 0000000000..096c31bffd --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/jaxrs/request/ResourceNameParser.java @@ -0,0 +1,27 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs.request; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.container.ResourceInfo; + +/** + * @author sea + */ +public interface ResourceNameParser { + + String parse(ContainerRequestContext containerRequestContext, ResourceInfo resourceInfo); +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ClientFilterTest.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ClientFilterTest.java new file mode 100644 index 0000000000..91c01e1192 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ClientFilterTest.java @@ -0,0 +1,413 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs; + +import com.alibaba.csp.sentinel.Constants; +import com.alibaba.csp.sentinel.CtSph; +import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; +import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback; +import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.node.EntranceNode; +import com.alibaba.csp.sentinel.node.Node; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.csp.sentinel.util.function.Supplier; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.SocketUtils; + +import javax.ws.rs.ProcessingException; +import javax.ws.rs.client.Client; +import javax.ws.rs.client.ClientBuilder; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.concurrent.*; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertNull; + +/** + * @author sea + */ +public class ClientFilterTest { + + private static final String HELLO_STR = "Hello!"; + + static int port; + + static String host; + + static Client client; + + static ConfigurableApplicationContext ctx; + + @BeforeClass + public static void startApplication() { + client = ClientBuilder.newBuilder() + .connectTimeout(3, TimeUnit.SECONDS) + .readTimeout(3, TimeUnit.SECONDS) + .build(); + + port = SocketUtils.findAvailableTcpPort(); + host = "http://127.0.0.1:" + port; + SpringApplication springApplication = new SpringApplication(TestApplication.class); + ctx = springApplication.run("--spring.profiles.active=client", "--server.port=" + port); + } + + @AfterClass + public static void shutdown() { + ctx.close(); + + Context context = ContextUtil.getContext(); + if (context != null) { + context.setCurEntry(null); + ContextUtil.exit(); + } + + Constants.ROOT.removeChildList(); + + ClusterBuilderSlot.getClusterNodeMap().clear(); + + // Clear chainMap in CtSph + try { + Method resetChainMapMethod = CtSph.class.getDeclaredMethod("resetChainMap"); + resetChainMapMethod.setAccessible(true); + resetChainMapMethod.invoke(null); + } catch (Exception e) { + // Empty + } + } + + @After + public void cleanUp() { + FlowRuleManager.loadRules(null); + ClusterBuilderSlot.resetClusterNodes(); + } + + @Test + public void testClientGetHello() { + final String url = "/test/hello"; + String resourceName = "GET:" + url; + Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { + + @Override + public Response get() { + return client.target(host).path(url).request() + .get(); + } + }); + assertEquals(200, response.getStatus()); + assertEquals(HELLO_STR, response.readEntity(String.class)); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + + String context = ""; + for (Node n : Constants.ROOT.getChildList()) { + if (n instanceof EntranceNode) { + String id = ((EntranceNode) n).getId().getName(); + if (url.equals(id)) { + context = ((EntranceNode) n).getId().getName(); + } + } + } + assertEquals("", context); + } + + @Test + public void testClientAsyncGetHello() throws InterruptedException, ExecutionException { + final String url = "/test/async-hello"; + final String resourceName = "GET:" + url; + + Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { + @Override + public Future get() { + return client.target(host).path(url).request() + .async() + .get(); + } + }); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(HELLO_STR, future.get().readEntity(String.class)); + + cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + } + + @Test + public void testCustomResourceName() { + final String url = "/test/hello/{name}"; + final String resourceName = "GET:" + url; + + Response response1 = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { + @Override + public Response get() { + return client.target(host) + .path(url) + .resolveTemplate("name", "abc") + .request() + .get(); + } + }); + assertEquals(200, response1.getStatus()); + assertEquals("Hello abc !", response1.readEntity(String.class)); + + Response response2 = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { + @Override + public Response get() { + return client.target(host) + .path(url) + .resolveTemplate("name", "def") + .request() + .get(); + } + }); + assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(), response2.getStatus()); + assertEquals("Hello def !", response2.readEntity(String.class)); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(2, cn.passQps(), 0.01); + + assertNull(ClusterBuilderSlot.getClusterNode("/test/hello/abc")); + assertNull(ClusterBuilderSlot.getClusterNode("/test/hello/def")); + } + + @Test + public void testClientFallback() { + final String url = "/test/hello"; + final String resourceName = "GET:" + url; + configureRulesFor(resourceName, 0); + + Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { + @Override + public Response get() { + return client.target(host).path(url).request() + .get(); + } + }); + assertEquals(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode(), response.getStatus()); + assertEquals("Blocked by Sentinel (flow limiting)", response.readEntity(String.class)); + + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(0, cn.passQps(), 0.01); + } + + @Test + public void testClientCustomFallback() { + final String url = "/test/hello"; + final String resourceName = "GET:" + url; + configureRulesFor(resourceName, 0); + + SentinelJaxRsConfig.setJaxRsFallback(new SentinelJaxRsFallback() { + @Override + public javax.ws.rs.core.Response fallbackResponse(String route, Throwable cause) { + return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.OK) + .entity("Blocked by Sentinel (flow limiting)") + .type(MediaType.APPLICATION_JSON_TYPE) + .build(); + } + + @Override + public Future fallbackFutureResponse(final String route, final Throwable cause) { + return new FutureTask<>(new Callable() { + @Override + public Response call() throws Exception { + return fallbackResponse(route, cause); + } + }); + } + }); + + Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { + @Override + public Response get() { + return client.target(host).path(url).request() + .get(); + } + }); + assertEquals(javax.ws.rs.core.Response.Status.OK.getStatusCode(), response.getStatus()); + assertEquals("Blocked by Sentinel (flow limiting)", response.readEntity(String.class)); + + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(0, cn.passQps(), 0.01); + } + + @Test + public void testServerReturn400() { + final String url = "/test/400"; + final String resourceName = "GET:" + url; + Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { + @Override + public Response get() { + return client.target(host).path(url).request() + .get(); + } + }); + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + assertEquals("test return 400", response.readEntity(String.class)); + + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + } + + @Test + public void testServerReturn500() { + final String url = "/test/ex"; + final String resourceName = "GET:" + url; + Response response = SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { + @Override + public Response get() { + return client.target(host).path(url).request() + .get(); + } + }); + assertEquals(Response.Status.INTERNAL_SERVER_ERROR.getStatusCode(), response.getStatus()); + assertEquals("test exception mapper", response.readEntity(String.class)); + + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + } + + @Test + public void testServerTimeout() { + final String url = "/test/delay/10"; + final String resourceName = "GET:/test/delay/{seconds}"; + try { + SentinelJaxRsClientTemplate.execute(resourceName, new Supplier() { + @Override + public Response get() { + return client.target(host).path(url).request() + .get(); + } + }); + } catch (ProcessingException e) { + //ignore + } + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(0, cn.passQps(), 0.01); + } + + @Test + public void testFutureGetServerTimeout() { + final String url = "/test/delay/10"; + final String resourceName = "GET:/test/delay/{seconds}"; + try { + Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { + @Override + public Future get() { + return client.target(host).path(url).request() + .async() + .get(); + } + }); + future.get(); + } catch (Exception e) { + //ignore + } + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(0, cn.passQps(), 0.01); + } + + @Test + public void testFutureGetTimeout() { + final String url = "/test/delay/10"; + final String resourceName = "GET:/test/delay/{seconds}"; + try { + Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { + @Override + public Future get() { + return client.target(host).path(url).request() + .async() + .get(); + } + }); + future.get(1, TimeUnit.SECONDS); + } catch (Exception e) { + //ignore + } + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(0, cn.passQps(), 0.01); + } + + @Test + public void testCancelFuture() { + final String url = "/test/delay/10"; + final String resourceName = "GET:/test/delay/{seconds}"; + try { + Future future = SentinelJaxRsClientTemplate.executeAsync(resourceName, new Supplier>() { + @Override + public Future get() { + return client.target(host).path(url).request() + .async() + .get(); + } + }); + future.cancel(false); + } catch (Exception e) { + //ignore + } + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + } + + private void configureRulesFor(String resource, int count) { + configureRulesFor(resource, count, "default"); + } + + private void configureRulesFor(String resource, int count, String limitApp) { + FlowRule rule = new FlowRule() + .setCount(count) + .setGrade(RuleConstant.FLOW_GRADE_QPS); + rule.setResource(resource); + if (StringUtil.isNotBlank(limitApp)) { + rule.setLimitApp(limitApp); + } + FlowRuleManager.loadRules(Collections.singletonList(rule)); + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ProviderFilterTest.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ProviderFilterTest.java new file mode 100644 index 0000000000..3061440476 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/ProviderFilterTest.java @@ -0,0 +1,257 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs; + +import java.util.Collections; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; + +import com.alibaba.csp.sentinel.Constants; +import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; +import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback; +import com.alibaba.csp.sentinel.adapter.jaxrs.request.RequestOriginParser; +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.node.EntranceNode; +import com.alibaba.csp.sentinel.node.Node; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.alibaba.csp.sentinel.util.StringUtil; + +import io.restassured.RestAssured; +import io.restassured.response.Response; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.boot.SpringApplication; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.SocketUtils; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.MediaType; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.*; + +/** + * @author sea + */ +public class ProviderFilterTest { + + private static final String HELLO_STR = "Hello!"; + + static ConfigurableApplicationContext ctx; + + @BeforeClass + public static void startApplication() { + RestAssured.basePath = ""; + int port = SocketUtils.findAvailableTcpPort(); + RestAssured.port = port; + SpringApplication springApplication = new SpringApplication(TestApplication.class); + ctx = springApplication.run("--spring.profiles.active=provider", "--server.port=" + port); + } + + @AfterClass + public static void shutdown() { + ctx.close(); + } + + @After + public void cleanUp() { + FlowRuleManager.loadRules(null); + ClusterBuilderSlot.resetClusterNodes(); + } + + + @Test + public void testGetHello() { + String url = "/test/hello"; + String resourceName = "GET:" + url; + Response response = given().get(url); + response.then().statusCode(200).body(equalTo(HELLO_STR)); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + + String context = ""; + for (Node n : Constants.ROOT.getChildList()) { + if (n instanceof EntranceNode) { + String id = ((EntranceNode) n).getId().getName(); + if (url.equals(id)) { + context = ((EntranceNode) n).getId().getName(); + } + } + } + assertEquals("", context); + } + + @Test + public void testAsyncGetHello() { + String url = "/test/async-hello"; + String resourceName = "GET:" + url; + Response response = given().get(url); + response.then().statusCode(200).body(equalTo(HELLO_STR)); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + + String context = ""; + for (Node n : Constants.ROOT.getChildList()) { + if (n instanceof EntranceNode) { + String id = ((EntranceNode) n).getId().getName(); + if (url.equals(id)) { + context = ((EntranceNode) n).getId().getName(); + } + } + } + assertEquals("", context); + } + + @Test + public void testUrlPathParam() { + String url = "/test/hello/{name}"; + String resourceName = "GET:" + url; + + String url1 = "/test/hello/abc"; + Response response1 = given().get(url1); + response1.then().statusCode(200).body(equalTo("Hello abc !")); + + String url2 = "/test/hello/def"; + Response response2 = given().get(url2); + response2.then().statusCode(200).body(equalTo("Hello def !")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(2, cn.passQps(), 0.01); + + assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url1)); + assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url2)); + } + + @Test + public void testDefaultFallback() { + String url = "/test/hello"; + String resourceName = "GET:" + url; + configureRulesFor(resourceName, 0); + Response response = given().get(url); + response.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode()) + .body(equalTo("Blocked by Sentinel (flow limiting)")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(0, cn.passQps(), 0.01); + } + + @Test + public void testCustomFallback() { + String url = "/test/hello"; + String resourceName = "GET:" + url; + SentinelJaxRsConfig.setJaxRsFallback(new SentinelJaxRsFallback() { + @Override + public javax.ws.rs.core.Response fallbackResponse(String route, Throwable cause) { + return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.OK) + .entity("Blocked by Sentinel (flow limiting)") + .type(MediaType.APPLICATION_JSON_TYPE) + .build(); + } + + @Override + public Future fallbackFutureResponse(final String route, final Throwable cause) { + return new FutureTask<>(new Callable() { + @Override + public javax.ws.rs.core.Response call() throws Exception { + return fallbackResponse(route, cause); + } + }); + } + }); + + + configureRulesFor(resourceName, 0); + Response response = given().get(url); + response.then().statusCode(javax.ws.rs.core.Response.Status.OK.getStatusCode()) + .body(equalTo("Blocked by Sentinel (flow limiting)")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(0, cn.passQps(), 0.01); + } + + @Test + public void testCustomRequestOriginParser() { + String url = "/test/hello"; + String resourceName = "GET:" + url; + + String limitOrigin = "appB"; + final String headerName = "X-APP"; + configureRulesFor(resourceName, 0, limitOrigin); + + SentinelJaxRsConfig.setRequestOriginParser(new RequestOriginParser() { + @Override + public String parseOrigin(ContainerRequestContext request) { + String origin = request.getHeaderString(headerName); + return origin != null ? origin : ""; + } + }); + + Response response = given() + .header(headerName, "appA").get(url); + response.then().statusCode(200).body(equalTo(HELLO_STR)); + + Response blockedResp = given() + .header(headerName, "appB") + .get(url); + blockedResp.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode()) + .body(equalTo("Blocked by Sentinel (flow limiting)")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + assertEquals(1, cn.blockQps(), 0.01); + } + + @Test + public void testExceptionMapper() { + String url = "/test/ex"; + String resourceName = "GET:" + url; + Response response = given().get(url); + response.then().statusCode(javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()).body(equalTo("test exception mapper")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + } + + private void configureRulesFor(String resource, int count) { + configureRulesFor(resource, count, "default"); + } + + private void configureRulesFor(String resource, int count, String limitApp) { + FlowRule rule = new FlowRule() + .setCount(count) + .setGrade(RuleConstant.FLOW_GRADE_QPS); + rule.setResource(resource); + if (StringUtil.isNotBlank(limitApp)) { + rule.setLimitApp(limitApp); + } + FlowRuleManager.loadRules(Collections.singletonList(rule)); + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestApplication.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestApplication.java new file mode 100644 index 0000000000..6ffafb9592 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestApplication.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author sea + */ +@SpringBootApplication +public class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class, args); + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestResource.java b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestResource.java new file mode 100644 index 0000000000..f5d8bf1839 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/jaxrs/TestResource.java @@ -0,0 +1,92 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.jaxrs; + +import org.springframework.stereotype.Component; + +import javax.ws.rs.*; +import javax.ws.rs.container.AsyncResponse; +import javax.ws.rs.container.Suspended; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * @author sea + */ +@Path("/test") +@Component +public class TestResource { + + ExecutorService executor = Executors.newFixedThreadPool(5); + + @Path("/hello") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String sayHello() { + return "Hello!"; + } + + @Path("/async-hello") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public void asyncSayHello(@Suspended final AsyncResponse asyncResponse) { + executor.submit(new Runnable() { + @Override + public void run() { + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + asyncResponse.resume("Hello!"); + } + }); + } + + @Path("/hello/{name}") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String sayHelloWithName(@PathParam(value = "name") String name) { + return "Hello " + name + " !"; + } + + @Path("/ex") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String exception() { + throw new RuntimeException("test exception mapper"); + } + + @Path("/400") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String badRequest() { + throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) + .entity("test return 400") + .build()); + } + + @Path("/delay/{seconds}") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String delay(@PathParam(value = "seconds") long seconds) throws InterruptedException { + TimeUnit.SECONDS.sleep(seconds); + return "finish"; + } +} diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-client.yml b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-client.yml new file mode 100644 index 0000000000..af8e43ed87 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-client.yml @@ -0,0 +1,3 @@ +resteasy: + jaxrs: + scan-packages: com.alibaba.csp.sentinel.adapter.jaxrs.exception diff --git a/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-provider.yml b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-provider.yml new file mode 100644 index 0000000000..dc47def275 --- /dev/null +++ b/sentinel-adapter/sentinel-jax-rs-adapter/src/test/resources/application-provider.yml @@ -0,0 +1,3 @@ +resteasy: + jaxrs: + scan-packages: com.alibaba.csp.sentinel.adapter.jaxrs diff --git a/sentinel-adapter/sentinel-okhttp-adapter/README.md b/sentinel-adapter/sentinel-okhttp-adapter/README.md new file mode 100755 index 0000000000..bcc824b84d --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/README.md @@ -0,0 +1,65 @@ +# Sentinel OkHttp Adapter + +## Introduction + +Sentinel provides integration for OkHttp client to enable flow control for web requests. + +Add the following dependency in `pom.xml` (if you are using Maven): + +```xml + + com.alibaba.csp + sentinel-okhttp-adapter + x.y.z + +``` + +We can add the `SentinelOkHttpInterceptor` interceptor when `OkHttpClient` at initialization, for example: + +```java +OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(new SentinelOkHttpInterceptor(new SentinelOkHttpConfig())) + .build(); +``` + +## Configuration + +`SentinelOkHttpConfig` configuration: + +| name | description | type | default value | +|------|------------|------|-------| +| resourcePrefix | customized resource name prefix | `String` | `okhttp:` | +| resourceExtractor | customized resource extractor | `OkHttpResourceExtractor` | `DefaultOkHttpResourceExtractor` | +| fallback | handle request when it is blocked | `OkHttpFallback` | `DefaultOkHttpFallback` | + +### Resource Extractor + +We can define `OkHttpResourceExtractor` to customize the logic of extracting resource name from the HTTP request. +For example: `okhttp:GET:ip:port/okhttp/back/1 ==> /okhttp/back/{id}` + +```java +OkHttpResourceExtractor extractor = (request, connection) -> { + String resource = request.url().toString(); + String regex = "/okhttp/back/"; + if (resource.contains(regex)) { + resource = resource.substring(0, resource.indexOf(regex) + regex.length()) + "{id}"; + } + return resource; +}; +``` + +The pattern of default resource name extractor is `${HTTP_METHOD}:${URL}` (e.g. `GET:/foo`). + +### Fallback (Block handling) + +We can define `OkHttpFallback` to handle blocked request. For example: + +```java +public class DefaultOkHttpFallback implements OkHttpFallback { + + @Override + public Response handle(Request request, Connection connection, BlockException e) { + return new Response(myErrorBuilder); + } +} +``` \ No newline at end of file diff --git a/sentinel-adapter/sentinel-okhttp-adapter/pom.xml b/sentinel-adapter/sentinel-okhttp-adapter/pom.xml new file mode 100644 index 0000000000..23a638026c --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/pom.xml @@ -0,0 +1,68 @@ + + + + sentinel-adapter + com.alibaba.csp + 1.8.1-SNAPSHOT + + 4.0.0 + + sentinel-okhttp-adapter + jar + + + 3.6.0 + 2.1.3.RELEASE + 5.1.5.RELEASE + + + + + com.alibaba.csp + sentinel-core + + + + com.squareup.okhttp3 + okhttp + ${okhttp.version} + provided + + + + junit + junit + test + + + org.mockito + mockito-core + test + + + com.alibaba + fastjson + test + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + test + + + org.springframework.boot + spring-boot-test + ${spring.boot.version} + test + + + org.springframework + spring-test + ${spring-test.version} + test + + + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpConfig.java b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpConfig.java new file mode 100644 index 0000000000..45ee7f4de2 --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpConfig.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp; + +import com.alibaba.csp.sentinel.adapter.okhttp.extractor.DefaultOkHttpResourceExtractor; +import com.alibaba.csp.sentinel.adapter.okhttp.extractor.OkHttpResourceExtractor; +import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback; +import com.alibaba.csp.sentinel.adapter.okhttp.fallback.OkHttpFallback; +import com.alibaba.csp.sentinel.util.AssertUtil; + +/** + * @author zhaoyuguang + * @author Eric Zhao + */ +public class SentinelOkHttpConfig { + + public static final String DEFAULT_RESOURCE_PREFIX = "okhttp:"; + + private final String resourcePrefix; + private final OkHttpResourceExtractor resourceExtractor; + private final OkHttpFallback fallback; + + public SentinelOkHttpConfig() { + this(DEFAULT_RESOURCE_PREFIX); + } + + public SentinelOkHttpConfig(String resourcePrefix) { + this(resourcePrefix, new DefaultOkHttpResourceExtractor(), new DefaultOkHttpFallback()); + } + + public SentinelOkHttpConfig(OkHttpResourceExtractor resourceExtractor, OkHttpFallback fallback) { + this(DEFAULT_RESOURCE_PREFIX, resourceExtractor, fallback); + } + + public SentinelOkHttpConfig(String resourcePrefix, + OkHttpResourceExtractor resourceExtractor, + OkHttpFallback fallback) { + AssertUtil.notNull(resourceExtractor, "resourceExtractor cannot be null"); + AssertUtil.notNull(fallback, "fallback cannot be null"); + this.resourcePrefix = resourcePrefix; + this.resourceExtractor = resourceExtractor; + this.fallback = fallback; + } + + public String getResourcePrefix() { + return resourcePrefix; + } + + public OkHttpResourceExtractor getResourceExtractor() { + return resourceExtractor; + } + + public OkHttpFallback getFallback() { + return fallback; + } + + @Override + public String toString() { + return "SentinelOkHttpConfig{" + + "resourcePrefix='" + resourcePrefix + '\'' + + ", resourceExtractor=" + resourceExtractor + + ", fallback=" + fallback + + '}'; + } +} diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpInterceptor.java b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpInterceptor.java new file mode 100644 index 0000000000..664e3af27b --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpInterceptor.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp; + +import com.alibaba.csp.sentinel.*; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; + +import okhttp3.Interceptor; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; + +/** + * @author zhaoyuguang + */ +public class SentinelOkHttpInterceptor implements Interceptor { + + private final SentinelOkHttpConfig config; + + public SentinelOkHttpInterceptor() { + this.config = new SentinelOkHttpConfig(); + } + + public SentinelOkHttpInterceptor(SentinelOkHttpConfig config) { + AssertUtil.notNull(config, "config cannot be null"); + this.config = config; + } + + @Override + public Response intercept(Chain chain) throws IOException { + Entry entry = null; + try { + Request request = chain.request(); + String name = config.getResourceExtractor().extract(request, chain.connection()); + if (StringUtil.isNotBlank(config.getResourcePrefix())) { + name = config.getResourcePrefix() + name; + } + entry = SphU.entry(name, ResourceTypeConstants.COMMON_WEB, EntryType.OUT); + return chain.proceed(request); + } catch (BlockException e) { + return config.getFallback().handle(chain.request(), chain.connection(), e); + } catch (IOException ex) { + Tracer.traceEntry(ex, entry); + throw ex; + } finally { + if (entry != null) { + entry.exit(); + } + } + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/DefaultOkHttpResourceExtractor.java b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/DefaultOkHttpResourceExtractor.java new file mode 100644 index 0000000000..9492db33ba --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/DefaultOkHttpResourceExtractor.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp.extractor; + +import okhttp3.Connection; +import okhttp3.Request; + +/** + * @author zhaoyuguang + */ +public class DefaultOkHttpResourceExtractor implements OkHttpResourceExtractor { + + @Override + public String extract(Request request, Connection connection) { + return request.method() + ":" + request.url().toString(); + } +} diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/OkHttpResourceExtractor.java b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/OkHttpResourceExtractor.java new file mode 100644 index 0000000000..4a9ee8727e --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/OkHttpResourceExtractor.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp.extractor; + +import okhttp3.Connection; +import okhttp3.Request; + +/** + * @author zhaoyuguang + */ +public interface OkHttpResourceExtractor { + + /** + * Extracts the resource name from the HTTP request. + * + * @param request HTTP request entity + * @param connection HTTP connection + * @return the resource name of current request + */ + String extract(Request request, Connection connection); +} diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/DefaultOkHttpFallback.java b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/DefaultOkHttpFallback.java new file mode 100644 index 0000000000..6ece854fc3 --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/DefaultOkHttpFallback.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp.fallback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; +import okhttp3.Connection; +import okhttp3.Request; +import okhttp3.Response; + +/** + * @author zhaoyuguang + */ +public class DefaultOkHttpFallback implements OkHttpFallback { + + @Override + public Response handle(Request request, Connection connection, BlockException e) { + // Just wrap and throw the exception. + throw new SentinelRpcException(e); + } +} diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/OkHttpFallback.java b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/OkHttpFallback.java new file mode 100644 index 0000000000..4127c195fa --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/OkHttpFallback.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp.fallback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import okhttp3.Connection; +import okhttp3.Request; +import okhttp3.Response; + +/** + * @author zhaoyuguang + */ +public interface OkHttpFallback { + + Response handle(Request request, Connection connection, BlockException e); +} diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpInterceptorTest.java b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpInterceptorTest.java new file mode 100644 index 0000000000..40c0282b8a --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/SentinelOkHttpInterceptorTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp; + +import com.alibaba.csp.sentinel.Constants; +import com.alibaba.csp.sentinel.adapter.okhttp.app.TestApplication; +import com.alibaba.csp.sentinel.adapter.okhttp.extractor.OkHttpResourceExtractor; +import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback; +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import okhttp3.Connection; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +import static org.junit.Assert.assertNotNull; + +/** + * @author zhaoyuguang + */ +@RunWith(SpringRunner.class) +@SpringBootTest(classes = TestApplication.class, + webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, + properties = { + "server.port=8086" + }) +public class SentinelOkHttpInterceptorTest { + + @Value("${server.port}") + private Integer port; + + @Test + public void testSentinelOkHttpInterceptor0() throws Exception { + // With prefix + SentinelOkHttpConfig config = new SentinelOkHttpConfig("okhttp:"); + String url0 = "http://localhost:" + port + "/okhttp/back"; + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(new SentinelOkHttpInterceptor(config)) + .build(); + Request request = new Request.Builder() + .url(url0) + .build(); + System.out.println(client.newCall(request).execute().body().string()); + ClusterNode cn = ClusterBuilderSlot.getClusterNode(config.getResourcePrefix() + "GET:" + url0); + assertNotNull(cn); + + Constants.ROOT.removeChildList(); + ClusterBuilderSlot.getClusterNodeMap().clear(); + } + + @Test + public void testSentinelOkHttpInterceptor1() throws Exception { + + String url0 = "http://localhost:" + port + "/okhttp/back/1"; + SentinelOkHttpConfig config = new SentinelOkHttpConfig(new OkHttpResourceExtractor() { + @Override + public String extract(Request request, Connection connection) { + String regex = "/okhttp/back/"; + String url = request.url().toString(); + if (url.contains(regex)) { + url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}"; + } + return request.method() + ":" + url; + } + }, new DefaultOkHttpFallback()); + OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(new SentinelOkHttpInterceptor(config)) + .build(); + Request request = new Request.Builder() + .url(url0) + .build(); + System.out.println(client.newCall(request).execute().body().string()); + + String url1 = config.getResourcePrefix() + "GET:http://localhost:" + port + "/okhttp/back/{id}"; + ClusterNode cn = ClusterBuilderSlot.getClusterNode(url1); + assertNotNull(cn); + + Constants.ROOT.removeChildList(); + ClusterBuilderSlot.getClusterNodeMap().clear(); + } +} diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/app/TestApplication.java b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/app/TestApplication.java new file mode 100644 index 0000000000..8eef58939a --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/app/TestApplication.java @@ -0,0 +1,30 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp.app; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +/** + * @author zhaoyuguang + */ +@SpringBootApplication +public class TestApplication { + + public static void main(String[] args) { + SpringApplication.run(TestApplication.class); + } +} diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/app/controller/TestController.java b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/app/controller/TestController.java new file mode 100644 index 0000000000..96dd3674c4 --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/app/controller/TestController.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp.app.controller; + +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * @author zhaoyuguang + */ +@RestController +public class TestController { + + @RequestMapping("/okhttp/back") + public String back() { + return "Welcome Back!"; + } + + @RequestMapping("/okhttp/back/{id}") + public String back(@PathVariable String id) { + return "Welcome Back! " + id; + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/config/SentinelOkHttpConfigTest.java b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/config/SentinelOkHttpConfigTest.java new file mode 100644 index 0000000000..aed008dac3 --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/config/SentinelOkHttpConfigTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp.config; + +import com.alibaba.csp.sentinel.adapter.okhttp.SentinelOkHttpConfig; +import com.alibaba.csp.sentinel.adapter.okhttp.extractor.DefaultOkHttpResourceExtractor; +import com.alibaba.csp.sentinel.adapter.okhttp.fallback.DefaultOkHttpFallback; + +import org.junit.Test; + +/** + * @author zhaoyuguang + */ +public class SentinelOkHttpConfigTest { + + @Test(expected = IllegalArgumentException.class) + public void testConfigSetCleaner() { + SentinelOkHttpConfig config = new SentinelOkHttpConfig(null, new DefaultOkHttpFallback()); + } + + @Test(expected = IllegalArgumentException.class) + public void testConfigSetFallback() { + SentinelOkHttpConfig config = new SentinelOkHttpConfig(new DefaultOkHttpResourceExtractor(), null); + } +} diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/OkHttpResourceExtractorTest.java b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/OkHttpResourceExtractorTest.java new file mode 100644 index 0000000000..ead5fedfca --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/extractor/OkHttpResourceExtractorTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp.extractor; + +import okhttp3.Connection; +import okhttp3.Request; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * @author zhaoyuguang + */ +public class OkHttpResourceExtractorTest { + + @Test + public void testDefaultOkHttpResourceExtractor() { + OkHttpResourceExtractor extractor = new DefaultOkHttpResourceExtractor(); + String url = "http://localhost:8083/okhttp/back"; + Request request = new Request.Builder() + .url(url) + .build(); + String resource = extractor.extract(request, null); + assertEquals("GET:" + url, resource); + } + + @Test + public void testCustomizeOkHttpUrlCleaner() { + OkHttpResourceExtractor extractor = new OkHttpResourceExtractor() { + @Override + public String extract(Request request, Connection connection) { + String regex = "/okhttp/back/"; + String url = request.url().toString(); + if (url.contains(regex)) { + url = url.substring(0, url.indexOf(regex) + regex.length()) + "{id}"; + } + return request.method() + ":" + url; + } + }; + String url = "http://localhost:8083/okhttp/back/abc"; + Request request = new Request.Builder() + .url(url) + .build(); + assertEquals("GET:http://localhost:8083/okhttp/back/{id}", extractor.extract(request, null)); + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/OkHttpFallbackTest.java b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/OkHttpFallbackTest.java new file mode 100644 index 0000000000..f355275393 --- /dev/null +++ b/sentinel-adapter/sentinel-okhttp-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/okhttp/fallback/OkHttpFallbackTest.java @@ -0,0 +1,34 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.okhttp.fallback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; +import com.alibaba.csp.sentinel.slots.block.flow.FlowException; +import org.junit.Test; + +/** + * @author zhaoyuguang + */ +public class OkHttpFallbackTest { + + @Test(expected = SentinelRpcException.class) + public void testDefaultOkHttpFallback() { + BlockException e = new FlowException("xxx"); + OkHttpFallback fallback = new DefaultOkHttpFallback(); + fallback.handle(null, null, e); + } +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/README.md b/sentinel-adapter/sentinel-quarkus-adapter/README.md new file mode 100644 index 0000000000..c7ee5e8fbe --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/README.md @@ -0,0 +1,72 @@ +# Sentinel Quarkus Adapter + +Sentinel provides `sentinel-annotation-quarkus-adapter` and `sentinel-jax-rs-quarkus-adapter` to +adapt [sentinel-annotation-cdi-interceptor](https://github.com/alibaba/Sentinel/tree/master/sentinel-extension/sentiel-annotation-cdi-interceptor) +and [sentinel-jax-rs-adapter](https://github.com/alibaba/Sentinel/tree/master/sentinel-adapter/sentinel-jax-rs-adapter) for Quarkus. + +The integration module also provides `sentinel-native-image-quarkus-adapter` to support running Sentinel with Quarkus in native image mode. + +To use sentinel-jax-rs-quarkus-adapter, you can simply add the following dependency to your `pom.xml`: + +```xml + + com.alibaba.csp + sentinel-jax-rs-quarkus-adapter + x.y.z + +``` + +To use sentinel-annotation-quarkus-adapter, you can simply add the following dependency to your `pom.xml`: + +```xml + + com.alibaba.csp + sentinel-annotation-quarkus-adapter + x.y.z + +``` + +When Quarkus application started, you can see the enabled feature like: + +``` +INFO [io.quarkus] (Quarkus Main Thread) Installed features: [cdi, resteasy, sentinel-annotation, sentinel-jax-rs] +``` + +## For Quarkus native image + +If you want to integrate Quarkus with Sentinel while running in native image mode, +you should add the following dependency to your `pom.xml`: + +```xml + + com.alibaba.csp + sentinel-native-image-quarkus-adapter + x.y.z + +``` + +And then add `--allow-incomplete-classpath` to `quarkus.native.additional-build-args`. + +If you're using `sentinel-jax-rs-quarkus-adapter`, you'll need to set `quarkus.native.auto-service-loader-registration` to true. + +When Quarkus application started, you can see the enabled feature like: + +``` +INFO [io.quarkus] (main) Installed features: [cdi, resteasy, sentinel-annotation, sentinel-jax-rs, sentinel-native-image] +``` + +For more details you may refer to the `pom.xml` of [sentinel-demo-quarkus](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-quarkus). + +### Limitations + +`sentinel-native-image-quarkus-adapter` currently relies on `sentinel-logging-slf4j` to help Sentinel +run in native image mode easily, because `quarkus-core` provides `Target_org_slf4j_LoggerFactory` to substitute `getLogger` method. + +Currently `sentinel-transport-simple-http` can work in native image mode, while `sentinel-transport-netty-http` cannot work in native image mode without extra config or substitutions. + +## References for build native image or AOT + +- [Quarkus - Tips for writing native applications](https://quarkus.io/guides/writing-native-applications-tips) +- [Quarkus - Class Loading Reference](https://quarkus.io/guides/class-loading-reference) +- [SubstrateVM LIMITATIONS](https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md) +- [Accessing resources in Substrate VM images](https://github.com/oracle/graal/blob/master/substratevm/RESOURCES.md) diff --git a/sentinel-adapter/sentinel-quarkus-adapter/pom.xml b/sentinel-adapter/sentinel-quarkus-adapter/pom.xml new file mode 100755 index 0000000000..d316da7300 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + + com.alibaba.csp + sentinel-adapter + 1.8.1-SNAPSHOT + + + sentinel-quarkus-adapter-parent + pom + + + UTF-8 + UTF-8 + 1.8 + 1.8 + true + 1.4.1.Final + 3.8.1 + + + + sentinel-annotation-quarkus-adapter-deployment + sentinel-annotation-quarkus-adapter-runtime + sentinel-jax-rs-quarkus-adapter-deployment + sentinel-jax-rs-quarkus-adapter-runtime + sentinel-native-image-quarkus-adapter-deployment + sentinel-native-image-quarkus-adapter-runtime + + + + + io.quarkus + quarkus-bom-deployment + ${quarkus.version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${compiler-plugin.version} + + + + + diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/pom.xml b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/pom.xml new file mode 100644 index 0000000000..a94b8cd6b8 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + + com.alibaba.csp + sentinel-quarkus-adapter-parent + 1.8.1-SNAPSHOT + ../pom.xml + + + sentinel-annotation-quarkus-adapter-deployment + sentinel-annotation-quarkus-adapter-deployment + + + 1.8 + 1.8 + + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + com.alibaba.csp + sentinel-annotation-quarkus-adapter + ${project.version} + + + + + io.quarkus + quarkus-junit5-internal + test + + + org.assertj + assertj-core + test + + + io.quarkus + quarkus-arc-deployment + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/SentinelAnnotationQuarkusAdapterProcessor.java b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/SentinelAnnotationQuarkusAdapterProcessor.java new file mode 100644 index 0000000000..111d518516 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/SentinelAnnotationQuarkusAdapterProcessor.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.quarkus.annotation.deployment; + +import com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceInterceptor; +import io.quarkus.arc.deployment.AdditionalBeanBuildItem; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; + +import java.util.Arrays; +import java.util.List; + +/** + * @author sea + */ +class SentinelAnnotationQuarkusAdapterProcessor { + + private static final String FEATURE_ANNOTATION = "sentinel-annotation"; + + @BuildStep + void feature(BuildProducer featureProducer) { + featureProducer.produce(new FeatureBuildItem(FEATURE_ANNOTATION)); + } + + @BuildStep + List additionalBeans() { + return Arrays.asList( + new AdditionalBeanBuildItem(SentinelResourceInterceptor.class) + ); + } + +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/FooService.java b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/FooService.java new file mode 100644 index 0000000000..8f3b765421 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/FooService.java @@ -0,0 +1,84 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.quarkus.annotation.deployment; +import com.alibaba.csp.sentinel.annotation.cdi.interceptor.SentinelResourceBinding; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +import javax.enterprise.context.ApplicationScoped; +import java.util.concurrent.ThreadLocalRandom; + +/** + * @author Eric Zhao + * @author sea + */ +@ApplicationScoped +public class FooService { + + @SentinelResourceBinding(value = "apiFoo", blockHandler = "fooBlockHandler", + exceptionsToTrace = {IllegalArgumentException.class}) + public String foo(int i) throws Exception { + if (i == 5758) { + throw new IllegalAccessException(); + } + if (i == 5763) { + throw new IllegalArgumentException(); + } + return "Hello for " + i; + } + + @SentinelResourceBinding(value = "apiFooWithFallback", blockHandler = "fooBlockHandler", fallback = "fooFallbackFunc", + exceptionsToTrace = {IllegalArgumentException.class}) + public String fooWithFallback(int i) throws Exception { + if (i == 5758) { + throw new IllegalAccessException(); + } + if (i == 5763) { + throw new IllegalArgumentException(); + } + return "Hello for " + i; + } + + @SentinelResourceBinding(value = "apiAnotherFooWithDefaultFallback", defaultFallback = "globalDefaultFallback", + fallbackClass = {FooUtil.class}) + public String anotherFoo(int i) { + if (i == 5758) { + throw new IllegalArgumentException("oops"); + } + return "Hello for " + i; + } + + @SentinelResourceBinding(blockHandler = "globalBlockHandler", blockHandlerClass = FooUtil.class) + public int random() { + return ThreadLocalRandom.current().nextInt(0, 30000); + } + + @SentinelResourceBinding(value = "apiBaz", blockHandler = "bazBlockHandler", + exceptionsToIgnore = {IllegalMonitorStateException.class}) + public String baz(String name) { + if (name.equals("fail")) { + throw new IllegalMonitorStateException("boom!"); + } + return "cheers, " + name; + } + + public String fooBlockHandler(int i, BlockException ex) { + return "Oops, " + i; + } + + public String fooFallbackFunc(int i) { + return "eee..."; + } +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/FooUtil.java b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/FooUtil.java new file mode 100644 index 0000000000..adc1d8810a --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/FooUtil.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.quarkus.annotation.deployment; + +import com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * @author Eric Zhao + */ +public class FooUtil { + + public static final int BLOCK_FLAG = 88888; + public static final String FALLBACK_DEFAULT_RESULT = "fallback"; + + public static int globalBlockHandler(BlockException ex) { + System.out.println("Oops: " + ex.getClass().getSimpleName()); + return BLOCK_FLAG; + } + + public static String globalDefaultFallback(Throwable t) { + System.out.println("Fallback caught: " + t.getClass().getSimpleName()); + return FALLBACK_DEFAULT_RESULT; + } +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/SentinelAnnotationQuarkusAdapterTest.java b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/SentinelAnnotationQuarkusAdapterTest.java new file mode 100644 index 0000000000..1a0f34e154 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/annotation/deployment/SentinelAnnotationQuarkusAdapterTest.java @@ -0,0 +1,197 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.quarkus.annotation.deployment; + +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.alibaba.csp.sentinel.util.MethodUtil; +import io.quarkus.arc.ArcUndeclaredThrowableException; +import io.quarkus.test.QuarkusUnitTest; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.Collections; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; + +/** + * @author sea + */ +public class SentinelAnnotationQuarkusAdapterTest { + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap + .create(JavaArchive.class) + .addClasses(FooService.class, FooUtil.class) + .addPackage("com.alibaba.csp.sentinel.annotation.cdi.interceptor") + ); + + @Inject + FooService fooService; + + @BeforeEach + public void setUp() throws Exception { + FlowRuleManager.loadRules(new ArrayList()); + ClusterBuilderSlot.resetClusterNodes(); + } + + @AfterEach + public void tearDown() throws Exception { + FlowRuleManager.loadRules(new ArrayList()); + ClusterBuilderSlot.resetClusterNodes(); + } + + @Test + public void testForeignBlockHandlerClass() throws Exception { + assertThat(fooService.random()).isNotEqualTo(FooUtil.BLOCK_FLAG); + String resourceName = MethodUtil.resolveMethodName(FooService.class.getDeclaredMethod("random")); + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertThat(cn).isNotNull(); + assertThat(cn.passQps()).isPositive(); + + FlowRuleManager.loadRules(Collections.singletonList( + new FlowRule(resourceName).setCount(0) + )); + assertThat(fooService.random()).isEqualTo(FooUtil.BLOCK_FLAG); + assertThat(cn.blockQps()).isPositive(); + } + + @Test + public void testBlockHandlerNotFound() { + assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel"); + String resourceName = "apiBaz"; + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertThat(cn).isNotNull(); + assertThat(cn.passQps()).isPositive(); + + FlowRuleManager.loadRules(Collections.singletonList( + new FlowRule(resourceName).setCount(0) + )); + + Assertions.assertThrows(ArcUndeclaredThrowableException.class, () -> { + fooService.baz("Sentinel"); + }); + } + + @Test + public void testAnnotationExceptionsToIgnore() { + assertThat(fooService.baz("Sentinel")).isEqualTo("cheers, Sentinel"); + String resourceName = "apiBaz"; + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertThat(cn).isNotNull(); + assertThat(cn.passQps()).isPositive(); + + try { + fooService.baz("fail"); + fail("should not reach here"); + } catch (IllegalMonitorStateException ex) { + assertThat(cn.exceptionQps()).isZero(); + } + } + + @Test + public void testFallbackWithNoParams() throws Exception { + assertThat(fooService.fooWithFallback(1)).isEqualTo("Hello for 1"); + String resourceName = "apiFooWithFallback"; + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertThat(cn).isNotNull(); + assertThat(cn.passQps()).isPositive(); + + // Fallback should be ignored for this. + try { + fooService.fooWithFallback(5758); + fail("should not reach here"); + } catch (IllegalAccessException e) { + assertThat(cn.exceptionQps()).isZero(); + } + + // Fallback should take effect. + assertThat(fooService.fooWithFallback(5763)).isEqualTo("eee..."); + assertThat(cn.exceptionQps()).isPositive(); + assertThat(cn.blockQps()).isZero(); + + FlowRuleManager.loadRules(Collections.singletonList( + new FlowRule(resourceName).setCount(0) + )); + // Fallback should not take effect for BlockException, as blockHandler is configured. + assertThat(fooService.fooWithFallback(2221)).isEqualTo("Oops, 2221"); + assertThat(cn.blockQps()).isPositive(); + } + + @Test + public void testDefaultFallbackWithSingleParam() { + assertThat(fooService.anotherFoo(1)).isEqualTo("Hello for 1"); + String resourceName = "apiAnotherFooWithDefaultFallback"; + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertThat(cn).isNotNull(); + assertThat(cn.passQps()).isPositive(); + + // Default fallback should take effect. + assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT); + assertThat(cn.exceptionQps()).isPositive(); + assertThat(cn.blockQps()).isZero(); + + FlowRuleManager.loadRules(Collections.singletonList( + new FlowRule(resourceName).setCount(0) + )); + // Default fallback should also take effect for BlockException. + assertThat(fooService.anotherFoo(5758)).isEqualTo(FooUtil.FALLBACK_DEFAULT_RESULT); + assertThat(cn.blockQps()).isPositive(); + } + + @Test + public void testNormalBlockHandlerAndFallback() throws Exception { + assertThat(fooService.foo(1)).isEqualTo("Hello for 1"); + String resourceName = "apiFoo"; + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertThat(cn).isNotNull(); + assertThat(cn.passQps()).isPositive(); + + // Test for biz exception. + try { + fooService.foo(5758); + fail("should not reach here"); + } catch (Exception ex) { + // Should not be traced. + assertThat(cn.exceptionQps()).isZero(); + } + + try { + fooService.foo(5763); + fail("should not reach here"); + } catch (Exception ex) { + assertThat(cn.exceptionQps()).isPositive(); + } + + // Test for blockHandler + FlowRuleManager.loadRules(Collections.singletonList( + new FlowRule(resourceName).setCount(0) + )); + assertThat(fooService.foo(1121)).isEqualTo("Oops, 1121"); + assertThat(cn.blockQps()).isPositive(); + } +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-runtime/pom.xml b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-runtime/pom.xml new file mode 100644 index 0000000000..2e066f6816 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-runtime/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + com.alibaba.csp + sentinel-quarkus-adapter-parent + 1.8.1-SNAPSHOT + ../pom.xml + + + sentinel-annotation-quarkus-adapter + sentinel-annotation-quarkus-adapter + + + + io.quarkus + quarkus-core + ${quarkus.version} + + + com.alibaba.csp + sentinel-annotation-cdi-interceptor + ${project.version} + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000..0d3819f049 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-annotation-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,13 @@ +--- +name: "Sentinel annotation CDI extension" +metadata: + keywords: + - "sentinel" + - "rate-limiting" + - "resiliency" + - "circuit-breaker" + - "fault-tolerance" + categories: + - "fault-tolerance" + - "cloud" + status: "preview" diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/pom.xml b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/pom.xml new file mode 100644 index 0000000000..3137c5828d --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/pom.xml @@ -0,0 +1,77 @@ + + + 4.0.0 + + com.alibaba.csp + sentinel-quarkus-adapter-parent + 1.8.1-SNAPSHOT + ../pom.xml + + + sentinel-jax-rs-quarkus-adapter-deployment + sentinel-jax-rs-quarkus-adapter-deployment + + + 1.8 + 1.8 + + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + io.quarkus + quarkus-resteasy-server-common-deployment + + + com.alibaba.csp + sentinel-jax-rs-quarkus-adapter + ${project.version} + + + + + io.quarkus + quarkus-junit5-internal + test + + + io.quarkus + quarkus-resteasy-deployment + test + + + io.rest-assured + rest-assured + test + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/SentinelJaxRsQuarkusAdapterProcessor.java b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/SentinelJaxRsQuarkusAdapterProcessor.java new file mode 100644 index 0000000000..34ee9cc543 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/SentinelJaxRsQuarkusAdapterProcessor.java @@ -0,0 +1,37 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.quarkus.jaxrs.deployment; + +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import org.jboss.logging.Logger; + +/** + * @author sea + */ +class SentinelJaxRsQuarkusAdapterProcessor { + + private static final Logger logger = Logger.getLogger(SentinelJaxRsQuarkusAdapterProcessor.class); + + private static final String FEATURE_JAX_RS = "sentinel-jax-rs"; + + @BuildStep + void feature(BuildProducer featureProducer) { + featureProducer.produce(new FeatureBuildItem(FEATURE_JAX_RS)); + } + +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/SentinelJaxRsQuarkusAdapterTest.java b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/SentinelJaxRsQuarkusAdapterTest.java new file mode 100644 index 0000000000..bcdf5d73f6 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/SentinelJaxRsQuarkusAdapterTest.java @@ -0,0 +1,242 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.quarkus.jaxrs.deployment; + +import com.alibaba.csp.sentinel.Constants; +import com.alibaba.csp.sentinel.adapter.jaxrs.config.SentinelJaxRsConfig; +import com.alibaba.csp.sentinel.adapter.jaxrs.fallback.SentinelJaxRsFallback; +import com.alibaba.csp.sentinel.adapter.jaxrs.request.RequestOriginParser; +import com.alibaba.csp.sentinel.node.ClusterNode; +import com.alibaba.csp.sentinel.node.EntranceNode; +import com.alibaba.csp.sentinel.node.Node; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.alibaba.csp.sentinel.util.StringUtil; +import io.quarkus.test.QuarkusUnitTest; +import io.restassured.response.Response; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import javax.ws.rs.container.ContainerRequestContext; +import javax.ws.rs.core.MediaType; +import java.util.Collections; +import java.util.concurrent.Callable; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.jupiter.api.Assertions.*; + +/** + * @author sea + */ +public class SentinelJaxRsQuarkusAdapterTest { + + private static final String HELLO_STR = "Hello!"; + + @RegisterExtension + static final QuarkusUnitTest TEST = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap + .create(JavaArchive.class) + .addClasses(TestResource.class)); + + @AfterEach + public void cleanUp() { + FlowRuleManager.loadRules(null); + ClusterBuilderSlot.resetClusterNodes(); + } + + @Test + public void testGetHello() { + String url = "/test/hello"; + String resourceName = "GET:" + url; + Response response = given().get(url); + response.then().statusCode(200).body(equalTo(HELLO_STR)); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + + String context = ""; + for (Node n : Constants.ROOT.getChildList()) { + if (n instanceof EntranceNode) { + String id = ((EntranceNode) n).getId().getName(); + if (url.equals(id)) { + context = ((EntranceNode) n).getId().getName(); + } + } + } + assertEquals("", context); + } + + @Test + public void testAsyncGetHello() { + String url = "/test/async-hello"; + String resourceName = "GET:" + url; + Response response = given().get(url); + response.then().statusCode(200).body(equalTo(HELLO_STR)); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + + String context = ""; + for (Node n : Constants.ROOT.getChildList()) { + if (n instanceof EntranceNode) { + String id = ((EntranceNode) n).getId().getName(); + if (url.equals(id)) { + context = ((EntranceNode) n).getId().getName(); + } + } + } + assertEquals("", context); + } + + @Test + public void testUrlPathParam() { + String url = "/test/hello/{name}"; + String resourceName = "GET:" + url; + + String url1 = "/test/hello/abc"; + Response response1 = given().get(url1); + response1.then().statusCode(200).body(equalTo("Hello abc !")); + + String url2 = "/test/hello/def"; + Response response2 = given().get(url2); + response2.then().statusCode(200).body(equalTo("Hello def !")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(2, cn.passQps(), 0.01); + + assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url1)); + assertNull(ClusterBuilderSlot.getClusterNode("GET:" + url2)); + } + + @Test + public void testDefaultFallback() { + String url = "/test/hello"; + String resourceName = "GET:" + url; + configureRulesFor(resourceName, 0); + Response response = given().get(url); + response.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode()) + .body(equalTo("Blocked by Sentinel (flow limiting)")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(0, cn.passQps(), 0.01); + } + + @Test + public void testCustomFallback() { + String url = "/test/hello"; + String resourceName = "GET:" + url; + SentinelJaxRsConfig.setJaxRsFallback(new SentinelJaxRsFallback() { + @Override + public javax.ws.rs.core.Response fallbackResponse(String route, Throwable cause) { + return javax.ws.rs.core.Response.status(javax.ws.rs.core.Response.Status.OK) + .entity("Blocked by Sentinel (flow limiting)") + .type(MediaType.APPLICATION_JSON_TYPE) + .build(); + } + + @Override + public Future fallbackFutureResponse(final String route, final Throwable cause) { + return new FutureTask<>(new Callable() { + @Override + public javax.ws.rs.core.Response call() throws Exception { + return fallbackResponse(route, cause); + } + }); + } + }); + + + configureRulesFor(resourceName, 0); + Response response = given().get(url); + response.then().statusCode(javax.ws.rs.core.Response.Status.OK.getStatusCode()) + .body(equalTo("Blocked by Sentinel (flow limiting)")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(0, cn.passQps(), 0.01); + } + + @Test + public void testCustomRequestOriginParser() { + String url = "/test/hello"; + String resourceName = "GET:" + url; + + String limitOrigin = "appB"; + final String headerName = "X-APP"; + configureRulesFor(resourceName, 0, limitOrigin); + + SentinelJaxRsConfig.setRequestOriginParser(new RequestOriginParser() { + @Override + public String parseOrigin(ContainerRequestContext request) { + String origin = request.getHeaderString(headerName); + return origin != null ? origin : ""; + } + }); + + Response response = given() + .header(headerName, "appA").get(url); + response.then().statusCode(200).body(equalTo(HELLO_STR)); + + Response blockedResp = given() + .header(headerName, "appB") + .get(url); + blockedResp.then().statusCode(javax.ws.rs.core.Response.Status.TOO_MANY_REQUESTS.getStatusCode()) + .body(equalTo("Blocked by Sentinel (flow limiting)")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + assertEquals(1, cn.blockQps(), 0.01); + } + + @Test + public void testExceptionMapper() { + String url = "/test/ex"; + String resourceName = "GET:" + url; + Response response = given().get(url); + response.then().statusCode(javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR.getStatusCode()).body(equalTo("test exception mapper")); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(resourceName); + assertNotNull(cn); + } + + private void configureRulesFor(String resource, int count) { + configureRulesFor(resource, count, "default"); + } + + private void configureRulesFor(String resource, int count, String limitApp) { + FlowRule rule = new FlowRule() + .setCount(count) + .setGrade(RuleConstant.FLOW_GRADE_QPS); + rule.setResource(resource); + if (StringUtil.isNotBlank(limitApp)) { + rule.setLimitApp(limitApp); + } + FlowRuleManager.loadRules(Collections.singletonList(rule)); + } +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/TestResource.java b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/TestResource.java new file mode 100644 index 0000000000..bee78ed5f4 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-deployment/src/test/java/com/alibaba/csp/sentinel/adapter/quarkus/jaxrs/deployment/TestResource.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.quarkus.jaxrs.deployment; + + +import javax.ws.rs.*; +import javax.ws.rs.container.AsyncResponse; +import javax.ws.rs.container.Suspended; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +/** + * @author sea + */ +@Path("/test") +public class TestResource { + + ExecutorService executor = Executors.newFixedThreadPool(5); + + @Path("/hello") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String sayHello() { + return "Hello!"; + } + + @Path("/async-hello") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public void asyncSayHello(@Suspended final AsyncResponse asyncResponse) { + executor.submit(new Runnable() { + @Override + public void run() { + try { + TimeUnit.MILLISECONDS.sleep(100); + } catch (InterruptedException e) { + e.printStackTrace(); + } + asyncResponse.resume("Hello!"); + } + }); + } + + @Path("/hello/{name}") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String sayHelloWithName(@PathParam(value = "name") String name) { + return "Hello " + name + " !"; + } + + @Path("/ex") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String exception() { + throw new RuntimeException("test exception mapper"); + } + + @Path("/400") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String badRequest() { + throw new WebApplicationException(Response.status(Response.Status.BAD_REQUEST) + .entity("test return 400") + .build()); + } + + @Path("/delay/{seconds}") + @GET + @Produces({ MediaType.APPLICATION_JSON }) + public String delay(@PathParam(value = "seconds") long seconds) throws InterruptedException { + TimeUnit.SECONDS.sleep(seconds); + return "finish"; + } +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/pom.xml b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/pom.xml new file mode 100644 index 0000000000..6f72725b50 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + com.alibaba.csp + sentinel-quarkus-adapter-parent + 1.8.1-SNAPSHOT + ../pom.xml + + + sentinel-jax-rs-quarkus-adapter + sentinel-jax-rs-quarkus-adapter + + + + io.quarkus + quarkus-core + ${quarkus.version} + + + com.alibaba.csp + sentinel-jax-rs-adapter + ${project.version} + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000..cd2ae15e4a --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,14 @@ +--- +name: "Sentinel extension for JAX-RS" +metadata: + keywords: + - "sentinel" + - "rate-limiting" + - "resiliency" + - "circuit-breaker" + - "fault-tolerance" + - "jax-rs" + categories: + - "fault-tolerance" + - "cloud" + status: "preview" diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers new file mode 100644 index 0000000000..a26687b3ee --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-jax-rs-quarkus-adapter-runtime/src/main/resources/META-INF/services/javax.ws.rs.ext.Providers @@ -0,0 +1,2 @@ +com.alibaba.csp.sentinel.adapter.jaxrs.SentinelJaxRsProviderFilter +com.alibaba.csp.sentinel.adapter.jaxrs.exception.DefaultExceptionMapper diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-deployment/pom.xml b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-deployment/pom.xml new file mode 100644 index 0000000000..471966f2ce --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-deployment/pom.xml @@ -0,0 +1,60 @@ + + + 4.0.0 + + com.alibaba.csp + sentinel-quarkus-adapter-parent + 1.8.1-SNAPSHOT + ../pom.xml + + + sentinel-native-image-quarkus-adapter-deployment + sentinel-native-image-quarkus-adapter-deployment + + + 1.8 + 1.8 + + + + + io.quarkus + quarkus-core-deployment + + + io.quarkus + quarkus-arc-deployment + + + org.graalvm.nativeimage + svm + + + com.alibaba.csp + sentinel-native-image-quarkus-adapter + ${project.version} + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + + diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/nativeimage/SentinelNativeImageProcessor.java b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/nativeimage/SentinelNativeImageProcessor.java new file mode 100644 index 0000000000..32654a7809 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-deployment/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/nativeimage/SentinelNativeImageProcessor.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.quarkus.nativeimage; + +import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.annotations.ExecutionTime; +import io.quarkus.deployment.annotations.Record; +import io.quarkus.deployment.builditem.FeatureBuildItem; +import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; +import io.quarkus.deployment.builditem.nativeimage.RuntimeInitializedClassBuildItem; +import io.quarkus.deployment.pkg.steps.NativeBuild; + +import java.util.Arrays; +import java.util.List; + +/** + * @author sea + */ +class SentinelNativeImageProcessor { + + private static final String FEATURE_NATIVE_IMAGE = "sentinel-native-image"; + + @BuildStep + void feature(BuildProducer featureProducer) { + featureProducer.produce(new FeatureBuildItem(FEATURE_NATIVE_IMAGE)); + } + + @BuildStep(onlyIf = NativeBuild.class) + List runtimeInitializedClasses() { + return Arrays.asList( + new RuntimeInitializedClassBuildItem("com.alibaba.fastjson.serializer.JodaCodec"), + new RuntimeInitializedClassBuildItem("com.alibaba.fastjson.serializer.GuavaCodec"), + new RuntimeInitializedClassBuildItem("com.alibaba.fastjson.support.moneta.MonetaCodec"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.Env"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.init.InitExecutor"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.cluster.ClusterStateManager"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.node.metric.MetricTimerListener"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.node.metric.MetricWriter"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.util.TimeUtil"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.eagleeye.StatLogController"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.slots.logger.EagleEyeLogUtil"), + new RuntimeInitializedClassBuildItem("com.alibaba.csp.sentinel.eagleeye.EagleEye")); + } + + @BuildStep(onlyIf = NativeBuild.class) + ReflectiveClassBuildItem setupSentinelReflectiveClasses() { + return new ReflectiveClassBuildItem(true, true, true, + DefaultSlotChainBuilder.class.getName()); + } + + @BuildStep(onlyIf = NativeBuild.class) + @Record(ExecutionTime.STATIC_INIT) + void record(SentinelRecorder recorder) { + recorder.init(); + } +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/pom.xml b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/pom.xml new file mode 100644 index 0000000000..c8591cc261 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/pom.xml @@ -0,0 +1,75 @@ + + + 4.0.0 + + com.alibaba.csp + sentinel-quarkus-adapter-parent + 1.8.1-SNAPSHOT + ../pom.xml + + + sentinel-native-image-quarkus-adapter + sentinel-native-image-quarkus-adapter + + + + io.quarkus + quarkus-core + ${quarkus.version} + + + org.graalvm.nativeimage + svm + + + com.alibaba.csp + sentinel-transport-simple-http + + + com.alibaba.csp + sentinel-parameter-flow-control + + + com.alibaba.csp + sentinel-logging-slf4j + ${project.version} + + + + + + + io.quarkus + quarkus-bootstrap-maven-plugin + ${quarkus.version} + + + + extension-descriptor + + compile + + ${project.groupId}:${project.artifactId}-deployment:${project.version} + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + io.quarkus + quarkus-extension-processor + ${quarkus.version} + + + + + + + diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/nativeimage/SentinelRecorder.java b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/nativeimage/SentinelRecorder.java new file mode 100644 index 0000000000..9a2ae34625 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/src/main/java/com/alibaba/csp/sentinel/adapter/quarkus/nativeimage/SentinelRecorder.java @@ -0,0 +1,52 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.quarkus.nativeimage; + +import com.alibaba.csp.sentinel.command.vo.NodeVo; +import com.alibaba.csp.sentinel.slots.block.authority.AuthorityRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import com.alibaba.csp.sentinel.slots.system.SystemRule; +import com.alibaba.fastjson.parser.ParserConfig; +import com.alibaba.fastjson.serializer.SerializeConfig; +import io.quarkus.runtime.annotations.Recorder; + +/** + * @author sea + */ +@Recorder +public class SentinelRecorder { + + /** + * register fastjson serializer deserializer class info + */ + public void init() { + SerializeConfig.getGlobalInstance().getObjectWriter(NodeVo.class); + SerializeConfig.getGlobalInstance().getObjectWriter(FlowRule.class); + SerializeConfig.getGlobalInstance().getObjectWriter(SystemRule.class); + SerializeConfig.getGlobalInstance().getObjectWriter(DegradeRule.class); + SerializeConfig.getGlobalInstance().getObjectWriter(AuthorityRule.class); + SerializeConfig.getGlobalInstance().getObjectWriter(ParamFlowRule.class); + + ParserConfig.getGlobalInstance().getDeserializer(NodeVo.class); + ParserConfig.getGlobalInstance().getDeserializer(FlowRule.class); + ParserConfig.getGlobalInstance().getDeserializer(SystemRule.class); + ParserConfig.getGlobalInstance().getDeserializer(DegradeRule.class); + ParserConfig.getGlobalInstance().getDeserializer(AuthorityRule.class); + ParserConfig.getGlobalInstance().getDeserializer(ParamFlowRule.class); + } +} diff --git a/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml new file mode 100644 index 0000000000..f8eed19385 --- /dev/null +++ b/sentinel-adapter/sentinel-quarkus-adapter/sentinel-native-image-quarkus-adapter-runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -0,0 +1,13 @@ +--- +name: "Sentinel native image extension" +metadata: + keywords: + - "sentinel" + - "rate-limiting" + - "circuit-breaker" + - "native-image" + - "fault-tolerance" + categories: + - "cloud" + - "fault-tolerance" + status: "preview" diff --git a/sentinel-adapter/sentinel-reactor-adapter/pom.xml b/sentinel-adapter/sentinel-reactor-adapter/pom.xml index c02c5c5e8b..abb6a11650 100644 --- a/sentinel-adapter/sentinel-reactor-adapter/pom.xml +++ b/sentinel-adapter/sentinel-reactor-adapter/pom.xml @@ -5,7 +5,7 @@ sentinel-adapter com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-adapter/sentinel-sofa-rpc-adapter/pom.xml b/sentinel-adapter/sentinel-sofa-rpc-adapter/pom.xml index 12b91a9d14..f0f84a723d 100644 --- a/sentinel-adapter/sentinel-sofa-rpc-adapter/pom.xml +++ b/sentinel-adapter/sentinel-sofa-rpc-adapter/pom.xml @@ -5,7 +5,7 @@ sentinel-adapter com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml index 6c3b13128f..015d54850b 100644 --- a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml +++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/pom.xml @@ -5,7 +5,7 @@ sentinel-adapter com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java index fcf00ecb3e..e17f4e60d1 100644 --- a/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java +++ b/sentinel-adapter/sentinel-spring-cloud-gateway-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/sc/ServerWebExchangeItemParser.java @@ -55,7 +55,7 @@ public String getUrlParam(ServerWebExchange exchange, String paramName) { @Override public String getCookieValue(ServerWebExchange exchange, String cookieName) { - return Optional.ofNullable(exchange.getResponse().getCookies().getFirst(cookieName)) + return Optional.ofNullable(exchange.getRequest().getCookies().getFirst(cookieName)) .map(HttpCookie::getValue) .orElse(null); } diff --git a/sentinel-adapter/sentinel-spring-webflux-adapter/pom.xml b/sentinel-adapter/sentinel-spring-webflux-adapter/pom.xml index c940192811..9965c09bea 100644 --- a/sentinel-adapter/sentinel-spring-webflux-adapter/pom.xml +++ b/sentinel-adapter/sentinel-spring-webflux-adapter/pom.xml @@ -5,7 +5,7 @@ sentinel-adapter com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/SentinelWebFluxIntegrationTest.java b/sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/SentinelWebFluxIntegrationTest.java index 2e0ec9e46d..6d84d25610 100644 --- a/sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/SentinelWebFluxIntegrationTest.java +++ b/sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/SentinelWebFluxIntegrationTest.java @@ -71,6 +71,30 @@ public void testWebFluxFilterBasic() throws Exception { assertEquals(1, cn.passQps(), 0.01); } + @Test + public void testWebFluxRouterFunction() throws Exception { + + String url = "/router/hello"; + this.webClient.get() + .uri(url) + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus().isOk() + .expectBody(String.class).isEqualTo(HELLO_STR); + + ClusterNode cn = ClusterBuilderSlot.getClusterNode(url); + assertNotNull(cn); + assertEquals(1, cn.passQps(), 0.01); + + configureRulesFor(url, 0); + this.webClient.get() + .uri(url) + .accept(MediaType.TEXT_PLAIN) + .exchange() + .expectStatus().isEqualTo(HttpStatus.TOO_MANY_REQUESTS) + .expectBody(String.class).value(StringContains.containsString(BLOCK_MSG_PREFIX)); + } + @Test public void testCustomizedUrlCleaner() throws Exception { final String fooPrefix = "/foo/"; diff --git a/sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/test/WebFluxTestRouter.java b/sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/test/WebFluxTestRouter.java new file mode 100644 index 0000000000..d4ad648acf --- /dev/null +++ b/sentinel-adapter/sentinel-spring-webflux-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webflux/test/WebFluxTestRouter.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.spring.webflux.test; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.reactive.function.server.RouterFunction; +import org.springframework.web.reactive.function.server.ServerResponse; + +import static org.springframework.web.reactive.function.BodyInserters.fromObject; +import static org.springframework.web.reactive.function.server.RequestPredicates.GET; +import static org.springframework.web.reactive.function.server.RouterFunctions.route; + +/** + * @author liqiangz + */ +@Configuration +public class WebFluxTestRouter { + + @Bean + RouterFunction routingFunction() { + return route(GET("/router/hello"), + req -> ServerResponse.ok().body(fromObject("Hello!"))); + } +} diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md b/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md index 941daed42e..cfbeb2da95 100755 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/README.md @@ -97,6 +97,7 @@ config.setBlockExceptionHandler((request, response, e) -> { | urlCleaner | The `UrlCleaner` interface is designed for clean and unify the URL resource. | `UrlCleaner` | - | | requestAttributeName | Attribute key in request used by Sentinel (internal) | `String` | `$$sentinel_spring_web_entry_attr` | | httpMethodSpecify | Specify whether the URL resource name should contain the HTTP method prefix (e.g. `POST:`). | `boolean` | `false` | +| webContextUnify | Specify whether unify web context(i.e. use the default context name). | `boolean` | `true` | - `SentinelWebMvcTotalConfig` configuration: diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml b/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml index b74d2cc475..23755f477c 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/pom.xml @@ -5,7 +5,7 @@ com.alibaba.csp sentinel-adapter - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java index e9f0a39ee6..e793aac73c 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/AbstractSentinelInterceptor.java @@ -34,6 +34,21 @@ import org.springframework.web.servlet.ModelAndView; /** + * Since request may be reprocessed in flow if any forwarding or including or other action + * happened (see {@link javax.servlet.ServletRequest#getDispatcherType()}) we will only + * deal with the initial request. So we use reference count to track in + * dispathing "onion" though which we could figure out whether we are in initial type "REQUEST". + * That means the sub-requests which we rarely meet in practice will NOT be recorded in Sentinel. + *

+ * How to implement a forward sub-request in your action: + *

+ * initalRequest() {
+ *     ModelAndView mav = new ModelAndView();
+ *     mav.setViewName("another");
+ *     return mav;
+ * }
+ * 
+ * * @author kaizi2009 * @since 1.7.1 */ @@ -49,24 +64,53 @@ public AbstractSentinelInterceptor(BaseWebMvcConfig config) { AssertUtil.assertNotBlank(config.getRequestAttributeName(), "requestAttributeName should not be blank"); this.baseWebMvcConfig = config; } - + + /** + * @param request + * @param rcKey + * @param step + * @return reference count after increasing (initial value as zero to be increased) + */ + private Integer increaseReferece(HttpServletRequest request, String rcKey, int step) { + Object obj = request.getAttribute(rcKey); + + if (obj == null) { + // initial + obj = Integer.valueOf(0); + } + + Integer newRc = (Integer)obj + step; + request.setAttribute(rcKey, newRc); + return newRc; + } + @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { try { String resourceName = getResourceName(request); - if (StringUtil.isNotEmpty(resourceName)) { - // Parse the request origin using registered origin parser. - String origin = parseOrigin(request); - ContextUtil.enter(SENTINEL_SPRING_WEB_CONTEXT_NAME, origin); - Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); - - setEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName(), entry); + if (StringUtil.isEmpty(resourceName)) { + return true; + } + + if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), 1) != 1) { + return true; } + + // Parse the request origin using registered origin parser. + String origin = parseOrigin(request); + String contextName = getContextName(request); + ContextUtil.enter(contextName, origin); + Entry entry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_WEB, EntryType.IN); + request.setAttribute(baseWebMvcConfig.getRequestAttributeName(), entry); return true; } catch (BlockException e) { - handleBlockException(request, response, e); + try { + handleBlockException(request, response, e); + } finally { + ContextUtil.exit(); + } return false; } } @@ -79,14 +123,33 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons */ protected abstract String getResourceName(HttpServletRequest request); + /** + * Return the context name of the target web resource. + * + * @param request web request + * @return the context name of the target web resource. + */ + protected String getContextName(HttpServletRequest request) { + return SENTINEL_SPRING_WEB_CONTEXT_NAME; + } + @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { + if (increaseReferece(request, this.baseWebMvcConfig.getRequestRefName(), -1) != 0) { + return; + } + Entry entry = getEntryInRequest(request, baseWebMvcConfig.getRequestAttributeName()); - if (entry != null) { - traceExceptionAndExit(entry, ex); - removeEntryInRequest(request); + if (entry == null) { + // should not happen + RecordLog.warn("[{}] No entry found in request, key: {}", + getClass().getSimpleName(), baseWebMvcConfig.getRequestAttributeName()); + return; } + + traceExceptionAndExit(entry, ex); + removeEntryInRequest(request); ContextUtil.exit(); } @@ -95,16 +158,6 @@ public void postHandle(HttpServletRequest request, HttpServletResponse response, ModelAndView modelAndView) throws Exception { } - protected void setEntryInRequest(HttpServletRequest request, String name, Entry entry) { - Object attrVal = request.getAttribute(name); - if (attrVal != null) { - RecordLog.warn("[{}] The attribute key '{0}' already exists in request, please set `requestAttributeName`", - getClass().getSimpleName(), name); - } else { - request.setAttribute(name, entry); - } - } - protected Entry getEntryInRequest(HttpServletRequest request, String attrKey) { Object entryObject = request.getAttribute(attrKey); return entryObject == null ? null : (Entry)entryObject; diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java index b01b2c358a..0c244d680a 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelWebInterceptor.java @@ -66,4 +66,12 @@ protected String getResourceName(HttpServletRequest request) { return resourceName; } + @Override + protected String getContextName(HttpServletRequest request) { + if (config.isWebContextUnify()) { + return super.getContextName(request); + } + + return getResourceName(request); + } } diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java index 03ff0ebc7b..e1bd1542fd 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/BaseWebMvcConfig.java @@ -17,7 +17,6 @@ import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.BlockExceptionHandler; import com.alibaba.csp.sentinel.adapter.spring.webmvc.callback.RequestOriginParser; -import com.alibaba.csp.sentinel.util.AssertUtil; /** * Common base configuration for Spring Web MVC adapter. @@ -28,6 +27,7 @@ public abstract class BaseWebMvcConfig { protected String requestAttributeName; + protected String requestRefName; protected BlockExceptionHandler blockExceptionHandler; protected RequestOriginParser originParser; @@ -37,6 +37,16 @@ public String getRequestAttributeName() { public void setRequestAttributeName(String requestAttributeName) { this.requestAttributeName = requestAttributeName; + this.requestRefName = this.requestAttributeName + "-rc"; + } + + /** + * Paired with attr name used to track reference count. + * + * @return + */ + public String getRequestRefName() { + return requestRefName; } public BlockExceptionHandler getBlockExceptionHandler() { diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java index 29a2891ca6..c94ff8c20f 100755 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/SentinelWebMvcConfig.java @@ -29,11 +29,19 @@ public class SentinelWebMvcConfig extends BaseWebMvcConfig { * Specify the URL cleaner that unifies the URL resources. */ private UrlCleaner urlCleaner; + /** * Specify whether the URL resource name should contain the HTTP method prefix (e.g. {@code POST:}). */ private boolean httpMethodSpecify; + /** + * Specify whether unify web context(i.e. use the default context name), and is true by default. + * + * @since 1.7.2 + */ + private boolean webContextUnify = true; + public SentinelWebMvcConfig() { super(); setRequestAttributeName(DEFAULT_REQUEST_ATTRIBUTE_NAME); @@ -57,11 +65,21 @@ public SentinelWebMvcConfig setHttpMethodSpecify(boolean httpMethodSpecify) { return this; } + public boolean isWebContextUnify() { + return webContextUnify; + } + + public SentinelWebMvcConfig setWebContextUnify(boolean webContextUnify) { + this.webContextUnify = webContextUnify; + return this; + } + @Override public String toString() { return "SentinelWebMvcConfig{" + "urlCleaner=" + urlCleaner + ", httpMethodSpecify=" + httpMethodSpecify + + ", webContextUnify=" + webContextUnify + ", requestAttributeName='" + requestAttributeName + '\'' + ", blockExceptionHandler=" + blockExceptionHandler + ", originParser=" + originParser + diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java index f6a4b30fba..f1ddb937ce 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/SentinelSpringMvcIntegrationTest.java @@ -71,18 +71,21 @@ public void testOriginParser() throws Exception { final String headerName = "S-User"; configureRulesFor(springMvcPathVariableUrl, 0, limitOrigin); + // This will be passed since the caller is different: userB this.mvc.perform(get("/foo/1").accept(MediaType.TEXT_PLAIN).header(headerName, "userB")) .andExpect(status().isOk()) .andExpect(content().string("foo 1")); - // This will be blocked and reponse json. + // This will be blocked since the caller is same: userA this.mvc.perform( get("/foo/2").accept(MediaType.APPLICATION_JSON).header(headerName, limitOrigin)) .andExpect(status().isOk()) .andExpect(content().json(ResultWrapper.blocked().toJsonString())); + + // This will be passed since the caller is different: "" this.mvc.perform(get("/foo/3").accept(MediaType.APPLICATION_JSON)) .andExpect(status().isOk()) - .andExpect(content().string(ResultWrapper.blocked().toJsonString())); + .andExpect(content().string("foo 3")); FlowRuleManager.loadRules(null); } @@ -115,7 +118,7 @@ public void testRuntimeException() throws Exception { assertEquals(i + 1, cn.passQps(), 0.01); } - // This will be blocked and reponse json. + // This will be blocked and response json. this.mvc.perform(get(url)) .andExpect(status().isOk()) .andExpect(content().string(ResultWrapper.blocked().toJsonString())); diff --git a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java index 1ff24fd2cf..5b167a0c1d 100644 --- a/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java +++ b/sentinel-adapter/sentinel-spring-webmvc-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/spring/webmvc/config/InterceptorConfig.java @@ -66,6 +66,7 @@ public void handle(HttpServletRequest request, HttpServletResponse response, Blo //Custom configuration if necessary config.setHttpMethodSpecify(false); + config.setWebContextUnify(true); config.setOriginParser(new RequestOriginParser() { @Override public String parseOrigin(HttpServletRequest request) { diff --git a/sentinel-adapter/sentinel-web-servlet/pom.xml b/sentinel-adapter/sentinel-web-servlet/pom.xml index 32c6b0d3fa..618f71f6ef 100755 --- a/sentinel-adapter/sentinel-web-servlet/pom.xml +++ b/sentinel-adapter/sentinel-web-servlet/pom.xml @@ -6,7 +6,7 @@ com.alibaba.csp sentinel-adapter - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT sentinel-web-servlet diff --git a/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java b/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java index e246e06c65..f7bf3d6148 100755 --- a/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java +++ b/sentinel-adapter/sentinel-web-servlet/src/main/java/com/alibaba/csp/sentinel/adapter/servlet/CommonFilter.java @@ -54,7 +54,7 @@ public class CommonFilter implements Filter { */ public static final String HTTP_METHOD_SPECIFY = "HTTP_METHOD_SPECIFY"; /** - * If enabled, use the URL path as the context name, or else use the default + * If enabled, use the default context name, or else use the URL path as the context name, * {@link WebServletConfig#WEB_SERVLET_CONTEXT_NAME}. Please pay attention to the number of context (EntranceNode), * which may affect the memory footprint. * diff --git a/sentinel-adapter/sentinel-zuul-adapter/README.md b/sentinel-adapter/sentinel-zuul-adapter/README.md index 9814a948bc..dcfd8de6af 100755 --- a/sentinel-adapter/sentinel-zuul-adapter/README.md +++ b/sentinel-adapter/sentinel-zuul-adapter/README.md @@ -64,7 +64,7 @@ As Zuul run as per thread per connection block model, we add filters around rout - `SentinelZuulPreFilter`: This pre-filter will regard all proxy ID (`proxy` in `RequestContext`) and all customized API as resources. When a `BlockException` caught, the filter will try to find a fallback to execute. - `SentinelZuulPostFilter`: When the response has no exception caught, the post filter will complete the entries. -- `SentinelZuulPreFilter`: When an exception is caught, the filter will trace the exception and complete the entries. +- `SentinelZuulErrorFilter`: When an exception is caught, the filter will trace the exception and complete the entries. diff --git a/sentinel-adapter/sentinel-zuul-adapter/pom.xml b/sentinel-adapter/sentinel-zuul-adapter/pom.xml index 6857cb8e8e..7aa5181d2e 100755 --- a/sentinel-adapter/sentinel-zuul-adapter/pom.xml +++ b/sentinel-adapter/sentinel-zuul-adapter/pom.xml @@ -5,7 +5,7 @@ sentinel-adapter com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 sentinel-zuul-adapter @@ -69,6 +69,12 @@ mockito-core test
+ + org.springframework + spring-test + 4.3.20.RELEASE + test + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/PrefixRoutePathMatcher.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/PrefixRoutePathMatcher.java index 21e636e589..b592a9cf03 100644 --- a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/PrefixRoutePathMatcher.java +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/PrefixRoutePathMatcher.java @@ -17,11 +17,12 @@ import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; - import com.netflix.zuul.context.RequestContext; import org.springframework.util.AntPathMatcher; import org.springframework.util.PathMatcher; +import javax.servlet.http.HttpServletRequest; + /** * @author Eric Zhao * @since 1.6.0 @@ -42,7 +43,12 @@ public PrefixRoutePathMatcher(String pattern) { @Override public boolean test(RequestContext context) { - String path = context.getRequest().getServletPath(); + //Solve the problem of prefix matching + HttpServletRequest request = context.getRequest(); + String path = request.getRequestURI(); + if (path == null) { + AssertUtil.assertNotBlank(pattern, "requesturi cannot be blank"); + } if (canMatch) { return pathMatcher.match(pattern, path); } diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/RegexRoutePathMatcher.java b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/RegexRoutePathMatcher.java index daf1310ec3..4363e4e795 100644 --- a/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/RegexRoutePathMatcher.java +++ b/sentinel-adapter/sentinel-zuul-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/api/route/RegexRoutePathMatcher.java @@ -15,13 +15,13 @@ */ package com.alibaba.csp.sentinel.adapter.gateway.zuul.api.route; -import java.util.regex.Pattern; - import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Predicate; - import com.netflix.zuul.context.RequestContext; +import javax.servlet.http.HttpServletRequest; +import java.util.regex.Pattern; + /** * @author Eric Zhao * @since 1.6.0 @@ -39,7 +39,12 @@ public RegexRoutePathMatcher(String pattern) { @Override public boolean test(RequestContext context) { - String path = context.getRequest().getServletPath(); + //Solve the problem of route matching + HttpServletRequest request = context.getRequest(); + String path = request.getRequestURI(); + if (path == null) { + AssertUtil.assertNotBlank(pattern, "requesturi cannot be blank"); + } return regex.matcher(path).matches(); } diff --git a/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/route/SentinelZuulRouteTest.java b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/route/SentinelZuulRouteTest.java new file mode 100644 index 0000000000..eb67921a77 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul/route/SentinelZuulRouteTest.java @@ -0,0 +1,56 @@ +package com.alibaba.csp.sentinel.adapter.gateway.zuul.route; + +import com.alibaba.csp.sentinel.adapter.gateway.zuul.api.route.PrefixRoutePathMatcher; +import com.alibaba.csp.sentinel.adapter.gateway.zuul.api.route.RegexRoutePathMatcher; +import com.netflix.zuul.context.RequestContext; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.mock.web.MockHttpServletRequest; + +import static com.alibaba.csp.sentinel.adapter.gateway.zuul.constants.ZuulConstant.SERVICE_ID_KEY; + +/** + * @author: jiangzian + **/ +public class SentinelZuulRouteTest { + + private final String SERVICE_ID = "servicea"; + + private final String SERVER_NAME = "www.example.com"; + private final String REQUEST_URI = "/servicea/test.jsp"; + private final String QUERY_STRING = "param1=value1¶m"; + + private RequestContext requestContext = new RequestContext(); + + + @Before + public void setUp() { + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setServerName(SERVER_NAME); + request.setRequestURI(REQUEST_URI); + request.setQueryString(QUERY_STRING); + requestContext.set(SERVICE_ID_KEY, SERVICE_ID); + requestContext.setRequest(request); + RequestContext.testSetCurrentContext(requestContext); + } + + @Test + public void testPrefixRoutePathMatche() { + PrefixRoutePathMatcher prefixRoutePathMatcher = new PrefixRoutePathMatcher("/servicea/????.jsp"); + Assert.assertTrue(prefixRoutePathMatcher.test(requestContext)); + + prefixRoutePathMatcher = new PrefixRoutePathMatcher("/servicea/????.do"); + Assert.assertTrue(!prefixRoutePathMatcher.test(requestContext)); + } + + @Test + public void testRegexRoutePathMatcher() { + RegexRoutePathMatcher regexRoutePathMatcher = new RegexRoutePathMatcher("/servicea/[a-zA-z]+(\\.jsp)"); + Assert.assertTrue(regexRoutePathMatcher.test(requestContext)); + + regexRoutePathMatcher = new RegexRoutePathMatcher("/serviceb/[a-zA-z]+(\\.jsp)"); + Assert.assertTrue(!regexRoutePathMatcher.test(requestContext)); + } + +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/README.md b/sentinel-adapter/sentinel-zuul2-adapter/README.md new file mode 100755 index 0000000000..e0387ee516 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/README.md @@ -0,0 +1,90 @@ +# Sentinel Zuul 2.x Adapter + +This adapter provides **route level** and **customized API level** +flow control for Zuul 2.x API Gateway. + +> *Note*: this adapter only supports Zuul 2.x. + +## How to use + +> You can refer to demo [`sentinel-demo-zuul2-gateway`](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-zuul2-gateway). + +1. Add Maven dependency to your `pom.xml`: + +```xml + + com.alibaba.csp + sentinel-zuul2-adapter + x.y.z + +``` + +2. Register filters + +```java +filterMultibinder.addBinding().toInstance(new SentinelZuulInboundFilter(500)); +filterMultibinder.addBinding().toInstance(new SentinelZuulOutboundFilter(500)); +filterMultibinder.addBinding().toInstance(new SentinelZuulEndpoint()); +``` + +## How it works + +As Zuul 2.x is based on Netty, an event-driven asynchronous model, so we use `AsyncEntry`. + +- `SentinelZuulInboundFilter`: This inbound filter will regard all routes (`routeVIP` in `SessionContext` by default) and all customized API as resources. When a `BlockException` caught, the filter will set endpoint to find a fallback to execute. +- `SentinelZuulOutboundFilter`: When the response has no exception caught, the post filter will trace the exception and complete the entries. +- `SentinelZuulEndpoint`: When an exception is caught, the filter will find a fallback to execute. + +## Integration with Sentinel Dashboard + +1. Start [Sentinel Dashboard](https://github.com/alibaba/Sentinel/wiki/Dashboard). +2. You can configure the rules in Sentinel dashboard or via dynamic rule configuration. + +> You may need to add `-Dcsp.sentinel.app.type=1` property to mark this application as API gateway. + +## Fallbacks + +You can implement `ZuulBlockFallbackProvider` to define your own fallback provider when Sentinel `BlockException` is thrown. +The default fallback provider is `DefaultBlockFallbackProvider`. + +By default fallback route is proxy ID (or customized API name). + +Here is an example: + +```java + +// custom provider +public class MyBlockFallbackProvider implements ZuulBlockFallbackProvider { + + private Logger logger = LoggerFactory.getLogger(DefaultBlockFallbackProvider.class); + + // you can define root as service level + @Override + public String getRoute() { + return "my-route"; + } + + @Override + public BlockResponse fallbackResponse(String route, Throwable cause) { + RecordLog.info(String.format("[Sentinel DefaultBlockFallbackProvider] Run fallback route: %s", route)); + if (cause instanceof BlockException) { + return new BlockResponse(429, "Sentinel block exception", route); + } else { + return new BlockResponse(500, "System Error", route); + } + } + } + + // register fallback + ZuulBlockFallbackManager.registerProvider(new MyBlockFallbackProvider()); +``` + +Default block response: + +```json +{ + "code":429, + "message":"Sentinel block exception", + "route":"/" +} +``` diff --git a/sentinel-adapter/sentinel-zuul2-adapter/pom.xml b/sentinel-adapter/sentinel-zuul2-adapter/pom.xml new file mode 100644 index 0000000000..c841a53588 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/pom.xml @@ -0,0 +1,63 @@ + + + + sentinel-adapter + com.alibaba.csp + 1.8.1-SNAPSHOT + + 4.0.0 + + sentinel-zuul2-adapter + jar + + + 1.8 + 1.8 + 2.1.5 + + + + + com.alibaba.csp + sentinel-core + + + com.alibaba.csp + sentinel-api-gateway-adapter-common + + + + com.netflix.zuul + zuul-core + ${zuul.version} + provided + + + org.mockito + mockito-core + + + + + + + org.springframework + spring-core + 5.1.9.RELEASE + + + + junit + junit + test + + + org.mockito + mockito-core + test + + + + \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/HttpRequestMessageItemParser.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/HttpRequestMessageItemParser.java new file mode 100644 index 0000000000..8c3f3cff35 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/HttpRequestMessageItemParser.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.gateway.zuul2; + +import com.alibaba.csp.sentinel.adapter.gateway.common.param.RequestItemParser; +import com.netflix.zuul.message.http.HttpRequestMessage; + +/** + * @author wavesZh + * @since 1.7.2 + */ +public class HttpRequestMessageItemParser implements RequestItemParser { + + @Override + public String getPath(HttpRequestMessage request) { + return request.getInboundRequest().getPath(); + } + + @Override + public String getRemoteAddress(HttpRequestMessage request) { + return request.getOriginalHost(); + } + + @Override + public String getHeader(HttpRequestMessage request, String key) { + return String.valueOf(request.getInboundRequest().getHeaders().get(key)); + } + + @Override + public String getUrlParam(HttpRequestMessage request, String paramName) { + return String.valueOf(request.getInboundRequest().getQueryParams().get(paramName)); + } + + @Override + public String getCookieValue(HttpRequestMessage request, String cookieName) { + return String.valueOf(request.getInboundRequest().parseCookies().get(cookieName)); + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulApiDefinitionChangeObserver.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulApiDefinitionChangeObserver.java new file mode 100644 index 0000000000..0cbaa4e4b3 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulApiDefinitionChangeObserver.java @@ -0,0 +1,33 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api; + +import java.util.Set; + +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver; + +/** + * @author Eric Zhao + * @since 1.7.2 + */ +public class ZuulApiDefinitionChangeObserver implements ApiDefinitionChangeObserver { + + @Override + public void onChange(Set apiDefinitions) { + ZuulGatewayApiMatcherManager.loadApiDefinitions(apiDefinitions); + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulGatewayApiMatcherManager.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulGatewayApiMatcherManager.java new file mode 100644 index 0000000000..7c71724c89 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/ZuulGatewayApiMatcherManager.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.matcher.HttpRequestMessageApiMatcher; + +/** + * @author wavesZh + * @since 1.7.2 + */ +public final class ZuulGatewayApiMatcherManager { + + private static final Map API_MATCHER_MAP = new ConcurrentHashMap<>(); + + public static Map getApiMatcherMap() { + return Collections.unmodifiableMap(API_MATCHER_MAP); + } + + public static HttpRequestMessageApiMatcher getMatcher(final String apiName) { + if (apiName == null) { + return null; + } + return API_MATCHER_MAP.get(apiName); + } + + public static Set getApiDefinitionSet() { + Set set = new HashSet<>(); + for (HttpRequestMessageApiMatcher matcher : API_MATCHER_MAP.values()) { + set.add(matcher.getApiDefinition()); + } + return set; + } + + static synchronized void loadApiDefinitions(/*@Valid*/ Set definitions) { + if (definitions == null || definitions.isEmpty()) { + API_MATCHER_MAP.clear(); + return; + } + for (ApiDefinition definition : definitions) { + addApiDefinition(definition); + } + } + + static void addApiDefinition(ApiDefinition definition) { + API_MATCHER_MAP.put(definition.getApiName(), new HttpRequestMessageApiMatcher(definition)); + } + + private ZuulGatewayApiMatcherManager() {} +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/matcher/HttpRequestMessageApiMatcher.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/matcher/HttpRequestMessageApiMatcher.java new file mode 100644 index 0000000000..12924da7ab --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/matcher/HttpRequestMessageApiMatcher.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.matcher; + +import com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinition; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPathPredicateItem; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiPredicateItem; +import com.alibaba.csp.sentinel.adapter.gateway.common.api.matcher.AbstractApiMatcher; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.route.ZuulRouteMatchers; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.csp.sentinel.util.function.Predicate; +import com.netflix.zuul.message.http.HttpRequestMessage; + +/** + * @author wavesZh + */ +public class HttpRequestMessageApiMatcher extends AbstractApiMatcher { + + public HttpRequestMessageApiMatcher(ApiDefinition apiDefinition) { + super(apiDefinition); + } + + @Override + protected void initializeMatchers() { + if (apiDefinition.getPredicateItems() != null) { + for (ApiPredicateItem item : apiDefinition.getPredicateItems()) { + Predicate predicate = fromApiPredicate(item); + if (predicate != null) { + matchers.add(predicate); + } + } + } + } + + private Predicate fromApiPredicate(/*@NonNull*/ ApiPredicateItem item) { + if (item instanceof ApiPathPredicateItem) { + return fromApiPathPredicate((ApiPathPredicateItem)item); + } + return null; + } + + private Predicate fromApiPathPredicate(/*@Valid*/ ApiPathPredicateItem item) { + String pattern = item.getPattern(); + if (StringUtil.isBlank(pattern)) { + return null; + } + switch (item.getMatchStrategy()) { + case SentinelGatewayConstants.URL_MATCH_STRATEGY_REGEX: + return ZuulRouteMatchers.regexPath(pattern); + case SentinelGatewayConstants.URL_MATCH_STRATEGY_PREFIX: + return ZuulRouteMatchers.antPath(pattern); + default: + return ZuulRouteMatchers.exactPath(pattern); + } + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/PrefixRoutePathMatcher.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/PrefixRoutePathMatcher.java new file mode 100644 index 0000000000..2b979bc8a0 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/PrefixRoutePathMatcher.java @@ -0,0 +1,53 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.route; + +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.function.Predicate; +import com.netflix.zuul.message.http.HttpRequestMessage; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; + +/** + * @author wavesZh + */ +public class PrefixRoutePathMatcher implements Predicate { + + private final String pattern; + + private final PathMatcher pathMatcher; + private final boolean canMatch; + + public PrefixRoutePathMatcher(String pattern) { + AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); + this.pattern = pattern; + this.pathMatcher = new AntPathMatcher(); + this.canMatch = pathMatcher.isPattern(pattern); + } + + @Override + public boolean test(HttpRequestMessage context) { + String path = context.getPath(); + if (canMatch) { + return pathMatcher.match(pattern, path); + } + return false; + } + + public String getPattern() { + return pattern; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/RegexRoutePathMatcher.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/RegexRoutePathMatcher.java new file mode 100644 index 0000000000..fd88e6489b --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/RegexRoutePathMatcher.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.route; + +import java.util.regex.Pattern; + +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.function.Predicate; +import com.netflix.zuul.message.http.HttpRequestMessage; + +/** + * @author wavesZh + */ +public class RegexRoutePathMatcher implements Predicate { + + private final String pattern; + private final Pattern regex; + + public RegexRoutePathMatcher(String pattern) { + AssertUtil.assertNotBlank(pattern, "pattern cannot be blank"); + this.pattern = pattern; + this.regex = Pattern.compile(pattern); + } + + @Override + public boolean test(HttpRequestMessage input) { + String path = input.getInboundRequest().getPath(); + return regex.matcher(path).matches(); + } + + public String getPattern() { + return pattern; + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/ZuulRouteMatchers.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/ZuulRouteMatchers.java new file mode 100644 index 0000000000..7d62c63e45 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/api/route/ZuulRouteMatchers.java @@ -0,0 +1,43 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.route; + +import com.alibaba.csp.sentinel.util.function.Predicate; +import com.netflix.zuul.message.http.HttpRequestMessage; + +/** + * @author wavesZh + */ +public final class ZuulRouteMatchers { + + public static Predicate all() { + return requestContext -> true; + } + + public static Predicate antPath(String pathPattern) { + return new PrefixRoutePathMatcher(pathPattern); + } + + public static Predicate exactPath(final String path) { + return exchange -> exchange.getPath().equals(path); + } + + public static Predicate regexPath(String pathPattern) { + return new RegexRoutePathMatcher(pathPattern); + } + + private ZuulRouteMatchers() {} +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/constants/SentinelZuul2Constants.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/constants/SentinelZuul2Constants.java new file mode 100644 index 0000000000..0e01d490f1 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/constants/SentinelZuul2Constants.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants; + +/** + * @author wavesZh + */ +public class SentinelZuul2Constants { + /** + * The default entrance (context) name when the routeId is empty. + */ + public static final String ZUUL_DEFAULT_CONTEXT = "zuul2_default_context"; + /** + * Zuul context key for keeping Sentinel entries. + */ + public static final String ZUUL_CTX_SENTINEL_ENTRIES_KEY = "_sentinel_entries"; + + public static final String ZUUL_CTX_SENTINEL_FALLBACK_ROUTE = "_sentinel_fallback_route"; + /** + * Indicate if request is blocked . + */ + public static final String ZUUL_CTX_SENTINEL_BLOCKED_FLAG = "_sentinel_blocked_flag"; + + private SentinelZuul2Constants() {} +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/BlockResponse.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/BlockResponse.java new file mode 100644 index 0000000000..5209f2329a --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/BlockResponse.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; + +/** + * Fall back response for {@link com.alibaba.csp.sentinel.slots.block.BlockException} + * + * @author tiger + */ +public class BlockResponse { + + /** + * HTTP status code. + */ + private int code; + + private String message; + private String route; + + public BlockResponse(int code, String message, String route) { + this.code = code; + this.message = message; + this.route = route; + } + + public int getCode() { + return code; + } + + public void setCode(int code) { + this.code = code; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public String getRoute() { + return route; + } + + public void setRoute(String route) { + this.route = route; + } + + @Override + public String toString() { + return "{" + + "\"code\":" + code + + ", \"message\":" + "\"" + message + "\"" + + ", \"route\":" + "\"" + route + "\"" + + '}'; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/DefaultBlockFallbackProvider.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/DefaultBlockFallbackProvider.java new file mode 100644 index 0000000000..222630d539 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/DefaultBlockFallbackProvider.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; + +import com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * Default fallback provider for Sentinel {@link BlockException}, {@literal *} meant for all routes. + * + * @author tiger + */ +public class DefaultBlockFallbackProvider implements ZuulBlockFallbackProvider { + + @Override + public String getRoute() { + return "*"; + } + + @Override + public BlockResponse fallbackResponse(String route, Throwable cause) { + if (cause instanceof BlockException) { + return new BlockResponse(429, "SentinelBlockException", route); + } else { + return new BlockResponse(500, "System Error", route); + } + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManager.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManager.java new file mode 100644 index 0000000000..4e94e03aea --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManager.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; + +import java.util.HashMap; +import java.util.Map; + +import com.alibaba.csp.sentinel.util.AssertUtil; + +/** + * This provide fall back class manager. + * + * @author tiger + */ +public class ZuulBlockFallbackManager { + + private static Map fallbackProviderCache = new HashMap<>(); + + private static ZuulBlockFallbackProvider defaultFallbackProvider = new DefaultBlockFallbackProvider(); + + /** + * Register special provider for different route. + */ + public static synchronized void registerProvider(ZuulBlockFallbackProvider provider) { + AssertUtil.notNull(provider, "fallback provider cannot be null"); + String route = provider.getRoute(); + if ("*".equals(route) || route == null) { + defaultFallbackProvider = provider; + } else { + fallbackProviderCache.put(route, provider); + } + } + + public static ZuulBlockFallbackProvider getFallbackProvider(String route) { + ZuulBlockFallbackProvider provider = fallbackProviderCache.get(route); + if (provider == null) { + provider = defaultFallbackProvider; + } + return provider; + } + + public synchronized static void clear(){ + fallbackProviderCache.clear(); + } + +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProvider.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProvider.java new file mode 100644 index 0000000000..ee6f51f391 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProvider.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; + +/** + * This interface is compatible for different spring cloud version. + * + * @author tiger + */ +public interface ZuulBlockFallbackProvider { + + /** + * The route this fallback will be used for. + * @return The route the fallback will be used for. + */ + String getRoute(); + + /** + * Provides a fallback response based on the cause of the failed execution. + * + * @param route The route the fallback is for + * @param cause cause of the main method failure, may be null + * @return the fallback response + */ + BlockResponse fallbackResponse(String route, Throwable cause); +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/EntryHolder.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/EntryHolder.java new file mode 100644 index 0000000000..59fb2ef987 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/EntryHolder.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters; + +import com.alibaba.csp.sentinel.Entry; + +/** + * @author wavesZh + */ +public class EntryHolder { + + final private Entry entry; + + final private Object[] params; + + public EntryHolder(Entry entry, Object[] params) { + this.entry = entry; + this.params = params; + } + + public Entry getEntry() { + return entry; + } + + public Object[] getParams() { + return params; + } +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/endpoint/SentinelZuulEndpoint.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/endpoint/SentinelZuulEndpoint.java new file mode 100644 index 0000000000..6713127415 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/endpoint/SentinelZuulEndpoint.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint; + +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.BlockResponse; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackManager; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback.ZuulBlockFallbackProvider; + +import com.netflix.zuul.context.SessionContext; +import com.netflix.zuul.filters.http.HttpSyncEndpoint; +import com.netflix.zuul.message.http.HttpRequestMessage; +import com.netflix.zuul.message.http.HttpResponseMessage; +import com.netflix.zuul.message.http.HttpResponseMessageImpl; + +/** + * Default Endpoint for handling exception. + * + * @author wavesZh + */ +public class SentinelZuulEndpoint extends HttpSyncEndpoint { + + @Override + public HttpResponseMessage apply(HttpRequestMessage request) { + SessionContext context = request.getContext(); + Throwable throwable = context.getError(); + String fallBackRoute = (String) context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE); + ZuulBlockFallbackProvider zuulBlockFallbackProvider = ZuulBlockFallbackManager + .getFallbackProvider(fallBackRoute); + BlockResponse response = zuulBlockFallbackProvider.fallbackResponse(fallBackRoute, throwable); + HttpResponseMessage resp = new HttpResponseMessageImpl(context, request, response.getCode()); + resp.setBodyAsText(response.toString()); + return resp; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/inbound/SentinelZuulInboundFilter.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/inbound/SentinelZuulInboundFilter.java new file mode 100644 index 0000000000..ff17453889 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/inbound/SentinelZuulInboundFilter.java @@ -0,0 +1,182 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.inbound; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.Executor; +import java.util.function.Function; + +import com.alibaba.csp.sentinel.AsyncEntry; +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.ResourceTypeConstants; +import com.alibaba.csp.sentinel.SphU; +import com.alibaba.csp.sentinel.adapter.gateway.common.param.GatewayParamParser; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.HttpRequestMessageItemParser; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.ZuulGatewayApiMatcherManager; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.matcher.HttpRequestMessageApiMatcher; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.EntryHolder; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.endpoint.SentinelZuulEndpoint; +import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.netflix.zuul.context.SessionContext; +import com.netflix.zuul.filters.http.HttpInboundFilter; +import com.netflix.zuul.message.http.HttpRequestMessage; +import rx.Observable; +import rx.schedulers.Schedulers; + +import static com.alibaba.csp.sentinel.adapter.gateway.common.SentinelGatewayConstants.*; + +/** + * The Zuul inbound filter wrapped with Sentinel route and customized API group entries. + * + * @author wavesZh + */ +public class SentinelZuulInboundFilter extends HttpInboundFilter { + + private static final String DEFAULT_BLOCK_ENDPOINT_NAME = SentinelZuulEndpoint.class.getCanonicalName(); + + private final int order; + + private final String blockedEndpointName; + /** + * If the executor is null, flow control action will be performed on I/O thread + */ + private final Executor executor; + /** + * If true, the rest of inbound filters will be skipped when the request is blocked. + */ + private final boolean fastError; + private final Function routeExtractor; + + private final GatewayParamParser paramParser = new GatewayParamParser<>( + new HttpRequestMessageItemParser()); + + /** + * Constructor of the inbound filter, which extracts the route from the context route VIP attribute by default. + * + * @param order the order of the filter + */ + public SentinelZuulInboundFilter(int order) { + this(order, m -> m.getContext().getRouteVIP()); + } + + public SentinelZuulInboundFilter(int order, Function routeExtractor) { + this(order, null, routeExtractor); + } + + public SentinelZuulInboundFilter(int order, Executor executor, Function routeExtractor) { + this(order, DEFAULT_BLOCK_ENDPOINT_NAME, executor, true, routeExtractor); + } + + /** + * Constructor of the inbound filter. + * + * @param order the order of the filter + * @param blockedEndpointName the endpoint to go when the request is blocked + * @param executor the executor where Sentinel do flow checking. If null, it will be executed in current thread. + * @param fastError whether the rest of the filters will be skipped if the request is blocked + * @param routeExtractor the route ID extractor + */ + public SentinelZuulInboundFilter(int order, String blockedEndpointName, Executor executor, boolean fastError, + Function routeExtractor) { + AssertUtil.notEmpty(blockedEndpointName, "blockedEndpointName cannot be empty"); + AssertUtil.notNull(routeExtractor, "routeExtractor cannot be null"); + this.order = order; + this.blockedEndpointName = blockedEndpointName; + this.executor = executor; + this.fastError = fastError; + this.routeExtractor = routeExtractor; + } + + @Override + public int filterOrder() { + return order; + } + + @Override + public Observable applyAsync(HttpRequestMessage request) { + if (executor != null) { + return Observable.just(request).subscribeOn(Schedulers.from(executor)).flatMap(this::apply); + } else { + return Observable.just(request).flatMap(this::apply); + } + } + + private Observable apply(HttpRequestMessage request) { + SessionContext context = request.getContext(); + Deque holders = new ArrayDeque<>(); + String routeId = routeExtractor.apply(request); + String fallBackRoute = routeId; + try { + if (StringUtil.isNotBlank(routeId)) { + ContextUtil.enter(GATEWAY_CONTEXT_ROUTE_PREFIX + routeId); + doSentinelEntry(routeId, RESOURCE_MODE_ROUTE_ID, request, holders); + } + Set matchingApis = pickMatchingApiDefinitions(request); + if (!matchingApis.isEmpty() && ContextUtil.getContext() == null) { + ContextUtil.enter(SentinelZuul2Constants.ZUUL_DEFAULT_CONTEXT); + } + for (String apiName : matchingApis) { + fallBackRoute = apiName; + doSentinelEntry(apiName, RESOURCE_MODE_CUSTOM_API_NAME, request, holders); + } + return Observable.just(request); + } catch (BlockException t) { + context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_BLOCKED_FLAG, Boolean.TRUE); + context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_FALLBACK_ROUTE, fallBackRoute); + if (fastError) { + context.setShouldSendErrorResponse(true); + context.setErrorEndpoint(blockedEndpointName); + } else { + context.setEndpoint(blockedEndpointName); + } + return Observable.error(t); + } finally { + if (!holders.isEmpty()) { + context.put(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY, holders); + } + // clear context to avoid another request use incorrect context + ContextUtil.exit(); + } + } + + private void doSentinelEntry(String resourceName, final int resType, HttpRequestMessage input, Deque holders) throws BlockException { + Object[] params = paramParser.parseParameterFor(resourceName, input, r -> r.getResourceMode() == resType); + AsyncEntry entry = SphU.asyncEntry(resourceName, ResourceTypeConstants.COMMON_API_GATEWAY, EntryType.IN, params); + holders.push(new EntryHolder(entry, params)); + } + + private Set pickMatchingApiDefinitions(HttpRequestMessage message) { + Set apis = new HashSet<>(); + for (HttpRequestMessageApiMatcher matcher : ZuulGatewayApiMatcherManager.getApiMatcherMap().values()) { + if (matcher.test(message)) { + apis.add(matcher.getApiName()); + } + } + return apis; + } + + @Override + public boolean shouldFilter(HttpRequestMessage msg) { + return true; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/outbound/SentinelZuulOutboundFilter.java b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/outbound/SentinelZuulOutboundFilter.java new file mode 100644 index 0000000000..9946fc256e --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/filters/outbound/SentinelZuulOutboundFilter.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.outbound; + +import java.util.Deque; + +import com.alibaba.csp.sentinel.Tracer; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.constants.SentinelZuul2Constants; +import com.alibaba.csp.sentinel.adapter.gateway.zuul2.filters.EntryHolder; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.netflix.zuul.context.SessionContext; +import com.netflix.zuul.filters.http.HttpOutboundFilter; +import com.netflix.zuul.message.http.HttpResponseMessage; +import rx.Observable; + +/** + * The Zuul outbound filter which will complete the Sentinel entries and + * trace the exception that happened in previous filters. + * + * @author wavesZh + */ +public class SentinelZuulOutboundFilter extends HttpOutboundFilter { + + private final int order; + + public SentinelZuulOutboundFilter(int order) { + this.order = order; + } + + @Override + public int filterOrder() { + return order; + } + + @Override + public Observable applyAsync(HttpResponseMessage input) { + return Observable.just(apply(input)); + } + + public HttpResponseMessage apply(HttpResponseMessage response) { + SessionContext context = response.getContext(); + + if (context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY) == null) { + return response; + } + boolean previousBlocked = context.getFilterErrors().stream() + .anyMatch(e -> BlockException.isBlockException(e.getException())); + Deque holders = (Deque) context.get(SentinelZuul2Constants.ZUUL_CTX_SENTINEL_ENTRIES_KEY); + while (!holders.isEmpty()) { + EntryHolder holder = holders.pop(); + if (!previousBlocked) { + Tracer.traceEntry(context.getError(), holder.getEntry()); + holder.getEntry().exit(1, holder.getParams()); + } + } + return response; + } + + + @Override + public boolean shouldFilter(HttpResponseMessage msg) { + return true; + } +} diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver b/sentinel-adapter/sentinel-zuul2-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver new file mode 100644 index 0000000000..6c27b158ed --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.adapter.gateway.common.api.ApiDefinitionChangeObserver @@ -0,0 +1 @@ +com.alibaba.csp.sentinel.adapter.gateway.zuul2.api.ZuulApiDefinitionChangeObserver \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManagerTest.java b/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManagerTest.java new file mode 100644 index 0000000000..1b0416d5a2 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackManagerTest.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; + +import com.alibaba.csp.sentinel.slots.block.flow.FlowException; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author tiger + */ +public class ZuulBlockFallbackManagerTest { + + private String ROUTE = "/test"; + + private String DEFAULT_ROUTE = "*"; + + class MyNullResponseFallBackProvider implements ZuulBlockFallbackProvider { + @Override + public String getRoute() { + return ROUTE; + } + + @Override + public BlockResponse fallbackResponse(String route, Throwable cause) { + return null; + } + } + + @Test + public void testRegisterProvider() throws Exception { + MyNullResponseFallBackProvider myNullResponseFallBackProvider = new MyNullResponseFallBackProvider(); + ZuulBlockFallbackManager.registerProvider(myNullResponseFallBackProvider); + Assert.assertEquals(myNullResponseFallBackProvider.getRoute(), ROUTE); + Assert.assertNull(myNullResponseFallBackProvider.fallbackResponse(ROUTE, new FlowException("flow ex"))); + } + + @Test + public void clear() { + MyNullResponseFallBackProvider myNullResponseFallBackProvider = new MyNullResponseFallBackProvider(); + ZuulBlockFallbackManager.registerProvider(myNullResponseFallBackProvider); + Assert.assertEquals(myNullResponseFallBackProvider.getRoute(), ROUTE); + ZuulBlockFallbackManager.clear(); + Assert.assertEquals(ZuulBlockFallbackManager.getFallbackProvider(ROUTE).getRoute(), DEFAULT_ROUTE); + } + +} \ No newline at end of file diff --git a/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProviderTest.java b/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProviderTest.java new file mode 100644 index 0000000000..59f6d74a19 --- /dev/null +++ b/sentinel-adapter/sentinel-zuul2-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/gateway/zuul2/fallback/ZuulBlockFallbackProviderTest.java @@ -0,0 +1,62 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.alibaba.csp.sentinel.adapter.gateway.zuul2.fallback; + +import com.alibaba.csp.sentinel.slots.block.flow.FlowException; +import org.junit.Assert; +import org.junit.Test; + +/** + * @author tiger + */ +public class ZuulBlockFallbackProviderTest { + + private String ALL_ROUTE = "*"; + + @Test + public void testGetNullRoute() throws Exception { + ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(null); + Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE); + } + + @Test + public void testGetDefaultRoute() throws Exception { + ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE); + Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE); + } + + @Test + public void testGetNotInCacheRoute() throws Exception { + ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider("/not/in"); + Assert.assertEquals(fallbackProvider.getRoute(), ALL_ROUTE); + } + + @Test + public void testFlowControlFallbackResponse() throws Exception { + ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE); + BlockResponse clientHttpResponse = fallbackProvider.fallbackResponse(ALL_ROUTE, + new FlowException("flow exception")); + Assert.assertEquals(clientHttpResponse.getCode(), 429); + } + + @Test + public void testRuntimeExceptionFallbackResponse() throws Exception { + ZuulBlockFallbackProvider fallbackProvider = ZuulBlockFallbackManager.getFallbackProvider(ALL_ROUTE); + BlockResponse clientHttpResponse = fallbackProvider.fallbackResponse(ALL_ROUTE, new RuntimeException()); + Assert.assertEquals(clientHttpResponse.getCode(), 500); + } +} \ No newline at end of file diff --git a/sentinel-benchmark/pom.xml b/sentinel-benchmark/pom.xml index 50410f2dbd..e0c14884ab 100644 --- a/sentinel-benchmark/pom.xml +++ b/sentinel-benchmark/pom.xml @@ -5,7 +5,7 @@ sentinel-parent com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-cluster/pom.xml b/sentinel-cluster/pom.xml index 52170486a0..5bde8cc759 100644 --- a/sentinel-cluster/pom.xml +++ b/sentinel-cluster/pom.xml @@ -5,7 +5,7 @@ sentinel-parent com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 pom diff --git a/sentinel-cluster/sentinel-cluster-client-default/pom.xml b/sentinel-cluster/sentinel-cluster-client-default/pom.xml index 0775cb3196..630c1ff6df 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/pom.xml +++ b/sentinel-cluster/sentinel-cluster-client-default/pom.xml @@ -5,7 +5,7 @@ sentinel-cluster com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java index ecc727a5b2..1a0acf6c66 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/DefaultClusterTokenClient.java @@ -79,7 +79,7 @@ private void initNewConnection() { try { this.transportClient = new NettyTransportClient(host, port); this.serverDescriptor = new TokenServerDescriptor(host, port); - RecordLog.info("[DefaultClusterTokenClient] New client created: " + serverDescriptor); + RecordLog.info("[DefaultClusterTokenClient] New client created: {}", serverDescriptor); } catch (Exception ex) { RecordLog.warn("[DefaultClusterTokenClient] Failed to initialize new token client", ex); } @@ -97,7 +97,7 @@ private void changeServer(/*@Valid*/ ClusterClientAssignConfig config) { this.transportClient = new NettyTransportClient(config.getServerHost(), config.getServerPort()); this.serverDescriptor = new TokenServerDescriptor(config.getServerHost(), config.getServerPort()); startClientIfScheduled(); - RecordLog.info("[DefaultClusterTokenClient] New client created: " + serverDescriptor); + RecordLog.info("[DefaultClusterTokenClient] New client created: {}", serverDescriptor); } catch (Exception ex) { RecordLog.warn("[DefaultClusterTokenClient] Failed to change remote token server", ex); } @@ -182,6 +182,15 @@ public TokenResult requestParamToken(Long flowId, int acquireCount, Collection"); + RecordLog.info("[NettyTransportClient] Successfully connect to server <{}:{}>", host, port); } } }); @@ -144,7 +143,7 @@ public void run() { @Override public void run() { if (shouldRetry.get()) { - RecordLog.info("[NettyTransportClient] Reconnecting to server <" + host + ":" + port + ">"); + RecordLog.info("[NettyTransportClient] Reconnecting to server <{}:{}>", host, port); try { startInternal(); } catch (Exception e) { @@ -238,10 +237,12 @@ public ClusterResponse sendRequest(ClusterRequest request) throws Exception { } private int getCurrentId() { - if (idGenerator.get() > MAX_ID) { - idGenerator.set(0); - } - return idGenerator.incrementAndGet(); + int pre, next; + do { + pre = idGenerator.get(); + next = pre >= MAX_ID ? MIN_ID : pre + 1; + } while (!idGenerator.compareAndSet(pre, next)); + return next; } /*public CompletableFuture sendRequestAsync(ClusterRequest request) { @@ -266,5 +267,6 @@ private int getCurrentId() { return future; }*/ + private static final int MIN_ID = 1; private static final int MAX_ID = 999_999_999; } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java index 6cb0ebda09..e27ce68223 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/ClientEntityCodecProvider.java @@ -39,14 +39,16 @@ private static void resolveInstance() { RecordLog.warn("[ClientEntityCodecProvider] No existing request entity writer, resolve failed"); } else { requestEntityWriter = writer; - RecordLog.info("[ClientEntityCodecProvider] Request entity writer resolved: " + requestEntityWriter.getClass().getCanonicalName()); + RecordLog.info("[ClientEntityCodecProvider] Request entity writer resolved: {}", + requestEntityWriter.getClass().getCanonicalName()); } ResponseEntityDecoder decoder = SpiLoader.loadFirstInstance(ResponseEntityDecoder.class); if (decoder == null) { RecordLog.warn("[ClientEntityCodecProvider] No existing response entity decoder, resolve failed"); } else { responseEntityDecoder = decoder; - RecordLog.info("[ClientEntityCodecProvider] Response entity decoder resolved: " + responseEntityDecoder.getClass().getCanonicalName()); + RecordLog.info("[ClientEntityCodecProvider] Response entity decoder resolved: {}", + responseEntityDecoder.getClass().getCanonicalName()); } } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java index 0ba9680f69..62572f311b 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultRequestEntityWriter.java @@ -36,7 +36,7 @@ public void writeTo(ClusterRequest request, ByteBuf target) { EntityWriter requestDataWriter = RequestDataWriterRegistry.getWriter(type); if (requestDataWriter == null) { - RecordLog.warn("[DefaultRequestEntityWriter] Cannot find matching request writer for type <{0}>," + RecordLog.warn("[DefaultRequestEntityWriter] Cannot find matching request writer for type <{}>," + " dropping the request", type); return; } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java index 6cb3333c57..48767ab248 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/DefaultResponseEntityDecoder.java @@ -47,7 +47,7 @@ public ClusterResponse decode(ByteBuf source) { EntityDecoder decoder = ResponseDataDecodeRegistry.getDecoder(type); if (decoder == null) { - RecordLog.warn("Unknown type of response data decoder: {0}", type); + RecordLog.warn("Unknown type of response data decoder: {}", type); return null; } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java index a134d279b1..7c8acc81f7 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowRequestDataWriter.java @@ -22,7 +22,7 @@ /** * +-------------------+--------------+----------------+---------------+------------------+ - * | RequestID(8 byte) | Type(1 byte) | FlowID(4 byte) | Count(4 byte) | PriorityFlag (1) | + * | RequestID(8 byte) | Type(1 byte) | FlowID(8 byte) | Count(4 byte) | PriorityFlag (1) | * +-------------------+--------------+----------------+---------------+------------------+ * * @author Eric Zhao diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java index 27da9801ac..59a958e335 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/config/ClusterClientConfigManager.java @@ -131,7 +131,7 @@ private synchronized void applyConfig(ClusterClientAssignConfig config) { return; } - RecordLog.info("[ClusterClientConfigManager] Assign to new target token server: " + config); + RecordLog.info("[ClusterClientConfigManager] Assign to new target token server: {}", config); updateServerAssignment(config); } @@ -156,11 +156,11 @@ public void configUpdate(ClusterClientConfig config) { private synchronized void applyConfig(ClusterClientConfig config) { if (!isValidClientConfig(config)) { RecordLog.warn( - "[ClusterClientConfigManager] Invalid cluster client config, ignoring: " + config); + "[ClusterClientConfigManager] Invalid cluster client config, ignoring: {}", config); return; } - RecordLog.info("[ClusterClientConfigManager] Updating to new client config: " + config); + RecordLog.info("[ClusterClientConfigManager] Updating to new client config: {}", config); updateClientConfigChange(config); } diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java index aef1e37750..41ee220dbf 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/cluster/client/handler/TokenClientHandler.java @@ -48,7 +48,7 @@ public TokenClientHandler(AtomicInteger currentState, Runnable disconnectCallbac public void channelActive(ChannelHandlerContext ctx) throws Exception { currentState.set(ClientConstants.CLIENT_STATUS_STARTED); fireClientPing(ctx); - RecordLog.info("[TokenClientHandler] Client handler active, remote address: " + getRemoteAddress(ctx)); + RecordLog.info("[TokenClientHandler] Client handler active, remote address: {}", getRemoteAddress(ctx)); } @Override @@ -76,10 +76,10 @@ private void fireClientPing(ChannelHandlerContext ctx) { private void handlePingResponse(ChannelHandlerContext ctx, ClusterResponse response) { if (response.getStatus() == ClusterConstants.RESPONSE_STATUS_OK) { int count = (int) response.getData(); - RecordLog.info("[TokenClientHandler] Client ping OK (target server: {0}, connected count: {1})", + RecordLog.info("[TokenClientHandler] Client ping OK (target server: {}, connected count: {})", getRemoteAddress(ctx), count); } else { - RecordLog.warn("[TokenClientHandler] Client ping failed (target server: {0})", getRemoteAddress(ctx)); + RecordLog.warn("[TokenClientHandler] Client ping failed (target server: {})", getRemoteAddress(ctx)); } } @@ -90,12 +90,12 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { - RecordLog.info("[TokenClientHandler] Client handler inactive, remote address: " + getRemoteAddress(ctx)); + RecordLog.info("[TokenClientHandler] Client handler inactive, remote address: {}", getRemoteAddress(ctx)); } @Override public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { - RecordLog.info("[TokenClientHandler] Client channel unregistered, remote address: " + getRemoteAddress(ctx)); + RecordLog.info("[TokenClientHandler] Client channel unregistered, remote address: {}", getRemoteAddress(ctx)); currentState.set(ClientConstants.CLIENT_STATUS_OFF); disconnectCallback.run(); diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java index 189e7942bc..ecef1de91d 100644 --- a/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java +++ b/sentinel-cluster/sentinel-cluster-client-default/src/main/java/com/alibaba/csp/sentinel/command/handler/ModifyClusterClientConfigHandler.java @@ -44,7 +44,7 @@ public CommandResponse handle(CommandRequest request) { } try { data = URLDecoder.decode(data, "utf-8"); - RecordLog.info("[ModifyClusterClientConfigHandler] Receiving cluster client config: " + data); + RecordLog.info("[ModifyClusterClientConfigHandler] Receiving cluster client config: {}", data); ClusterClientStateEntity entity = JSON.parseObject(data, ClusterClientStateEntity.class); ClusterClientConfigManager.applyNewConfig(entity.toClientConfig()); diff --git a/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoderTest.java b/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoderTest.java new file mode 100644 index 0000000000..482716a2e3 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-client-default/src/test/java/com/alibaba/csp/sentinel/cluster/client/codec/data/FlowResponseDataDecoderTest.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.client.codec.data; + +import com.alibaba.csp.sentinel.cluster.response.data.FlowTokenResponseData; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.Assert; +import org.junit.Test; + +public class FlowResponseDataDecoderTest { + @Test + public void testDecode() { + ByteBuf buf = Unpooled.buffer(); + FlowResponseDataDecoder decoder = new FlowResponseDataDecoder(); + FlowTokenResponseData data = new FlowTokenResponseData(); + data.setRemainingCount(12); + data.setWaitInMs(13); + buf.writeInt(12); + buf.writeInt(13); + Assert.assertEquals(decoder.decode(buf), data); + } +} diff --git a/sentinel-cluster/sentinel-cluster-common-default/pom.xml b/sentinel-cluster/sentinel-cluster-common-default/pom.xml index 59549847b3..1a9f2a3bf5 100644 --- a/sentinel-cluster/sentinel-cluster-common-default/pom.xml +++ b/sentinel-cluster/sentinel-cluster-common-default/pom.xml @@ -5,7 +5,7 @@ sentinel-cluster com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java index c523413dd6..0930fda9bf 100644 --- a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/ClusterConstants.java @@ -24,6 +24,9 @@ public final class ClusterConstants { public static final int MSG_TYPE_PING = 0; public static final int MSG_TYPE_FLOW = 1; public static final int MSG_TYPE_PARAM_FLOW = 2; + public static final int MSG_TYPE_CONCURRENT_FLOW_ACQUIRE = 3; + public static final int MSG_TYPE_CONCURRENT_FLOW_RELEASE = 4; + public static final int RESPONSE_STATUS_BAD = -1; public static final int RESPONSE_STATUS_OK = 0; diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java index 63107d52e0..2284da939f 100644 --- a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/registry/ConfigSupplierRegistry.java @@ -52,8 +52,8 @@ public static Supplier getNamespaceSupplier() { public static void setNamespaceSupplier(Supplier namespaceSupplier) { AssertUtil.notNull(namespaceSupplier, "namespaceSupplier cannot be null"); ConfigSupplierRegistry.namespaceSupplier = namespaceSupplier; - RecordLog.info("[ConfigSupplierRegistry] New namespace supplier provided, current supplied: " - + namespaceSupplier.get()); + RecordLog.info("[ConfigSupplierRegistry] New namespace supplier provided, current supplied: {}", + namespaceSupplier.get()); } private ConfigSupplierRegistry() {} diff --git a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java index 2ca2842c36..6424b7d8a7 100644 --- a/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java +++ b/sentinel-cluster/sentinel-cluster-common-default/src/main/java/com/alibaba/csp/sentinel/cluster/response/data/FlowTokenResponseData.java @@ -42,11 +42,30 @@ public FlowTokenResponseData setWaitInMs(int waitInMs) { return this; } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof FlowTokenResponseData)) { + return false; + } + FlowTokenResponseData that = (FlowTokenResponseData) o; + return this.remainingCount == that.remainingCount && this.waitInMs == that.waitInMs; + } + + @Override + public int hashCode() { + int result = remainingCount; + result = 31 * result + waitInMs; + return result; + } + @Override public String toString() { return "FlowTokenResponseData{" + - "remainingCount=" + remainingCount + - ", waitInMs=" + waitInMs + - '}'; + "remainingCount=" + remainingCount + + ", waitInMs=" + waitInMs + + '}'; } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/pom.xml b/sentinel-cluster/sentinel-cluster-server-default/pom.xml index 6443bab92d..d383015627 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/pom.xml +++ b/sentinel-cluster/sentinel-cluster-server-default/pom.xml @@ -5,7 +5,7 @@ sentinel-cluster com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 @@ -41,7 +41,16 @@ sentinel-datasource-nacos test - + + org.powermock + powermock-module-junit4 + test + + + org.powermock + powermock-api-mockito2 + test + junit junit diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ConcurrentClusterFlowChecker.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ConcurrentClusterFlowChecker.java new file mode 100644 index 0000000000..9083bf929d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/ConcurrentClusterFlowChecker.java @@ -0,0 +1,102 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow; + +import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.TokenResultStatus; +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager; +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNode; +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNodeManager; +import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * @author yunfeiyanggzq + */ +final public class ConcurrentClusterFlowChecker { + + public static double calcGlobalThreshold(FlowRule rule) { + double count = rule.getCount(); + switch (rule.getClusterConfig().getThresholdType()) { + case ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL: + return count; + case ClusterRuleConstant.FLOW_THRESHOLD_AVG_LOCAL: + default: + int connectedCount = ClusterFlowRuleManager.getConnectedCount(rule.getClusterConfig().getFlowId()); + return count * connectedCount; + } + } + + public static TokenResult acquireConcurrentToken(/*@Valid*/ String clientAddress, FlowRule rule, int acquireCount) { + long flowId = rule.getClusterConfig().getFlowId(); + AtomicInteger nowCalls = CurrentConcurrencyManager.get(flowId); + if (nowCalls == null) { + RecordLog.warn("[ConcurrentClusterFlowChecker] Fail to get nowCalls by flowId<{}>", flowId); + return new TokenResult(TokenResultStatus.FAIL); + } + + // check before enter the lock to improve the efficiency + if (nowCalls.get() + acquireCount > calcGlobalThreshold(rule)) { + ClusterServerStatLogUtil.log("concurrent|block|" + flowId, acquireCount); + return new TokenResult(TokenResultStatus.BLOCKED); + } + + // ensure the atomicity of operations + // lock different nowCalls to improve the efficiency + synchronized (nowCalls) { + // check again whether the request can pass. + if (nowCalls.get() + acquireCount > calcGlobalThreshold(rule)) { + ClusterServerStatLogUtil.log("concurrent|block|" + flowId, acquireCount); + return new TokenResult(TokenResultStatus.BLOCKED); + } else { + nowCalls.getAndAdd(acquireCount); + } + } + ClusterServerStatLogUtil.log("concurrent|pass|" + flowId, acquireCount); + TokenCacheNode node = TokenCacheNode.generateTokenCacheNode(rule, acquireCount, clientAddress); + TokenCacheNodeManager.putTokenCacheNode(node.getTokenId(), node); + TokenResult tokenResult = new TokenResult(TokenResultStatus.OK); + tokenResult.setTokenId(node.getTokenId()); + return tokenResult; + } + + public static TokenResult releaseConcurrentToken(/*@Valid*/ long tokenId) { + TokenCacheNode node = TokenCacheNodeManager.getTokenCacheNode(tokenId); + if (node == null) { + RecordLog.info("[ConcurrentClusterFlowChecker] Token<{}> is already released", tokenId); + return new TokenResult(TokenResultStatus.ALREADY_RELEASE); + } + FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(node.getFlowId()); + if (rule == null) { + RecordLog.info("[ConcurrentClusterFlowChecker] Fail to get rule by flowId<{}>", node.getFlowId()); + return new TokenResult(TokenResultStatus.NO_RULE_EXISTS); + } + if (TokenCacheNodeManager.removeTokenCacheNode(tokenId) == null) { + RecordLog.info("[ConcurrentClusterFlowChecker] Token<{}> is already released for flowId<{}>", tokenId, node.getFlowId()); + return new TokenResult(TokenResultStatus.ALREADY_RELEASE); + } + int acquireCount = node.getAcquireCount(); + AtomicInteger nowCalls = CurrentConcurrencyManager.get(node.getFlowId()); + nowCalls.getAndAdd(-1 * acquireCount); + ClusterServerStatLogUtil.log("concurrent|release|" + rule.getClusterConfig().getFlowId(), acquireCount); + return new TokenResult(TokenResultStatus.RELEASE_OK); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java index 2953a1350b..4e78a50d30 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/DefaultTokenService.java @@ -15,16 +15,16 @@ */ package com.alibaba.csp.sentinel.cluster.flow; -import java.util.Collection; - -import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.TokenResultStatus; import com.alibaba.csp.sentinel.cluster.TokenService; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterParamFlowRuleManager; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowRule; +import java.util.Collection; + /** * Default implementation for cluster {@link TokenService}. * @@ -61,10 +61,35 @@ public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection>> DEFAULT_PROPERTY_SUPPLIER = - new Function>>() { - @Override - public SentinelProperty> apply(String namespace) { - return new DynamicSentinelProperty<>(); - } - }; + new Function>>() { + @Override + public SentinelProperty> apply(String namespace) { + return new DynamicSentinelProperty<>(); + } + }; /** * (flowId, clusterRule) @@ -87,7 +83,7 @@ public SentinelProperty> apply(String namespace) { * Cluster flow rule property supplier for a specific namespace. */ private static volatile Function>> propertySupplier - = DEFAULT_PROPERTY_SUPPLIER; + = DEFAULT_PROPERTY_SUPPLIER; private static final Object UPDATE_LOCK = new Object(); @@ -118,18 +114,18 @@ public static void register2Property(String namespace) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); if (propertySupplier == null) { RecordLog.warn( - "[ClusterFlowRuleManager] Cluster flow property supplier is absent, cannot register property"); + "[ClusterFlowRuleManager] Cluster flow property supplier is absent, cannot register property"); return; } SentinelProperty> property = propertySupplier.apply(namespace); if (property == null) { RecordLog.warn( - "[ClusterFlowRuleManager] Wrong created property from cluster flow property supplier, ignoring"); + "[ClusterFlowRuleManager] Wrong created property from cluster flow property supplier, ignoring"); return; } synchronized (UPDATE_LOCK) { RecordLog.info("[ClusterFlowRuleManager] Registering new property to cluster flow rule manager" - + " for namespace <{0}>", namespace); + + " for namespace <{}>", namespace); registerPropertyInternal(namespace, property); } } @@ -180,7 +176,7 @@ public static void removeProperty(String namespace) { PROPERTY_MAP.remove(namespace); } RecordLog.info("[ClusterFlowRuleManager] Removing property from cluster flow rule manager" - + " for namespace <{0}>", namespace); + + " for namespace <{}>", namespace); } } @@ -253,7 +249,7 @@ public static List getFlowRules(String namespace) { * Load flow rules for a specific namespace. The former rules of the namespace will be replaced. * * @param namespace a valid namespace - * @param rules rule list + * @param rules rule list */ public static void loadRules(String namespace, List rules) { AssertUtil.notEmpty(namespace, "namespace cannot be empty"); @@ -278,6 +274,9 @@ private static void clearAndResetRulesFor(/*@Valid*/ String namespace) { for (Long flowId : flowIdSet) { FLOW_RULES.remove(flowId); FLOW_NAMESPACE_MAP.remove(flowId); + if (CurrentConcurrencyManager.containsFlowId(flowId)) { + CurrentConcurrencyManager.remove(flowId); + } } flowIdSet.clear(); } else { @@ -293,6 +292,9 @@ private static void clearAndResetRulesConditional(/*@Valid*/ String namespace, P FLOW_RULES.remove(flowId); FLOW_NAMESPACE_MAP.remove(flowId); ClusterMetricStatistics.removeMetric(flowId); + if (CurrentConcurrencyManager.containsFlowId(flowId)) { + CurrentConcurrencyManager.remove(flowId); + } } } oldIdSet.clear(); @@ -335,7 +337,7 @@ private static void applyClusterFlowRule(List list, /*@Valid*/ String } if (!FlowRuleUtil.isValidRule(rule)) { RecordLog.warn( - "[ClusterFlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule); + "[ClusterFlowRuleManager] Ignoring invalid flow rule when loading new flow rules: " + rule); continue; } if (StringUtil.isBlank(rule.getLimitApp())) { @@ -351,10 +353,13 @@ private static void applyClusterFlowRule(List list, /*@Valid*/ String ruleMap.put(flowId, rule); FLOW_NAMESPACE_MAP.put(flowId, namespace); flowIdSet.add(flowId); + if (!CurrentConcurrencyManager.containsFlowId(flowId)) { + CurrentConcurrencyManager.put(flowId, 0); + } // Prepare cluster metric from valid flow ID. ClusterMetricStatistics.putMetricIfAbsent(flowId, - new ClusterMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())); + new ClusterMetric(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs())); } // Cleanup unused cluster metrics. @@ -380,17 +385,18 @@ public FlowRulePropertyListener(String namespace) { @Override public synchronized void configUpdate(List conf) { applyClusterFlowRule(conf, namespace); - RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received for namespace <{0}>: {1}", - namespace, FLOW_RULES); + RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules received for namespace <{}>: {}", + namespace, FLOW_RULES); } @Override public synchronized void configLoad(List conf) { applyClusterFlowRule(conf, namespace); - RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded for namespace <{0}>: {1}", - namespace, FLOW_RULES); + RecordLog.info("[ClusterFlowRuleManager] Cluster flow rules loaded for namespace <{}>: {}", + namespace, FLOW_RULES); } } - private ClusterFlowRuleManager() {} + private ClusterFlowRuleManager() { + } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java index ec5eca6553..b3f96ec69a 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/rule/ClusterParamFlowRuleManager.java @@ -127,7 +127,7 @@ public static void register2Property(String namespace) { } synchronized (UPDATE_LOCK) { RecordLog.info("[ClusterParamFlowRuleManager] Registering new property to cluster param rule manager" - + " for namespace <{0}>", namespace); + + " for namespace <{}>", namespace); registerPropertyInternal(namespace, property); } } @@ -167,7 +167,7 @@ public static void removeProperty(String namespace) { PROPERTY_MAP.remove(namespace); } RecordLog.info("[ClusterParamFlowRuleManager] Removing property from cluster flow rule manager" - + " for namespace <{0}>", namespace); + + " for namespace <{}>", namespace); } } @@ -303,14 +303,14 @@ public ParamRulePropertyListener(String namespace) { @Override public void configLoad(List conf) { applyClusterParamRules(conf, namespace); - RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules loaded for namespace <{0}>: {1}", + RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules loaded for namespace <{}>: {}", namespace, PARAM_RULES); } @Override public void configUpdate(List conf) { applyClusterParamRules(conf, namespace); - RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules received for namespace <{0}>: {1}", + RecordLog.info("[ClusterParamFlowRuleManager] Cluster parameter rules received for namespace <{}>: {}", namespace, PARAM_RULES); } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/ClusterConcurrentCheckerLogListener.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/ClusterConcurrentCheckerLogListener.java new file mode 100644 index 0000000000..4f13b40312 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/ClusterConcurrentCheckerLogListener.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; + +import com.alibaba.csp.sentinel.cluster.flow.ConcurrentClusterFlowChecker; +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.server.log.ClusterServerStatLogUtil; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; + +import java.util.Set; + +/** + * @author yunfeiyanggzq + */ +public class ClusterConcurrentCheckerLogListener implements Runnable { + @Override + public void run() { + try { + collectInformation(); + } catch (Exception e) { + RecordLog.warn("[ClusterConcurrentCheckerLogListener] Failed to record concurrent flow control regularly", e); + } + } + + private void collectInformation() { + Set keySet = CurrentConcurrencyManager.getConcurrencyMapKeySet(); + for (long flowId : keySet) { + FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(flowId); + if (rule == null || CurrentConcurrencyManager.get(flowId).get() == 0) { + continue; + } + double concurrencyLevel = ConcurrentClusterFlowChecker.calcGlobalThreshold(rule); + String resource = rule.getResource(); + ClusterServerStatLogUtil.log(String.format("concurrent|resource:%s|flowId:%dl|concurrencyLevel:%fl|currentConcurrency", resource, flowId,concurrencyLevel),CurrentConcurrencyManager.get(flowId).get()); + } + if (TokenCacheNodeManager.getSize() != 0){ + ClusterServerStatLogUtil.log("flow|totalTokenSize", TokenCacheNodeManager.getSize()); + } + + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/CurrentConcurrencyManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/CurrentConcurrencyManager.java new file mode 100644 index 0000000000..de91ab2f8c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/CurrentConcurrencyManager.java @@ -0,0 +1,98 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; + +import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; + +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * We use a ConcurrentHashMap type structure to store nowCalls corresponding to + * rules, where the key is flowId and the value is nowCalls. Because nowCalls may be accessed and + * modified by multiple threads, we consider to design it as an AtomicInteger class . Each newly + * created rule will add a nowCalls object to this map. If the concurrency corresponding to a rule changes, + * we will update the corresponding nowCalls in real time. Each request to obtain a token will increase the nowCalls; + * and the request to release the token will reduce the nowCalls. + * + * @author yunfeiyanggzq + */ +public final class CurrentConcurrencyManager { + /** + * use ConcurrentHashMap to store the nowCalls of rules. + */ + private static final ConcurrentHashMap NOW_CALLS_MAP = new ConcurrentHashMap(); + + @SuppressWarnings("PMD.ThreadPoolCreationRule") + private static final ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("sentinel-cluster-concurrency-record-task", true)); + + static { + ClusterConcurrentCheckerLogListener logTask = new ClusterConcurrentCheckerLogListener(); + SCHEDULER.scheduleAtFixedRate(logTask, 0, 1, TimeUnit.SECONDS); + } + + /** + * add current concurrency. + */ + public static void addConcurrency(Long flowId, Integer acquireCount) { + + AtomicInteger nowCalls = NOW_CALLS_MAP.get(flowId); + if (nowCalls == null) { + return; + } + nowCalls.getAndAdd(acquireCount); + } + + /** + * get the current concurrency. + */ + public static AtomicInteger get(Long flowId) { + return NOW_CALLS_MAP.get(flowId); + } + + /** + * delete the current concurrency. + */ + public static void remove(Long flowId) { + NOW_CALLS_MAP.remove(flowId); + } + + /** + * put the current concurrency. + */ + public static void put(Long flowId, Integer nowCalls) { + NOW_CALLS_MAP.put(flowId, new AtomicInteger(nowCalls)); + } + + /** + * check flow id. + */ + public static boolean containsFlowId(Long flowId) { + return NOW_CALLS_MAP.containsKey(flowId); + } + + /** + * get NOW_CALLS_MAP. + */ + public static Set getConcurrencyMapKeySet() { + return NOW_CALLS_MAP.keySet(); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNode.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNode.java new file mode 100644 index 0000000000..be734ad306 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNode.java @@ -0,0 +1,131 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; + +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; + +import java.util.UUID; + +/** + * We use TokenCacheNodeManager to store the tokenId, whose the underlying storage structure + * is ConcurrentLinkedHashMap, Its storage node is TokenCacheNode. In order to operate the nowCalls value when + * the expired tokenId is deleted regularly, we need to store the flowId in TokenCacheNode. + * + * @author yunfeiyanggzq + */ +public class TokenCacheNode { + /** + * the TokenId of the token + */ + private Long tokenId; + /** + * the client goes offline detection time + */ + private Long clientTimeout; + /** + * the resource called over time detection time + */ + private Long resourceTimeout; + /** + * the flow rule id corresponding to the token + */ + private Long flowId; + /** + * the number this token occupied + */ + private int acquireCount; + + /** + * the address of the client holds the token. + */ + private String clientAddress; + + public TokenCacheNode() { + } + + public static TokenCacheNode generateTokenCacheNode(FlowRule rule, int acquireCount, String clientAddress) { + TokenCacheNode node = new TokenCacheNode(); + // getMostSignificantBits() returns the most significant 64 bits of this UUID's 128 bit value. + // The probability of collision is extremely low. + node.setTokenId(UUID.randomUUID().getMostSignificantBits()); + node.setFlowId(rule.getClusterConfig().getFlowId()); + node.setClientTimeout(rule.getClusterConfig().getClientOfflineTime()); + node.setResourceTimeout(rule.getClusterConfig().getResourceTimeout()); + node.setAcquireCount(acquireCount); + node.setClientAddress(clientAddress); + return node; + } + + public Long getTokenId() { + return tokenId; + } + + public void setTokenId(Long tokenId) { + this.tokenId = tokenId; + } + + public Long getClientTimeout() { + return clientTimeout; + } + + public void setClientTimeout(Long clientTimeout) { + this.clientTimeout = clientTimeout + System.currentTimeMillis(); + } + + public Long getResourceTimeout() { + return this.resourceTimeout; + } + + public void setResourceTimeout(Long resourceTimeout) { + this.resourceTimeout = resourceTimeout + System.currentTimeMillis(); + } + + public Long getFlowId() { + return flowId; + } + + public void setFlowId(Long flowId) { + this.flowId = flowId; + } + + public int getAcquireCount() { + return acquireCount; + } + + public void setAcquireCount(int acquireCount) { + this.acquireCount = acquireCount; + } + + public String getClientAddress() { + return clientAddress; + } + + public void setClientAddress(String clientAddress) { + this.clientAddress = clientAddress; + } + + @Override + public String toString() { + return "TokenCacheNode{" + + "tokenId=" + tokenId + + ", clientTimeout=" + clientTimeout + + ", resourceTimeout=" + resourceTimeout + + ", flowId=" + flowId + + ", acquireCount=" + acquireCount + + ", clientAddress='" + clientAddress + '\'' + + '}'; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNodeManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNodeManager.java new file mode 100644 index 0000000000..890f62e6da --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNodeManager.java @@ -0,0 +1,82 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; + +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire.RegularExpireStrategy; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; +import com.googlecode.concurrentlinkedhashmap.Weighers; + +import java.util.Set; + +/** + * @author yunfeiyanggzq + */ +public class TokenCacheNodeManager { + private static ConcurrentLinkedHashMap TOKEN_CACHE_NODE_MAP; + + + private static final int DEFAULT_CONCURRENCY_LEVEL = 16; + private static final int DEFAULT_CAPACITY = Integer.MAX_VALUE; + + static { + prepare(DEFAULT_CONCURRENCY_LEVEL, DEFAULT_CAPACITY); + } + + public static void prepare(int concurrencyLevel, int maximumWeightedCapacity) { + AssertUtil.isTrue(concurrencyLevel > 0, "concurrencyLevel must be positive"); + AssertUtil.isTrue(maximumWeightedCapacity > 0, "maximumWeightedCapacity must be positive"); + + TOKEN_CACHE_NODE_MAP = new ConcurrentLinkedHashMap.Builder() + .concurrencyLevel(concurrencyLevel) + .maximumWeightedCapacity(maximumWeightedCapacity) + .weigher(Weighers.singleton()) + .build(); + // Start the task of regularly clearing expired keys + RegularExpireStrategy strategy = new RegularExpireStrategy(TOKEN_CACHE_NODE_MAP); + strategy.startClearTaskRegularly(); + } + + + public static TokenCacheNode getTokenCacheNode(long tokenId) { + //use getQuietly to prevent disorder + return TOKEN_CACHE_NODE_MAP.getQuietly(tokenId); + } + + public static void putTokenCacheNode(long tokenId, TokenCacheNode cacheNode) { + TOKEN_CACHE_NODE_MAP.put(tokenId, cacheNode); + } + + public static boolean isContainsTokenId(long tokenId) { + return TOKEN_CACHE_NODE_MAP.containsKey(tokenId); + } + + public static TokenCacheNode removeTokenCacheNode(long tokenId) { + return TOKEN_CACHE_NODE_MAP.remove(tokenId); + } + + public static int getSize() { + return TOKEN_CACHE_NODE_MAP.size(); + } + + public static Set getCacheKeySet() { + return TOKEN_CACHE_NODE_MAP.keySet(); + } + + public static boolean validToken(TokenCacheNode cacheNode) { + return cacheNode.getTokenId() != null && cacheNode.getFlowId() != null && cacheNode.getClientTimeout() >= 0 && cacheNode.getResourceTimeout() >= 0; + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/expire/ExpireStrategy.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/expire/ExpireStrategy.java new file mode 100644 index 0000000000..f26710af1f --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/expire/ExpireStrategy.java @@ -0,0 +1,26 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire; + +/** + * @author yunfeiyagnggzq + */ +public interface ExpireStrategy { + /** + * clean expired token regularly. + */ + void startClearTaskRegularly(); +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/expire/RegularExpireStrategy.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/expire/RegularExpireStrategy.java new file mode 100644 index 0000000000..0aa633a019 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/expire/RegularExpireStrategy.java @@ -0,0 +1,137 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.expire; + +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager; +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNode; +import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; +import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * We need to consider the situation that the token client goes offline + * or the resource call times out. It can be detected by sourceTimeout + * and clientTimeout. The resource calls timeout detection is triggered + * on the token client. If the resource is called over time, the token + * client will request the token server to release token or refresh the + * token. The client offline detection is triggered on the token server. + * If the offline detection time is exceeded, token server will trigger + * the detection token client’s status. If the token client is offline, + * token server will delete the corresponding tokenId. If it is not offline, + * token server will continue to save it. + * + * @author yunfeiyanggzq + **/ +public class RegularExpireStrategy implements ExpireStrategy { + /** + * The max number of token deleted each time, + * the number of expired key-value pairs deleted each time does not exceed this number + */ + private long executeCount = 1000; + /** + * Length of time for task execution + */ + private long executeDuration = 800; + /** + * Frequency of task execution + */ + private long executeRate = 1000; + /** + * the local cache of tokenId + */ + private ConcurrentLinkedHashMap localCache; + + @SuppressWarnings("PMD.ThreadPoolCreationRule") + private static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1, + new NamedThreadFactory("regular clear expired token thread")); + + + public RegularExpireStrategy(ConcurrentLinkedHashMap localCache) { + AssertUtil.isTrue(localCache != null, " local cache can't be null"); + this.localCache = localCache; + } + + @Override + public void startClearTaskRegularly() { + executor.scheduleAtFixedRate(new ClearExpiredTokenTask(), 0, executeRate, TimeUnit.MILLISECONDS); + } + + private class ClearExpiredTokenTask implements Runnable { + @Override + public void run() { + try { + clearToken(); + } catch (Throwable e) { + e.printStackTrace(); + RecordLog.warn("[RegularExpireStrategy] undefined throwable during clear token: ", e); + } + } + } + + private void clearToken() { + long start = System.currentTimeMillis(); + List keyList = new ArrayList<>(localCache.keySet()); + for (int i = 0; i < executeCount && i < keyList.size(); i++) { + // time out execution exit + if (System.currentTimeMillis() - start > executeDuration) { + RecordLog.info("[RegularExpireStrategy] End the process of expired token detection because of execute time is more than executeDuration: {}", executeDuration); + break; + } + Long key = keyList.get(i); + TokenCacheNode node = localCache.get(key); + if (node == null) { + continue; + } + + // remove the token whose client is offline and saved for more than clientTimeout + if (!ConnectionManager.isClientOnline(node.getClientAddress()) && node.getClientTimeout() - System.currentTimeMillis() < 0) { + removeToken(key, node); + RecordLog.info("[RegularExpireStrategy] Delete the expired token<{}> because of client offline for ruleId<{}>", node.getTokenId(), node.getFlowId()); + continue; + } + + // If we find that token's save time is more than 2 times of the client's call resource timeout time, + // the token will be determined to timeout. + long resourceTimeout = ClusterFlowRuleManager.getFlowRuleById(node.getFlowId()).getClusterConfig().getResourceTimeout(); + if (System.currentTimeMillis() - node.getResourceTimeout() > resourceTimeout) { + removeToken(key, node); + RecordLog.info("[RegularExpireStrategy] Delete the expired token<{}> because of resource timeout for ruleId<{}>", node.getTokenId(), node.getFlowId()); + } + } + } + + private void removeToken(long tokenId, TokenCacheNode node) { + if (localCache.remove(tokenId) == null) { + RecordLog.info("[RegularExpireStrategy] Token<{}> is already released for ruleId<{}>", tokenId, node.getFlowId()); + return; + } + AtomicInteger nowCalls = CurrentConcurrencyManager.get(node.getFlowId()); + if (nowCalls == null) { + return; + } + nowCalls.getAndAdd(node.getAcquireCount() * -1); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java index 12710e71af..20279b0589 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetric.java @@ -15,20 +15,13 @@ */ package com.alibaba.csp.sentinel.cluster.flow.statistic.metric; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - -import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; import com.alibaba.csp.sentinel.slots.statistic.cache.CacheMap; import com.alibaba.csp.sentinel.util.AssertUtil; +import java.util.*; +import java.util.Map.Entry; + /** * @author Eric Zhao * @since 1.4.0 @@ -89,6 +82,7 @@ public double getAvg(Object value) { } public Map getTopValues(int number) { + AssertUtil.isTrue(number > 0, "number must be positive"); metric.currentWindow(); List> buckets = metric.values(); @@ -114,7 +108,7 @@ public Map getTopValues(int number) { @Override public int compare(Entry a, Entry b) { - return (int)(b.getValue() == null ? 0 : b.getValue()) - (int)(a.getValue() == null ? 0 : a.getValue()); + return (int) (b.getValue() == null ? 0 : b.getValue()) - (int) (a.getValue() == null ? 0 : a.getValue()); } }); @@ -126,7 +120,7 @@ public int compare(Entry a, if (x.getValue() == 0) { break; } - doubleResult.put(x.getKey(), ((double)x.getValue()) / metric.getIntervalInSecond()); + doubleResult.put(x.getKey(), ((double) x.getValue()) / metric.getIntervalInSecond()); } return doubleResult; diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java index fd95cbf2f9..9fb21bdde5 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/DefaultEmbeddedTokenServer.java @@ -58,4 +58,13 @@ public TokenResult requestParamToken(Long ruleId, int acquireCount, Collection dataDecoder = RequestDataDecodeRegistry.getDecoder(type); if (dataDecoder == null) { - RecordLog.warn("Unknown type of request data decoder: {0}", type); + RecordLog.warn("Unknown type of request data decoder: {}", type); return null; } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java index e56faa09e1..1a5e4aa179 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/DefaultResponseEntityWriter.java @@ -38,7 +38,7 @@ public void writeTo(ClusterResponse response, ByteBuf out) { if (responseDataWriter == null) { writeHead(response.setStatus(ClusterConstants.RESPONSE_STATUS_BAD), out); - RecordLog.warn("[NettyResponseEncoder] Cannot find matching writer for type <{0}>", response.getType()); + RecordLog.warn("[NettyResponseEncoder] Cannot find matching writer for type <{}>", response.getType()); return; } writeHead(response, out); diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java index ada283ab2b..741570e219 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/ServerEntityCodecProvider.java @@ -39,18 +39,16 @@ private static void resolveInstance() { RecordLog.warn("[ServerEntityCodecProvider] No existing response entity writer, resolve failed"); } else { responseEntityWriter = writer; - RecordLog.info( - "[ServerEntityCodecProvider] Response entity writer resolved: " + responseEntityWriter.getClass() - .getCanonicalName()); + RecordLog.info("[ServerEntityCodecProvider] Response entity writer resolved: {}", + responseEntityWriter.getClass().getCanonicalName()); } RequestEntityDecoder decoder = SpiLoader.loadFirstInstance(RequestEntityDecoder.class); if (decoder == null) { RecordLog.warn("[ServerEntityCodecProvider] No existing request entity decoder, resolve failed"); } else { requestEntityDecoder = decoder; - RecordLog.info( - "[ServerEntityCodecProvider] Request entity decoder resolved: " + requestEntityDecoder.getClass() - .getCanonicalName()); + RecordLog.info("[ServerEntityCodecProvider] Request entity decoder resolved: {}", + requestEntityDecoder.getClass().getCanonicalName()); } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java index 803d08b28a..5391be58d6 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/codec/data/FlowRequestDataDecoder.java @@ -25,7 +25,7 @@ * Decoder for {@link FlowRequestData} from {@code ByteBuf} stream. The layout: *

*
- * | flow ID (4) | count (4) | priority flag (1) |
+ * | flow ID (8) | count (4) | priority flag (1) |
  * 
* * @author Eric Zhao diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java index 66833cb5d1..135a3d89cf 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterFlowRulesCommandHandler.java @@ -47,7 +47,7 @@ public CommandResponse handle(CommandRequest request) { } try { data = URLDecoder.decode(data, "UTF-8"); - RecordLog.info("[ModifyClusterFlowRulesCommandHandler] Receiving cluster flow rules for namespace <{0}>: {1}", namespace, data); + RecordLog.info("[ModifyClusterFlowRulesCommandHandler] Receiving cluster flow rules for namespace <{}>: {}", namespace, data); List flowRules = JSONArray.parseArray(data, FlowRule.class); ClusterFlowRuleManager.loadRules(namespace, flowRules); diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java index 393950f786..1a46cd6e48 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterParamFlowRulesCommandHandler.java @@ -47,7 +47,7 @@ public CommandResponse handle(CommandRequest request) { } try { data = URLDecoder.decode(data, "UTF-8"); - RecordLog.info("[ModifyClusterParamFlowRulesCommandHandler] Receiving cluster param rules for namespace <{0}>: {1}", namespace, data); + RecordLog.info("Receiving cluster param rules for namespace <{}> from command handler: {}", namespace, data); List flowRules = JSONArray.parseArray(data, ParamFlowRule.class); ClusterParamFlowRuleManager.loadRules(namespace, flowRules); diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java index 228851051f..c5802421e9 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyClusterServerFlowConfigHandler.java @@ -46,14 +46,14 @@ public CommandResponse handle(CommandRequest request) { data = URLDecoder.decode(data, "utf-8"); if (StringUtil.isEmpty(namespace)) { - RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server global flow config: " + data); + RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server global flow config: {}", data); ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class); if (!ClusterServerConfigManager.isValidFlowConfig(config)) { CommandResponse.ofFailure(new IllegalArgumentException("Bad flow config")); } ClusterServerConfigManager.loadGlobalFlowConfig(config); } else { - RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server flow config for namespace <{0}>: {1}", namespace, data); + RecordLog.info("[ModifyClusterServerFlowConfigHandler] Receiving cluster server flow config for namespace <{}>: {}", namespace, data); ServerFlowConfig config = JSON.parseObject(data, ServerFlowConfig.class); if (!ClusterServerConfigManager.isValidFlowConfig(config)) { CommandResponse.ofFailure(new IllegalArgumentException("Bad flow config")); diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java index 2891623c70..4e6da032d6 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/command/handler/ModifyServerNamespaceSetHandler.java @@ -44,7 +44,7 @@ public CommandResponse handle(CommandRequest request) { } try { data = URLDecoder.decode(data, "utf-8"); - RecordLog.info("[ModifyServerNamespaceSetHandler] Receiving cluster server namespace set: " + data); + RecordLog.info("[ModifyServerNamespaceSetHandler] Receiving cluster server namespace set: {}", data); Set set = JSON.parseObject(data, new TypeReference>() {}); ClusterServerConfigManager.loadServerNamespaceSet(set); return CommandResponse.ofSuccess("success"); diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java index b3d3fcd651..5da3b78223 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/config/ClusterServerConfigManager.java @@ -219,7 +219,7 @@ private static void applyNamespaceSetChange(Set newSet) { if (newSet == null) { return; } - RecordLog.info("[ClusterServerConfigManager] Server namespace set will be update to: " + newSet); + RecordLog.info("[ClusterServerConfigManager] Server namespace set will be update to: {}", newSet); if (newSet.isEmpty()) { ClusterServerConfigManager.namespaceSet = Collections.singleton(ServerConstants.DEFAULT_NAMESPACE); return; @@ -276,10 +276,10 @@ public void configUpdate(ServerTransportConfig config) { private synchronized void applyConfig(ServerTransportConfig config) { if (!isValidTransportConfig(config)) { RecordLog.warn( - "[ClusterServerConfigManager] Invalid cluster server transport config, ignoring: " + config); + "[ClusterServerConfigManager] Invalid cluster server transport config, ignoring: {}", config); return; } - RecordLog.info("[ClusterServerConfigManager] Updating new server transport config: " + config); + RecordLog.info("[ClusterServerConfigManager] Updating new server transport config: {}", config); if (config.getIdleSeconds() != idleSeconds) { idleSeconds = config.getIdleSeconds(); } @@ -315,10 +315,10 @@ public void configLoad(ServerFlowConfig config) { private synchronized void applyGlobalFlowConfig(ServerFlowConfig config) { if (!isValidFlowConfig(config)) { RecordLog.warn( - "[ClusterServerConfigManager] Invalid cluster server global flow config, ignoring: " + config); + "[ClusterServerConfigManager] Invalid cluster server global flow config, ignoring: {}", config); return; } - RecordLog.info("[ClusterServerConfigManager] Updating new server global flow config: " + config); + RecordLog.info("[ClusterServerConfigManager] Updating new server global flow config: {}", config); if (config.getExceedCount() != exceedCount) { exceedCount = config.getExceedCount(); } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java index 37f88cedd7..aa3a87be00 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ConnectionManager.java @@ -73,7 +73,7 @@ public static void removeConnection(String address) { return; } group.removeConnection(address); - RecordLog.info("[ConnectionManager] Client <{0}> disconnected and removed from namespace <{1}>", address, namespace); + RecordLog.info("[ConnectionManager] Client <{}> disconnected and removed from namespace <{}>", address, namespace); } NAMESPACE_MAP.remove(address); } @@ -87,7 +87,7 @@ public static void removeConnection(String namespace, String address) { } group.removeConnection(address); NAMESPACE_MAP.remove(address); - RecordLog.info("[ConnectionManager] Client <{0}> disconnected and removed from namespace <{1}>", address, namespace); + RecordLog.info("[ConnectionManager] Client <{}> disconnected and removed from namespace <{}>", address, namespace); } public static ConnectionGroup addConnection(String namespace, String address) { @@ -96,7 +96,7 @@ public static ConnectionGroup addConnection(String namespace, String address) { ConnectionGroup group = getOrCreateGroup(namespace); group.addConnection(address); NAMESPACE_MAP.put(address, namespace); - RecordLog.info("[ConnectionManager] Client <{0}> registered with namespace <{1}>", address, namespace); + RecordLog.info("[ConnectionManager] Client <{}> registered with namespace <{}>", address, namespace); return group; } @@ -112,6 +112,9 @@ public static ConnectionGroup getConnectionGroup(String namespace) { return group; } + public static boolean isClientOnline(String address){ + return NAMESPACE_MAP.containsKey(address); + } static void clear() { CONN_MAP.clear(); NAMESPACE_MAP.clear(); diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java index a559aeeb06..b3ed118072 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/connection/ScanIdleConnectionTask.java @@ -31,10 +31,8 @@ public void run() { List connections = connectionPool.listAllConnection(); for (Connection conn : connections) { if ((now - conn.getLastReadTime()) > idleTimeMillis) { - RecordLog.info( - String.format("[ScanIdleConnectionTask] The connection <%s:%d> has been idle for <%d>s. " - + "It will be closed now.", conn.getRemoteIP(), conn.getRemotePort(), idleSeconds) - ); + RecordLog.info("[ScanIdleConnectionTask] The connection <{}:{}> has been idle for <{}>s. It will be closed now.", + conn.getRemoteIP(), conn.getRemotePort(), idleSeconds); conn.close(); } } diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java index da5375d5ed..44f5d6691c 100644 --- a/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java +++ b/sentinel-cluster/sentinel-cluster-server-default/src/main/java/com/alibaba/csp/sentinel/cluster/server/handler/TokenServerHandler.java @@ -47,7 +47,6 @@ public TokenServerHandler(ConnectionPool globalConnectionPool) { @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { globalConnectionPool.createConnection(ctx.channel()); - String remoteAddress = getRemoteAddress(ctx); } @Override diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ConcurrentClusterFlowCheckerTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ConcurrentClusterFlowCheckerTest.java new file mode 100644 index 0000000000..3a7e2e9618 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/ConcurrentClusterFlowCheckerTest.java @@ -0,0 +1,125 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow; + +import com.alibaba.csp.sentinel.cluster.TokenResult; +import com.alibaba.csp.sentinel.cluster.TokenResultStatus; +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager; +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNodeManager; +import com.alibaba.csp.sentinel.cluster.server.connection.ConnectionManager; +import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * @author yunfeiyanggzq + */ +public class ConcurrentClusterFlowCheckerTest extends AbstractTimeBasedTest { + @Before + public void setUp() { + FlowRule rule = new FlowRule(); + ClusterFlowConfig config = new ClusterFlowConfig(); + config.setResourceTimeout(500); + config.setClientOfflineTime(1000); + config.setFlowId(111L); + config.setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL); + rule.setClusterConfig(config); + rule.setClusterMode(true); + rule.setCount(10); + rule.setResource("test"); + rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); + ArrayList rules = new ArrayList<>(); + rules.add(rule); + ClusterFlowRuleManager.registerPropertyIfAbsent("1-name"); + ClusterFlowRuleManager.loadRules("1-name", rules); + } + + @Test + public void testEasyAcquireAndRelease() throws InterruptedException { + setCurrentMillis(System.currentTimeMillis()); + FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(111L); + ArrayList list = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + TokenResult result = ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1); + Assert.assertTrue("fail to acquire token", + result.getStatus() == TokenResultStatus.OK && result.getTokenId() != 0); + list.add(result.getTokenId()); + } + for (int i = 0; i < 10; i++) { + TokenResult result = ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1); + Assert.assertTrue("fail to acquire block token", + result.getStatus() == TokenResultStatus.BLOCKED); + } + for (int i = 0; i < 10; i++) { + TokenResult result = ConcurrentClusterFlowChecker.releaseConcurrentToken(list.get(i)); + Assert.assertTrue("fail to release token", + result.getStatus() == TokenResultStatus.RELEASE_OK); + } + Assert.assertTrue("fail to release token", + CurrentConcurrencyManager.get(111L).get() == 0 && TokenCacheNodeManager.getSize() == 0); + } + + @Test + public void testConcurrentAcquireAndRelease() throws InterruptedException { + setCurrentMillis(System.currentTimeMillis()); + final FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(111L); + final CountDownLatch countDownLatch = new CountDownLatch(1000); + ExecutorService pool = Executors.newFixedThreadPool(100); + + for (long i = 0; i < 1000; i++) { + Runnable task = new Runnable() { + @Override + public void run() { + assert rule != null; + TokenResult result = ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1); + Assert.assertTrue("concurrent control fail", CurrentConcurrencyManager.get(111L).get() <= rule.getCount()); + if (result.getStatus() == TokenResultStatus.OK) { + ConcurrentClusterFlowChecker.releaseConcurrentToken(result.getTokenId()); + } + countDownLatch.countDown(); + } + }; + pool.execute(task); + } + countDownLatch.await(); + pool.shutdown(); + assert rule != null; + Assert.assertTrue("fail to acquire and release token", + CurrentConcurrencyManager.get(rule.getClusterConfig().getFlowId()).get() == 0 && TokenCacheNodeManager.getSize() == 0); + } + + @Test + public void testReleaseExpiredToken() throws InterruptedException { + ConnectionManager.addConnection("test", "127.0.0.1"); + FlowRule rule = ClusterFlowRuleManager.getFlowRuleById(111L); + for (int i = 0; i < 10; i++) { + ConcurrentClusterFlowChecker.acquireConcurrentToken("127.0.0.1", rule, 1); + } + Thread.sleep(3000); + Assert.assertTrue("fail to acquire and release token", CurrentConcurrencyManager.get(rule.getClusterConfig().getFlowId()).get() == 0 && TokenCacheNodeManager.getSize() == 0); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/CurrentConcurrencyManagerTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/CurrentConcurrencyManagerTest.java new file mode 100644 index 0000000000..dff3ca949d --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/CurrentConcurrencyManagerTest.java @@ -0,0 +1,49 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; + +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.CurrentConcurrencyManager; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +public class CurrentConcurrencyManagerTest { + @Test + public void updateTest() throws InterruptedException { + CurrentConcurrencyManager.put(111L, 0); + CurrentConcurrencyManager.put(222L, 0); + final CountDownLatch countDownLatch = new CountDownLatch(1000); + ExecutorService pool = Executors.newFixedThreadPool(100); + for (int i = 0; i < 1000; i++) { + Runnable task = new Runnable() { + @Override + public void run() { + CurrentConcurrencyManager.addConcurrency(111L, 1); + CurrentConcurrencyManager.addConcurrency(222L, 2); + countDownLatch.countDown(); + } + }; + pool.execute(task); + } + countDownLatch.await(); + pool.shutdown(); + Assert.assertEquals(1000, CurrentConcurrencyManager.get(111L).get()); + Assert.assertEquals(2000, CurrentConcurrencyManager.get(222L).get()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNodeManagerTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNodeManagerTest.java new file mode 100644 index 0000000000..8ca4f97a40 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/concurrent/TokenCacheNodeManagerTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent; + +import com.alibaba.csp.sentinel.cluster.flow.rule.ClusterFlowRuleManager; +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNode; +import com.alibaba.csp.sentinel.cluster.flow.statistic.concurrent.TokenCacheNodeManager; +import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.flow.ClusterFlowConfig; +import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; +import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class TokenCacheNodeManagerTest extends AbstractTimeBasedTest { + @Before + public void setUp() { + FlowRule rule = new FlowRule(); + ClusterFlowConfig config = new ClusterFlowConfig(); + config.setResourceTimeout(500); + config.setClientOfflineTime(1000); + config.setFlowId(111L); + config.setThresholdType(ClusterRuleConstant.FLOW_THRESHOLD_GLOBAL); + rule.setClusterConfig(config); + rule.setClusterMode(true); + rule.setCount(10); + rule.setResource("test"); + rule.setGrade(RuleConstant.FLOW_GRADE_THREAD); + ArrayList rules = new ArrayList<>(); + rules.add(rule); + ClusterFlowRuleManager.registerPropertyIfAbsent("1-name"); + ClusterFlowRuleManager.loadRules("1-name", rules); + } + + @Test + public void testPutTokenCacheNode() throws InterruptedException { + setCurrentMillis(System.currentTimeMillis()); + + for (long i = 0; i < 100; i++) { + final TokenCacheNode node = new TokenCacheNode(); + node.setTokenId(i); + node.setFlowId(111L); + node.setResourceTimeout(10000L); + node.setClientTimeout(10000L); + node.setClientAddress("localhost"); + if (TokenCacheNodeManager.validToken(node)) { + TokenCacheNodeManager.putTokenCacheNode(node.getTokenId(), node); + + } + } + Assert.assertEquals(100, TokenCacheNodeManager.getSize()); + for (int i = 0; i < 100; i++) { + TokenCacheNodeManager.getTokenCacheNode((long) (Math.random() * 100)); + } + List keyList = new ArrayList<>(TokenCacheNodeManager.getCacheKeySet()); + for (int i = 0; i < 100; i++) { + Assert.assertEquals(i, (long) keyList.get(i)); + TokenCacheNodeManager.removeTokenCacheNode(i); + } + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/GlobalRequestLimiterTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/GlobalRequestLimiterTest.java new file mode 100644 index 0000000000..e618d7dec5 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/GlobalRequestLimiterTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.limit; + +import com.alibaba.csp.sentinel.cluster.server.config.ClusterServerConfigManager; +import com.alibaba.csp.sentinel.cluster.test.AbstractTimeBasedTest; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class GlobalRequestLimiterTest extends AbstractTimeBasedTest { + @Before + public void preTest() { + ClusterServerConfigManager.setMaxAllowedQps(3); + } + + @Test + public void testPass() throws InterruptedException { + setCurrentMillis(System.currentTimeMillis()); + GlobalRequestLimiter.initIfAbsent("user"); + Assert.assertNotNull(GlobalRequestLimiter.getRequestLimiter("user")); + Assert.assertEquals(3, GlobalRequestLimiter.getMaxAllowedQps("user"), 0.01); + Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); + Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); + Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); + Assert.assertFalse(GlobalRequestLimiter.tryPass("user")); + Assert.assertEquals(3, GlobalRequestLimiter.getCurrentQps("user"), 0.01); + + // wait a second to refresh the window + sleep(1000); + Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); + Assert.assertTrue(GlobalRequestLimiter.tryPass("user")); + Assert.assertEquals(2, GlobalRequestLimiter.getCurrentQps("user"), 0.01); + } + + @Test + public void testChangeMaxAllowedQps() { + GlobalRequestLimiter.initIfAbsent("foo"); + Assert.assertEquals(3, GlobalRequestLimiter.getMaxAllowedQps("foo"), 0.01); + GlobalRequestLimiter.applyMaxQpsChange(10); + Assert.assertEquals(10, GlobalRequestLimiter.getMaxAllowedQps("foo"), 0.01); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/RequestLimiterTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/RequestLimiterTest.java new file mode 100644 index 0000000000..0d3f95e6af --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/limit/RequestLimiterTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.limit; + +import com.alibaba.csp.sentinel.cluster.test.AbstractTimeBasedTest; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class RequestLimiterTest extends AbstractTimeBasedTest { + + @Test + public void testRequestLimiter() { + setCurrentMillis(System.currentTimeMillis()); + RequestLimiter limiter = new RequestLimiter(10); + limiter.add(3); + limiter.add(3); + limiter.add(3); + assertTrue(limiter.canPass()); + assertEquals(9, limiter.getSum()); + limiter.add(3); + assertFalse(limiter.canPass()); + + // wait a second to refresh the window + sleep(1000); + limiter.add(3); + assertTrue(limiter.tryPass()); + assertTrue(limiter.canPass()); + assertEquals(4, limiter.getSum()); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricTest.java new file mode 100644 index 0000000000..e8c286bc7c --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterMetricTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.metric; + +import com.alibaba.csp.sentinel.cluster.flow.statistic.data.ClusterFlowEvent; +import com.alibaba.csp.sentinel.cluster.test.AbstractTimeBasedTest; +import org.junit.Assert; +import org.junit.Test; + +public class ClusterMetricTest extends AbstractTimeBasedTest { + + @Test + public void testTryOccupyNext() { + setCurrentMillis(System.currentTimeMillis()); + ClusterMetric metric = new ClusterMetric(5, 25); + metric.add(ClusterFlowEvent.PASS, 1); + metric.add(ClusterFlowEvent.PASS, 2); + metric.add(ClusterFlowEvent.PASS, 1); + metric.add(ClusterFlowEvent.BLOCK, 1); + Assert.assertEquals(4, metric.getSum(ClusterFlowEvent.PASS)); + Assert.assertEquals(1, metric.getSum(ClusterFlowEvent.BLOCK)); + Assert.assertEquals(160, metric.getAvg(ClusterFlowEvent.PASS), 0.01); + Assert.assertEquals(200, metric.tryOccupyNext(ClusterFlowEvent.PASS, 111, 900)); + metric.add(ClusterFlowEvent.PASS, 1); + metric.add(ClusterFlowEvent.PASS, 2); + metric.add(ClusterFlowEvent.PASS, 1); + Assert.assertEquals(200, metric.tryOccupyNext(ClusterFlowEvent.PASS, 222, 900)); + metric.add(ClusterFlowEvent.PASS, 1); + metric.add(ClusterFlowEvent.PASS, 2); + metric.add(ClusterFlowEvent.PASS, 1); + Assert.assertEquals(0, metric.tryOccupyNext(ClusterFlowEvent.PASS, 333, 900)); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetricTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetricTest.java new file mode 100644 index 0000000000..82645877ad --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/flow/statistic/metric/ClusterParamMetricTest.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.flow.statistic.metric; + +import com.alibaba.csp.sentinel.cluster.test.AbstractTimeBasedTest; +import org.junit.Assert; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; + +public class ClusterParamMetricTest extends AbstractTimeBasedTest { + + @Test + public void testClusterParamMetric() { + setCurrentMillis(System.currentTimeMillis()); + Map topMap = new HashMap(); + ClusterParamMetric metric = new ClusterParamMetric(5, 25, 100); + metric.addValue("e1", -1); + metric.addValue("e1", -2); + metric.addValue("e2", 100); + metric.addValue("e2", 23); + metric.addValue("e3", 100); + metric.addValue("e3", 230); + Assert.assertEquals(-3, metric.getSum("e1")); + Assert.assertEquals(-120, metric.getAvg("e1"), 0.01); + topMap.put("e3", (double) 13200); + Assert.assertEquals(topMap, metric.getTopValues(1)); + topMap.put("e2", (double) 4920); + topMap.put("e1", (double) -120); + Assert.assertEquals(topMap, metric.getTopValues(5)); + metric.addValue("e2", 100); + metric.addValue("e2", 23); + Assert.assertEquals(246, metric.getSum("e2")); + Assert.assertEquals(9840, metric.getAvg("e2"), 0.01); + } + + @Test(expected = IllegalArgumentException.class) + public void testIllegalArgument() { + ClusterParamMetric metric = new ClusterParamMetric(5, 25, 100); + metric.getTopValues(-1); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/test/AbstractTimeBasedTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/test/AbstractTimeBasedTest.java new file mode 100644 index 0000000000..d4458a0d21 --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/cluster/test/AbstractTimeBasedTest.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.cluster.test; + +import com.alibaba.csp.sentinel.util.TimeUtil; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * Mock support for {@link TimeUtil}. + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({TimeUtil.class}) +public abstract class AbstractTimeBasedTest { + + private long currentMillis = 0; + + { + PowerMockito.mockStatic(TimeUtil.class); + PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis); + } + + protected final void useActualTime() { + PowerMockito.when(TimeUtil.currentTimeMillis()).thenCallRealMethod(); + } + + protected final void setCurrentMillis(long cur) { + currentMillis = cur; + PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis); + } + + protected final void sleep(int t) { + currentMillis += t; + PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis); + } + + protected final void sleepSecond(int timeSec) { + sleep(timeSec * 1000); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/test/AbstractTimeBasedTest.java b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/test/AbstractTimeBasedTest.java new file mode 100644 index 0000000000..6e8bab4d1e --- /dev/null +++ b/sentinel-cluster/sentinel-cluster-server-default/src/test/java/com/alibaba/csp/sentinel/test/AbstractTimeBasedTest.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.test; + +import com.alibaba.csp.sentinel.util.TimeUtil; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; + +/** + * Mock support for {@link TimeUtil} + * + * @author jason + */ +@RunWith(PowerMockRunner.class) +@PrepareForTest({TimeUtil.class}) +public abstract class AbstractTimeBasedTest { + + private long currentMillis = 0; + + { + PowerMockito.mockStatic(TimeUtil.class); + PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis); + } + + protected final void useActualTime() { + PowerMockito.when(TimeUtil.currentTimeMillis()).thenCallRealMethod(); + } + + protected final void setCurrentMillis(long cur) { + currentMillis = cur; + PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis); + } + + protected final void sleep(int t) { + currentMillis += t; + PowerMockito.when(TimeUtil.currentTimeMillis()).thenReturn(currentMillis); + } + + protected final void sleepSecond(int timeSec) { + sleep(timeSec * 1000); + } +} diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/README.md b/sentinel-cluster/sentinel-cluster-server-envoy-rls/README.md index e610043686..253a9b67d9 100644 --- a/sentinel-cluster/sentinel-cluster-server-envoy-rls/README.md +++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/README.md @@ -16,10 +16,10 @@ mvn clean package -P prod ## Rule configuration -Currently Sentinel RLS token server supports dynamic rule configuration via the yaml file. +Sentinel RLS token server supports dynamic rule configuration via the yaml file. The file may provide rules for one *domain* (defined in Envoy's conf file). In Envoy, one rate limit request might carry multiple *rate limit descriptors* -(which will be generated from [Envoy rate limit actions](https://www.envoyproxy.io/docs/envoy/latest/api-v2/api/v2/route/route.proto#envoy-api-msg-route-ratelimit)). +(which will be generated from [Envoy rate limit actions](https://www.envoyproxy.io/docs/envoy/v1.12.1/api-v2/api/v2/route/route.proto#envoy-api-msg-route-ratelimit)). One rate limit descriptor may have multiple entries (key-value pair). We may set different threshold for each rate limit descriptors. diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/pom.xml b/sentinel-cluster/sentinel-cluster-server-envoy-rls/pom.xml index 3c84641c79..a52247b44d 100644 --- a/sentinel-cluster/sentinel-cluster-server-envoy-rls/pom.xml +++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/pom.xml @@ -5,7 +5,7 @@ sentinel-cluster com.alibaba.csp - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT 4.0.0 @@ -16,7 +16,7 @@ 1.8 3.10.0 - 1.24.0 + 1.30.2 3.2.1
@@ -35,6 +35,11 @@ com.alibaba.csp sentinel-transport-simple-http + + javax.annotation + javax.annotation-api + ${javax.annotation-api.version} + io.grpc @@ -153,4 +158,4 @@ - \ No newline at end of file + diff --git a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRuleManager.java b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRuleManager.java index 1acca60040..029fc148ff 100644 --- a/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRuleManager.java +++ b/sentinel-cluster/sentinel-cluster-server-envoy-rls/src/main/java/com/alibaba/csp/sentinel/cluster/server/envoy/rls/rule/EnvoyRlsRuleManager.java @@ -91,7 +91,7 @@ public synchronized void configUpdate(List conf) { RULE_MAP.clear(); RULE_MAP.putAll(ruleMap); - RecordLog.info("[EnvoyRlsRuleManager] Envoy RLS rules loaded: " + flowRules); + RecordLog.info("[EnvoyRlsRuleManager] Envoy RLS rules loaded: {}", flowRules); // Use the "default" namespace. ClusterFlowRuleManager.loadRules(ServerConstants.DEFAULT_NAMESPACE, flowRules); diff --git a/sentinel-core/pom.xml b/sentinel-core/pom.xml index 1cb08f7d44..06c50f4aa4 100755 --- a/sentinel-core/pom.xml +++ b/sentinel-core/pom.xml @@ -6,7 +6,7 @@ com.alibaba.csp sentinel-parent - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT sentinel-core jar diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java index c3bbadf091..9c50ddf832 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Constants.java @@ -31,7 +31,7 @@ */ public final class Constants { - public static final String SENTINEL_VERSION = VersionUtil.getVersion("1.7.2"); + public static final String SENTINEL_VERSION = VersionUtil.getVersion("1.8.1"); public final static int MAX_CONTEXT_NAME_SIZE = 2000; public final static int MAX_SLOT_CHAIN_SIZE = 6000; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtEntry.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtEntry.java index d59e52a5ff..6c62abe810 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtEntry.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtEntry.java @@ -15,12 +15,16 @@ */ package com.alibaba.csp.sentinel; +import java.util.LinkedList; + import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.context.NullContext; +import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.util.function.BiConsumer; /** * Linked entry within current context. @@ -35,6 +39,7 @@ class CtEntry extends Entry { protected ProcessorSlot chain; protected Context context; + protected LinkedList> exitHandlers; CtEntry(ResourceWrapper resourceWrapper, ProcessorSlot chain, Context context) { super(resourceWrapper); @@ -51,7 +56,7 @@ private void setUpEntryFor(Context context) { } this.parent = context.getCurEntry(); if (parent != null) { - ((CtEntry)parent).child = this; + ((CtEntry) parent).child = this; } context.setCurEntry(this); } @@ -61,31 +66,55 @@ public void exit(int count, Object... args) throws ErrorEntryFreeException { trueExit(count, args); } + /** + * Note: the exit handlers will be called AFTER onExit of slot chain. + */ + private void callExitHandlersAndCleanUp(Context ctx) { + if (exitHandlers != null && !exitHandlers.isEmpty()) { + for (BiConsumer handler : this.exitHandlers) { + try { + handler.accept(ctx, this); + } catch (Exception e) { + RecordLog.warn("Error occurred when invoking entry exit handler, current entry: " + + resourceWrapper.getName(), e); + } + } + exitHandlers = null; + } + } + protected void exitForContext(Context context, int count, Object... args) throws ErrorEntryFreeException { if (context != null) { // Null context should exit without clean-up. if (context instanceof NullContext) { return; } + if (context.getCurEntry() != this) { - String curEntryNameInContext = context.getCurEntry() == null ? null : context.getCurEntry().getResourceWrapper().getName(); + String curEntryNameInContext = context.getCurEntry() == null ? null + : context.getCurEntry().getResourceWrapper().getName(); // Clean previous call stack. - CtEntry e = (CtEntry)context.getCurEntry(); + CtEntry e = (CtEntry) context.getCurEntry(); while (e != null) { e.exit(count, args); - e = (CtEntry)e.parent; + e = (CtEntry) e.parent; } String errorMessage = String.format("The order of entry exit can't be paired with the order of entry" - + ", current entry in context: <%s>, but expected: <%s>", curEntryNameInContext, resourceWrapper.getName()); + + ", current entry in context: <%s>, but expected: <%s>", curEntryNameInContext, + resourceWrapper.getName()); throw new ErrorEntryFreeException(errorMessage); } else { + // Go through the onExit hook of all slots. if (chain != null) { chain.exit(context, resourceWrapper, count, args); } + // Go through the existing terminate handlers (associated to this invocation). + callExitHandlersAndCleanUp(context); + // Restore the call stack. context.setCurEntry(parent); if (parent != null) { - ((CtEntry)parent).child = null; + ((CtEntry) parent).child = null; } if (parent == null) { // Default context (auto entered) will be exited automatically. @@ -103,6 +132,14 @@ protected void clearEntryContext() { this.context = null; } + @Override + public void whenTerminate(BiConsumer handler) { + if (this.exitHandlers == null) { + this.exitHandlers = new LinkedList<>(); + } + this.exitHandlers.add(handler); + } + @Override protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeException { exitForContext(context, count, args); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java index 5c5b137b1d..84ebf17dcb 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/CtSph.java @@ -181,7 +181,7 @@ public Entry entry(ResourceWrapper resourceWrapper, int count, Object... args) t * be created if the resource doesn't relate one. * *

Same resource({@link ResourceWrapper#equals(Object)}) will share the same - * {@link ProcessorSlotChain} globally, no matter in witch {@link Context}.

+ * {@link ProcessorSlotChain} globally, no matter in which {@link Context}.

* *

* Note that total {@link ProcessorSlot} count must not exceed {@link Constants#MAX_SLOT_CHAIN_SIZE}, diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java index 8740022992..c3114821c3 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Entry.java @@ -15,7 +15,9 @@ */ package com.alibaba.csp.sentinel; +import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.util.TimeUtil; +import com.alibaba.csp.sentinel.util.function.BiConsumer; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; @@ -44,6 +46,7 @@ * @author qinan.qn * @author jialiang.linjl * @author leyou(lihao) + * @author Eric Zhao * @see SphU * @see Context * @see ContextUtil @@ -52,18 +55,23 @@ public abstract class Entry implements AutoCloseable { private static final Object[] OBJECTS0 = new Object[0]; - private long createTime; + private final long createTimestamp; + private long completeTimestamp; + private Node curNode; /** * {@link Node} of the specific origin, Usually the origin is the Service Consumer. */ private Node originNode; + private Throwable error; - protected ResourceWrapper resourceWrapper; + private BlockException blockError; + + protected final ResourceWrapper resourceWrapper; public Entry(ResourceWrapper resourceWrapper) { this.resourceWrapper = resourceWrapper; - this.createTime = TimeUtil.currentTimeMillis(); + this.createTimestamp = TimeUtil.currentTimeMillis(); } public ResourceWrapper getResourceWrapper() { @@ -119,8 +127,17 @@ public void close() { */ public abstract Node getLastNode(); - public long getCreateTime() { - return createTime; + public long getCreateTimestamp() { + return createTimestamp; + } + + public long getCompleteTimestamp() { + return completeTimestamp; + } + + public Entry setCompleteTimestamp(long completeTimestamp) { + this.completeTimestamp = completeTimestamp; + return this; } public Node getCurNode() { @@ -131,6 +148,15 @@ public void setCurNode(Node node) { this.curNode = node; } + public BlockException getBlockError() { + return blockError; + } + + public Entry setBlockError(BlockException blockError) { + this.blockError = blockError; + return this; + } + public Throwable getError() { return error; } @@ -153,4 +179,14 @@ public void setOriginNode(Node originNode) { this.originNode = originNode; } + /** + * Like {@code CompletableFuture} since JDK 8, it guarantees specified handler + * is invoked when this entry terminated (exited), no matter it's blocked or permitted. + * Use it when you did some STATEFUL operations on entries. + * + * @param handler handler function on the invocation terminates + * @since 1.8.0 + */ + public abstract void whenTerminate(BiConsumer handler); + } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java index 32643b4697..ab34176a57 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Sph.java @@ -18,11 +18,10 @@ import java.lang.reflect.Method; import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.system.SystemRule; /** - * Interface to get {@link Entry} for resource protection. If any block criteria is met, - * a {@link BlockException} or its subclasses will be thrown. Successfully getting a entry - * indicates permitting the invocation pass. + * The basic interface for recording statistics and performing rule checking for resources. * * @author qinan.qn * @author jialiang.linjl @@ -32,158 +31,166 @@ public interface Sph extends SphResourceTypeSupport { /** - * Create a protected resource. + * Record statistics and perform rule checking for the given resource. * * @param name the unique name of the protected resource - * @return entry get. + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(String name) throws BlockException; /** - * Create a protected method. + * Record statistics and perform rule checking for the given method. * * @param method the protected method - * @return entry get. + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ Entry entry(Method method) throws BlockException; /** - * Create a protected method. + * Record statistics and perform rule checking for the given method. * - * @param method the protected method - * @param count the count that the resource requires - * @return entry get. + * @param method the protected method + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ - Entry entry(Method method, int count) throws BlockException; + Entry entry(Method method, int batchCount) throws BlockException; /** - * Create a protected resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique string for the resource - * @param count the count that the resource requires - * @return entry get. + * @param name the unique string for the resource + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ - Entry entry(String name, int count) throws BlockException; + Entry entry(String name, int batchCount) throws BlockException; /** - * Create a protected method. + * Record statistics and perform rule checking for the given method. * - * @param method the protected method - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable - * @return entry get. + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ - Entry entry(Method method, EntryType type) throws BlockException; + Entry entry(Method method, EntryType trafficType) throws BlockException; /** - * Create a protected resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable - * @return entry get. + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ - Entry entry(String name, EntryType type) throws BlockException; + Entry entry(String name, EntryType trafficType) throws BlockException; /** - * Create a protected method. + * Record statistics and perform rule checking for the given method. * - * @param method the protected method - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable - * @param count the count that the resource requires - * @return entry get. + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ - Entry entry(Method method, EntryType type, int count) throws BlockException; + Entry entry(Method method, EntryType trafficType, int batchCount) throws BlockException; /** - * Create a protected resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable - * @param count the count that the resource requires - * @return entry get. + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ - Entry entry(String name, EntryType type, int count) throws BlockException; + Entry entry(String name, EntryType trafficType, int batchCount) throws BlockException; /** - * Create a protected resource. + * Record statistics and perform rule checking for the given resource. * - * @param method the protected method - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable - * @param count the count that the resource requires - * @param args the parameters of the method. It can also be counted by setting - * hot parameter rule - * @return entry get. + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args parameters of the method for flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data). * @throws BlockException if the block criteria is met */ - Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException; + Entry entry(Method method, EntryType trafficType, int batchCount, Object... args) throws BlockException; /** - * Create a protected resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable - * @param count the count that the resource requires - * @param args the parameters of the method. It can also be counted by setting hot parameter rule - * @return entry get + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met */ - Entry entry(String name, EntryType type, int count, Object... args) throws BlockException; + Entry entry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException; /** * Create a protected asynchronous resource. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable - * @param count the count that the resource requires - * @param args the parameters of the method. It can also be counted by setting hot parameter rule + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots * @return created asynchronous entry * @throws BlockException if the block criteria is met * @since 0.2.0 */ - AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException; + AsyncEntry asyncEntry(String name, EntryType trafficType, int batchCount, Object... args) throws BlockException; /** * Create a protected resource with priority. * * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable - * @param count the count that the resource requires + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param prioritized whether the entry is prioritized - * @return entry get + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met * @since 1.4.0 */ - Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized) throws BlockException; + Entry entryWithPriority(String name, EntryType trafficType, int batchCount, boolean prioritized) + throws BlockException; /** * Create a protected resource with priority. * * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable - * @param count the count that the resource requires + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @param prioritized whether the entry is prioritized - * @param args the parameters of the method. It can also be counted by setting hot parameter - * rule - * @return entry get + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met * @since 1.5.0 */ - Entry entryWithPriority(String name, EntryType type, int count, boolean prioritized, Object... args) + Entry entryWithPriority(String name, EntryType trafficType, int batchCount, boolean prioritized, Object... args) throws BlockException; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java index 6f6231da43..7667741269 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphO.java @@ -18,8 +18,8 @@ import java.lang.reflect.Method; import java.util.List; -import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.Rule; import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; @@ -63,6 +63,7 @@ * * @author jialiang.linjl * @author leyou + * @author Eric Zhao * @see SphU */ public class SphO { @@ -70,7 +71,7 @@ public class SphO { private static final Object[] OBJECTS0 = new Object[0]; /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * * @param name the unique name of the protected resource * @return true if no rule's threshold is exceeded, otherwise return false. @@ -92,23 +93,23 @@ public static boolean entry(Method method) { /** * Checking all {@link Rule}s about the protected method. * - * @param method the protected method - * @param count tokens required + * @param method the protected method + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return true if no rule's threshold is exceeded, otherwise return false. */ - public static boolean entry(Method method, int count) { - return entry(method, EntryType.OUT, count, OBJECTS0); + public static boolean entry(Method method, int batchCount) { + return entry(method, EntryType.OUT, batchCount, OBJECTS0); } /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique string for the resource - * @param count tokens required + * @param name the unique string for the resource + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return true if no rule's threshold is exceeded, otherwise return false. */ - public static boolean entry(String name, int count) { - return entry(name, EntryType.OUT, count, OBJECTS0); + public static boolean entry(String name, int batchCount) { + return entry(name, EntryType.OUT, batchCount, OBJECTS0); } /** @@ -125,7 +126,7 @@ public static boolean entry(Method method, EntryType type) { } /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param type the resource is an inbound or an outbound method. This is used @@ -144,7 +145,7 @@ public static boolean entry(String name, EntryType type) { * @param type the resource is an inbound or an outbound method. This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} - * @param count tokens required + * @param count the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(Method method, EntryType type, int count) { @@ -152,13 +153,13 @@ public static boolean entry(Method method, EntryType type, int count) { } /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param type the resource is an inbound or an outbound method. This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} - * @param count tokens required + * @param count the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) * @return true if no rule's threshold is exceeded, otherwise return false. */ public static boolean entry(String name, EntryType type, int count) { @@ -166,46 +167,46 @@ public static boolean entry(String name, EntryType type, int count) { } /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @param count tokens required - * @param args extra parameters. + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots * @return true if no rule's threshold is exceeded, otherwise return false. */ - public static boolean entry(String name, EntryType type, int count, Object... args) { + public static boolean entry(String name, EntryType trafficType, int batchCount, Object... args) { try { - Env.sph.entry(name, type, count, args); + Env.sph.entry(name, trafficType, batchCount, args); } catch (BlockException e) { return false; } catch (Throwable e) { - RecordLog.info("[Sentinel] Fatal error", e); + RecordLog.warn("SphO fatal error", e); return true; } return true; } /** - * Checking all {@link Rule}s about the protected method. + * Record statistics and perform rule checking for the given method resource. * - * @param method the protected method - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @param count tokens required - * @param args the parameters of the method. + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots * @return true if no rule's threshold is exceeded, otherwise return false. */ - public static boolean entry(Method method, EntryType type, int count, Object... args) { + public static boolean entry(Method method, EntryType trafficType, int batchCount, Object... args) { try { - Env.sph.entry(method, type, count, args); + Env.sph.entry(method, trafficType, batchCount, args); } catch (BlockException e) { return false; } catch (Throwable e) { - RecordLog.info("[Sentinel] Fatal error", e); + RecordLog.warn("SphO fatal error", e); return true; } return true; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphResourceTypeSupport.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphResourceTypeSupport.java index 4349473f6c..ab9574e434 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphResourceTypeSupport.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphResourceTypeSupport.java @@ -16,6 +16,7 @@ package com.alibaba.csp.sentinel; import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.system.SystemRule; /** * @author Eric Zhao @@ -24,46 +25,53 @@ public interface SphResourceTypeSupport { /** - * Create a protected resource with provided classification. + * Record statistics and perform rule checking for the given resource with provided classification. * - * @param name the unique name of the protected resource + * @param name the unique name of the protected resource * @param resourceType the classification of the resource - * @param entryType the traffic entry type (IN/OUT) of the resource - * @param count tokens required - * @param args extra parameters - * @return new entry of the resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met */ - Entry entryWithType(String name, int resourceType, EntryType entryType, int count, Object[] args) + Entry entryWithType(String name, int resourceType, EntryType trafficType, int batchCount, Object[] args) throws BlockException; /** - * Create a protected resource with provided classification. + * Record statistics and perform rule checking for the given resource with the provided classification. * - * @param name the unique name of the protected resource - * @param resourceType the classification of the resource - * @param entryType the traffic entry type (IN/OUT) of the resource - * @param count tokens required - * @param prioritized whether the entry is prioritized - * @param args extra parameters - * @return new entry of the resource + * @param name the unique name of the protected resource + * @param resourceType classification of the resource (e.g. Web or RPC) + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param prioritized whether the entry is prioritized + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met */ - Entry entryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized, + Entry entryWithType(String name, int resourceType, EntryType trafficType, int batchCount, boolean prioritized, Object[] args) throws BlockException; /** - * Create an asynchronous resource with provided classification. + * Record statistics and perform rule checking for the given resource that indicates an async invocation. * - * @param name the unique name of the protected resource - * @param resourceType the classification of the resource - * @param entryType the traffic entry type (IN/OUT) of the resource - * @param count tokens required - * @param prioritized whether the entry is prioritized - * @param args extra parameters - * @return new entry of the resource + * @param name the unique name for the protected resource + * @param resourceType classification of the resource (e.g. Web or RPC) + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param prioritized whether the entry is prioritized + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) * @throws BlockException if the block criteria is met */ - AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType entryType, int count, boolean prioritized, + AsyncEntry asyncEntryWithType(String name, int resourceType, EntryType trafficType, int batchCount, + boolean prioritized, Object[] args) throws BlockException; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java index a7ad2aa19d..04feea0ccd 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/SphU.java @@ -16,25 +16,21 @@ package com.alibaba.csp.sentinel; import java.lang.reflect.Method; -import java.util.List; import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.Rule; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; -import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import com.alibaba.csp.sentinel.slots.system.SystemRule; -import com.alibaba.csp.sentinel.slots.system.SystemRuleManager; /** + *

The fundamental Sentinel API for recording statistics and performing rule checking for resources.

+ *

* Conceptually, physical or logical resource that need protection should be * surrounded by an entry. The requests to this resource will be blocked if any * criteria is met, eg. when any {@link Rule}'s threshold is exceeded. Once blocked, * a {@link BlockException} will be thrown. - * + *

*

- * To configure the criteria, we can use XXXRuleManager.loadRules() to add rules, eg. - * {@link FlowRuleManager#loadRules(List)}, {@link DegradeRuleManager#loadRules(List)}, - * {@link SystemRuleManager#loadRules(List)}. + * To configure the criteria, we can use XxxRuleManager.loadRules() to load rules. *

* *

@@ -79,10 +75,11 @@ public class SphU { private SphU() {} /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * * @param name the unique name of the protected resource - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(String name) throws BlockException { return Env.sph.entry(name, EntryType.OUT, 1, OBJECTS0); @@ -92,7 +89,8 @@ public static Entry entry(String name) throws BlockException { * Checking all {@link Rule}s about the protected method. * * @param method the protected method - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ public static Entry entry(Method method) throws BlockException { return Env.sph.entry(method, EntryType.OUT, 1, OBJECTS0); @@ -101,114 +99,120 @@ public static Entry entry(Method method) throws BlockException { /** * Checking all {@link Rule}s about the protected method. * - * @param method the protected method - * @param count tokens required - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @param method the protected method + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ - public static Entry entry(Method method, int count) throws BlockException { - return Env.sph.entry(method, EntryType.OUT, count, OBJECTS0); + public static Entry entry(Method method, int batchCount) throws BlockException { + return Env.sph.entry(method, EntryType.OUT, batchCount, OBJECTS0); } /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique string for the resource - * @param count tokens required - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @param name the unique string for the resource + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ - public static Entry entry(String name, int count) throws BlockException { - return Env.sph.entry(name, EntryType.OUT, count, OBJECTS0); + public static Entry entry(String name, int batchCount) throws BlockException { + return Env.sph.entry(name, EntryType.OUT, batchCount, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * - * @param method the protected method - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ - public static Entry entry(Method method, EntryType type) throws BlockException { - return Env.sph.entry(method, type, 1, OBJECTS0); + public static Entry entry(Method method, EntryType trafficType) throws BlockException { + return Env.sph.entry(method, trafficType, 1, OBJECTS0); } /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ - public static Entry entry(String name, EntryType type) throws BlockException { - return Env.sph.entry(name, type, 1, OBJECTS0); + public static Entry entry(String name, EntryType trafficType) throws BlockException { + return Env.sph.entry(name, trafficType, 1, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * - * @param method the protected method - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @param count tokens required - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ - public static Entry entry(Method method, EntryType type, int count) throws BlockException { - return Env.sph.entry(method, type, count, OBJECTS0); + public static Entry entry(Method method, EntryType trafficType, int batchCount) throws BlockException { + return Env.sph.entry(method, trafficType, batchCount, OBJECTS0); } /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @param count tokens required - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ - public static Entry entry(String name, EntryType type, int count) throws BlockException { - return Env.sph.entry(name, type, count, OBJECTS0); + public static Entry entry(String name, EntryType trafficType, int batchCount) throws BlockException { + return Env.sph.entry(name, trafficType, batchCount, OBJECTS0); } /** * Checking all {@link Rule}s about the protected method. * - * @param method the protected method - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @param count tokens required - * @param args the parameters of the method. - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @param method the protected method + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ - public static Entry entry(Method method, EntryType type, int count, Object... args) throws BlockException { - return Env.sph.entry(method, type, count, args); + public static Entry entry(Method method, EntryType trafficType, int batchCount, Object... args) + throws BlockException { + return Env.sph.entry(method, trafficType, batchCount, args); } /** - * Checking all {@link Rule}s about the resource. + * Record statistics and perform rule checking for the given resource. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @param count tokens required - * @param args extra parameters. - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) */ - public static Entry entry(String name, EntryType type, int count, Object... args) throws BlockException { - return Env.sph.entry(name, type, count, args); + public static Entry entry(String name, EntryType trafficType, int batchCount, Object... args) + throws BlockException { + return Env.sph.entry(name, trafficType, batchCount, args); } /** - * Checking all rules about the asynchronous resource. + * Record statistics and check all rules of the resource that indicates an async invocation. * * @param name the unique name of the protected resource - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 0.2.0 */ public static AsyncEntry asyncEntry(String name) throws BlockException { @@ -216,40 +220,43 @@ public static AsyncEntry asyncEntry(String name) throws BlockException { } /** - * Checking all {@link Rule}s about the asynchronous resource. + * Record statistics and check all rules of the resource that indicates an async invocation. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 0.2.0 */ - public static AsyncEntry asyncEntry(String name, EntryType type) throws BlockException { - return Env.sph.asyncEntry(name, type, 1, OBJECTS0); + public static AsyncEntry asyncEntry(String name, EntryType trafficType) throws BlockException { + return Env.sph.asyncEntry(name, trafficType, 1, OBJECTS0); } /** - * Checking all {@link Rule}s about the asynchronous resource. + * Record statistics and check all rules of the resource that indicates an async invocation. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @param count tokens required - * @param args extra parameters - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 0.2.0 */ - public static AsyncEntry asyncEntry(String name, EntryType type, int count, Object... args) throws BlockException { - return Env.sph.asyncEntry(name, type, count, args); + public static AsyncEntry asyncEntry(String name, EntryType trafficType, int batchCount, Object... args) + throws BlockException { + return Env.sph.asyncEntry(name, trafficType, batchCount, args); } /** - * Checking all {@link Rule}s related the resource. The entry is prioritized. + * Record statistics and perform rule checking for the given resource. The entry is prioritized. * * @param name the unique name for the protected resource - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.4.0 */ public static Entry entryWithPriority(String name) throws BlockException { @@ -257,99 +264,105 @@ public static Entry entryWithPriority(String name) throws BlockException { } /** - * Checking all {@link Rule}s related the resource. The entry is prioritized. + * Record statistics and perform rule checking for the given resource. The entry is prioritized. * - * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used - * to mark whether it can be blocked when the system is unstable, - * only inbound traffic could be blocked by {@link SystemRule} - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded. + * @param name the unique name for the protected resource + * @param trafficType the traffic type (inbound, outbound or internal). This is used + * to mark whether it can be blocked when the system is unstable, + * only inbound traffic could be blocked by {@link SystemRule} + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.4.0 */ - public static Entry entryWithPriority(String name, EntryType type) throws BlockException { - return Env.sph.entryWithPriority(name, type, 1, true); + public static Entry entryWithPriority(String name, EntryType trafficType) throws BlockException { + return Env.sph.entryWithPriority(name, trafficType, 1, true); } /** - * Record statistics and check all rules of the resource. + * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource * @param resourceType classification of the resource (e.g. Web or RPC) - * @param type the resource is an inbound or an outbound method. This is used + * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ - public static Entry entry(String name, int resourceType, EntryType type) throws BlockException { - return Env.sph.entryWithType(name, resourceType, type, 1, OBJECTS0); + public static Entry entry(String name, int resourceType, EntryType trafficType) throws BlockException { + return Env.sph.entryWithType(name, resourceType, trafficType, 1, OBJECTS0); } /** - * Record statistics and check all rules of the resource. + * Record statistics and perform rule checking for the given resource. * * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used + * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param resourceType classification of the resource (e.g. Web or RPC) - * @param args extra parameters. - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ - public static Entry entry(String name, int resourceType, EntryType type, Object[] args) + public static Entry entry(String name, int resourceType, EntryType trafficType, Object[] args) throws BlockException { - return Env.sph.entryWithType(name, resourceType, type, 1, args); + return Env.sph.entryWithType(name, resourceType, trafficType, 1, args); } /** - * Record statistics and check all rules of the resource. + * Record statistics and perform rule checking for the given resource that indicates an async invocation. * * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used + * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param resourceType classification of the resource (e.g. Web or RPC) - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ - public static AsyncEntry asyncEntry(String name, int resourceType, EntryType type) + public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType) throws BlockException { - return Env.sph.asyncEntryWithType(name, resourceType, type, 1, false, OBJECTS0); + return Env.sph.asyncEntryWithType(name, resourceType, trafficType, 1, false, OBJECTS0); } /** - * Record statistics and check all rules of the resource. + * Record statistics and perform rule checking for the given resource that indicates an async invocation. * * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used + * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param resourceType classification of the resource (e.g. Web or RPC) - * @param args extra parameters - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ - public static AsyncEntry asyncEntry(String name, int resourceType, EntryType type, Object[] args) + public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType, Object[] args) throws BlockException { - return Env.sph.asyncEntryWithType(name, resourceType, type, 1, false, args); + return Env.sph.asyncEntryWithType(name, resourceType, trafficType, 1, false, args); } /** - * Record statistics and check all rules of the resource. + * Record statistics and perform rule checking for the given resource that indicates an async invocation. * * @param name the unique name for the protected resource - * @param type the resource is an inbound or an outbound method. This is used + * @param trafficType the traffic type (inbound, outbound or internal). This is used * to mark whether it can be blocked when the system is unstable, * only inbound traffic could be blocked by {@link SystemRule} * @param resourceType classification of the resource (e.g. Web or RPC) - * @param acquireCount tokens required - * @param args extra parameters - * @throws BlockException if the block criteria is met, eg. when any rule's threshold is exceeded + * @param batchCount the amount of calls within the invocation (e.g. batchCount=2 means request for 2 tokens) + * @param args args for parameter flow control or customized slots + * @return the {@link Entry} of this invocation (used for mark the invocation complete and get context data) + * @throws BlockException if the block criteria is met (e.g. metric exceeded the threshold of any rules) * @since 1.7.0 */ - public static AsyncEntry asyncEntry(String name, int resourceType, EntryType type, int acquireCount, + public static AsyncEntry asyncEntry(String name, int resourceType, EntryType trafficType, int batchCount, Object[] args) throws BlockException { - return Env.sph.asyncEntryWithType(name, resourceType, type, acquireCount, false, args); + return Env.sph.asyncEntryWithType(name, resourceType, trafficType, batchCount, false, args); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Tracer.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Tracer.java index 4d0e6da3ae..299c80ee88 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Tracer.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/Tracer.java @@ -18,12 +18,9 @@ import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.context.ContextUtil; import com.alibaba.csp.sentinel.context.NullContext; -import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; -import com.alibaba.csp.sentinel.node.ClusterNode; -import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slots.block.BlockException; -import com.alibaba.csp.sentinel.metric.extension.MetricExtension; import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.function.Predicate; /** * This class is used to record other exceptions except block exception. @@ -36,35 +33,38 @@ public class Tracer { protected static Class[] traceClasses; protected static Class[] ignoreClasses; + protected static Predicate exceptionPredicate; + protected Tracer() {} /** - * Trace provided {@link Throwable} and increment exception count to entry in current context. + * Trace provided {@link Throwable} to the resource entry in current context. * * @param e exception to record */ public static void trace(Throwable e) { - trace(e, 1); + traceContext(e, ContextUtil.getContext()); } /** - * Trace provided {@link Throwable} and add exception count to entry in current context. + * Trace provided {@link Throwable} to current entry in current context. * * @param e exception to record * @param count exception count to add */ + @Deprecated public static void trace(Throwable e, int count) { traceContext(e, count, ContextUtil.getContext()); } /** - * Trace provided {@link Throwable} and add exception count to current entry in provided context. + * Trace provided {@link Throwable} to current entry of given entrance context. * * @param e exception to record - * @param count exception count to add - * @since 1.4.2 + * @param context target entrance context + * @since 1.8.0 */ - public static void traceContext(Throwable e, int count, Context context) { + public static void traceContext(Throwable e, Context context) { if (!shouldTrace(e)) { return; } @@ -72,54 +72,47 @@ public static void traceContext(Throwable e, int count, Context context) { if (context == null || context instanceof NullContext) { return; } - - DefaultNode curNode = (DefaultNode)context.getCurNode(); - traceExceptionToNode(e, count, context.getCurEntry(), curNode); - } - - /** - * Trace provided {@link Throwable} and increment exception count to provided entry. - * - * @param e exception to record - * @since 1.4.2 - */ - public static void traceEntry(Throwable e, Entry entry) { - traceEntry(e, 1, entry); + traceEntryInternal(e, context.getCurEntry()); } /** - * Trace provided {@link Throwable} and add exception count to provided entry. + * Trace provided {@link Throwable} and add exception count to current entry in provided context. * * @param e exception to record * @param count exception count to add * @since 1.4.2 */ - public static void traceEntry(Throwable e, int count, Entry entry) { + @Deprecated + public static void traceContext(Throwable e, int count, Context context) { if (!shouldTrace(e)) { return; } - if (entry == null || entry.getCurNode() == null) { + + if (context == null || context instanceof NullContext) { return; } - - DefaultNode curNode = (DefaultNode)entry.getCurNode(); - traceExceptionToNode(e, count, entry, curNode); + traceEntryInternal(e, context.getCurEntry()); } - private static void traceExceptionToNode(Throwable t, int count, Entry entry, DefaultNode curNode) { - if (curNode == null) { + /** + * Trace provided {@link Throwable} to the given resource entry. + * + * @param e exception to record + * @since 1.4.2 + */ + public static void traceEntry(Throwable e, Entry entry) { + if (!shouldTrace(e)) { return; } - for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { - m.addException(entry.getResourceWrapper().getName(), count, t); - } + traceEntryInternal(e, entry); + } - // clusterNode can be null when Constants.ON is false. - ClusterNode clusterNode = curNode.getClusterNode(); - if (clusterNode == null) { + private static void traceEntryInternal(/*@NeedToTrace*/ Throwable e, Entry entry) { + if (entry == null) { return; } - clusterNode.trace(t, count); + + entry.setError(e); } /** @@ -174,6 +167,24 @@ public static Class[] getExceptionsToIgnore() { return ignoreClasses; } + /** + * Get exception predicate + * @return the exception predicate. + */ + public static Predicate getExceptionPredicate() { + return exceptionPredicate; + } + + /** + * set an exception predicate which indicates the exception should be traced(return true) or ignored(return false) + * except for {@link BlockException} + * @param exceptionPredicate the exception predicate + */ + public static void setExceptionPredicate(Predicate exceptionPredicate) { + AssertUtil.notNull(exceptionPredicate, "exception predicate must not be null"); + Tracer.exceptionPredicate = exceptionPredicate; + } + private static void checkNotNull(Class[] classes) { AssertUtil.notNull(classes, "trace or ignore classes must not be null"); for (Class clazz : classes) { @@ -191,6 +202,10 @@ protected static boolean shouldTrace(Throwable t) { if (t == null || t instanceof BlockException) { return false; } + if (exceptionPredicate != null) { + return exceptionPredicate.test(t); + } + if (ignoreClasses != null) { for (Class clazz : ignoreClasses) { if (clazz != null && clazz.isAssignableFrom(t.getClass())) { @@ -208,4 +223,4 @@ protected static boolean shouldTrace(Throwable t) { } return true; } -} +} \ No newline at end of file diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java index 95f6584841..fe1ab396dd 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/annotation/SentinelResource.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2018 Alibaba Group Holding Ltd. + * Copyright 1999-2020 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,10 @@ * The annotation indicates a definition of Sentinel resource. * * @author Eric Zhao + * @author zhaoyuguang * @since 0.1.1 */ -@Target(ElementType.METHOD) +@Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Inherited public @interface SentinelResource { diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java index 29fa064a2f..b16b76f0e8 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResult.java @@ -30,14 +30,25 @@ public class TokenResult { private int remaining; private int waitInMs; + private long tokenId; + private Map attachments; - public TokenResult() {} + public TokenResult() { + } public TokenResult(Integer status) { this.status = status; } + public long getTokenId() { + return tokenId; + } + + public void setTokenId(long tokenId) { + this.tokenId = tokenId; + } + public Integer getStatus() { return status; } @@ -77,10 +88,11 @@ public TokenResult setAttachments(Map attachments) { @Override public String toString() { return "TokenResult{" + - "status=" + status + - ", remaining=" + remaining + - ", waitInMs=" + waitInMs + - ", attachments=" + attachments + - '}'; + "status=" + status + + ", remaining=" + remaining + + ", waitInMs=" + waitInMs + + ", attachments=" + attachments + + ", tokenId=" + tokenId + + '}'; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java index ff5dddf5f1..4ba1b2621d 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenResultStatus.java @@ -59,6 +59,15 @@ public final class TokenResultStatus { * Token acquire failed (strategy not available). */ public static final int NOT_AVAILABLE = 5; + /** + * Token is successfully released. + */ + public static final int RELEASE_OK = 6; + /** + * Token already is released before the request arrives. + */ + public static final int ALREADY_RELEASE=7; - private TokenResultStatus() {} + private TokenResultStatus() { + } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java index f1d062921d..9e8c7a49fe 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/TokenService.java @@ -44,4 +44,20 @@ public interface TokenService { * @return result of the token request */ TokenResult requestParamToken(Long ruleId, int acquireCount, Collection params); + + /** + * Request acquire concurrent tokens from remote token server. + * + * @param clientAddress the address of the request belong. + * @param ruleId ruleId the unique rule ID + * @param acquireCount token count to acquire + * @return result of the token request + */ + TokenResult requestConcurrentToken(String clientAddress,Long ruleId,int acquireCount); + /** + * Request release concurrent tokens from remote token server asynchronously. + * + * @param tokenId the unique token ID + */ + void releaseConcurrentToken(Long tokenId); } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java index def2f01642..85c9da2fac 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/client/TokenClientProvider.java @@ -44,8 +44,8 @@ private static void resolveTokenClientInstance() { "[TokenClientProvider] No existing cluster token client, cluster client mode will not be activated"); } else { client = resolvedClient; - RecordLog.info( - "[TokenClientProvider] Cluster token client resolved: " + client.getClass().getCanonicalName()); + RecordLog.info("[TokenClientProvider] Cluster token client resolved: {}", + client.getClass().getCanonicalName()); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java index 6b76af933e..2f55e8112c 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/cluster/server/EmbeddedClusterTokenServerProvider.java @@ -36,7 +36,8 @@ private static void resolveInstance() { RecordLog.warn("[EmbeddedClusterTokenServerProvider] No existing cluster token server, cluster server mode will not be activated"); } else { server = s; - RecordLog.info("[EmbeddedClusterTokenServerProvider] Cluster token server resolved: " + server.getClass().getCanonicalName()); + RecordLog.info("[EmbeddedClusterTokenServerProvider] Cluster token server resolved: {}", + server.getClass().getCanonicalName()); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java index a0d7c2268d..f86bdc4e21 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfig.java @@ -16,10 +16,10 @@ package com.alibaba.csp.sentinel.config; import com.alibaba.csp.sentinel.log.RecordLog; -import com.alibaba.csp.sentinel.util.AppNameUtil; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; +import java.io.File; import java.util.Map; import java.util.Properties; import java.util.concurrent.ConcurrentHashMap; @@ -30,6 +30,7 @@ * * @author leyou * @author Eric Zhao + * @author Lin Liang */ public final class SentinelConfig { @@ -41,9 +42,13 @@ public final class SentinelConfig { public static final int APP_TYPE_COMMON = 0; private static final Map props = new ConcurrentHashMap<>(); + private static int appType = APP_TYPE_COMMON; + private static String appName = ""; - public static final String APP_TYPE = "csp.sentinel.app.type"; + public static final String PROJECT_NAME_PROP_KEY = "project.name"; + public static final String APP_NAME_PROP_KEY = "csp.sentinel.app.name"; + public static final String APP_TYPE_PROP_KEY = "csp.sentinel.app.type"; public static final String CHARSET = "csp.sentinel.charset"; public static final String SINGLE_METRIC_FILE_SIZE = "csp.sentinel.metric.file.single.size"; public static final String TOTAL_METRIC_FILE_COUNT = "csp.sentinel.metric.file.total.count"; @@ -51,19 +56,19 @@ public final class SentinelConfig { public static final String STATISTIC_MAX_RT = "csp.sentinel.statistic.max.rt"; public static final String SPI_CLASSLOADER = "csp.sentinel.spi.classloader"; - static final String DEFAULT_CHARSET = "UTF-8"; - static final long DEFAULT_SINGLE_METRIC_FILE_SIZE = 1024 * 1024 * 50; - static final int DEFAULT_TOTAL_METRIC_FILE_COUNT = 6; - static final int DEFAULT_COLD_FACTOR = 3; - - public static final int DEFAULT_STATISTIC_MAX_RT = 4900; + public static final String DEFAULT_CHARSET = "UTF-8"; + public static final long DEFAULT_SINGLE_METRIC_FILE_SIZE = 1024 * 1024 * 50; + public static final int DEFAULT_TOTAL_METRIC_FILE_COUNT = 6; + public static final int DEFAULT_COLD_FACTOR = 3; + public static final int DEFAULT_STATISTIC_MAX_RT = 5000; static { try { initialize(); loadProps(); + resolveAppName(); resolveAppType(); - RecordLog.info("[SentinelConfig] Application type resolved: " + appType); + RecordLog.info("[SentinelConfig] Application type resolved: {}", appType); } catch (Throwable ex) { RecordLog.warn("[SentinelConfig] Failed to initialize", ex); ex.printStackTrace(); @@ -72,7 +77,7 @@ public final class SentinelConfig { private static void resolveAppType() { try { - String type = getConfig(APP_TYPE); + String type = getConfig(APP_TYPE_PROP_KEY); if (type == null) { appType = APP_TYPE_COMMON; return; @@ -134,7 +139,7 @@ public static void setConfigIfAbsent(String key, String value) { } public static String getAppName() { - return AppNameUtil.getAppName(); + return appName; } /** @@ -189,9 +194,7 @@ public static int coldFactor() { } /** - *

Get the max RT value that Sentinel could accept.

- *

Response time that exceeds {@code statisticMaxRt} will be recorded as this value. - * The default value is {@link #DEFAULT_STATISTIC_MAX_RT}.

+ *

Get the max RT value that Sentinel could accept for system BBR strategy.

* * @return the max allowed RT value * @since 1.4.1 @@ -204,12 +207,85 @@ public static int statisticMaxRt() { } return Integer.parseInt(v); } catch (Throwable throwable) { - RecordLog.warn("[SentinelConfig] Invalid statisticMaxRt value: {0}, using the default value instead: " + RecordLog.warn("[SentinelConfig] Invalid statisticMaxRt value: {}, using the default value instead: " + DEFAULT_STATISTIC_MAX_RT, v, throwable); SentinelConfig.setConfig(STATISTIC_MAX_RT, String.valueOf(DEFAULT_STATISTIC_MAX_RT)); return DEFAULT_STATISTIC_MAX_RT; } } + /** + * Function for resolving project name. The order is elaborated below: + * + *
    + *
  1. Resolve the value from {@code CSP_SENTINEL_APP_NAME} system environment;
  2. + *
  3. Resolve the value from {@code csp.sentinel.app.name} system property;
  4. + *
  5. Resolve the value from {@code project.name} system property (for compatibility);
  6. + *
  7. Resolve the value from {@code sun.java.command} system property, then remove path, arguments and ".jar" or ".JAR" + * suffix, use the result as app name. Note that whitespace in file name or path is not allowed, or a + * wrong app name may be gotten, For example: + *

    + * + * "test.Main" -> test.Main
    + * "/target/test.Main" -> test.Main
    + * "/target/test.Main args1" -> test.Main
    + * "Main.jar" -> Main
    + * "/target/Main.JAR args1" -> Main
    + * "Mai n.jar" -> Mai // whitespace in file name is not allowed
    + *
    + *

    + *
  8. + *
+ */ + private static void resolveAppName() { + // Priority: system env -> csp.sentinel.app.name -> project.name -> main class (or jar) name + String envKey = toEnvKey(APP_NAME_PROP_KEY); + String n = System.getenv(envKey); + if (!StringUtil.isBlank(n)) { + appName = n; + RecordLog.info("App name resolved from system env {}: {}", envKey, appName); + return; + } + n = props.get(APP_NAME_PROP_KEY); + if (!StringUtil.isBlank(n)) { + appName = n; + RecordLog.info("App name resolved from property {}: {}", APP_NAME_PROP_KEY, appName); + return; + } + n = props.get(PROJECT_NAME_PROP_KEY); + if (!StringUtil.isBlank(n)) { + appName = n; + RecordLog.info("App name resolved from property {}: {}", PROJECT_NAME_PROP_KEY, appName); + return; + } + // Parse sun.java.command property by default. + String command = System.getProperty("sun.java.command"); + if (StringUtil.isBlank(command)) { + RecordLog.warn("Cannot resolve default appName from property sun.java.command"); + return; + } + command = command.split("\\s")[0]; + String separator = File.separator; + if (command.contains(separator)) { + String[] strs; + if ("\\".equals(separator)) { + // Handle separator in Windows. + strs = command.split("\\\\"); + } else { + strs = command.split(separator); + } + command = strs[strs.length - 1]; + } + if (command.toLowerCase().endsWith(".jar")) { + command = command.substring(0, command.length() - 4); + } + appName = command; + RecordLog.info("App name resolved from default: {}", appName); + } + + private static String toEnvKey(/*@NotBlank*/ String propKey) { + return propKey.toUpperCase().replace('.', '_'); + } + private SentinelConfig() {} } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java index 67cd4805df..e267ed7742 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/config/SentinelConfigLoader.java @@ -38,9 +38,6 @@ public final class SentinelConfigLoader { public static final String SENTINEL_CONFIG_ENV_KEY = "CSP_SENTINEL_CONFIG_FILE"; public static final String SENTINEL_CONFIG_PROPERTY_KEY = "csp.sentinel.config.file"; - private static final String DIR_NAME = "logs" + File.separator + "csp"; - private static final String USER_HOME = "user.home"; - private static final String DEFAULT_SENTINEL_CONFIG_FILE = "classpath:sentinel.properties"; private static Properties properties = new Properties(); @@ -64,19 +61,8 @@ private static void load() { } Properties p = ConfigUtil.loadProperties(fileName); - - // Compatible with legacy config file path. - if (p == null) { - String path = addSeparator(System.getProperty(USER_HOME)) + DIR_NAME + File.separator; - fileName = path + AppNameUtil.getAppName() + ".properties"; - File file = new File(fileName); - if (file.exists()) { - p = ConfigUtil.loadProperties(fileName); - } - } - if (p != null && !p.isEmpty()) { - RecordLog.info("[SentinelConfigLoader] Loading Sentinel config from " + fileName); + RecordLog.info("[SentinelConfigLoader] Loading Sentinel config from {}", fileName); properties.putAll(p); } @@ -86,7 +72,7 @@ private static void load() { String oldConfigValue = properties.getProperty(configKey); properties.put(configKey, newConfigValue); if (oldConfigValue != null) { - RecordLog.info("[SentinelConfigLoader] JVM parameter overrides {0}: {1} -> {2}", + RecordLog.info("[SentinelConfigLoader] JVM parameter overrides {}: {} -> {}", configKey, oldConfigValue, newConfigValue); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextUtil.java index 9af5f76292..5e0ae0236f 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/context/ContextUtil.java @@ -127,8 +127,8 @@ protected static Context trueEnter(String name, String origin) { setNullContext(); return NULL_CONTEXT; } else { + LOCK.lock(); try { - LOCK.lock(); node = contextNameNodeMap.get(name); if (node == null) { if (contextNameNodeMap.size() > Constants.MAX_CONTEXT_NAME_SIZE) { diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java index 30a7cbade3..eefc6d4fa3 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/eagleeye/FastDateFormat.java @@ -75,7 +75,7 @@ String formatWithoutMs(long timestamp) { private SimpleDateFormat createSimpleDateFormat() { SimpleDateFormat fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); - fmt.setTimeZone(TimeZone.getTimeZone("GMT+8:00")); + fmt.setTimeZone(TimeZone.getDefault()); return fmt; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java index e68fd599e3..8e63889db0 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/init/InitExecutor.java @@ -46,13 +46,13 @@ public static void doInit() { ServiceLoader loader = ServiceLoaderUtil.getServiceLoader(InitFunc.class); List initList = new ArrayList(); for (InitFunc initFunc : loader) { - RecordLog.info("[InitExecutor] Found init func: " + initFunc.getClass().getCanonicalName()); + RecordLog.info("[InitExecutor] Found init func: {}", initFunc.getClass().getCanonicalName()); insertSorted(initList, initFunc); } for (OrderWrapper w : initList) { w.func.init(); - RecordLog.info(String.format("[InitExecutor] Executing %s with order %d", - w.func.getClass().getCanonicalName(), w.order)); + RecordLog.info("[InitExecutor] Executing {} with order {}", + w.func.getClass().getCanonicalName(), w.order); } } catch (Exception ex) { RecordLog.warn("[InitExecutor] WARN: Initialization failed", ex); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java index a237dde7d1..091ce15eae 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogBase.java @@ -15,19 +15,14 @@ */ package com.alibaba.csp.sentinel.log; -import com.alibaba.csp.sentinel.util.PidUtil; import java.io.File; -import java.io.IOException; import java.util.Properties; -import java.util.logging.Handler; -import java.util.logging.Level; -import java.util.logging.Logger; import static com.alibaba.csp.sentinel.util.ConfigUtil.addSeparator; /** - *

The base class for logging.

+ *

The base config class for logging.

* *

* The default log base directory is {@code ${user.home}/logs/csp/}. We can use the {@link #LOG_DIR} @@ -36,7 +31,8 @@ * In this case, {@link #LOG_NAME_USE_PID} property could be configured as "true" to turn on this switch. *

* - * @author leyou + * @author Carpenter Lee + * @author Eric Zhao */ public class LogBase { @@ -66,15 +62,15 @@ public class LogBase { static { try { - initialize(); + initializeDefault(); loadProperties(); } catch (Throwable t) { - System.err.println("[LogBase] FATAL ERROR when initializing log class"); + System.err.println("[LogBase] FATAL ERROR when initializing logging config"); t.printStackTrace(); } } - private static void initialize() { + private static void initializeDefault() { logNameUsePid = false; logOutputType = LOG_OUTPUT_TYPE_FILE; logBaseDir = addSeparator(System.getProperty(USER_HOME)) + DIR_NAME + File.separator; @@ -88,10 +84,10 @@ private static void loadProperties() { if (!LOG_OUTPUT_TYPE_FILE.equalsIgnoreCase(logOutputType) && !LOG_OUTPUT_TYPE_CONSOLE.equalsIgnoreCase(logOutputType)) { logOutputType = LOG_OUTPUT_TYPE_FILE; } - System.out.println("INFO: log output type is: " + logOutputType); + System.out.println("INFO: Sentinel log output type is: " + logOutputType); logCharSet = properties.getProperty(LOG_CHARSET) == null ? logCharSet : properties.getProperty(LOG_CHARSET); - System.out.println("INFO: log charset is: " + logCharSet); + System.out.println("INFO: Sentinel log charset is: " + logCharSet); logBaseDir = properties.getProperty(LOG_DIR) == null ? logBaseDir : properties.getProperty(LOG_DIR); @@ -99,15 +95,14 @@ private static void loadProperties() { File dir = new File(logBaseDir); if (!dir.exists()) { if (!dir.mkdirs()) { - System.err.println("ERROR: create log base dir error: " + logBaseDir); + System.err.println("ERROR: create Sentinel log base directory error: " + logBaseDir); } } - System.out.println("INFO: log base dir is: " + logBaseDir); - + System.out.println("INFO: Sentinel log base directory is: " + logBaseDir); String usePid = properties.getProperty(LOG_NAME_USE_PID); logNameUsePid = "true".equalsIgnoreCase(usePid); - System.out.println("INFO: log name use pid is: " + logNameUsePid); + System.out.println("INFO: Sentinel log name use pid is: " + logNameUsePid); } @@ -147,64 +142,4 @@ public static String getLogCharset() { return logCharSet; } - protected static void log(Logger logger, Handler handler, Level level, String detail, Object... params) { - if (detail == null) { - return; - } - LoggerUtils.disableOtherHandlers(logger, handler); - - FormattingTuple formattingTuple = MessageFormatter.arrayFormat(detail, params); - String message = formattingTuple.getMessage(); - logger.log(level, message); - } - - protected static void log(Logger logger, Handler handler, Level level, String detail, Throwable throwable) { - if (detail == null) { - return; - } - LoggerUtils.disableOtherHandlers(logger, handler); - logger.log(level, detail, throwable); - } - - - protected static Handler makeLogger(String logName, Logger heliumRecordLog) { - CspFormatter formatter = new CspFormatter(); - - Handler handler = null; - - // Create handler according to logOutputType, set formatter to CspFormatter, set encoding to LOG_CHARSET - switch (logOutputType) { - case LOG_OUTPUT_TYPE_FILE: - String fileName = LogBase.getLogBaseDir() + logName; - if (isLogNameUsePid()) { - fileName += ".pid" + PidUtil.getPid(); - } - try { - handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 4, true); - handler.setFormatter(formatter); - handler.setEncoding(logCharSet); - } catch (IOException e) { - e.printStackTrace(); - } - break; - case LOG_OUTPUT_TYPE_CONSOLE: - try { - handler = new ConsoleHandler(); - handler.setFormatter(formatter); - handler.setEncoding(logCharSet); - } catch (IOException e) { - e.printStackTrace(); - } - break; - default: - break; - } - - if (handler != null) { - LoggerUtils.disableOtherHandlers(heliumRecordLog, handler); - } - heliumRecordLog.setLevel(Level.ALL); - return handler; - } - } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogTarget.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogTarget.java index 0dc3ef975b..2a2e025a0f 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogTarget.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogTarget.java @@ -19,14 +19,16 @@ /** * @author xue8 + * @since 1.7.2 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) @Documented public @interface LogTarget { /** - * Returns the kinds of log type. - * @return Returns the kinds of log type + * Returns the logger name. + * + * @return the logger name. Record logger by default */ - LogType value() default LogType.RECORD_LOG; + String value() default RecordLog.LOGGER_NAME; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/Logger.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/Logger.java index 8b4d580944..75e5d701d2 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/Logger.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/Logger.java @@ -16,18 +16,19 @@ package com.alibaba.csp.sentinel.log; /** - * Provide logger SPI interface. - * The default implementation is {@link java.util.logging}. - * - * Notice, the placeholder only supports the most popular placeholder convention (slf4j). - * So, if you're not using slf4j, you should create adapters compatible with placeholders "{}". + *

The universal logger SPI interface.

+ *

Notice: the placeholder only supports the most popular placeholder convention (slf4j). + * So, if you're not using slf4j, you should create adapters compatible with placeholders "{}".

* * @author xue8 + * @since 1.7.2 */ public interface Logger { + /** * Log a message at the INFO level according to the specified format * and arguments. + * * @param format the format string * @param arguments a list of arguments */ @@ -45,6 +46,7 @@ public interface Logger { /** * Log a message at the WARN level according to the specified format * and arguments. + * * @param format the format string * @param arguments a list of arguments */ @@ -62,6 +64,7 @@ public interface Logger { /** * Log a message at the TRACE level according to the specified format * and arguments. + * * @param format the format string * @param arguments a list of arguments */ @@ -79,6 +82,7 @@ public interface Logger { /** * Log a message at the DEBUG level according to the specified format * and arguments. + * * @param format the format string * @param arguments a list of arguments */ @@ -96,6 +100,7 @@ public interface Logger { /** * Log a message at the ERROR level according to the specified format * and arguments. + * * @param format the format string * @param arguments a list of arguments */ diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LoggerSpiProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LoggerSpiProvider.java new file mode 100644 index 0000000000..7bdb71b68f --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LoggerSpiProvider.java @@ -0,0 +1,72 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.log; + +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; + +import com.alibaba.csp.sentinel.util.StringUtil; + +/** + * SPI provider of Sentinel {@link Logger}. + * + * @author Eric Zhao + * @since 1.7.2 + */ +public final class LoggerSpiProvider { + + private static final Map LOGGER_MAP = new HashMap<>(); + + static { + // NOTE: this class SHOULD NOT depend on any other Sentinel classes + // except the util classes to avoid circular dependency. + try { + resolveLoggers(); + } catch (Throwable t) { + System.err.println("Failed to resolve Sentinel Logger SPI"); + t.printStackTrace(); + } + } + + public static Logger getLogger(String name) { + if (name == null) { + return null; + } + return LOGGER_MAP.get(name); + } + + private static void resolveLoggers() { + // NOTE: Here we cannot use {@code SpiLoader} directly because it depends on the RecordLog. + ServiceLoader loggerLoader = ServiceLoader.load(Logger.class); + + for (Logger logger : loggerLoader) { + LogTarget annotation = logger.getClass().getAnnotation(LogTarget.class); + if (annotation == null) { + continue; + } + String name = annotation.value(); + // Load first encountered logger if multiple loggers are associated with the same name. + if (StringUtil.isNotBlank(name) && !LOGGER_MAP.containsKey(name)) { + LOGGER_MAP.put(name, logger); + System.out.println("Sentinel Logger SPI loaded for <" + name + ">: " + + logger.getClass().getCanonicalName()); + } + } + } + + private LoggerSpiProvider() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LoggerUtils.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LoggerUtils.java deleted file mode 100755 index 2fe0d468ba..0000000000 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LoggerUtils.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.csp.sentinel.log; - -import java.util.logging.Handler; -import java.util.logging.Logger; - -/** - * Util class for logger. - */ -class LoggerUtils { - - /** - * Remove all current handlers from the logger and attach it with the given log handler. - * - * @param logger logger - * @param handler the log handler - */ - static void disableOtherHandlers(Logger logger, Handler handler) { - if (logger == null) { - return; - } - - synchronized (logger) { - Handler[] handlers = logger.getHandlers(); - if (handlers == null) { - return; - } - if (handlers.length == 1 && handlers[0].equals(handler)) { - return; - } - - logger.setUseParentHandlers(false); - // Remove all current handlers. - for (Handler h : handlers) { - logger.removeHandler(h); - } - // Attach the given handler. - logger.addHandler(handler); - } - } -} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/RecordLog.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/RecordLog.java index da7bb2ed00..19ee01fe36 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/RecordLog.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/RecordLog.java @@ -15,80 +15,74 @@ */ package com.alibaba.csp.sentinel.log; -import java.util.Iterator; -import java.util.ServiceLoader; +import com.alibaba.csp.sentinel.log.jul.JavaLoggingAdapter; -/*** - * The basic logger for vital events. +/** + * The basic biz logger of Sentinel. * * @author youji.zj + * @author Eric Zhao */ -public class RecordLog extends LogBase { - private static com.alibaba.csp.sentinel.log.Logger log = null; +public class RecordLog { + + public static final String LOGGER_NAME = "sentinelRecordLogger"; + public static final String DEFAULT_LOG_FILENAME = "sentinel-record.log"; + + private static com.alibaba.csp.sentinel.log.Logger logger = null; static { - ServiceLoader load = ServiceLoader.load(Logger.class); - Logger logger = null; - Iterator iterator = load.iterator(); - while (iterator.hasNext()) { - Logger next = iterator.next(); - LogTarget annotation = next.getClass().getAnnotation(LogTarget.class); - if (annotation == null) { - continue; + try { + // Load user-defined logger implementation first. + logger = LoggerSpiProvider.getLogger(LOGGER_NAME); + if (logger == null) { + // If no customized loggers are provided, we use the default logger based on JUL. + logger = new JavaLoggingAdapter(LOGGER_NAME, DEFAULT_LOG_FILENAME); } - String value = annotation.value().name(); - if (value.equals(LogType.RECORD_LOG.name())) { - logger = next; - break; - } - } - // Use user implementations. - if (logger != null) { - log = logger; - } else { - // Use default implementations. - log = new RecordLogLogging(); + } catch (Throwable t) { + System.err.println("Error: failed to initialize Sentinel RecordLog"); + t.printStackTrace(); } } public static void info(String format, Object... arguments) { - log.info(format, arguments); + logger.info(format, arguments); } public static void info(String msg, Throwable e) { - log.info(msg, e); + logger.info(msg, e); } public static void warn(String format, Object... arguments) { - log.warn(format, arguments); + logger.warn(format, arguments); } public static void warn(String msg, Throwable e) { - log.warn(msg, e); + logger.warn(msg, e); } public static void trace(String format, Object... arguments) { - log.trace(format, arguments); + logger.trace(format, arguments); } public static void trace(String msg, Throwable e) { - log.trace(msg, e); + logger.trace(msg, e); } public static void debug(String format, Object... arguments) { - log.debug(format, arguments); + logger.debug(format, arguments); } public static void debug(String msg, Throwable e) { - log.debug(msg, e); + logger.debug(msg, e); } public static void error(String format, Object... arguments) { - log.error(format, arguments); + logger.error(format, arguments); } public static void error(String msg, Throwable e) { - log.error(msg, e); + logger.error(msg, e); } + private RecordLog() {} } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/BaseJulLogger.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/BaseJulLogger.java new file mode 100644 index 0000000000..0490efd299 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/BaseJulLogger.java @@ -0,0 +1,128 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.log.jul; + +import java.io.IOException; +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.alibaba.csp.sentinel.log.LogBase; +import com.alibaba.csp.sentinel.util.PidUtil; + +import static com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE_CONSOLE; +import static com.alibaba.csp.sentinel.log.LogBase.LOG_OUTPUT_TYPE_FILE; + +/** + * The default logger based on java.util.logging. + * + * @author Eric Zhao + * @since 1.7.2 + */ +public class BaseJulLogger { + + protected void log(Logger logger, Handler handler, Level level, String detail, Object... params) { + if (detail == null) { + return; + } + disableOtherHandlers(logger, handler); + + // Compatible with slf4j placeholder format "{}". + FormattingTuple formattingTuple = MessageFormatter.arrayFormat(detail, params); + String message = formattingTuple.getMessage(); + logger.log(level, message); + } + + protected void log(Logger logger, Handler handler, Level level, String detail, Throwable throwable) { + if (detail == null) { + return; + } + disableOtherHandlers(logger, handler); + logger.log(level, detail, throwable); + } + + protected Handler makeLoggingHandler(String logName, Logger heliumRecordLog) { + CspFormatter formatter = new CspFormatter(); + String logCharSet = LogBase.getLogCharset(); + Handler handler = null; + + // Create handler according to logOutputType, set formatter to CspFormatter, set encoding to LOG_CHARSET + switch (LogBase.getLogOutputType()) { + case LOG_OUTPUT_TYPE_FILE: + String fileName = LogBase.getLogBaseDir() + logName; + if (LogBase.isLogNameUsePid()) { + fileName += ".pid" + PidUtil.getPid(); + } + try { + handler = new DateFileLogHandler(fileName + ".%d", 1024 * 1024 * 200, 4, true); + handler.setFormatter(formatter); + handler.setEncoding(logCharSet); + } catch (IOException e) { + e.printStackTrace(); + } + break; + case LOG_OUTPUT_TYPE_CONSOLE: + try { + handler = new ConsoleHandler(); + handler.setFormatter(formatter); + handler.setEncoding(logCharSet); + } catch (IOException e) { + e.printStackTrace(); + } + break; + default: + break; + } + + if (handler != null) { + disableOtherHandlers(heliumRecordLog, handler); + } + + // Set log level to INFO by default + heliumRecordLog.setLevel(Level.INFO); + return handler; + } + + /** + * Remove all current handlers from the logger and attach it with the given log handler. + * + * @param logger logger + * @param handler the log handler + */ + static void disableOtherHandlers(Logger logger, Handler handler) { + if (logger == null) { + return; + } + + synchronized (logger) { + Handler[] handlers = logger.getHandlers(); + if (handlers == null) { + return; + } + if (handlers.length == 1 && handlers[0].equals(handler)) { + return; + } + + logger.setUseParentHandlers(false); + // Remove all current handlers. + for (Handler h : handlers) { + logger.removeHandler(h); + } + // Attach the given handler. + logger.addHandler(handler); + } + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java similarity index 98% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java index 7654fa2a03..589b094961 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/ConsoleHandler.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.csp.sentinel.log; +package com.alibaba.csp.sentinel.log.jul; import java.io.UnsupportedEncodingException; import java.util.logging.*; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/CspFormatter.java similarity index 97% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/CspFormatter.java index 5d24c21217..b93f11af03 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/CspFormatter.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/CspFormatter.java @@ -10,7 +10,7 @@ * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ -package com.alibaba.csp.sentinel.log; +package com.alibaba.csp.sentinel.log.jul; import java.io.PrintWriter; import java.io.StringWriter; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/DateFileLogHandler.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java similarity index 99% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/DateFileLogHandler.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java index 347882208b..30a5ebeeec 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/DateFileLogHandler.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/DateFileLogHandler.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.csp.sentinel.log; +package com.alibaba.csp.sentinel.log.jul; import java.io.File; import java.io.IOException; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/FormattingTuple.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/FormattingTuple.java similarity index 90% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/FormattingTuple.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/FormattingTuple.java index cfaa1e64f6..acba7304b7 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/FormattingTuple.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/FormattingTuple.java @@ -14,12 +14,8 @@ * limitations under the License. */ -/** - * Copyright notice - * This code copy from SLF4J which licensed under the MIT License. - * - */ -package com.alibaba.csp.sentinel.log; +// Copyright notice: This code was copied from SLF4J which licensed under the MIT License. +package com.alibaba.csp.sentinel.log.jul; /** * Holds the results of formatting done by {@link MessageFormatter}. diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/JavaLoggingAdapter.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/JavaLoggingAdapter.java new file mode 100644 index 0000000000..a31a132b2b --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/JavaLoggingAdapter.java @@ -0,0 +1,104 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.log.jul; + +import java.util.logging.Handler; + +import com.alibaba.csp.sentinel.log.Logger; +import com.alibaba.csp.sentinel.util.AssertUtil; + +/** + * JUL adapter for Sentinel {@link Logger} SPI. + * + * @author Eric Zhao + * @since 1.7.2 + */ +public class JavaLoggingAdapter extends BaseJulLogger implements Logger { + + private final String loggerName; + private final String fileNamePattern; + + private final java.util.logging.Logger julLogger; + private final Handler logHandler; + + public JavaLoggingAdapter(String loggerName, String fileNamePattern) { + AssertUtil.assertNotBlank(loggerName, "loggerName cannot be blank"); + AssertUtil.assertNotBlank(fileNamePattern, "fileNamePattern cannot be blank"); + this.loggerName = loggerName; + this.fileNamePattern = fileNamePattern; + + this.julLogger = java.util.logging.Logger.getLogger(loggerName); + this.logHandler = makeLoggingHandler(fileNamePattern, julLogger); + } + + @Override + public void info(String format, Object... arguments) { + log(julLogger, logHandler, Level.INFO, format, arguments); + } + + @Override + public void info(String msg, Throwable e) { + log(julLogger, logHandler, Level.INFO, msg, e); + } + + @Override + public void warn(String format, Object... arguments) { + log(julLogger, logHandler, Level.WARNING, format, arguments); + } + + @Override + public void warn(String msg, Throwable e) { + log(julLogger, logHandler, Level.WARNING, msg, e); + } + + @Override + public void trace(String format, Object... arguments) { + log(julLogger, logHandler, Level.TRACE, format, arguments); + } + + @Override + public void trace(String msg, Throwable e) { + log(julLogger, logHandler, Level.TRACE, msg, e); + } + + @Override + public void debug(String format, Object... arguments) { + log(julLogger, logHandler, Level.DEBUG, format, arguments); + } + + @Override + public void debug(String msg, Throwable e) { + log(julLogger, logHandler, Level.DEBUG, msg, e); + } + + @Override + public void error(String format, Object... arguments) { + log(julLogger, logHandler, Level.ERROR, format, arguments); + } + + @Override + public void error(String msg, Throwable e) { + log(julLogger, logHandler, Level.ERROR, msg, e); + } + + public String getLoggerName() { + return loggerName; + } + + public String getFileNamePattern() { + return fileNamePattern; + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/Level.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/Level.java similarity index 94% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/Level.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/Level.java index d64c4bb076..6dcc9f05b9 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/Level.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/Level.java @@ -13,10 +13,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.csp.sentinel.log; +package com.alibaba.csp.sentinel.log.jul; /** - * Logging levels + * JUL logging levels. + * * @author xue8 */ public class Level extends java.util.logging.Level { diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/MessageFormatter.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/MessageFormatter.java similarity index 98% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/MessageFormatter.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/MessageFormatter.java index e2c30f2edd..2d038bd794 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/MessageFormatter.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/jul/MessageFormatter.java @@ -14,18 +14,8 @@ * limitations under the License. */ -/** - * Copyright notice - * This code copy from SLF4J which licensed under the MIT License. - * - */ - -/** - * Copyright notice - * This code copy from SLF4J which licensed under the MIT License. - * - */ -package com.alibaba.csp.sentinel.log; +// Copyright notice: This code was copied from SLF4J which licensed under the MIT License. +package com.alibaba.csp.sentinel.log.jul; // contributors: lizongbo: proposed special treatment of array parameter values diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/AdvancedMetricExtension.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/AdvancedMetricExtension.java new file mode 100644 index 0000000000..9bd5102f93 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/AdvancedMetricExtension.java @@ -0,0 +1,74 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.metric.extension; + +import com.alibaba.csp.sentinel.EntryType; +import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * Extended {@link MetricExtension} extending input parameters of each metric + * collection method with {@link EntryType}. + * + * @author bill_yip + * @author Eric Zhao + * @since 1.8.0 + */ +public interface AdvancedMetricExtension extends MetricExtension { + + /** + * Add current pass count of the resource name. + * + * @param rw resource representation (including resource name, traffic type, etc.) + * @param batchCount count to add + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void onPass(ResourceWrapper rw, int batchCount, Object[] args); + + /** + * Add current block count of the resource name. + * + * @param rw resource representation (including resource name, traffic type, etc.) + * @param batchCount count to add + * @param origin the origin of caller (if present) + * @param e the associated {@code BlockException} + * @param args additional arguments of the resource, eg. if the resource is a method name, + * the args will be the parameters of the method. + */ + void onBlocked(ResourceWrapper rw, int batchCount, String origin, BlockException e, + Object[] args); + + /** + * Add current completed count of the resource name. + * + * @param rw resource representation (including resource name, traffic type, etc.) + * @param batchCount count to add + * @param rt response time of current invocation + * @param args additional arguments of the resource + */ + void onComplete(ResourceWrapper rw, long rt, int batchCount, Object[] args); + + /** + * Add current exception count of the resource name. + * + * @param rw resource representation (including resource name, traffic type, etc.) + * @param batchCount count to add + * @param throwable exception related. + * @param args additional arguments of the resource + */ + void onError(ResourceWrapper rw, Throwable throwable, int batchCount, Object[] args); +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricCallbackInit.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricCallbackInit.java index 0ffef9e12f..01c73d6a70 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricCallbackInit.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricCallbackInit.java @@ -1,3 +1,18 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.alibaba.csp.sentinel.metric.extension; import com.alibaba.csp.sentinel.init.InitFunc; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtension.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtension.java index 8f21328b8f..88e6cc8968 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtension.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtension.java @@ -1,3 +1,18 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.alibaba.csp.sentinel.metric.extension; import com.alibaba.csp.sentinel.slots.block.BlockException; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtensionProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtensionProvider.java index 9980a6e137..75da7fe78e 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtensionProvider.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/MetricExtensionProvider.java @@ -1,3 +1,18 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.alibaba.csp.sentinel.metric.extension; import java.util.ArrayList; @@ -7,7 +22,7 @@ import com.alibaba.csp.sentinel.util.SpiLoader; /** - * Get all {@link MetricExtension}s via SPI. + * Get all {@link MetricExtension} via SPI. * * @author Carpenter Lee * @since 1.6.1 @@ -22,18 +37,19 @@ public class MetricExtensionProvider { private static void resolveInstance() { List extensions = SpiLoader.loadInstanceList(MetricExtension.class); - if (extensions == null) { - RecordLog.warn("[MetricExtensionProvider] WARN: No existing MetricExtension found"); + if (extensions.isEmpty()) { + RecordLog.info("[MetricExtensionProvider] No existing MetricExtension found"); } else { metricExtensions.addAll(extensions); - RecordLog.info("[MetricExtensionProvider] MetricExtension resolved, size=" + extensions.size()); + RecordLog.info("[MetricExtensionProvider] MetricExtension resolved, size={}", extensions.size()); } } /** - * Get all metric extensions. DO NOT MODIFY the returned list, use {@link #addMetricExtension(MetricExtension)}. + *

Get all registered metric extensions.

+ *

DO NOT MODIFY the returned list, use {@link #addMetricExtension(MetricExtension)}.

* - * @return all metric extensions. + * @return all registered metric extensions */ public static List getMetricExtensions() { return metricExtensions; @@ -42,7 +58,7 @@ public static List getMetricExtensions() { /** * Add metric extension. *

- * Not that this method is NOT thread safe. + * Note that this method is NOT thread safe. *

* * @param metricExtension the metric extension to add. diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallback.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallback.java index e5409970ee..147d6ad8a1 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallback.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallback.java @@ -1,8 +1,24 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.alibaba.csp.sentinel.metric.extension.callback; import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; +import com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension; import com.alibaba.csp.sentinel.metric.extension.MetricExtension; +import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; @@ -15,20 +31,29 @@ * @since 1.6.1 */ public class MetricEntryCallback implements ProcessorSlotEntryCallback { + @Override - public void onPass(Context context, ResourceWrapper resourceWrapper, DefaultNode param, - int count, Object... args) throws Exception { + public void onPass(Context context, ResourceWrapper rw, DefaultNode param, int count, Object... args) + throws Exception { for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { - m.increaseThreadNum(resourceWrapper.getName(), args); - m.addPass(resourceWrapper.getName(), count, args); + if (m instanceof AdvancedMetricExtension) { + ((AdvancedMetricExtension) m).onPass(rw, count, args); + } else { + m.increaseThreadNum(rw.getName(), args); + m.addPass(rw.getName(), count, args); + } } } @Override - public void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, - DefaultNode param, int count, Object... args) { + public void onBlocked(BlockException ex, Context context, ResourceWrapper resourceWrapper, DefaultNode param, + int count, Object... args) { for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { - m.addBlock(resourceWrapper.getName(), count, context.getOrigin(), ex, args); + if (m instanceof AdvancedMetricExtension) { + ((AdvancedMetricExtension) m).onBlocked(resourceWrapper, count, context.getOrigin(), ex, args); + } else { + m.addBlock(resourceWrapper.getName(), count, context.getOrigin(), ex, args); + } } } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallback.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallback.java index 334a8f3a1b..a90211d999 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallback.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallback.java @@ -1,8 +1,25 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.alibaba.csp.sentinel.metric.extension.callback; +import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; +import com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension; import com.alibaba.csp.sentinel.metric.extension.MetricExtension; +import com.alibaba.csp.sentinel.metric.extension.MetricExtensionProvider; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.util.TimeUtil; @@ -11,17 +28,42 @@ * Metric extension exit callback. * * @author Carpenter Lee + * @author Eric Zhao * @since 1.6.1 */ public class MetricExitCallback implements ProcessorSlotExitCallback { + @Override - public void onExit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { + public void onExit(Context context, ResourceWrapper rw, int acquireCount, Object... args) { + Entry curEntry = context.getCurEntry(); + if (curEntry == null) { + return; + } for (MetricExtension m : MetricExtensionProvider.getMetricExtensions()) { - if (context.getCurEntry().getError() == null) { - long realRt = TimeUtil.currentTimeMillis() - context.getCurEntry().getCreateTime(); - m.addRt(resourceWrapper.getName(), realRt, args); - m.addSuccess(resourceWrapper.getName(), count, args); - m.decreaseThreadNum(resourceWrapper.getName(), args); + if (curEntry.getBlockError() != null) { + continue; + } + String resource = rw.getName(); + Throwable ex = curEntry.getError(); + long completeTime = curEntry.getCompleteTimestamp(); + if (completeTime <= 0) { + completeTime = TimeUtil.currentTimeMillis(); + } + long rt = completeTime - curEntry.getCreateTimestamp(); + + if (m instanceof AdvancedMetricExtension) { + // Since 1.8.0 (as a temporary workaround for compatibility) + ((AdvancedMetricExtension) m).onComplete(rw, rt, acquireCount, args); + if (ex != null) { + ((AdvancedMetricExtension) m).onError(rw, ex, acquireCount, args); + } + } else { + m.addRt(resource, rt, args); + m.addSuccess(resource, acquireCount, args); + m.decreaseThreadNum(resource, args); + if (null != ex) { + m.addException(resource, acquireCount, ex); + } } } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java index 6f5f4ef4f8..1f16ce5b2a 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/ClusterNode.java @@ -101,8 +101,8 @@ public int getResourceType() { public Node getOrCreateOriginNode(String origin) { StatisticNode statisticNode = originCountMap.get(origin); if (statisticNode == null) { + lock.lock(); try { - lock.lock(); statisticNode = originCountMap.get(origin); if (statisticNode == null) { // The node is absent, create a new node for the origin. @@ -123,18 +123,4 @@ public Map getOriginCountMap() { return originCountMap; } - /** - * Add exception count only when given {@code throwable} is not a {@link BlockException}. - * - * @param throwable target exception - * @param count count to add - */ - public void trace(Throwable throwable, int count) { - if (count <= 0) { - return; - } - if (!BlockException.isBlockException(throwable)) { - this.increaseExceptionQps(count); - } - } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/DefaultNode.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/DefaultNode.java index 537ab687d0..f878637520 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/DefaultNode.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/DefaultNode.java @@ -79,7 +79,7 @@ public void setClusterNode(ClusterNode clusterNode) { */ public void addChild(Node node) { if (node == null) { - RecordLog.warn("Trying to add null child to node <{0}>, ignored", id.getName()); + RecordLog.warn("Trying to add null child to node <{}>, ignored", id.getName()); return; } if (!childList.contains(node)) { @@ -91,7 +91,7 @@ public void addChild(Node node) { childList = newSet; } } - RecordLog.info("Add child <{0}> to node <{1}>", ((DefaultNode)node).id.getName(), id.getName()); + RecordLog.info("Add child <{}> to node <{}>", ((DefaultNode)node).id.getName(), id.getName()); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/IntervalProperty.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/IntervalProperty.java index 4f0dfa7b0b..8bdd4d7e7a 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/IntervalProperty.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/IntervalProperty.java @@ -62,7 +62,7 @@ public static void updateInterval(int newInterval) { INTERVAL = newInterval; ClusterBuilderSlot.resetClusterNodes(); } - RecordLog.info("[IntervalProperty] INTERVAL updated to: " + INTERVAL); + RecordLog.info("[IntervalProperty] INTERVAL updated to: {}", INTERVAL); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/OccupyTimeoutProperty.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/OccupyTimeoutProperty.java index a2d7ecf3cb..386b09c44e 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/OccupyTimeoutProperty.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/OccupyTimeoutProperty.java @@ -67,13 +67,13 @@ public static void updateTimeout(int newInterval) { return; } if (newInterval > IntervalProperty.INTERVAL) { - RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: " + occupyTimeout - + ", should <= " + IntervalProperty.INTERVAL); + RecordLog.warn("[OccupyTimeoutProperty] Illegal timeout value will be ignored: {}, should <= {}", + occupyTimeout, IntervalProperty.INTERVAL); return; } if (newInterval != occupyTimeout) { occupyTimeout = newInterval; } - RecordLog.info("[OccupyTimeoutProperty] occupyTimeout updated to: " + occupyTimeout); + RecordLog.info("[OccupyTimeoutProperty] occupyTimeout updated to: {}", occupyTimeout); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/SampleCountProperty.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/SampleCountProperty.java index 8001a80e6a..7d70b887e9 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/SampleCountProperty.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/SampleCountProperty.java @@ -60,6 +60,6 @@ public static void updateSampleCount(int newSampleCount) { SAMPLE_COUNT = newSampleCount; ClusterBuilderSlot.resetClusterNodes(); } - RecordLog.info("SAMPLE_COUNT updated to: " + SAMPLE_COUNT); + RecordLog.info("SAMPLE_COUNT updated to: {}", SAMPLE_COUNT); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricWriter.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricWriter.java index 30fd14ee02..7f4e230e8f 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricWriter.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/node/metric/MetricWriter.java @@ -42,15 +42,15 @@ *
  • every metric file is accompanied with an index file, which file name is {@code ${metricFileName}.idx}
  • * * - * @author leyou + * @author Carpenter Lee */ public class MetricWriter { private static final String CHARSET = SentinelConfig.charset(); - public static final String METRIC_BASE_DIR = RecordLog.getLogBaseDir(); + public static final String METRIC_BASE_DIR = LogBase.getLogBaseDir(); /** - * Note: {@link MetricFileNameComparator}'s implementation relays on the metric file name, - * we should be careful when changing the metric file name. + * Note: {@link MetricFileNameComparator}'s implementation relies on the metric file name, + * so we should be careful when changing the metric file name. * * @see #formMetricFileName(String, int) */ @@ -92,9 +92,8 @@ public MetricWriter(long singleFileSize, int totalFileCount) { if (singleFileSize <= 0 || totalFileCount <= 0) { throw new IllegalArgumentException(); } - RecordLog.info( - "[MetricWriter] Creating new MetricWriter, singleFileSize=" + singleFileSize + ", totalFileCount=" - + totalFileCount); + RecordLog.info("[MetricWriter] Creating new MetricWriter, singleFileSize={}, totalFileCount={}", + singleFileSize, totalFileCount); this.baseDir = METRIC_BASE_DIR; File dir = new File(baseDir); if (!dir.exists()) { @@ -328,9 +327,9 @@ private void removeMoreFiles() throws Exception { String fileName = list.get(i); String indexFile = formIndexFileName(fileName); new File(fileName).delete(); - RecordLog.info("[MetricWriter] Removing metric file: " + fileName); + RecordLog.info("[MetricWriter] Removing metric file: {}", fileName); new File(indexFile).delete(); - RecordLog.info("[MetricWriter] Removing metric index file: " + indexFile); + RecordLog.info("[MetricWriter] Removing metric index file: {}", indexFile); } } @@ -348,8 +347,8 @@ private void closeAndNewFile(String fileName) throws Exception { String idxFile = formIndexFileName(fileName); curMetricIndexFile = new File(idxFile); outIndex = new DataOutputStream(new BufferedOutputStream(new FileOutputStream(idxFile, append))); - RecordLog.info("[MetricWriter] New metric file created: " + fileName); - RecordLog.info("[MetricWriter] New metric index file created: " + idxFile); + RecordLog.info("[MetricWriter] New metric file created: {}", fileName); + RecordLog.info("[MetricWriter] New metric index file created: {}", idxFile); } private boolean validSize() throws Exception { diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java index c143710505..b432df7107 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/property/DynamicSentinelProperty.java @@ -50,7 +50,7 @@ public boolean updateValue(T newValue) { if (isEqual(value, newValue)) { return false; } - RecordLog.info("[DynamicSentinelProperty] Config will be updated to: " + newValue); + RecordLog.info("[DynamicSentinelProperty] Config will be updated to: {}", newValue); value = newValue; for (PropertyListener listener : listeners) { diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java index 9aabbb3376..49ab60a358 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slotchain/SlotChainProvider.java @@ -48,8 +48,8 @@ public static ProcessorSlotChain newSlotChain() { RecordLog.warn("[SlotChainProvider] Wrong state when resolving slot chain builder, using default"); slotChainBuilder = new DefaultSlotChainBuilder(); } else { - RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: " - + slotChainBuilder.getClass().getCanonicalName()); + RecordLog.info("[SlotChainProvider] Global slot chain builder resolved: {}", + slotChainBuilder.getClass().getCanonicalName()); } return slotChainBuilder.build(); } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java index 56aa851a13..cf0f72c5e5 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilder.java @@ -15,17 +15,15 @@ */ package com.alibaba.csp.sentinel.slots; +import com.alibaba.csp.sentinel.log.RecordLog; +import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.DefaultProcessorSlotChain; +import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; -import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; -import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; -import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; -import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; -import com.alibaba.csp.sentinel.slots.logger.LogSlot; -import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; -import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; -import com.alibaba.csp.sentinel.slots.system.SystemSlot; +import com.alibaba.csp.sentinel.util.SpiLoader; + +import java.util.List; /** * Builder for a default {@link ProcessorSlotChain}. @@ -38,16 +36,18 @@ public class DefaultSlotChainBuilder implements SlotChainBuilder { @Override public ProcessorSlotChain build() { ProcessorSlotChain chain = new DefaultProcessorSlotChain(); - chain.addLast(new NodeSelectorSlot()); - chain.addLast(new ClusterBuilderSlot()); - chain.addLast(new LogSlot()); - chain.addLast(new StatisticSlot()); - chain.addLast(new AuthoritySlot()); - chain.addLast(new SystemSlot()); - chain.addLast(new FlowSlot()); - chain.addLast(new DegradeSlot()); + + // Note: the instances of ProcessorSlot should be different, since they are not stateless. + List sortedSlotList = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class); + for (ProcessorSlot slot : sortedSlotList) { + if (!(slot instanceof AbstractLinkedProcessorSlot)) { + RecordLog.warn("The ProcessorSlot(" + slot.getClass().getCanonicalName() + ") is not an instance of AbstractLinkedProcessorSlot, can't be added into ProcessorSlotChain"); + continue; + } + + chain.addLast((AbstractLinkedProcessorSlot) slot); + } return chain; } - } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/AbstractRule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/AbstractRule.java index 2a0d7260c1..f6f10a76b4 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/AbstractRule.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/AbstractRule.java @@ -39,6 +39,7 @@ public abstract class AbstractRule implements Rule { */ private String limitApp; + @Override public String getResource() { return resource; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/BlockException.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/BlockException.java index ada98383f2..ad972d440c 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/BlockException.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/BlockException.java @@ -23,7 +23,11 @@ */ public abstract class BlockException extends Exception { + private static final int MAX_SEARCH_DEPTH = 10; + public static final String BLOCK_EXCEPTION_FLAG = "SentinelBlockException"; + public static final String BLOCK_EXCEPTION_MSG_PREFIX = "SentinelBlockException: "; + /** *

    this constant RuntimeException has no stack trace, just has a message * {@link #BLOCK_EXCEPTION_FLAG} that marks its name. @@ -85,12 +89,18 @@ public void setRuleLimitApp(String ruleLimitApp) { this.ruleLimitApp = ruleLimitApp; } + public RuntimeException toRuntimeException() { + RuntimeException t = new RuntimeException(BLOCK_EXCEPTION_MSG_PREFIX + getClass().getSimpleName()); + t.setStackTrace(sentinelStackTrace); + return t; + } + /** * Check whether the exception is sentinel blocked exception. One exception is sentinel blocked * exception only when: *

      *
    • the exception or its (sub-)cause is {@link BlockException}, or
    • - *
    • the exception's message is or any of its sub-cause's message equals to {@link #BLOCK_EXCEPTION_FLAG}
    • + *
    • the exception's message or any of its sub-cause's message is prefixed by {@link #BLOCK_EXCEPTION_FLAG}
    • *
    * * @param t the exception. @@ -103,8 +113,11 @@ public static boolean isBlockException(Throwable t) { int counter = 0; Throwable cause = t; - while (cause != null && counter++ < 50) { - if ((cause instanceof BlockException) || (BLOCK_EXCEPTION_FLAG.equals(cause.getMessage()))) { + while (cause != null && counter++ < MAX_SEARCH_DEPTH) { + if (cause instanceof BlockException) { + return true; + } + if (cause.getMessage() != null && cause.getMessage().startsWith(BLOCK_EXCEPTION_FLAG)) { return true; } cause = cause.getCause(); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/Rule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/Rule.java index e429b2a575..35630edac0 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/Rule.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/Rule.java @@ -15,9 +15,6 @@ */ package com.alibaba.csp.sentinel.slots.block; -import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.node.DefaultNode; - /** * Base interface of all rules. * @@ -26,14 +23,10 @@ public interface Rule { /** - * Check whether current statistical indicators meet this rule, which means not exceeding any threshold. + * Get target resource of this rule. * - * @param context current {@link Context} - * @param node current {@link com.alibaba.csp.sentinel.node.Node} - * @param count tokens needed. - * @param args arguments of the original invocation. - * @return If current statistical indicators not exceeding any threshold return true, otherwise return false. + * @return target resource of this rule */ - boolean passCheck(Context context, DefaultNode node, int count, Object... args); + String getResource(); } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java index e712ff343c..0b157675c5 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/RuleConstant.java @@ -51,6 +51,14 @@ public final class RuleConstant { public static final int CONTROL_BEHAVIOR_RATE_LIMITER = 2; public static final int CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER = 3; + public static final int DEFAULT_BLOCK_STRATEGY = 0; + public static final int TRY_AGAIN_BLOCK_STRATEGY = 1; + public static final int TRY_UNTIL_SUCCESS_BLOCK_STRATEGY = 2; + + public static final int DEFAULT_RESOURCE_TIMEOUT_STRATEGY = 0; + public static final int RELEASE_RESOURCE_TIMEOUT_STRATEGY = 1; + public static final int KEEP_RESOURCE_TIMEOUT_STRATEGY = 2; + public static final String LIMIT_APP_DEFAULT = "default"; public static final String LIMIT_APP_OTHER = "other"; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java index a5b5f277d3..c1606d0aa3 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRule.java @@ -15,8 +15,6 @@ */ package com.alibaba.csp.sentinel.slots.block.authority; -import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.RuleConstant; @@ -59,11 +57,6 @@ public int hashCode() { return result; } - @Override - public boolean passCheck(Context context, DefaultNode node, int count, Object... args) { - return true; - } - @Override public String toString() { return "AuthorityRule{" + diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java index 7962c50e00..22b698dd46 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthorityRuleManager.java @@ -99,7 +99,7 @@ public void configUpdate(List conf) { if (rules != null) { authorityRules.putAll(rules); } - RecordLog.info("[AuthorityRuleManager] Authority rules received: " + authorityRules); + RecordLog.info("[AuthorityRuleManager] Authority rules received: {}", authorityRules); } private Map> loadAuthorityConf(List list) { @@ -111,7 +111,7 @@ private Map> loadAuthorityConf(List li for (AuthorityRule rule : list) { if (!isValidRule(rule)) { - RecordLog.warn("[AuthorityRuleManager] Ignoring invalid authority rule when loading new rules: " + rule); + RecordLog.warn("[AuthorityRuleManager] Ignoring invalid authority rule when loading new rules: {}", rule); continue; } @@ -128,7 +128,7 @@ private Map> loadAuthorityConf(List li newRuleMap.put(identity, ruleSet); } else { // One resource should only have at most one authority rule, so just ignore redundant rules. - RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: " + rule.toString()); + RecordLog.warn("[AuthorityRuleManager] Ignoring redundant rule: {}", rule.toString()); } } @@ -143,7 +143,7 @@ public void configLoad(List value) { if (rules != null) { authorityRules.putAll(rules); } - RecordLog.info("[AuthorityRuleManager] Load authority rules: " + authorityRules); + RecordLog.info("[AuthorityRuleManager] Load authority rules: {}", authorityRules); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java index f7920baa9c..617521a676 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/authority/AuthoritySlot.java @@ -23,6 +23,7 @@ import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.spi.SpiOrder; /** * A {@link ProcessorSlot} that dedicates to {@link AuthorityRule} checking. @@ -30,6 +31,7 @@ * @author leyou * @author Eric Zhao */ +@SpiOrder(-6000) public class AuthoritySlot extends AbstractLinkedProcessorSlot { @Override diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java index aee71ff543..edc9f1b35b 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRule.java @@ -15,19 +15,10 @@ */ package com.alibaba.csp.sentinel.slots.block.degrade; -import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; -import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.node.ClusterNode; -import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.RuleConstant; -import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; +import java.util.Objects; /** *

    @@ -52,13 +43,10 @@ * * * @author jialiang.linjl + * @author Eric Zhao */ public class DegradeRule extends AbstractRule { - @SuppressWarnings("PMD.ThreadPoolCreationRule") - private static ScheduledExecutorService pool = Executors.newScheduledThreadPool( - Runtime.getRuntime().availableProcessors(), new NamedThreadFactory("sentinel-degrade-reset-task", true)); - public DegradeRule() {} public DegradeRule(String resourceName) { @@ -66,33 +54,34 @@ public DegradeRule(String resourceName) { } /** - * RT threshold or exception ratio threshold count. + * Circuit breaking strategy (0: average RT, 1: exception ratio, 2: exception count). */ - private double count; + private int grade = RuleConstant.DEGRADE_GRADE_RT; /** - * Degrade recover timeout (in seconds) when degradation occurs. + * Threshold count. */ - private int timeWindow; + private double count; /** - * Degrade strategy (0: average RT, 1: exception ratio, 2: exception count). + * Recovery timeout (in seconds) when circuit breaker opens. After the timeout, the circuit breaker will + * transform to half-open state for trying a few requests. */ - private int grade = RuleConstant.DEGRADE_GRADE_RT; + private int timeWindow; /** - * Minimum number of consecutive slow requests that can trigger RT circuit breaking. + * Minimum number of requests (in an active statistic time span) that can trigger circuit breaking. * * @since 1.7.0 */ - private int rtSlowRequestAmount = RuleConstant.DEGRADE_DEFAULT_SLOW_REQUEST_AMOUNT; + private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT; /** - * Minimum number of requests (in an active statistic time span) that can trigger circuit breaking. - * - * @since 1.7.0 + * The threshold of slow request ratio in RT mode. */ - private int minRequestAmount = RuleConstant.DEGRADE_DEFAULT_MIN_REQUEST_AMOUNT; + private double slowRatioThreshold = 1.0d; + + private int statIntervalMs = 1000; public int getGrade() { return grade; @@ -121,21 +110,30 @@ public DegradeRule setTimeWindow(int timeWindow) { return this; } - public int getRtSlowRequestAmount() { - return rtSlowRequestAmount; + public int getMinRequestAmount() { + return minRequestAmount; + } + + public DegradeRule setMinRequestAmount(int minRequestAmount) { + this.minRequestAmount = minRequestAmount; + return this; + } + + public double getSlowRatioThreshold() { + return slowRatioThreshold; } - public DegradeRule setRtSlowRequestAmount(int rtSlowRequestAmount) { - this.rtSlowRequestAmount = rtSlowRequestAmount; + public DegradeRule setSlowRatioThreshold(double slowRatioThreshold) { + this.slowRatioThreshold = slowRatioThreshold; return this; } - public int getMinRequestAmount() { - return minRequestAmount; + public int getStatIntervalMs() { + return statIntervalMs; } - public DegradeRule setMinRequestAmount(int minRequestAmount) { - this.minRequestAmount = minRequestAmount; + public DegradeRule setStatIntervalMs(int statIntervalMs) { + this.statIntervalMs = statIntervalMs; return this; } @@ -144,23 +142,19 @@ public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } if (!super.equals(o)) { return false; } - DegradeRule that = (DegradeRule) o; - return Double.compare(that.count, count) == 0 && - timeWindow == that.timeWindow && - grade == that.grade && - rtSlowRequestAmount == that.rtSlowRequestAmount && - minRequestAmount == that.minRequestAmount; + DegradeRule rule = (DegradeRule)o; + return Double.compare(rule.count, count) == 0 && + timeWindow == rule.timeWindow && + grade == rule.grade && + minRequestAmount == rule.minRequestAmount && + Double.compare(rule.slowRatioThreshold, slowRatioThreshold) == 0 && + statIntervalMs == rule.statIntervalMs; } @Override public int hashCode() { - int result = super.hashCode(); - result = 31 * result + new Double(count).hashCode(); - result = 31 * result + timeWindow; - result = 31 * result + grade; - result = 31 * result + rtSlowRequestAmount; - result = 31 * result + minRequestAmount; - return result; + return Objects.hash(super.hashCode(), count, timeWindow, grade, minRequestAmount, + slowRatioThreshold, statIntervalMs); } @Override @@ -171,84 +165,9 @@ public String toString() { ", count=" + count + ", limitApp=" + getLimitApp() + ", timeWindow=" + timeWindow + - ", rtSlowRequestAmount=" + rtSlowRequestAmount + ", minRequestAmount=" + minRequestAmount + - "}"; - } - - // Internal implementation (will be deprecated and moved outside). - - private AtomicLong passCount = new AtomicLong(0); - private final AtomicBoolean cut = new AtomicBoolean(false); - - @Override - public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) { - if (cut.get()) { - return false; - } - - ClusterNode clusterNode = ClusterBuilderSlot.getClusterNode(this.getResource()); - if (clusterNode == null) { - return true; - } - - if (grade == RuleConstant.DEGRADE_GRADE_RT) { - double rt = clusterNode.avgRt(); - if (rt < this.count) { - passCount.set(0); - return true; - } - - // Sentinel will degrade the service only if count exceeds. - if (passCount.incrementAndGet() < rtSlowRequestAmount) { - return true; - } - } else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) { - double exception = clusterNode.exceptionQps(); - double success = clusterNode.successQps(); - double total = clusterNode.totalQps(); - // If total amount is less than minRequestAmount, the request will pass. - if (total < minRequestAmount) { - return true; - } - - // In the same aligned statistic time window, - // "success" (aka. completed count) = exception count + non-exception count (realSuccess) - double realSuccess = success - exception; - if (realSuccess <= 0 && exception < minRequestAmount) { - return true; - } - - if (exception / success < count) { - return true; - } - } else if (grade == RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) { - double exception = clusterNode.totalException(); - if (exception < count) { - return true; - } - } - - if (cut.compareAndSet(false, true)) { - ResetTask resetTask = new ResetTask(this); - pool.schedule(resetTask, timeWindow, TimeUnit.SECONDS); - } - - return false; - } - - private static final class ResetTask implements Runnable { - - private DegradeRule rule; - - ResetTask(DegradeRule rule) { - this.rule = rule; - } - - @Override - public void run() { - rule.passCount.set(0); - rule.cut.set(false); - } + ", slowRatioThreshold=" + slowRatioThreshold + + ", statIntervalMs=" + statIntervalMs + + '}'; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java index 6cb09f8e97..a1214a7b14 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManager.java @@ -21,29 +21,29 @@ import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import com.alibaba.csp.sentinel.config.SentinelConfig; -import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.log.RecordLog; -import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.property.DynamicSentinelProperty; import com.alibaba.csp.sentinel.property.PropertyListener; import com.alibaba.csp.sentinel.property.SentinelProperty; -import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; -import com.alibaba.csp.sentinel.slots.block.BlockException; import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ExceptionCircuitBreaker; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.ResponseTimeCircuitBreaker; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.StringUtil; /** + * The rule manager for circuit breaking rules ({@link DegradeRule}). + * * @author youji.zj * @author jialiang.linjl * @author Eric Zhao */ public final class DegradeRuleManager { - private static final Map> degradeRules = new ConcurrentHashMap<>(); + private static volatile Map> circuitBreakers = new HashMap<>(); + private static volatile Map> ruleMap = new HashMap<>(); private static final RulePropertyListener LISTENER = new RulePropertyListener(); private static SentinelProperty> currentProperty @@ -69,41 +69,37 @@ public static void register2Property(SentinelProperty> propert } } - public static void checkDegrade(ResourceWrapper resource, Context context, DefaultNode node, int count) - throws BlockException { - - Set rules = degradeRules.get(resource.getName()); - if (rules == null) { - return; - } - - for (DegradeRule rule : rules) { - if (!rule.passCheck(context, node, count)) { - throw new DegradeException(rule.getLimitApp(), rule); - } - } + static List getCircuitBreakers(String resourceName) { + return circuitBreakers.get(resourceName); } public static boolean hasConfig(String resource) { if (resource == null) { return false; } - return degradeRules.containsKey(resource); + return circuitBreakers.containsKey(resource); } /** - * Get a copy of the rules. + *

    Get existing circuit breaking rules.

    + *

    Note: DO NOT modify the rules from the returned list directly. + * The behavior is undefined.

    * - * @return a new copy of the rules. + * @return list of existing circuit breaking rules, or empty list if no rules were loaded */ public static List getRules() { List rules = new ArrayList<>(); - for (Map.Entry> entry : degradeRules.entrySet()) { + for (Map.Entry> entry : ruleMap.entrySet()) { rules.addAll(entry.getValue()); } return rules; } + public static Set getRulesOfResource(String resource) { + AssertUtil.assertNotBlank(resource, "resource name cannot be blank"); + return ruleMap.get(resource); + } + /** * Load {@link DegradeRule}s, former rules will be replaced. * @@ -113,7 +109,7 @@ public static void loadRules(List rules) { try { currentProperty.updateValue(rules); } catch (Throwable e) { - RecordLog.warn("[DegradeRuleManager] Unexpected error when loading degrade rules", e); + RecordLog.error("[DegradeRuleManager] Unexpected error when loading degrade rules", e); } } @@ -128,7 +124,7 @@ public static void loadRules(List rules) { public static boolean setRulesForResource(String resourceName, Set rules) { AssertUtil.notEmpty(resourceName, "resourceName cannot be empty"); try { - Map> newRuleMap = new HashMap<>(degradeRules); + Map> newRuleMap = new HashMap<>(ruleMap); if (rules == null) { newRuleMap.remove(resourceName); } else { @@ -146,88 +142,127 @@ public static boolean setRulesForResource(String resourceName, Set } return currentProperty.updateValue(allRules); } catch (Throwable e) { - RecordLog.warn( - "[DegradeRuleManager] Unexpected error when setting degrade rules for resource: " + resourceName, e); + RecordLog.error("[DegradeRuleManager] Unexpected error when setting circuit breaking" + + " rules for resource: " + resourceName, e); + return false; + } + } + + private static CircuitBreaker getExistingSameCbOrNew(/*@Valid*/ DegradeRule rule) { + List cbs = getCircuitBreakers(rule.getResource()); + if (cbs == null || cbs.isEmpty()) { + return newCircuitBreakerFrom(rule); + } + for (CircuitBreaker cb : cbs) { + if (rule.equals(cb.getRule())) { + // Reuse the circuit breaker if the rule remains unchanged. + return cb; + } + } + return newCircuitBreakerFrom(rule); + } + + /** + * Create a circuit breaker instance from provided circuit breaking rule. + * + * @param rule a valid circuit breaking rule + * @return new circuit breaker based on provided rule; null if rule is invalid or unsupported type + */ + private static CircuitBreaker newCircuitBreakerFrom(/*@Valid*/ DegradeRule rule) { + switch (rule.getGrade()) { + case RuleConstant.DEGRADE_GRADE_RT: + return new ResponseTimeCircuitBreaker(rule); + case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO: + case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT: + return new ExceptionCircuitBreaker(rule); + default: + return null; + } + } + + public static boolean isValidRule(DegradeRule rule) { + boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) + && rule.getCount() >= 0 && rule.getTimeWindow() > 0; + if (!baseValid) { + return false; + } + if (rule.getMinRequestAmount() <= 0 || rule.getStatIntervalMs() <= 0) { return false; } + switch (rule.getGrade()) { + case RuleConstant.DEGRADE_GRADE_RT: + return rule.getSlowRatioThreshold() >= 0 && rule.getSlowRatioThreshold() <= 1; + case RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO: + return rule.getCount() <= 1; + case RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT: + return true; + default: + return false; + } } private static class RulePropertyListener implements PropertyListener> { + private synchronized void reloadFrom(List list) { + Map> cbs = buildCircuitBreakers(list); + Map> rm = new HashMap<>(cbs.size()); + + for (Map.Entry> e : cbs.entrySet()) { + assert e.getValue() != null && !e.getValue().isEmpty(); + + Set rules = new HashSet<>(e.getValue().size()); + for (CircuitBreaker cb : e.getValue()) { + rules.add(cb.getRule()); + } + rm.put(e.getKey(), rules); + } + + DegradeRuleManager.circuitBreakers = cbs; + DegradeRuleManager.ruleMap = rm; + } + @Override public void configUpdate(List conf) { - Map> rules = loadDegradeConf(conf); - if (rules != null) { - degradeRules.clear(); - degradeRules.putAll(rules); - } - RecordLog.info("[DegradeRuleManager] Degrade rules received: " + degradeRules); + reloadFrom(conf); + RecordLog.info("[DegradeRuleManager] Degrade rules has been updated to: {}", ruleMap); } @Override public void configLoad(List conf) { - Map> rules = loadDegradeConf(conf); - if (rules != null) { - degradeRules.clear(); - degradeRules.putAll(rules); - } - RecordLog.info("[DegradeRuleManager] Degrade rules loaded: " + degradeRules); + reloadFrom(conf); + RecordLog.info("[DegradeRuleManager] Degrade rules loaded: {}", ruleMap); } - private Map> loadDegradeConf(List list) { - Map> newRuleMap = new ConcurrentHashMap<>(); - + private Map> buildCircuitBreakers(List list) { + Map> cbMap = new HashMap<>(8); if (list == null || list.isEmpty()) { - return newRuleMap; + return cbMap; } - for (DegradeRule rule : list) { if (!isValidRule(rule)) { - RecordLog.warn( - "[DegradeRuleManager] Ignoring invalid degrade rule when loading new rules: " + rule); + RecordLog.warn("[DegradeRuleManager] Ignoring invalid rule when loading new rules: {}", rule); continue; } if (StringUtil.isBlank(rule.getLimitApp())) { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } - - String identity = rule.getResource(); - Set ruleSet = newRuleMap.get(identity); - if (ruleSet == null) { - ruleSet = new HashSet<>(); - newRuleMap.put(identity, ruleSet); + CircuitBreaker cb = getExistingSameCbOrNew(rule); + if (cb == null) { + RecordLog.warn("[DegradeRuleManager] Unknown circuit breaking strategy, ignoring: {}", rule); + continue; } - ruleSet.add(rule); - } - return newRuleMap; - } - } + String resourceName = rule.getResource(); - public static boolean isValidRule(DegradeRule rule) { - boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) - && rule.getCount() >= 0 && rule.getTimeWindow() > 0; - if (!baseValid) { - return false; - } - int maxAllowedRt = SentinelConfig.statisticMaxRt(); - if (rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT) { - if (rule.getRtSlowRequestAmount() <= 0) { - return false; - } - // Warn for RT mode that exceeds the {@code TIME_DROP_VALVE}. - if (rule.getCount() > maxAllowedRt) { - RecordLog.warn(String.format("[DegradeRuleManager] WARN: setting large RT threshold (%.1f ms)" - + " in RT mode will not take effect since it exceeds the max allowed value (%d ms)", - rule.getCount(), maxAllowedRt)); + List cbList = cbMap.get(resourceName); + if (cbList == null) { + cbList = new ArrayList<>(); + cbMap.put(resourceName, cbList); + } + cbList.add(cb); } + return cbMap; } - - // Check exception ratio mode. - if (rule.getGrade() == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) { - return rule.getCount() <= 1 && rule.getMinRequestAmount() > 0; - } - return true; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java index f76d81f8cd..c66d056d4f 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeSlot.java @@ -15,28 +15,67 @@ */ package com.alibaba.csp.sentinel.slots.block.degrade; +import java.util.List; + +import com.alibaba.csp.sentinel.Entry; import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; +import com.alibaba.csp.sentinel.spi.SpiOrder; /** - * A {@link ProcessorSlot} dedicates to {@link DegradeRule} checking. + * A {@link ProcessorSlot} dedicates to circuit breaking. * - * @author leyou + * @author Carpenter Lee + * @author Eric Zhao */ +@SpiOrder(-1000) public class DegradeSlot extends AbstractLinkedProcessorSlot { @Override - public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, boolean prioritized, Object... args) - throws Throwable { - DegradeRuleManager.checkDegrade(resourceWrapper, context, node, count); + public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode node, int count, + boolean prioritized, Object... args) throws Throwable { + performChecking(context, resourceWrapper); + fireEntry(context, resourceWrapper, node, count, prioritized, args); } + void performChecking(Context context, ResourceWrapper r) throws BlockException { + List circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName()); + if (circuitBreakers == null || circuitBreakers.isEmpty()) { + return; + } + for (CircuitBreaker cb : circuitBreakers) { + if (!cb.tryPass(context)) { + throw new DegradeException(cb.getRule().getLimitApp(), cb.getRule()); + } + } + } + @Override - public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { - fireExit(context, resourceWrapper, count, args); + public void exit(Context context, ResourceWrapper r, int count, Object... args) { + Entry curEntry = context.getCurEntry(); + if (curEntry.getBlockError() != null) { + fireExit(context, r, count, args); + return; + } + List circuitBreakers = DegradeRuleManager.getCircuitBreakers(r.getName()); + if (circuitBreakers == null || circuitBreakers.isEmpty()) { + fireExit(context, r, count, args); + return; + } + + if (curEntry.getBlockError() == null) { + // passed request + for (CircuitBreaker circuitBreaker : circuitBreakers) { + circuitBreaker.onRequestComplete(context); + } + } + + fireExit(context, r, count, args); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java new file mode 100644 index 0000000000..86be556fe9 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/AbstractCircuitBreaker.java @@ -0,0 +1,163 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import java.util.concurrent.atomic.AtomicReference; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.TimeUtil; +import com.alibaba.csp.sentinel.util.function.BiConsumer; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public abstract class AbstractCircuitBreaker implements CircuitBreaker { + + protected final DegradeRule rule; + protected final int recoveryTimeoutMs; + + private final EventObserverRegistry observerRegistry; + + protected final AtomicReference currentState = new AtomicReference<>(State.CLOSED); + protected volatile long nextRetryTimestamp; + + public AbstractCircuitBreaker(DegradeRule rule) { + this(rule, EventObserverRegistry.getInstance()); + } + + AbstractCircuitBreaker(DegradeRule rule, EventObserverRegistry observerRegistry) { + AssertUtil.notNull(observerRegistry, "observerRegistry cannot be null"); + if (!DegradeRuleManager.isValidRule(rule)) { + throw new IllegalArgumentException("Invalid DegradeRule: " + rule); + } + this.observerRegistry = observerRegistry; + this.rule = rule; + this.recoveryTimeoutMs = rule.getTimeWindow() * 1000; + } + + @Override + public DegradeRule getRule() { + return rule; + } + + @Override + public State currentState() { + return currentState.get(); + } + + @Override + public boolean tryPass(Context context) { + // Template implementation. + if (currentState.get() == State.CLOSED) { + return true; + } + if (currentState.get() == State.OPEN) { + // For half-open state we allow a request for probing. + return retryTimeoutArrived() && fromOpenToHalfOpen(context); + } + return false; + } + + /** + * Reset the statistic data. + */ + abstract void resetStat(); + + protected boolean retryTimeoutArrived() { + return TimeUtil.currentTimeMillis() >= nextRetryTimestamp; + } + + protected void updateNextRetryTimestamp() { + this.nextRetryTimestamp = TimeUtil.currentTimeMillis() + recoveryTimeoutMs; + } + + protected boolean fromCloseToOpen(double snapshotValue) { + State prev = State.CLOSED; + if (currentState.compareAndSet(prev, State.OPEN)) { + updateNextRetryTimestamp(); + + notifyObservers(prev, State.OPEN, snapshotValue); + return true; + } + return false; + } + + protected boolean fromOpenToHalfOpen(Context context) { + if (currentState.compareAndSet(State.OPEN, State.HALF_OPEN)) { + notifyObservers(State.OPEN, State.HALF_OPEN, null); + Entry entry = context.getCurEntry(); + entry.whenTerminate(new BiConsumer() { + @Override + public void accept(Context context, Entry entry) { + // Note: This works as a temporary workaround for https://github.com/alibaba/Sentinel/issues/1638 + // Without the hook, the circuit breaker won't recover from half-open state in some circumstances + // when the request is actually blocked by upcoming rules (not only degrade rules). + if (entry.getBlockError() != null) { + // Fallback to OPEN due to detecting request is blocked + currentState.compareAndSet(State.HALF_OPEN, State.OPEN); + notifyObservers(State.HALF_OPEN, State.OPEN, 1.0d); + } + } + }); + return true; + } + return false; + } + + private void notifyObservers(CircuitBreaker.State prevState, CircuitBreaker.State newState, Double snapshotValue) { + for (CircuitBreakerStateChangeObserver observer : observerRegistry.getStateChangeObservers()) { + observer.onStateChange(prevState, newState, rule, snapshotValue); + } + } + + protected boolean fromHalfOpenToOpen(double snapshotValue) { + if (currentState.compareAndSet(State.HALF_OPEN, State.OPEN)) { + updateNextRetryTimestamp(); + notifyObservers(State.HALF_OPEN, State.OPEN, snapshotValue); + return true; + } + return false; + } + + protected boolean fromHalfOpenToClose() { + if (currentState.compareAndSet(State.HALF_OPEN, State.CLOSED)) { + resetStat(); + notifyObservers(State.HALF_OPEN, State.CLOSED, null); + return true; + } + return false; + } + + protected void transformToOpen(double triggerValue) { + State cs = currentState.get(); + switch (cs) { + case CLOSED: + fromCloseToOpen(triggerValue); + break; + case HALF_OPEN: + fromHalfOpenToOpen(triggerValue); + break; + default: + break; + } + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java new file mode 100644 index 0000000000..0ecc26c6a3 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreaker.java @@ -0,0 +1,81 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; + +/** + *

    Basic circuit breaker interface.

    + * + * @author Eric Zhao + */ +public interface CircuitBreaker { + + /** + * Get the associated circuit breaking rule. + * + * @return associated circuit breaking rule + */ + DegradeRule getRule(); + + /** + * Acquires permission of an invocation only if it is available at the time of invoking. + * + * @param context context of current invocation + * @return {@code true} if permission was acquired and {@code false} otherwise + */ + boolean tryPass(Context context); + + /** + * Get current state of the circuit breaker. + * + * @return current state of the circuit breaker + */ + State currentState(); + + /** + *

    Record a completed request with the context and handle state transformation of the circuit breaker.

    + *

    Called when a passed invocation finished.

    + * + * @param context context of current invocation + */ + void onRequestComplete(Context context); + + /** + * Circuit breaker state. + */ + enum State { + /** + * In {@code OPEN} state, all requests will be rejected until the next recovery time point. + */ + OPEN, + /** + * In {@code HALF_OPEN} state, the circuit breaker will allow a "probe" invocation. + * If the invocation is abnormal according to the strategy (e.g. it's slow), the circuit breaker + * will re-transform to the {@code OPEN} state and wait for the next recovery time point; + * otherwise the resource will be regarded as "recovered" and the circuit breaker + * will cease cutting off requests and transform to {@code CLOSED} state. + */ + HALF_OPEN, + /** + * In {@code CLOSED} state, all requests are permitted. When current metric value exceeds the threshold, + * the circuit breaker will transform to {@code OPEN} state. + */ + CLOSED + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java new file mode 100644 index 0000000000..e7e5b2dc76 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStateChangeObserver.java @@ -0,0 +1,42 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public interface CircuitBreakerStateChangeObserver { + + /** + *

    Observer method triggered when circuit breaker state changed. The transformation could be:

    + *
      + *
    • From {@code CLOSED} to {@code OPEN} (with the triggered metric)
    • + *
    • From {@code OPEN} to {@code HALF_OPEN}
    • + *
    • From {@code OPEN} to {@code CLOSED}
    • + *
    • From {@code HALF_OPEN} to {@code OPEN} (with the triggered metric)
    • + *
    + * + * @param prevState previous state of the circuit breaker + * @param newState new state of the circuit breaker + * @param rule associated rule + * @param snapshotValue triggered value on circuit breaker opens (null if the new state is CLOSED or HALF_OPEN) + */ + void onStateChange(CircuitBreaker.State prevState, CircuitBreaker.State newState, DegradeRule rule, + Double snapshotValue); +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java new file mode 100644 index 0000000000..79d3b12a32 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/CircuitBreakerStrategy.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public enum CircuitBreakerStrategy { + + /** + * Circuit breaker opens (cuts off) when slow request ratio exceeds the threshold. + */ + SLOW_REQUEST_RATIO(0), + /** + * Circuit breaker opens (cuts off) when error ratio exceeds the threshold. + */ + ERROR_RATIO(1), + /** + * Circuit breaker opens (cuts off) when error count exceeds the threshold. + */ + ERROR_COUNT(2); + + private int type; + + CircuitBreakerStrategy(int type) { + this.type = type; + } + + public int getType() { + return type; + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java new file mode 100644 index 0000000000..2243eebaa5 --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/EventObserverRegistry.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2020 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.alibaba.csp.sentinel.util.AssertUtil; + +/** + *

    Registry for circuit breaker event observers.

    + * + * @author Eric Zhao + * @since 1.8.0 + */ +public class EventObserverRegistry { + + private final Map stateChangeObserverMap = new HashMap<>(); + + /** + * Register a circuit breaker state change observer. + * + * @param name observer name + * @param observer a valid observer + */ + public void addStateChangeObserver(String name, CircuitBreakerStateChangeObserver observer) { + AssertUtil.notNull(name, "name cannot be null"); + AssertUtil.notNull(observer, "observer cannot be null"); + stateChangeObserverMap.put(name, observer); + } + + public boolean removeStateChangeObserver(String name) { + AssertUtil.notNull(name, "name cannot be null"); + return stateChangeObserverMap.remove(name) != null; + } + + /** + * Get all registered state chane observers. + * + * @return all registered state chane observers + */ + public List getStateChangeObservers() { + return new ArrayList<>(stateChangeObserverMap.values()); + } + + public static EventObserverRegistry getInstance() { + return InstanceHolder.instance; + } + + private static class InstanceHolder { + private static EventObserverRegistry instance = new EventObserverRegistry(); + } + + EventObserverRegistry() {} +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java new file mode 100644 index 0000000000..be8c1b20ab --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreaker.java @@ -0,0 +1,167 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import java.util.List; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; +import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; +import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.alibaba.csp.sentinel.util.AssertUtil; + +import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO; +import static com.alibaba.csp.sentinel.slots.block.RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public class ExceptionCircuitBreaker extends AbstractCircuitBreaker { + + private final int strategy; + private final int minRequestAmount; + private final double threshold; + + private final LeapArray stat; + + public ExceptionCircuitBreaker(DegradeRule rule) { + this(rule, new SimpleErrorCounterLeapArray(1, rule.getStatIntervalMs())); + } + + ExceptionCircuitBreaker(DegradeRule rule, LeapArray stat) { + super(rule); + this.strategy = rule.getGrade(); + boolean modeOk = strategy == DEGRADE_GRADE_EXCEPTION_RATIO || strategy == DEGRADE_GRADE_EXCEPTION_COUNT; + AssertUtil.isTrue(modeOk, "rule strategy should be error-ratio or error-count"); + AssertUtil.notNull(stat, "stat cannot be null"); + this.minRequestAmount = rule.getMinRequestAmount(); + this.threshold = rule.getCount(); + this.stat = stat; + } + + @Override + protected void resetStat() { + // Reset current bucket (bucket count = 1). + stat.currentWindow().value().reset(); + } + + @Override + public void onRequestComplete(Context context) { + Entry entry = context.getCurEntry(); + if (entry == null) { + return; + } + Throwable error = entry.getError(); + SimpleErrorCounter counter = stat.currentWindow().value(); + if (error != null) { + counter.getErrorCount().add(1); + } + counter.getTotalCount().add(1); + + handleStateChangeWhenThresholdExceeded(error); + } + + private void handleStateChangeWhenThresholdExceeded(Throwable error) { + if (currentState.get() == State.OPEN) { + return; + } + + if (currentState.get() == State.HALF_OPEN) { + // In detecting request + if (error == null) { + fromHalfOpenToClose(); + } else { + fromHalfOpenToOpen(1.0d); + } + return; + } + + List counters = stat.values(); + long errCount = 0; + long totalCount = 0; + for (SimpleErrorCounter counter : counters) { + errCount += counter.errorCount.sum(); + totalCount += counter.totalCount.sum(); + } + if (totalCount < minRequestAmount) { + return; + } + double curCount = errCount; + if (strategy == DEGRADE_GRADE_EXCEPTION_RATIO) { + // Use errorRatio + curCount = errCount * 1.0d / totalCount; + } + if (curCount > threshold) { + transformToOpen(curCount); + } + } + + static class SimpleErrorCounter { + private LongAdder errorCount; + private LongAdder totalCount; + + public SimpleErrorCounter() { + this.errorCount = new LongAdder(); + this.totalCount = new LongAdder(); + } + + public LongAdder getErrorCount() { + return errorCount; + } + + public LongAdder getTotalCount() { + return totalCount; + } + + public SimpleErrorCounter reset() { + errorCount.reset(); + totalCount.reset(); + return this; + } + + @Override + public String toString() { + return "SimpleErrorCounter{" + + "errorCount=" + errorCount + + ", totalCount=" + totalCount + + '}'; + } + } + + static class SimpleErrorCounterLeapArray extends LeapArray { + + public SimpleErrorCounterLeapArray(int sampleCount, int intervalInMs) { + super(sampleCount, intervalInMs); + } + + @Override + public SimpleErrorCounter newEmptyBucket(long timeMillis) { + return new SimpleErrorCounter(); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { + // Update the start time and reset value. + w.resetTo(startTime); + w.value().reset(); + return w; + } + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java new file mode 100644 index 0000000000..2ab98d4dfd --- /dev/null +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreaker.java @@ -0,0 +1,170 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import java.util.List; + +import com.alibaba.csp.sentinel.Entry; +import com.alibaba.csp.sentinel.context.Context; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.statistic.base.LeapArray; +import com.alibaba.csp.sentinel.slots.statistic.base.LongAdder; +import com.alibaba.csp.sentinel.slots.statistic.base.WindowWrap; +import com.alibaba.csp.sentinel.util.AssertUtil; +import com.alibaba.csp.sentinel.util.TimeUtil; + +/** + * @author Eric Zhao + * @since 1.8.0 + */ +public class ResponseTimeCircuitBreaker extends AbstractCircuitBreaker { + + private static final double SLOW_REQUEST_RATIO_MAX_VALUE = 1.0d; + + private final long maxAllowedRt; + private final double maxSlowRequestRatio; + private final int minRequestAmount; + + private final LeapArray slidingCounter; + + public ResponseTimeCircuitBreaker(DegradeRule rule) { + this(rule, new SlowRequestLeapArray(1, rule.getStatIntervalMs())); + } + + ResponseTimeCircuitBreaker(DegradeRule rule, LeapArray stat) { + super(rule); + AssertUtil.isTrue(rule.getGrade() == RuleConstant.DEGRADE_GRADE_RT, "rule metric type should be RT"); + AssertUtil.notNull(stat, "stat cannot be null"); + this.maxAllowedRt = Math.round(rule.getCount()); + this.maxSlowRequestRatio = rule.getSlowRatioThreshold(); + this.minRequestAmount = rule.getMinRequestAmount(); + this.slidingCounter = stat; + } + + @Override + public void resetStat() { + // Reset current bucket (bucket count = 1). + slidingCounter.currentWindow().value().reset(); + } + + @Override + public void onRequestComplete(Context context) { + SlowRequestCounter counter = slidingCounter.currentWindow().value(); + Entry entry = context.getCurEntry(); + if (entry == null) { + return; + } + long completeTime = entry.getCompleteTimestamp(); + if (completeTime <= 0) { + completeTime = TimeUtil.currentTimeMillis(); + } + long rt = completeTime - entry.getCreateTimestamp(); + if (rt > maxAllowedRt) { + counter.slowCount.add(1); + } + counter.totalCount.add(1); + + handleStateChangeWhenThresholdExceeded(rt); + } + + private void handleStateChangeWhenThresholdExceeded(long rt) { + if (currentState.get() == State.OPEN) { + return; + } + + if (currentState.get() == State.HALF_OPEN) { + // In detecting request + // TODO: improve logic for half-open recovery + if (rt > maxAllowedRt) { + fromHalfOpenToOpen(1.0d); + } else { + fromHalfOpenToClose(); + } + return; + } + + List counters = slidingCounter.values(); + long slowCount = 0; + long totalCount = 0; + for (SlowRequestCounter counter : counters) { + slowCount += counter.slowCount.sum(); + totalCount += counter.totalCount.sum(); + } + if (totalCount < minRequestAmount) { + return; + } + double currentRatio = slowCount * 1.0d / totalCount; + if (currentRatio > maxSlowRequestRatio) { + transformToOpen(currentRatio); + } + if (Double.compare(currentRatio, maxSlowRequestRatio) == 0 && + Double.compare(maxSlowRequestRatio, SLOW_REQUEST_RATIO_MAX_VALUE) == 0) { + transformToOpen(currentRatio); + } + } + + static class SlowRequestCounter { + private LongAdder slowCount; + private LongAdder totalCount; + + public SlowRequestCounter() { + this.slowCount = new LongAdder(); + this.totalCount = new LongAdder(); + } + + public LongAdder getSlowCount() { + return slowCount; + } + + public LongAdder getTotalCount() { + return totalCount; + } + + public SlowRequestCounter reset() { + slowCount.reset(); + totalCount.reset(); + return this; + } + + @Override + public String toString() { + return "SlowRequestCounter{" + + "slowCount=" + slowCount + + ", totalCount=" + totalCount + + '}'; + } + } + + static class SlowRequestLeapArray extends LeapArray { + + public SlowRequestLeapArray(int sampleCount, int intervalInMs) { + super(sampleCount, intervalInMs); + } + + @Override + public SlowRequestCounter newEmptyBucket(long timeMillis) { + return new SlowRequestCounter(); + } + + @Override + protected WindowWrap resetWindowTo(WindowWrap w, long startTime) { + w.resetTo(startTime); + w.value().reset(); + return w; + } + } +} diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java index 2b3564f2e6..0ce97548ff 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/ClusterFlowConfig.java @@ -18,6 +18,8 @@ import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import java.util.Objects; + /** * Flow rule config in cluster mode. * @@ -48,6 +50,61 @@ public class ClusterFlowConfig { */ private int windowIntervalMs = RuleConstant.DEFAULT_WINDOW_INTERVAL_MS; + /** + * if the client keep the token for more than resourceTimeout,resourceTimeoutStrategy will work. + */ + private long resourceTimeout = 2000; + + /** + * 0:ignore,1:release the token. + */ + private int resourceTimeoutStrategy = RuleConstant.DEFAULT_RESOURCE_TIMEOUT_STRATEGY; + + /** + * if the request(prioritized=true) is block,acquireRefuseStrategy will work.. + * 0:ignore and block. + * 1:try again . + * 2:try until success. + */ + private int acquireRefuseStrategy = RuleConstant.DEFAULT_BLOCK_STRATEGY; + + /** + * if a client is offline,the server will delete all the token the client holds after clientOfflineTime. + */ + private long clientOfflineTime = 2000; + + public long getResourceTimeout() { + return resourceTimeout; + } + + public void setResourceTimeout(long resourceTimeout) { + this.resourceTimeout = resourceTimeout; + } + + public int getResourceTimeoutStrategy() { + return resourceTimeoutStrategy; + } + + public void setResourceTimeoutStrategy(int resourceTimeoutStrategy) { + this.resourceTimeoutStrategy = resourceTimeoutStrategy; + } + + public int getAcquireRefuseStrategy() { + return acquireRefuseStrategy; + } + + public void setAcquireRefuseStrategy(int acquireRefuseStrategy) { + this.acquireRefuseStrategy = acquireRefuseStrategy; + } + + public long getClientOfflineTime() { + return clientOfflineTime; + } + + public void setClientOfflineTime(long clientOfflineTime) { + this.clientOfflineTime = clientOfflineTime; + } + public Long getFlowId() { return flowId; } @@ -104,17 +161,43 @@ public ClusterFlowConfig setWindowIntervalMs(int windowIntervalMs) { @Override public boolean equals(Object o) { - if (this == o) { return true; } - if (o == null || getClass() != o.getClass()) { return false; } - - ClusterFlowConfig that = (ClusterFlowConfig)o; - - if (thresholdType != that.thresholdType) { return false; } - if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { return false; } - if (strategy != that.strategy) { return false; } - if (sampleCount != that.sampleCount) { return false; } - if (windowIntervalMs != that.windowIntervalMs) { return false; } - return flowId != null ? flowId.equals(that.flowId) : that.flowId == null; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + ClusterFlowConfig that = (ClusterFlowConfig) o; + + if (thresholdType != that.thresholdType) { + return false; + } + if (fallbackToLocalWhenFail != that.fallbackToLocalWhenFail) { + return false; + } + if (strategy != that.strategy) { + return false; + } + if (sampleCount != that.sampleCount) { + return false; + } + if (windowIntervalMs != that.windowIntervalMs) { + return false; + } + if (resourceTimeout != that.resourceTimeout) { + return false; + } + if (clientOfflineTime != that.clientOfflineTime) { + return false; + } + if (resourceTimeoutStrategy != that.resourceTimeoutStrategy) { + return false; + } + if (acquireRefuseStrategy != that.acquireRefuseStrategy) { + return false; + } + return Objects.equals(flowId, that.flowId); } @Override @@ -125,18 +208,26 @@ public int hashCode() { result = 31 * result + strategy; result = 31 * result + sampleCount; result = 31 * result + windowIntervalMs; + result = (int) (31 * result + resourceTimeout); + result = (int) (31 * result + clientOfflineTime); + result = 31 * result + resourceTimeoutStrategy; + result = 31 * result + acquireRefuseStrategy; return result; } @Override public String toString() { return "ClusterFlowConfig{" + - "flowId=" + flowId + - ", thresholdType=" + thresholdType + - ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail + - ", strategy=" + strategy + - ", sampleCount=" + sampleCount + - ", windowIntervalMs=" + windowIntervalMs + - '}'; + "flowId=" + flowId + + ", thresholdType=" + thresholdType + + ", fallbackToLocalWhenFail=" + fallbackToLocalWhenFail + + ", strategy=" + strategy + + ", sampleCount=" + sampleCount + + ", windowIntervalMs=" + windowIntervalMs + + ", resourceTimeout=" + resourceTimeout + + ", resourceTimeoutStrategy=" + resourceTimeoutStrategy + + ", acquireRefuseStrategy=" + acquireRefuseStrategy + + ", clientOfflineTime=" + clientOfflineTime + + '}'; } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java index e7413ea219..f05d1cbff4 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRule.java @@ -15,8 +15,6 @@ */ package com.alibaba.csp.sentinel.slots.block.flow; -import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slots.block.AbstractRule; import com.alibaba.csp.sentinel.slots.block.RuleConstant; @@ -186,11 +184,6 @@ public FlowRule setClusterConfig(ClusterFlowConfig clusterConfig) { return this; } - @Override - public boolean passCheck(Context context, DefaultNode node, int acquireCount, Object... args) { - return true; - } - @Override public boolean equals(Object o) { if (this == o) { return true; } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java index 40d2f92a5a..dfcc4cb591 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManager.java @@ -16,12 +16,14 @@ package com.alibaba.csp.sentinel.slots.block.flow; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import com.alibaba.csp.sentinel.concurrent.NamedThreadFactory; import com.alibaba.csp.sentinel.log.RecordLog; @@ -43,10 +45,11 @@ * * @author jialiang.linjl * @author Eric Zhao + * @author Weihua */ public class FlowRuleManager { - private static final Map> flowRules = new ConcurrentHashMap>(); + private static final AtomicReference>> flowRules = new AtomicReference>>(); private static final FlowPropertyListener LISTENER = new FlowPropertyListener(); private static SentinelProperty> currentProperty = new DynamicSentinelProperty>(); @@ -56,6 +59,7 @@ public class FlowRuleManager { new NamedThreadFactory("sentinel-metrics-record-task", true)); static { + flowRules.set(Collections.>emptyMap()); currentProperty.addListener(LISTENER); SCHEDULER.scheduleAtFixedRate(new MetricTimerListener(), 0, 1, TimeUnit.SECONDS); } @@ -83,7 +87,7 @@ public static void register2Property(SentinelProperty> property) */ public static List getRules() { List rules = new ArrayList(); - for (Map.Entry> entry : flowRules.entrySet()) { + for (Map.Entry> entry : flowRules.get().entrySet()) { rules.addAll(entry.getValue()); } return rules; @@ -99,11 +103,11 @@ public static void loadRules(List rules) { } static Map> getFlowRuleMap() { - return flowRules; + return flowRules.get(); } public static boolean hasConfig(String resource) { - return flowRules.containsKey(resource); + return flowRules.get().containsKey(resource); } public static boolean isOtherOrigin(String origin, String resourceName) { @@ -111,7 +115,7 @@ public static boolean isOtherOrigin(String origin, String resourceName) { return false; } - List rules = flowRules.get(resourceName); + List rules = flowRules.get().get(resourceName); if (rules != null) { for (FlowRule rule : rules) { @@ -129,21 +133,17 @@ private static final class FlowPropertyListener implements PropertyListener value) { Map> rules = FlowRuleUtil.buildFlowRuleMap(value); - if (rules != null) { - flowRules.clear(); - flowRules.putAll(rules); - } - RecordLog.info("[FlowRuleManager] Flow rules received: " + flowRules); + //the rules was always not null, it's no need to check nullable + //remove checking to avoid IDE warning + flowRules.set(rules); + RecordLog.info("[FlowRuleManager] Flow rules received: {}", rules); } @Override public void configLoad(List conf) { Map> rules = FlowRuleUtil.buildFlowRuleMap(conf); - if (rules != null) { - flowRules.clear(); - flowRules.putAll(rules); - } - RecordLog.info("[FlowRuleManager] Flow rules loaded: " + flowRules); + flowRules.set(rules); + RecordLog.info("[FlowRuleManager] Flow rules loaded: {}", rules); } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java index 6a79e074d9..f349e952b9 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleUtil.java @@ -15,16 +15,6 @@ */ package com.alibaba.csp.sentinel.slots.block.flow; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - import com.alibaba.csp.sentinel.log.RecordLog; import com.alibaba.csp.sentinel.slots.block.ClusterRuleConstant; import com.alibaba.csp.sentinel.slots.block.RuleConstant; @@ -36,6 +26,10 @@ import com.alibaba.csp.sentinel.util.function.Function; import com.alibaba.csp.sentinel.util.function.Predicate; +import java.util.*; +import java.util.Map.Entry; +import java.util.concurrent.ConcurrentHashMap; + /** * @author Eric Zhao * @since 1.4.0 @@ -55,8 +49,8 @@ public static Map> buildFlowRuleMap(List list) /** * Build the flow rule map from raw list of flow rules, grouping by resource name. * - * @param list raw list of flow rules - * @param filter rule filter + * @param list raw list of flow rules + * @param filter rule filter * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules */ public static Map> buildFlowRuleMap(List list, Predicate filter) { @@ -66,9 +60,9 @@ public static Map> buildFlowRuleMap(List list, /** * Build the flow rule map from raw list of flow rules, grouping by resource name. * - * @param list raw list of flow rules - * @param filter rule filter - * @param shouldSort whether the rules should be sorted + * @param list raw list of flow rules + * @param filter rule filter + * @param shouldSort whether the rules should be sorted * @return constructed new flow rule map; empty map if list is null or empty, or no wanted rules */ public static Map> buildFlowRuleMap(List list, Predicate filter, @@ -105,7 +99,6 @@ public static Map> buildFlowRuleMap(List list, F if (StringUtil.isBlank(rule.getLimitApp())) { rule.setLimitApp(RuleConstant.LIMIT_APP_DEFAULT); } - TrafficShapingController rater = generateRater(rule); rule.setRater(rater); @@ -141,12 +134,12 @@ private static TrafficShapingController generateRater(/*@Valid*/ FlowRule rule) switch (rule.getControlBehavior()) { case RuleConstant.CONTROL_BEHAVIOR_WARM_UP: return new WarmUpController(rule.getCount(), rule.getWarmUpPeriodSec(), - ColdFactorProperty.coldFactor); + ColdFactorProperty.coldFactor); case RuleConstant.CONTROL_BEHAVIOR_RATE_LIMITER: return new RateLimiterController(rule.getMaxQueueingTimeMs(), rule.getCount()); case RuleConstant.CONTROL_BEHAVIOR_WARM_UP_RATE_LIMITER: return new WarmUpRateLimiterController(rule.getCount(), rule.getWarmUpPeriodSec(), - rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor); + rule.getMaxQueueingTimeMs(), ColdFactorProperty.coldFactor); case RuleConstant.CONTROL_BEHAVIOR_DEFAULT: default: // Default mode or unknown mode: default traffic shaping controller (fast-reject). @@ -173,12 +166,42 @@ public static boolean validClusterRuleId(Long id) { */ public static boolean isValidRule(FlowRule rule) { boolean baseValid = rule != null && !StringUtil.isBlank(rule.getResource()) && rule.getCount() >= 0 - && rule.getGrade() >= 0 && rule.getStrategy() >= 0 && rule.getControlBehavior() >= 0; + && rule.getGrade() >= 0 && rule.getStrategy() >= 0 && rule.getControlBehavior() >= 0; if (!baseValid) { return false; } - // Check strategy and control (shaping) behavior. - return checkClusterField(rule) && checkStrategyField(rule) && checkControlBehaviorField(rule); + if (rule.getGrade() == RuleConstant.FLOW_GRADE_QPS) { + // Check strategy and control (shaping) behavior. + return checkClusterField(rule) && checkStrategyField(rule) && checkControlBehaviorField(rule); + } else if (rule.getGrade() == RuleConstant.FLOW_GRADE_THREAD) { + return checkClusterConcurrentField(rule); + } else { + return false; + } + + } + + public static boolean checkClusterConcurrentField(/*@NonNull*/ FlowRule rule) { + if (!rule.isClusterMode()) { + return true; + } + ClusterFlowConfig clusterConfig = rule.getClusterConfig(); + if (clusterConfig == null) { + return false; + } + if (clusterConfig.getClientOfflineTime() <= 0 || clusterConfig.getResourceTimeout() <= 0) { + return false; + } + + if (clusterConfig.getAcquireRefuseStrategy() < 0 || clusterConfig.getResourceTimeoutStrategy() < 0) { + return false; + } + + if (!validClusterRuleId(clusterConfig.getFlowId())) { + return false; + } + + return isWindowConfigValid(clusterConfig.getSampleCount(), clusterConfig.getWindowIntervalMs()); } private static boolean checkClusterField(/*@NonNull*/ FlowRule rule) { @@ -234,5 +257,6 @@ public String apply(FlowRule rule) { } }; - private FlowRuleUtil() {} + private FlowRuleUtil() { + } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java index f99987f7ba..d05d51c8e7 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlot.java @@ -15,18 +15,19 @@ */ package com.alibaba.csp.sentinel.slots.block.flow; -import java.util.Collection; -import java.util.List; -import java.util.Map; - import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.spi.SpiOrder; import com.alibaba.csp.sentinel.util.AssertUtil; import com.alibaba.csp.sentinel.util.function.Function; +import java.util.Collection; +import java.util.List; +import java.util.Map; + /** *

    * Combined the runtime statistics collected from the previous @@ -136,6 +137,7 @@ * @author jialiang.linjl * @author Eric Zhao */ +@SpiOrder(-2000) public class FlowSlot extends AbstractLinkedProcessorSlot { private final FlowRuleChecker checker; diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java index b4ee59181b..5994be47b5 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/clusterbuilder/ClusterBuilderSlot.java @@ -30,6 +30,7 @@ import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; +import com.alibaba.csp.sentinel.spi.SpiOrder; /** *

    @@ -44,12 +45,13 @@ * * @author jialiang.linjl */ +@SpiOrder(-9000) public class ClusterBuilderSlot extends AbstractLinkedProcessorSlot { /** *

    * Remember that same resource({@link ResourceWrapper#equals(Object)}) will share - * the same {@link ProcessorSlotChain} globally, no matter in witch context. So if + * the same {@link ProcessorSlotChain} globally, no matter in which context. So if * code goes into {@link #entry(Context, ResourceWrapper, DefaultNode, int, boolean, Object...)}, * the resource name must be same but context name may not. *

    diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/logger/LogSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/logger/LogSlot.java index 802690a9ab..a7515b0168 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/logger/LogSlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/logger/LogSlot.java @@ -21,11 +21,13 @@ import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.spi.SpiOrder; /** * A {@link com.alibaba.csp.sentinel.slotchain.ProcessorSlot} that is response for logging block exceptions * to provide concrete logs for troubleshooting. */ +@SpiOrder(-8000) public class LogSlot extends AbstractLinkedProcessorSlot { @Override diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java index f09f19e7f0..c965137f06 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/nodeselector/NodeSelectorSlot.java @@ -22,6 +22,7 @@ import com.alibaba.csp.sentinel.node.EntranceNode; import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.spi.SpiOrder; import java.util.HashMap; import java.util.Map; @@ -122,6 +123,7 @@ * @see EntranceNode * @see ContextUtil */ +@SpiOrder(-10000) public class NodeSelectorSlot extends AbstractLinkedProcessorSlot { /** diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java index 09affecc9b..d491a16aca 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/StatisticSlot.java @@ -17,10 +17,11 @@ import java.util.Collection; -import com.alibaba.csp.sentinel.config.SentinelConfig; +import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotEntryCallback; import com.alibaba.csp.sentinel.slotchain.ProcessorSlotExitCallback; import com.alibaba.csp.sentinel.slots.block.flow.PriorityWaitException; +import com.alibaba.csp.sentinel.spi.SpiOrder; import com.alibaba.csp.sentinel.util.TimeUtil; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.EntryType; @@ -47,6 +48,7 @@ * @author jialiang.linjl * @author Eric Zhao */ +@SpiOrder(-7000) public class StatisticSlot extends AbstractLinkedProcessorSlot { @Override @@ -93,7 +95,7 @@ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode } } catch (BlockException e) { // Blocked, set block exception to current entry. - context.getCurEntry().setError(e); + context.getCurEntry().setBlockError(e); // Add block count. node.increaseBlockQps(count); @@ -113,52 +115,31 @@ public void entry(Context context, ResourceWrapper resourceWrapper, DefaultNode throw e; } catch (Throwable e) { - // Unexpected error, set error to current entry. + // Unexpected internal error, set error to current entry. context.getCurEntry().setError(e); - // This should not happen. - node.increaseExceptionQps(count); - if (context.getCurEntry().getOriginNode() != null) { - context.getCurEntry().getOriginNode().increaseExceptionQps(count); - } - - if (resourceWrapper.getEntryType() == EntryType.IN) { - Constants.ENTRY_NODE.increaseExceptionQps(count); - } throw e; } } @Override public void exit(Context context, ResourceWrapper resourceWrapper, int count, Object... args) { - DefaultNode node = (DefaultNode)context.getCurNode(); - - if (context.getCurEntry().getError() == null) { - // Calculate response time (max RT is statisticMaxRt from SentinelConfig). - long rt = TimeUtil.currentTimeMillis() - context.getCurEntry().getCreateTime(); - int maxStatisticRt = SentinelConfig.statisticMaxRt(); - if (rt > maxStatisticRt) { - rt = maxStatisticRt; - } + Node node = context.getCurNode(); - // Record response time and success count. - node.addRtAndSuccess(rt, count); - if (context.getCurEntry().getOriginNode() != null) { - context.getCurEntry().getOriginNode().addRtAndSuccess(rt, count); - } + if (context.getCurEntry().getBlockError() == null) { + // Calculate response time (use completeStatTime as the time of completion). + long completeStatTime = TimeUtil.currentTimeMillis(); + context.getCurEntry().setCompleteTimestamp(completeStatTime); + long rt = completeStatTime - context.getCurEntry().getCreateTimestamp(); - node.decreaseThreadNum(); - - if (context.getCurEntry().getOriginNode() != null) { - context.getCurEntry().getOriginNode().decreaseThreadNum(); - } + Throwable error = context.getCurEntry().getError(); + // Record response time and success count. + recordCompleteFor(node, count, rt, error); + recordCompleteFor(context.getCurEntry().getOriginNode(), count, rt, error); if (resourceWrapper.getEntryType() == EntryType.IN) { - Constants.ENTRY_NODE.addRtAndSuccess(rt, count); - Constants.ENTRY_NODE.decreaseThreadNum(); + recordCompleteFor(Constants.ENTRY_NODE, count, rt, error); } - } else { - // Error may happen. } // Handle exit event with registered exit callback handlers. @@ -169,4 +150,16 @@ public void exit(Context context, ResourceWrapper resourceWrapper, int count, Ob fireExit(context, resourceWrapper, count); } + + private void recordCompleteFor(Node node, int batchCount, long rt, Throwable error) { + if (node == null) { + return; + } + node.addRtAndSuccess(rt, batchCount); + node.decreaseThreadNum(); + + if (error != null && !(error instanceof BlockException)) { + node.increaseExceptionQps(batchCount); + } + } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java index 2bba544be1..32b854e863 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/statistic/base/LeapArray.java @@ -43,6 +43,7 @@ public abstract class LeapArray { protected int windowLengthInMs; protected int sampleCount; protected int intervalInMs; + private double intervalInSecond; protected final AtomicReferenceArray> array; @@ -64,6 +65,7 @@ public LeapArray(int sampleCount, int intervalInMs) { this.windowLengthInMs = intervalInMs / sampleCount; this.intervalInMs = intervalInMs; + this.intervalInSecond = intervalInMs / 1000.0; this.sampleCount = sampleCount; this.array = new AtomicReferenceArray<>(sampleCount); @@ -393,7 +395,7 @@ public int getIntervalInMs() { * @return interval in second */ public double getIntervalInSecond() { - return intervalInMs / 1000.0; + return intervalInSecond; } public void debug(long time) { diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRule.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRule.java index 7a2ac333c5..66afdb29f8 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRule.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemRule.java @@ -15,8 +15,6 @@ */ package com.alibaba.csp.sentinel.slots.system; -import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.slots.block.AbstractRule; /** @@ -133,11 +131,6 @@ public void setHighestCpuUsage(double highestCpuUsage) { this.highestCpuUsage = highestCpuUsage; } - @Override - public boolean passCheck(Context context, DefaultNode node, int count, Object... args) { - return true; - } - @Override public boolean equals(Object o) { if (this == o) { diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemSlot.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemSlot.java index 1f67e4263e..5912e62210 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemSlot.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemSlot.java @@ -20,6 +20,7 @@ import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.spi.SpiOrder; /** * A {@link ProcessorSlot} that dedicates to {@link SystemRule} checking. @@ -27,6 +28,7 @@ * @author jialiang.linjl * @author leyou */ +@SpiOrder(-5000) public class SystemSlot extends AbstractLinkedProcessorSlot { @Override diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java index 2310160a16..01c697c35e 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/slots/system/SystemStatusListener.java @@ -16,6 +16,8 @@ package com.alibaba.csp.sentinel.slots.system; import java.lang.management.ManagementFactory; +import java.lang.management.RuntimeMXBean; +import java.util.concurrent.TimeUnit; import com.alibaba.csp.sentinel.Constants; import com.alibaba.csp.sentinel.log.RecordLog; @@ -33,6 +35,9 @@ public class SystemStatusListener implements Runnable { volatile String reason = StringUtil.EMPTY; + volatile long processCpuTime = 0; + volatile long processUpTime = 0; + public double getSystemAverageLoad() { return currentLoad; } @@ -46,6 +51,7 @@ public void run() { try { OperatingSystemMXBean osBean = ManagementFactory.getPlatformMXBean(OperatingSystemMXBean.class); currentLoad = osBean.getSystemLoadAverage(); + /* * Java Doc copied from {@link OperatingSystemMXBean#getSystemCpuLoad()}:
    * Returns the "recent cpu usage" for the whole system. This value is a double in the [0.0,1.0] interval. @@ -54,7 +60,21 @@ public void run() { * observed. All values between 0.0 and 1.0 are possible depending of the activities going on in the * system. If the system recent cpu usage is not available, the method returns a negative value. */ - currentCpuUsage = osBean.getSystemCpuLoad(); + double systemCpuUsage = osBean.getSystemCpuLoad(); + + // calculate process cpu usage to support application running in container environment + RuntimeMXBean runtimeBean = ManagementFactory.getPlatformMXBean(RuntimeMXBean.class); + long newProcessCpuTime = osBean.getProcessCpuTime(); + long newProcessUpTime = runtimeBean.getUptime(); + int cpuCores = osBean.getAvailableProcessors(); + long processCpuTimeDiffInMs = TimeUnit.NANOSECONDS + .toMillis(newProcessCpuTime - processCpuTime); + long processUpTimeDiffInMs = newProcessUpTime - processUpTime; + double processCpuUsage = (double) processCpuTimeDiffInMs / processUpTimeDiffInMs / cpuCores; + processCpuTime = newProcessCpuTime; + processUpTime = newProcessUpTime; + + currentCpuUsage = Math.max(processCpuUsage, systemCpuUsage); if (currentLoad > SystemRuleManager.getSystemLoadThreshold()) { writeSystemStatusLog(); diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AppNameUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AppNameUtil.java index 5cfa766ee2..1f291302ee 100755 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AppNameUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/AppNameUtil.java @@ -15,86 +15,21 @@ */ package com.alibaba.csp.sentinel.util; -import com.alibaba.csp.sentinel.log.RecordLog; - -import java.io.File; +import com.alibaba.csp.sentinel.config.SentinelConfig; /** - * Util class for getting application name. This class uses the flowing order to get app's name: - * - *
      - *
    1. get {@code project.name} from System Properties, if not null, use the value as app name;
    2. - *
    3. get {@code sun.java.command} from System properties, remove path, arguments and ".jar" or ".JAR" - * suffix, use the result as app name. Note that whitespace in file name or path is not allowed, or a - * wrong app name may be gotten, For example: - *

      - * - * "test.Main" -> test.Main
      - * "/target/test.Main" -> test.Main
      - * "/target/test.Main args1" -> test.Main
      - * "Main.jar" -> Main
      - * "/target/Main.JAR args1" -> Main
      - * "Mai n.jar" -> Mai // whitespace in file name is not allowed
      - *
      - *

      - *
    4. - *
    - * * @author Eric Zhao * @author leyou */ -public final class AppNameUtil { - public static final String APP_NAME = "project.name"; - public static final String SUN_JAVA_COMMAND = "sun.java.command"; - private static final String JAR_SUFFIX_LOWER = ".jar"; - private static final String JAR_SUFFIX_UPPER = ".JAR"; +public final class AppNameUtil { - private static String appName; private AppNameUtil() { } - static { - resolveAppName(); - RecordLog.info("App name resolved: " + appName); - } - - public static void resolveAppName() { - String app = System.getProperty(APP_NAME); - // use -Dproject.name first - if (!isEmpty(app)) { - appName = app; - return; - } - - // parse sun.java.command property - String command = System.getProperty(SUN_JAVA_COMMAND); - if (isEmpty(command)) { - return; - } - command = command.split("\\s")[0]; - String separator = File.separator; - if (command.contains(separator)) { - String[] strs; - if ("\\".equals(separator)) { - strs = command.split("\\\\"); - } else { - strs = command.split(separator); - } - command = strs[strs.length - 1]; - } - if (command.endsWith(JAR_SUFFIX_LOWER) || command.endsWith(JAR_SUFFIX_UPPER)) { - command = command.substring(0, command.length() - 4); - } - appName = command; - } - public static String getAppName() { - return appName; + return SentinelConfig.getAppName(); } - private static boolean isEmpty(String str) { - return str == null || "".equals(str); - } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java index e36a6e8761..47b3342a5c 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/SpiLoader.java @@ -34,6 +34,14 @@ public final class SpiLoader { private static final Map SERVICE_LOADER_MAP = new ConcurrentHashMap(); + /** + * Load the first-found specific SPI instance + * + * @param clazz class of the SPI interface + * @param SPI type + * @return the first specific SPI instance if exists, or else return null + * @since 1.7.0 + */ public static T loadFirstInstance(Class clazz) { AssertUtil.notNull(clazz, "SPI class cannot be null"); try { @@ -52,7 +60,7 @@ public static T loadFirstInstance(Class clazz) { return null; } } catch (Throwable t) { - RecordLog.warn("[SpiLoader] ERROR: loadFirstInstance failed", t); + RecordLog.error("[SpiLoader] ERROR: loadFirstInstance failed", t); t.printStackTrace(); return null; } @@ -87,7 +95,7 @@ public static T loadFirstInstanceOrDefault(Class clazz, Class T loadFirstInstanceOrDefault(Class clazz, Class SPI type * @return the SPI instance with highest priority if exists, or else false @@ -114,23 +124,26 @@ public static T loadHighestPriorityInstance(Class clazz) { SpiOrderWrapper w = null; for (T spi : serviceLoader) { int order = SpiOrderResolver.resolveOrder(spi); - RecordLog.info("[SpiLoader] Found {0} SPI: {1} with order " + order, clazz.getSimpleName(), - spi.getClass().getCanonicalName()); + RecordLog.info("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(), + spi.getClass().getCanonicalName(), order); if (w == null || order < w.order) { w = new SpiOrderWrapper<>(order, spi); } } return w == null ? null : w.spi; } catch (Throwable t) { - RecordLog.warn("[SpiLoader] ERROR: loadHighestPriorityInstance failed", t); + RecordLog.error("[SpiLoader] ERROR: loadHighestPriorityInstance failed", t); t.printStackTrace(); return null; } } /** + * Load and sorted SPI instance list. * Load the SPI instance list for provided SPI interface. * + * Note: each call return same instances. + * * @param clazz class of the SPI * @param SPI type * @return sorted SPI instance list @@ -148,11 +161,13 @@ public static List loadInstanceList(Class clazz) { List list = new ArrayList<>(); for (T spi : serviceLoader) { + RecordLog.info("[SpiLoader] Found {} SPI: {}", clazz.getSimpleName(), + spi.getClass().getCanonicalName()); list.add(spi); } return list; } catch (Throwable t) { - RecordLog.warn("[SpiLoader] ERROR: loadInstanceListSorted failed", t); + RecordLog.error("[SpiLoader] ERROR: loadInstanceList failed", t); t.printStackTrace(); return new ArrayList<>(); } @@ -161,6 +176,8 @@ public static List loadInstanceList(Class clazz) { /** * Load the sorted SPI instance list for provided SPI interface. * + * Note: each call return same instances. + * * @param clazz class of the SPI * @param SPI type * @return sorted SPI instance list @@ -181,16 +198,51 @@ public static List loadInstanceListSorted(Class clazz) { int order = SpiOrderResolver.resolveOrder(spi); // Since SPI is lazy initialized in ServiceLoader, we use online sort algorithm here. SpiOrderResolver.insertSorted(orderWrappers, spi, order); - RecordLog.info("[SpiLoader] Found {0} SPI: {1} with order " + order, clazz.getSimpleName(), - spi.getClass().getCanonicalName()); + RecordLog.info("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(), + spi.getClass().getCanonicalName(), order); } - List list = new ArrayList<>(); + List list = new ArrayList<>(orderWrappers.size()); + for (int i = 0; i < orderWrappers.size(); i++) { + list.add(orderWrappers.get(i).spi); + } + return list; + } catch (Throwable t) { + RecordLog.error("[SpiLoader] ERROR: loadInstanceListSorted failed", t); + t.printStackTrace(); + return new ArrayList<>(); + } + } + + /** + * Load the sorted and prototype SPI instance list for provided SPI interface. + * + * Note: each call return different instances, i.e. prototype instance, not singleton instance. + * + * @param clazz class of the SPI + * @param SPI type + * @return sorted and different SPI instance list + * @since 1.7.2 + */ + public static List loadPrototypeInstanceListSorted(Class clazz) { + try { + // Not use SERVICE_LOADER_MAP, to make sure the instances loaded are different. + ServiceLoader serviceLoader = ServiceLoaderUtil.getServiceLoader(clazz); + + List> orderWrappers = new ArrayList<>(); + for (T spi : serviceLoader) { + int order = SpiOrderResolver.resolveOrder(spi); + // Since SPI is lazy initialized in ServiceLoader, we use online sort algorithm here. + SpiOrderResolver.insertSorted(orderWrappers, spi, order); + RecordLog.debug("[SpiLoader] Found {} SPI: {} with order {}", clazz.getSimpleName(), + spi.getClass().getCanonicalName(), order); + } + List list = new ArrayList<>(orderWrappers.size()); for (int i = 0; i < orderWrappers.size(); i++) { - list.add(i, orderWrappers.get(i).spi); + list.add(orderWrappers.get(i).spi); } return list; } catch (Throwable t) { - RecordLog.warn("[SpiLoader] ERROR: loadInstanceListSorted failed", t); + RecordLog.error("[SpiLoader] ERROR: loadPrototypeInstanceListSorted failed", t); t.printStackTrace(); return new ArrayList<>(); } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/VersionUtil.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/VersionUtil.java index 8f9d82cd15..21e0943c77 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/VersionUtil.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/VersionUtil.java @@ -36,4 +36,69 @@ public static String getVersion(String defaultVersion) { } private VersionUtil() {} + + private static int parseInt(String str) { + if (str == null || str.length() < 1) { + return 0; + } + int num = 0; + for (int i = 0; i < str.length(); i ++) { + char ch = str.charAt(i); + if (ch < '0' || ch > '9') { + break; + } + num = num * 10 + (ch - '0'); + } + return num; + } + + /** + * Convert version in string like x.y.z or x.y.z.b into number
    + * Each segment has one byte space(unsigned)
    + * eg.
    + *
    +     * 1.2.3.4 => 01 02 03 04
    +     * 1.2.3   => 01 02 03 00
    +     * 1.2     => 01 02 00 00
    +     * 1       => 01 00 00 00
    +     * 
    + * + * @return + */ + public static int fromVersionString(String verStr) { + if (verStr == null || verStr.length() < 1) { + return 0; + } + int[] versions = new int[] {0, 0, 0, 0}; + int index = 0; + String segment; + int cur = 0; + int pos; + do { + if (index >= versions.length) { + // More dots than "x.y.z.b" contains + return 0; + } + pos = verStr.indexOf('.', cur); + if (pos == -1) { + segment = verStr.substring(cur); + } else if (cur < pos) { + segment = verStr.substring(cur, pos); + } else { + // Illegal format + return 0; + } + versions[index] = parseInt(segment); + if (versions[index] < 0 || versions[index] > 255) { + // Out of range [0, 255] + return 0; + } + cur = pos + 1; + index ++; + } while (pos > 0); + return ((versions[0] & 0xff) << 24) + | ((versions[1] & 0xff) << 16) + | ((versions[2] & 0xff) << 8) + | (versions[3] & 0xff); + } } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogType.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/BiConsumer.java similarity index 80% rename from sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogType.java rename to sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/BiConsumer.java index c9c8cb29b4..48bdcee263 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/log/LogType.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/BiConsumer.java @@ -13,13 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.csp.sentinel.log; +package com.alibaba.csp.sentinel.util.function; /** - * An enum marks log type. - * @author xue8 + * BiConsumer interface from JDK 8. */ -public enum LogType { - COMMAND_CENTER_LOG, - RECORD_LOG, +public interface BiConsumer { + + void accept(T t, U u); } diff --git a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Tuple2.java b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Tuple2.java index 2b8ad3cdc5..65b5dfc965 100644 --- a/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Tuple2.java +++ b/sentinel-core/src/main/java/com/alibaba/csp/sentinel/util/function/Tuple2.java @@ -1,5 +1,7 @@ package com.alibaba.csp.sentinel.util.function; +import java.util.Objects; + /** * A tuple of 2 elements. */ @@ -31,11 +33,30 @@ public Tuple2 swap() { return new Tuple2(this.r2, this.r1); } + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Tuple2)) { + return false; + } + Tuple2 that = (Tuple2) o; + return Objects.equals(this.r1, that.r1) && Objects.equals(this.r2, that.r2); + } + + @Override + public int hashCode() { + int result = r1 != null ? r1.hashCode() : 0; + result = 31 * result + (r2 != null ? r2.hashCode() : 0); + return result; + } + @Override public String toString() { return "Tuple2{" + - "r1=" + r1 + - ", r2=" + r2 + - '}'; + "r1=" + r1 + + ", r2=" + r2 + + '}'; } -} \ No newline at end of file +} diff --git a/sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot b/sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot new file mode 100644 index 0000000000..afd9777162 --- /dev/null +++ b/sentinel-core/src/main/resources/META-INF/services/com.alibaba.csp.sentinel.slotchain.ProcessorSlot @@ -0,0 +1,9 @@ +# Sentinel default ProcessorSlots +com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot +com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot +com.alibaba.csp.sentinel.slots.logger.LogSlot +com.alibaba.csp.sentinel.slots.statistic.StatisticSlot +com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot +com.alibaba.csp.sentinel.slots.system.SystemSlot +com.alibaba.csp.sentinel.slots.block.flow.FlowSlot +com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot \ No newline at end of file diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/ConfigPropertyHelper.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/ConfigPropertyHelper.java index 773722e9ba..c64e06270e 100755 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/ConfigPropertyHelper.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/ConfigPropertyHelper.java @@ -20,6 +20,7 @@ import java.io.OutputStream; import java.util.Properties; +import com.alibaba.csp.sentinel.config.SentinelConfig; import com.alibaba.csp.sentinel.log.LogBase; import com.alibaba.csp.sentinel.util.AppNameUtil; @@ -31,11 +32,11 @@ public final class ConfigPropertyHelper { public static void setAppNameProperty(String appName) { - System.setProperty(AppNameUtil.APP_NAME, appName); + System.setProperty(SentinelConfig.APP_NAME_PROP_KEY, appName); } public static void clearAppNameProperty() { - System.clearProperty(AppNameUtil.APP_NAME); + System.clearProperty(SentinelConfig.APP_NAME_PROP_KEY); } public static void runWithConfig(Properties prop, String appName, Task task) throws Exception { diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/EntryTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/EntryTest.java index 3cfb8c0966..78b5650174 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/EntryTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/EntryTest.java @@ -1,8 +1,10 @@ package com.alibaba.csp.sentinel; +import com.alibaba.csp.sentinel.context.Context; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; +import com.alibaba.csp.sentinel.util.function.BiConsumer; import org.junit.Test; @@ -64,5 +66,10 @@ protected Entry trueExit(int count, Object... args) throws ErrorEntryFreeExcepti public Node getLastNode() { return null; } + + @Override + public void whenTerminate(BiConsumer consumer) { + // do nothing + } } -} \ No newline at end of file +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/RecordLogTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/RecordLogTest.java index e90d8c638c..d18a4122a2 100755 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/RecordLogTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/RecordLogTest.java @@ -54,7 +54,7 @@ public void testChangeLogBase() { System.setProperty(LogBase.LOG_DIR, newLogBase); RecordLog.info("testChangeLogBase"); - String logFileName = RecordLog.getLogBaseDir(); + String logFileName = LogBase.getLogBaseDir(); Assert.assertTrue(newLogBase.equals(logFileName)); File[] files = new File(logFileName).listFiles(); assertTrue(files != null && files.length > 0); @@ -65,7 +65,7 @@ public void testChangeLogBase() { @Test public void testLogBaseDir() { - assertTrue(RecordLog.getLogBaseDir().startsWith(System.getProperty("user.home"))); + assertTrue(LogBase.getLogBaseDir().startsWith(System.getProperty("user.home"))); } public void testLogNameNotUsePid() { diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/TracerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/TracerTest.java index 2a0e21a6ab..7acb3d7e4f 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/TracerTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/TracerTest.java @@ -2,6 +2,7 @@ import com.alibaba.csp.sentinel.context.ContextTestUtil; import com.alibaba.csp.sentinel.context.ContextUtil; +import com.alibaba.csp.sentinel.util.function.Predicate; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -52,6 +53,25 @@ public void setExceptionsToTrace() { Assert.assertFalse(Tracer.shouldTrace(new Exception())); } + @Test + public void setExceptionPredicate() { + + Predicate throwablePredicate = new Predicate() { + @Override + public boolean test(Throwable throwable) { + if (throwable instanceof TraceException) { + return true; + } else if (throwable instanceof IgnoreException) { + return false; + } + return false; + } + }; + Tracer.setExceptionPredicate(throwablePredicate); + Assert.assertTrue(Tracer.shouldTrace(new TraceException())); + Assert.assertFalse(Tracer.shouldTrace(new IgnoreException())); + } + @Test public void setExceptionsToIgnore() { Tracer.ignoreClasses = null; @@ -88,6 +108,11 @@ public void testNull2() { Tracer.setExceptionsToIgnore(IgnoreException.class, null); } + @Test(expected = IllegalArgumentException.class) + public void testNull3() { + Tracer.setExceptionPredicate(null); + } + private class TraceException extends Exception {} private class TraceException2 extends Exception {} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/config/SentinelConfigTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/config/SentinelConfigTest.java index 3108a08dcd..44273e19fb 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/config/SentinelConfigTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/config/SentinelConfigTest.java @@ -69,7 +69,7 @@ public void testColdFactoryLargerThanOne() { //add Jvm parameter - //-Dcsp.sentinel.config.file=sentinel-propertiesTest.properties + //-Dcsp.sentinel.config.file=classpath:sentinel-propertiesTest.properties //-Dcsp.sentinel.flow.cold.factor=5 //-Dcsp.sentinel.statistic.max.rt=1000 //@Test @@ -92,6 +92,8 @@ public void testLoadProperties() throws IOException { out.write(buildPropertyStr(COLD_FACTOR, "123")); out.write("\n"); out.write(buildPropertyStr(STATISTIC_MAX_RT, "6000")); + out.write("\n"); + out.write(buildPropertyStr(PROJECT_NAME_PROP_KEY, "sentinel_test")); out.flush(); out.close(); @@ -100,6 +102,7 @@ public void testLoadProperties() throws IOException { Assert.assertTrue(SentinelConfig.getConfig(TOTAL_METRIC_FILE_COUNT).equals("20")); Assert.assertTrue(SentinelConfig.getConfig(COLD_FACTOR).equals("5")); Assert.assertTrue(SentinelConfig.getConfig(STATISTIC_MAX_RT).equals("1000")); + Assert.assertTrue(SentinelConfig.getAppName().equals("sentinel_test")); } finally { if (file != null) { diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtilsTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtilsTest.java index 41c8e0329b..f7e4f93df8 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtilsTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/EagleEyeCoreUtilsTest.java @@ -15,6 +15,8 @@ */ package com.alibaba.csp.sentinel.eagleeye; +import java.util.TimeZone; + import org.junit.Assert; import org.junit.Rule; import org.junit.Test; @@ -153,8 +155,8 @@ public void testAppendLog() { @Test public void testFormatTime() { - Assert.assertEquals("2019-06-15 20:13:14.000", - EagleEyeCoreUtils.formatTime(1560600794000L)); + Assert.assertEquals("2019-06-15 12:13:14.000", + EagleEyeCoreUtils.formatTime(1560600794000L - TimeZone.getDefault().getRawOffset())); } @Test diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/TokenBucketTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/TokenBucketTest.java new file mode 100644 index 0000000000..6ff0ba7ab5 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/eagleeye/TokenBucketTest.java @@ -0,0 +1,41 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.eagleeye; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.TimeUnit; + +public class TokenBucketTest { + + @Test(expected = IllegalArgumentException.class) + public void testTokenBucketFailToken() { + TokenBucket tokenBucket = new TokenBucket(-1, TimeUnit.SECONDS.toMillis(10)); + } + + @Test(expected = IllegalArgumentException.class) + public void testTokenBucketFailTime() { + TokenBucket tokenBucket = new TokenBucket(10, 10); + } + + @Test + public void testAccept() { + TokenBucket tokenBucket = new TokenBucket(1, TimeUnit.SECONDS.toMillis(10)); + Assert.assertTrue(tokenBucket.accept(System.currentTimeMillis())); + Assert.assertFalse(tokenBucket.accept(System.currentTimeMillis())); + } +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandlerTest.java similarity index 94% rename from sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java rename to sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandlerTest.java index 57413603ca..b30d9b2ac1 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/ConsoleHandlerTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/log/jul/ConsoleHandlerTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.alibaba.csp.sentinel.log; +package com.alibaba.csp.sentinel.log.jul; import org.junit.Test; @@ -22,6 +22,9 @@ import java.util.logging.Level; import java.util.logging.LogRecord; +import com.alibaba.csp.sentinel.log.jul.ConsoleHandler; +import com.alibaba.csp.sentinel.log.jul.CspFormatter; + import static org.junit.Assert.*; /** diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/FakeAdvancedMetricExtension.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/FakeAdvancedMetricExtension.java new file mode 100644 index 0000000000..bd1b53ee7f --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/FakeAdvancedMetricExtension.java @@ -0,0 +1,93 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.metric.extension.callback; + +import com.alibaba.csp.sentinel.metric.extension.AdvancedMetricExtension; +import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; +import com.alibaba.csp.sentinel.slots.block.BlockException; + +/** + * @author bill_yip + * @author Eric Zhao + */ +class FakeAdvancedMetricExtension implements AdvancedMetricExtension { + + long pass = 0; + long block = 0; + long complete = 0; + long exception = 0; + long rt = 0; + + long concurrency = 0; + + @Override + public void onPass(ResourceWrapper rw, int batchCount, Object[] args) { + this.pass += batchCount; + this.concurrency++; + } + + @Override + public void onBlocked(ResourceWrapper rw, int batchCount, String origin, BlockException e, Object[] args) { + this.block += batchCount; + } + + @Override + public void onComplete(ResourceWrapper rw, long rt, int batchCount, Object[] args) { + this.complete += batchCount; + this.rt += rt; + this.concurrency--; + } + + @Override + public void onError(ResourceWrapper rw, Throwable throwable, int batchCount, Object[] args) { + this.exception += batchCount; + } + + @Override + public void addPass(String resource, int n, Object... args) { + // Do nothing because of using the enhanced one + } + + @Override + public void addBlock(String resource, int n, String origin, BlockException blockException, Object... args) { + // Do nothing because of using the enhanced one + } + + @Override + public void addSuccess(String resource, int n, Object... args) { + // Do nothing because of using the enhanced one + } + + @Override + public void addException(String resource, int n, Throwable throwable) { + // Do nothing because of using the enhanced one + } + + @Override + public void addRt(String resource, long rt, Object... args) { + // Do nothing because of using the enhanced one + } + + @Override + public void increaseThreadNum(String resource, Object... args) { + // Do nothing because of using the enhanced one + } + + @Override + public void decreaseThreadNum(String resource, Object... args) { + // Do nothing because of using the enhanced one + } +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallbackTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallbackTest.java index f1972c8e9e..924f333e6f 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallbackTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricEntryCallbackTest.java @@ -1,3 +1,18 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.alibaba.csp.sentinel.metric.extension.callback; import com.alibaba.csp.sentinel.EntryType; @@ -20,21 +35,30 @@ public class MetricEntryCallbackTest { @Test public void onPass() throws Exception { FakeMetricExtension extension = new FakeMetricExtension(); + FakeAdvancedMetricExtension advancedExtension = new FakeAdvancedMetricExtension(); MetricExtensionProvider.addMetricExtension(extension); + MetricExtensionProvider.addMetricExtension(advancedExtension); MetricEntryCallback entryCallback = new MetricEntryCallback(); StringResourceWrapper resourceWrapper = new StringResourceWrapper("resource", EntryType.OUT); int count = 2; Object[] args = {"args1", "args2"}; entryCallback.onPass(null, resourceWrapper, null, count, args); + // assert extension Assert.assertEquals(extension.pass, count); Assert.assertEquals(extension.thread, 1); + + // assert advancedExtension + Assert.assertEquals(advancedExtension.pass, count); + Assert.assertEquals(advancedExtension.concurrency, 1); } @Test public void onBlocked() throws Exception { FakeMetricExtension extension = new FakeMetricExtension(); + FakeAdvancedMetricExtension advancedExtension = new FakeAdvancedMetricExtension(); MetricExtensionProvider.addMetricExtension(extension); + MetricExtensionProvider.addMetricExtension(advancedExtension); MetricEntryCallback entryCallback = new MetricEntryCallback(); StringResourceWrapper resourceWrapper = new StringResourceWrapper("resource", EntryType.OUT); @@ -43,6 +67,9 @@ public void onBlocked() throws Exception { int count = 2; Object[] args = {"args1", "args2"}; entryCallback.onBlocked(new FlowException("xx"), context, resourceWrapper, null, count, args); + // assert extension Assert.assertEquals(extension.block, count); + // assert advancedExtension + Assert.assertEquals(advancedExtension.block, count); } } \ No newline at end of file diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallbackTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallbackTest.java index 604c94c29e..f7da610f78 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallbackTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/metric/extension/callback/MetricExitCallbackTest.java @@ -55,11 +55,44 @@ public void onExit() { int deltaMs = 100; when(entry.getError()).thenReturn(null); - when(entry.getCreateTime()).thenReturn(curMillis - deltaMs); + when(entry.getCreateTimestamp()).thenReturn(curMillis - deltaMs); when(context.getCurEntry()).thenReturn(entry); exitCallback.onExit(context, resourceWrapper, count, args); Assert.assertEquals(prevRt + deltaMs, extension.rt); Assert.assertEquals(extension.success, 6 + count); Assert.assertEquals(extension.thread, 10 - 1); } + + /** + * @author bill_yip + */ + @Test + public void advancedExtensionOnExit() { + FakeAdvancedMetricExtension extension = new FakeAdvancedMetricExtension(); + MetricExtensionProvider.addMetricExtension(extension); + + MetricExitCallback exitCallback = new MetricExitCallback(); + StringResourceWrapper resourceWrapper = new StringResourceWrapper("resource", EntryType.OUT); + int count = 2; + Object[] args = {"args1", "args2"}; + long prevRt = 20; + extension.rt = prevRt; + extension.complete = 6; + extension.concurrency = 10; + Context context = mock(Context.class); + Entry entry = mock(Entry.class); + + // Mock current time + long curMillis = System.currentTimeMillis(); + setCurrentMillis(curMillis); + + int deltaMs = 100; + when(entry.getError()).thenReturn(null); + when(entry.getCreateTimestamp()).thenReturn(curMillis - deltaMs); + when(context.getCurEntry()).thenReturn(entry); + exitCallback.onExit(context, resourceWrapper, count, args); + Assert.assertEquals(prevRt + deltaMs, extension.rt); + Assert.assertEquals(extension.complete, 6 + count); + Assert.assertEquals(extension.concurrency, 10 - 1); + } } diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java index 9c5710573c..e13ba553db 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/node/ClusterNodeTest.java @@ -15,8 +15,6 @@ */ package com.alibaba.csp.sentinel.node; -import com.alibaba.csp.sentinel.slots.block.flow.FlowException; - import org.junit.Test; import java.util.ArrayList; @@ -127,28 +125,4 @@ public Object call() throws Exception { } } } - - @Test - public void testTraceException() { - ClusterNode clusterNode = new ClusterNode("test"); - - Exception exception = new RuntimeException("test"); - - // test count<=0, no exceptionQps added - clusterNode.trace(exception, 0); - clusterNode.trace(exception, -1); - assertEquals(0, clusterNode.exceptionQps(), 0.01); - assertEquals(0, clusterNode.totalException()); - - // test count=1, not BlockException, 1 exceptionQps added - clusterNode.trace(exception, 1); - assertEquals(1, clusterNode.exceptionQps(), 0.01); - assertEquals(1, clusterNode.totalException()); - - // test count=1, BlockException, no exceptionQps added - FlowException flowException = new FlowException("flow"); - clusterNode.trace(flowException, 1); - assertEquals(1, clusterNode.exceptionQps(), 0.01); - assertEquals(1, clusterNode.totalException()); - } } diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java new file mode 100644 index 0000000000..ecdea6dad1 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/DefaultSlotChainBuilderTest.java @@ -0,0 +1,89 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots; + +import com.alibaba.csp.sentinel.slotchain.AbstractLinkedProcessorSlot; +import com.alibaba.csp.sentinel.slotchain.ProcessorSlotChain; +import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; +import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.alibaba.csp.sentinel.slots.logger.LogSlot; +import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; +import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; +import com.alibaba.csp.sentinel.slots.system.SystemSlot; +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * Test cases for {@link DefaultSlotChainBuilder}. + * + * @author cdfive + */ +public class DefaultSlotChainBuilderTest { + + @Test + public void testBuild() { + DefaultSlotChainBuilder builder = new DefaultSlotChainBuilder(); + ProcessorSlotChain slotChain = builder.build(); + assertNotNull(slotChain); + + // Verify the order of slot + AbstractLinkedProcessorSlot next = slotChain.getNext(); + assertTrue(next instanceof NodeSelectorSlot); + + // Store the first NodeSelectorSlot instance + NodeSelectorSlot nodeSelectorSlot = (NodeSelectorSlot) next; + + next = next.getNext(); + assertTrue(next instanceof ClusterBuilderSlot); + + next = next.getNext(); + assertTrue(next instanceof LogSlot); + + next = next.getNext(); + assertTrue(next instanceof StatisticSlot); + + next = next.getNext(); + assertTrue(next instanceof AuthoritySlot); + + next = next.getNext(); + assertTrue(next instanceof SystemSlot); + + next = next.getNext(); + assertTrue(next instanceof FlowSlot); + + next = next.getNext(); + assertTrue(next instanceof DegradeSlot); + + next = next.getNext(); + assertNull(next); + + // Build again to verify different instances + ProcessorSlotChain slotChain2 = builder.build(); + assertNotNull(slotChain2); + // Verify the two ProcessorSlotChain instances are different + assertNotSame(slotChain, slotChain2); + + next = slotChain2.getNext(); + assertTrue(next instanceof NodeSelectorSlot); + // Store the second NodeSelectorSlot instance + NodeSelectorSlot nodeSelectorSlot2 = (NodeSelectorSlot) next; + // Verify the two NodeSelectorSlot instances are different + assertNotSame(nodeSelectorSlot, nodeSelectorSlot2); + } +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/CircuitBreakingIntegrationTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/CircuitBreakingIntegrationTest.java new file mode 100755 index 0000000000..eb9dac797c --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/CircuitBreakingIntegrationTest.java @@ -0,0 +1,233 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade; + +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker.State; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStateChangeObserver; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.EventObserverRegistry; +import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +/** + * @author jialiang.linjl + * @author Eric Zhao + */ +public class CircuitBreakingIntegrationTest extends AbstractTimeBasedTest { + + @Before + public void setUp() { + DegradeRuleManager.loadRules(new ArrayList()); + } + + @After + public void tearDown() throws Exception { + DegradeRuleManager.loadRules(new ArrayList()); + } + + @Test + public void testSlowRequestMode() throws Exception { + CircuitBreakerStateChangeObserver observer = mock(CircuitBreakerStateChangeObserver.class); + setCurrentMillis(System.currentTimeMillis() / 1000 * 1000); + int retryTimeoutSec = 5; + int maxRt = 50; + int statIntervalMs = 20000; + int minRequestAmount = 10; + String res = "CircuitBreakingIntegrationTest_testSlowRequestMode"; + EventObserverRegistry.getInstance().addStateChangeObserver(res, observer); + DegradeRuleManager.loadRules(Arrays.asList( + new DegradeRule(res).setTimeWindow(retryTimeoutSec).setCount(maxRt) + .setStatIntervalMs(statIntervalMs).setMinRequestAmount(minRequestAmount) + .setSlowRatioThreshold(0.8d).setGrade(0) + )); + + // Try first N requests where N = minRequestAmount. + for (int i = 0; i < minRequestAmount; i++) { + if (i < 7) { + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); + } else { + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(-20, -10))); + } + } + + // Till now slow ratio should be 70%. + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); + // Circuit breaker has transformed to OPEN since here. + verify(observer) + .onStateChange(eq(State.CLOSED), eq(State.OPEN), any(DegradeRule.class), anyDouble()); + assertEquals(State.OPEN, DegradeRuleManager.getCircuitBreakers(res).get(0).currentState()); + assertFalse(entryAndSleepFor(res, 1)); + + sleepSecond(1); + assertFalse(entryAndSleepFor(res, 1)); + sleepSecond(retryTimeoutSec); + // Test HALF-OPEN to OPEN. + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); + + verify(observer) + .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); + verify(observer) + .onStateChange(eq(State.HALF_OPEN), eq(State.OPEN), any(DegradeRule.class), anyDouble()); + // Wait for next retry timeout; + reset(observer); + sleepSecond(retryTimeoutSec + 1); + assertTrue(entryAndSleepFor(res, maxRt - ThreadLocalRandom.current().nextInt(10, 20))); + verify(observer) + .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); + verify(observer) + .onStateChange(eq(State.HALF_OPEN), eq(State.CLOSED), any(DegradeRule.class), nullable(Double.class)); + // Now circuit breaker has been closed. + assertTrue(entryAndSleepFor(res, maxRt + ThreadLocalRandom.current().nextInt(10, 20))); + + EventObserverRegistry.getInstance().removeStateChangeObserver(res); + } + + @Test + public void testExceptionRatioMode() throws Exception { + CircuitBreakerStateChangeObserver observer = mock(CircuitBreakerStateChangeObserver.class); + setCurrentMillis(System.currentTimeMillis() / 1000 * 1000); + int retryTimeoutSec = 5; + double maxRatio = 0.5; + int statIntervalMs = 25000; + final int minRequestAmount = 10; + String res = "CircuitBreakingIntegrationTest_testExceptionRatioMode"; + EventObserverRegistry.getInstance().addStateChangeObserver(res, observer); + DegradeRuleManager.loadRules(Arrays.asList( + new DegradeRule(res).setTimeWindow(retryTimeoutSec).setCount(maxRatio) + .setStatIntervalMs(statIntervalMs).setMinRequestAmount(minRequestAmount) + .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) + )); + + // Try first N requests where N = minRequestAmount. + for (int i = 0; i < minRequestAmount - 1; i++) { + if (i < 6) { + assertTrue(entryWithErrorIfPresent(res, new IllegalArgumentException())); + } else { + assertTrue(entryWithErrorIfPresent(res, null)); + } + } + + // Till now slow ratio should be 60%. + assertTrue(entryWithErrorIfPresent(res, new IllegalArgumentException())); + // Circuit breaker has transformed to OPEN since here. + assertEquals(State.OPEN, DegradeRuleManager.getCircuitBreakers(res).get(0).currentState()); + assertFalse(entryWithErrorIfPresent(res, null)); + + sleepSecond(2); + assertFalse(entryWithErrorIfPresent(res, null)); + sleepSecond(retryTimeoutSec); + // Test HALF-OPEN to OPEN. + assertTrue(entryWithErrorIfPresent(res, new IllegalArgumentException())); + verify(observer) + .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); + verify(observer) + .onStateChange(eq(State.HALF_OPEN), eq(State.OPEN), any(DegradeRule.class), anyDouble()); + // Wait for next retry timeout; + reset(observer); + sleepSecond(retryTimeoutSec + 1); + assertTrue(entryWithErrorIfPresent(res, null)); + verify(observer) + .onStateChange(eq(State.OPEN), eq(State.HALF_OPEN), any(DegradeRule.class), nullable(Double.class)); + verify(observer) + .onStateChange(eq(State.HALF_OPEN), eq(State.CLOSED), any(DegradeRule.class), nullable(Double.class)); + // Now circuit breaker has been closed. + assertTrue(entryWithErrorIfPresent(res, new IllegalArgumentException())); + + EventObserverRegistry.getInstance().removeStateChangeObserver(res); + } + + @Test + public void testExceptionCountMode() throws Throwable { + // TODO + } + + private void verifyState(List breakers, int target) { + int state = 0; + for (CircuitBreaker breaker : breakers) { + if (breaker.currentState() == State.OPEN) { + state ++; + } else if (breaker.currentState() == State.HALF_OPEN) { + state --; + } else { + state -= 2; + } + } + assertEquals(target, state); + } + + @Test + public void testMultipleHalfOpenedBreakers() throws Exception { + CircuitBreakerStateChangeObserver observer = mock(CircuitBreakerStateChangeObserver.class); + setCurrentMillis(System.currentTimeMillis() / 1000 * 1000); + int retryTimeoutSec = 2; + int maxRt = 50; + int statIntervalMs = 20000; + int minRequestAmount = 1; + String res = "CircuitBreakingIntegrationTest_testMultipleHalfOpenedBreakers"; + EventObserverRegistry.getInstance().addStateChangeObserver(res, observer); + // initial two rules + DegradeRuleManager.loadRules(Arrays.asList( + new DegradeRule(res).setTimeWindow(retryTimeoutSec).setCount(maxRt) + .setStatIntervalMs(statIntervalMs).setMinRequestAmount(minRequestAmount) + .setSlowRatioThreshold(0.8d).setGrade(0), + new DegradeRule(res).setTimeWindow(retryTimeoutSec * 2).setCount(maxRt) + .setStatIntervalMs(statIntervalMs).setMinRequestAmount(minRequestAmount) + .setSlowRatioThreshold(0.8d).setGrade(0) + )); + assertTrue(entryAndSleepFor(res, 100)); + // they are open now + for (CircuitBreaker breaker : DegradeRuleManager.getCircuitBreakers(res)) { + assertEquals(CircuitBreaker.State.OPEN, breaker.currentState()); + } + + sleepSecond(3); + + for (int i = 0; i < 10; i ++) { + assertFalse(entryAndSleepFor(res, 100)); + } + // Now one is in open state while the other experiences open -> half-open -> open + verifyState(DegradeRuleManager.getCircuitBreakers(res), 2); + + sleepSecond(3); + + // They will all recover + for (int i = 0; i < 10; i ++) { + assertTrue(entryAndSleepFor(res, 1)); + } + + verifyState(DegradeRuleManager.getCircuitBreakers(res), -4); + } + +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManagerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManagerTest.java index 0d73610bd5..fa7c6358cb 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManagerTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleManagerTest.java @@ -15,8 +15,14 @@ */ package com.alibaba.csp.sentinel.slots.block.degrade; +import java.util.ArrayList; +import java.util.Arrays; + import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreaker; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import static org.junit.Assert.*; @@ -28,6 +34,32 @@ */ public class DegradeRuleManagerTest { + @Before + public void setUp() throws Exception { + DegradeRuleManager.loadRules(new ArrayList()); + } + + @After + public void tearDown() throws Exception { + DegradeRuleManager.loadRules(new ArrayList()); + } + + @Test + public void loadSameRuleUseSameCircuitBreaker() { + String resource = "loadSameRuleUseSameCircuitBreaker"; + DegradeRule rule = new DegradeRule(resource) + .setCount(100) + .setSlowRatioThreshold(0.9d) + .setTimeWindow(20) + .setStatIntervalMs(20000); + DegradeRuleManager.loadRules(Arrays.asList(rule)); + CircuitBreaker cb = DegradeRuleManager.getCircuitBreakers(resource).get(0); + + DegradeRuleManager.loadRules(Arrays.asList(rule, + new DegradeRule("abc").setTimeWindow(20).setCount(20).setSlowRatioThreshold(0.8d))); + assertSame(cb, DegradeRuleManager.getCircuitBreakers(resource).get(0)); + } + @Test public void testIsValidRule() { DegradeRule rule1 = new DegradeRule("abc"); @@ -46,13 +78,19 @@ public void testIsValidRule() { DegradeRule rule5 = new DegradeRule("Sentinel") .setCount(97) .setGrade(RuleConstant.DEGRADE_GRADE_RT) - .setTimeWindow(15) - .setRtSlowRequestAmount(0); + .setSlowRatioThreshold(15) + .setTimeWindow(15); DegradeRule rule6 = new DegradeRule("Sentinel") .setCount(0.93d) .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) .setTimeWindow(20) .setMinRequestAmount(0); + DegradeRule rule7 = new DegradeRule("Sentinel") + .setCount(100) + .setSlowRatioThreshold(0.8d) + .setTimeWindow(10) + .setStatIntervalMs(0) + .setMinRequestAmount(20); assertFalse(DegradeRuleManager.isValidRule(rule1)); assertFalse(DegradeRuleManager.isValidRule(rule2)); assertFalse(DegradeRuleManager.isValidRule(rule3)); @@ -61,5 +99,6 @@ public void testIsValidRule() { assertFalse(DegradeRuleManager.isValidRule(rule4)); assertFalse(DegradeRuleManager.isValidRule(rule5)); assertFalse(DegradeRuleManager.isValidRule(rule6)); + assertFalse(DegradeRuleManager.isValidRule(rule7)); } } \ No newline at end of file diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleTest.java new file mode 100644 index 0000000000..c879f26010 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeRuleTest.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade; + +import com.alibaba.csp.sentinel.slots.block.RuleConstant; + +import org.junit.Test; + +import static org.junit.Assert.*; + +/** + * @author Eric Zhao + */ +public class DegradeRuleTest { + + @Test + public void testRuleEquals() { + DegradeRule degradeRule1 = new DegradeRule(); + DegradeRule degradeRule2 = new DegradeRule(); + + int minRequestAmount = 20; + double count = 1.0; + int timeWindow = 2; + degradeRule1.setMinRequestAmount(minRequestAmount); + degradeRule1.setCount(count); + degradeRule1.setTimeWindow(timeWindow); + degradeRule1.setGrade(RuleConstant.DEGRADE_GRADE_RT); + + degradeRule2.setMinRequestAmount(minRequestAmount); + degradeRule2.setCount(count); + degradeRule2.setGrade(RuleConstant.DEGRADE_GRADE_RT); + degradeRule2.setTimeWindow(timeWindow); + assertEquals(degradeRule1, degradeRule2); + + degradeRule2.setMinRequestAmount(100); + assertNotEquals(degradeRule1, degradeRule2); + } +} \ No newline at end of file diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeTest.java deleted file mode 100755 index 75668c0be9..0000000000 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/DegradeTest.java +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.csp.sentinel.slots.block.degrade; - -import com.alibaba.csp.sentinel.EntryType; -import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.node.ClusterNode; -import com.alibaba.csp.sentinel.node.DefaultNode; -import com.alibaba.csp.sentinel.slotchain.StringResourceWrapper; -import com.alibaba.csp.sentinel.slots.block.RuleConstant; -import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; -import org.junit.Test; - -import java.util.concurrent.TimeUnit; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -/** - * @author jialiang.linjl - */ -public class DegradeTest { - - @Test - public void testAverageRtDegrade() throws InterruptedException { - String key = "test_degrade_average_rt"; - ClusterNode cn = mock(ClusterNode.class); - ClusterBuilderSlot.getClusterNodeMap().put(new StringResourceWrapper(key, EntryType.IN), cn); - - Context context = mock(Context.class); - DefaultNode node = mock(DefaultNode.class); - when(node.getClusterNode()).thenReturn(cn); - when(cn.avgRt()).thenReturn(2d); - - int rtSlowRequestAmount = 10; - DegradeRule rule = new DegradeRule(); - rule.setCount(1); - rule.setResource(key); - rule.setTimeWindow(2); - rule.setGrade(RuleConstant.DEGRADE_GRADE_RT); - rule.setRtSlowRequestAmount(rtSlowRequestAmount); - - //Will true - for (int i = 0; i < rtSlowRequestAmount - 1; i++) { - assertTrue(rule.passCheck(context, node, 1)); - } - - // The third time will fail. - assertFalse(rule.passCheck(context, node, 1)); - assertFalse(rule.passCheck(context, node, 1)); - - // Restore. - TimeUnit.MILLISECONDS.sleep(2200); - assertTrue(rule.passCheck(context, node, 1)); - } - - @Test - public void testExceptionRatioModeDegrade() throws Throwable { - String key = "test_degrade_exception_ratio"; - ClusterNode cn = mock(ClusterNode.class); - ClusterBuilderSlot.getClusterNodeMap().put(new StringResourceWrapper(key, EntryType.IN), cn); - - Context context = mock(Context.class); - DefaultNode node = mock(DefaultNode.class); - when(node.getClusterNode()).thenReturn(cn); - - DegradeRule rule = new DegradeRule(); - rule.setCount(0.15); - rule.setResource(key); - rule.setTimeWindow(2); - rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO); - rule.setMinRequestAmount(20); - - - // Will true. While totalQps < minRequestAmount - when(cn.totalQps()).thenReturn(8d); - assertTrue(rule.passCheck(context, node, 1)); - - // Will true. - when(cn.totalQps()).thenReturn(21d); - when(cn.successQps()).thenReturn(9d); - when(cn.exceptionQps()).thenReturn(9d); - assertTrue(rule.passCheck(context, node, 1)); - - - // Will true. While totalQps > minRequestAmount and exceptionRation < count - when(cn.totalQps()).thenReturn(100d); - when(cn.successQps()).thenReturn(90d); - when(cn.exceptionQps()).thenReturn(10d); - assertTrue(rule.passCheck(context, node, 1)); - - // Will fail. While totalQps > minRequestAmount and exceptionRation > count - rule.setMinRequestAmount(5); - when(cn.totalQps()).thenReturn(12d); - when(cn.successQps()).thenReturn(8d); - when(cn.exceptionQps()).thenReturn(6d); - assertFalse(rule.passCheck(context, node, 1)); - - // Restore from the degrade timeout. - TimeUnit.MILLISECONDS.sleep(2200); - - // Will pass. - when(cn.totalQps()).thenReturn(106d); - when(cn.successQps()).thenReturn(100d); - assertTrue(rule.passCheck(context, node, 1)); - } - - @Test - public void testExceptionCountModeDegrade() throws Throwable { - String key = "test_degrade_exception_count"; - ClusterNode cn = mock(ClusterNode.class); - when(cn.totalException()).thenReturn(10L); - ClusterBuilderSlot.getClusterNodeMap().put(new StringResourceWrapper(key, EntryType.IN), cn); - - Context context = mock(Context.class); - DefaultNode node = mock(DefaultNode.class); - when(node.getClusterNode()).thenReturn(cn); - - DegradeRule rule = new DegradeRule(); - rule.setCount(4); - rule.setResource(key); - rule.setTimeWindow(2); - rule.setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT); - - when(cn.totalException()).thenReturn(4L); - - // Will fail. - assertFalse(rule.passCheck(context, node, 1)); - - // Restore from the degrade timeout. - TimeUnit.MILLISECONDS.sleep(2200); - - when(cn.totalException()).thenReturn(0L); - // Will pass. - assertTrue(rule.passCheck(context, node, 1)); - } - - @Test - public void testEquals() { - DegradeRule degradeRule1 = new DegradeRule(); - DegradeRule degradeRule2 = new DegradeRule(); - assertTrue(degradeRule1.equals(degradeRule2)); - - int rtSlowRequestAmount = 10; - int minRequestAmount = 20; - double count = 1.0; - int timeWindow = 2; - degradeRule1.setRtSlowRequestAmount(rtSlowRequestAmount); - degradeRule1.setMinRequestAmount(minRequestAmount); - degradeRule1.setCount(count); - degradeRule1.setTimeWindow(timeWindow); - degradeRule1.setGrade(RuleConstant.DEGRADE_GRADE_RT); - - degradeRule2.setRtSlowRequestAmount(rtSlowRequestAmount); - degradeRule2.setMinRequestAmount(minRequestAmount); - degradeRule2.setCount(count); - degradeRule2.setGrade(RuleConstant.DEGRADE_GRADE_RT); - degradeRule2.setTimeWindow(timeWindow); - assertTrue(degradeRule1.equals(degradeRule2)); - - degradeRule2.setMinRequestAmount(100); - assertFalse(degradeRule1.equals(degradeRule2)); - - - } - -} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreakerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreakerTest.java new file mode 100644 index 0000000000..e9d7178d20 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ExceptionCircuitBreakerTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.ArrayList; +import java.util.Arrays; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import com.alibaba.csp.sentinel.slots.block.BlockException; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; + +/** + * @author Eric Zhao + */ +public class ExceptionCircuitBreakerTest extends AbstractTimeBasedTest { + + @Before + public void setUp() { + DegradeRuleManager.loadRules(new ArrayList()); + } + + @After + public void tearDown() throws Exception { + DegradeRuleManager.loadRules(new ArrayList()); + } + + @Test + public void testRecordErrorOrSuccess() throws BlockException { + String resource = "testRecordErrorOrSuccess"; + int retryTimeoutMillis = 10 * 1000; + int retryTimeout = retryTimeoutMillis / 1000; + DegradeRule rule = new DegradeRule("abc") + .setCount(0.2d) + .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) + .setStatIntervalMs(20 * 1000) + .setTimeWindow(retryTimeout) + .setMinRequestAmount(1); + rule.setResource(resource); + DegradeRuleManager.loadRules(Arrays.asList(rule)); + + assertTrue(entryAndSleepFor(resource, 10)); + + assertTrue(entryWithErrorIfPresent(resource, new IllegalArgumentException())); // -> open + assertFalse(entryWithErrorIfPresent(resource, new IllegalArgumentException())); + assertFalse(entryAndSleepFor(resource, 100)); + sleep(retryTimeoutMillis / 2); + assertFalse(entryAndSleepFor(resource, 100)); + sleep(retryTimeoutMillis / 2); + assertTrue(entryWithErrorIfPresent(resource, new IllegalArgumentException())); // -> half -> open + assertFalse(entryAndSleepFor(resource, 100)); + assertFalse(entryAndSleepFor(resource, 100)); + sleep(retryTimeoutMillis); + assertTrue(entryAndSleepFor(resource, 100)); // -> half -> closed + assertTrue(entryAndSleepFor(resource, 100)); + assertTrue(entryAndSleepFor(resource, 100)); + assertTrue(entryAndSleepFor(resource, 100)); + assertTrue(entryAndSleepFor(resource, 100)); + assertTrue(entryAndSleepFor(resource, 100)); + assertTrue(entryAndSleepFor(resource, 100)); + assertTrue(entryWithErrorIfPresent(resource, new IllegalArgumentException())); + assertTrue(entryAndSleepFor(resource, 100)); + } +} \ No newline at end of file diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreakerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreakerTest.java new file mode 100644 index 0000000000..66dd469145 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/degrade/circuitbreaker/ResponseTimeCircuitBreakerTest.java @@ -0,0 +1,58 @@ +package com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker; + +import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager; +import com.alibaba.csp.sentinel.test.AbstractTimeBasedTest; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collections; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * @author xierz + * @date 2020/10/4 + */ +public class ResponseTimeCircuitBreakerTest extends AbstractTimeBasedTest { + @Before + public void setUp() { + DegradeRuleManager.loadRules(new ArrayList()); + } + + @After + public void tearDown() throws Exception { + DegradeRuleManager.loadRules(new ArrayList()); + } + + @Test + public void testMaxSlowRatioThreshold() { + String resource = "testMaxSlowRatioThreshold"; + DegradeRule rule = new DegradeRule("resource") + .setCount(10) + .setGrade(RuleConstant.DEGRADE_GRADE_RT) + .setMinRequestAmount(3) + .setSlowRatioThreshold(1) + .setStatIntervalMs(5000) + .setTimeWindow(5); + rule.setResource(resource); + DegradeRuleManager.loadRules(Collections.singletonList(rule)); + + assertTrue(entryAndSleepFor(resource, 20)); + assertTrue(entryAndSleepFor(resource, 20)); + assertTrue(entryAndSleepFor(resource, 20)); + + // should be blocked, cause 3/3 requests' rt is bigger than max rt. + assertFalse(entryAndSleepFor(resource,20)); + sleep(1000); + assertFalse(entryAndSleepFor(resource,20)); + sleep(4000); + + assertTrue(entryAndSleepFor(resource, 20)); + } + +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowPartialIntegrationTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowPartialIntegrationTest.java index ae43a85242..faf8cca70f 100755 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowPartialIntegrationTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowPartialIntegrationTest.java @@ -116,15 +116,16 @@ public void run() { @Test public void testOriginFlowRule() { + String RESOURCE_NAME = "testOriginFlowRule"; // normal FlowRule flowRule = new FlowRule(); - flowRule.setResource("testOriginFlowRule"); + flowRule.setResource(RESOURCE_NAME); flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule.setCount(0); flowRule.setLimitApp("other"); FlowRule flowRule2 = new FlowRule(); - flowRule2.setResource("testOriginFlowRule"); + flowRule2.setResource(RESOURCE_NAME); flowRule2.setGrade(RuleConstant.FLOW_GRADE_QPS); flowRule2.setCount(1); flowRule2.setLimitApp("app2"); @@ -134,7 +135,7 @@ public void testOriginFlowRule() { ContextUtil.enter("node1", "app1"); Entry e = null; try { - e = SphU.entry("testOriginFlowRule"); + e = SphU.entry(RESOURCE_NAME); fail("Should had failed"); } catch (BlockException e1) { e1.printStackTrace(); @@ -146,7 +147,7 @@ public void testOriginFlowRule() { ContextUtil.enter("node1", "app2"); e = null; try { - e = SphU.entry("testOriginFlowRule"); + e = SphU.entry(RESOURCE_NAME); } catch (BlockException e1) { fail("Should had failed"); } diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManagerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManagerTest.java new file mode 100644 index 0000000000..867dad82cf --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleManagerTest.java @@ -0,0 +1,79 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.slots.block.flow; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; + +/** + * @author Weihua + */ +public class FlowRuleManagerTest { + + public static final List STATIC_RULES_1 = new ArrayList(); + public static final List STATIC_RULES_2 = new ArrayList(); + + static { + FlowRule first = new FlowRule(); + first.setResource("/a/b/c"); + first.setCount(100); + STATIC_RULES_1.add(first); + + FlowRule second = new FlowRule(); + second.setResource("/a/b/c"); + second.setCount(200); + STATIC_RULES_2.add(second); + + } + + @Test + public void testLoadAndGetRules() throws InterruptedException{ + FlowRuleManager.loadRules(STATIC_RULES_1); + assertEquals(1, FlowRuleManager.getRules().size()); // the initial size + final CountDownLatch latchStart = new CountDownLatch(1); + final CountDownLatch latchEnd = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + try { + latchStart.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + return; + } + for(int i = 0; i < 10000; i++){ + //to guarantee that they're different and change happens + FlowRuleManager.loadRules(i % 2 == 0 ? STATIC_RULES_2 : STATIC_RULES_1); + } + latchEnd.countDown(); + } + }).start(); + + latchStart.countDown(); + for (int i = 0; i < 10000; i++) { + //The initial size is 1, and the size after updating should also be 1, + //if the actual size is 0, that must be called after clear(), + // but before putAll() in FlowPropertyListener.configUpdate + assertEquals(1, FlowRuleManager.getRules().size()); + } + latchEnd.await(10, TimeUnit.SECONDS); + } +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleTest.java deleted file mode 100755 index 0180501e61..0000000000 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowRuleTest.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 1999-2018 Alibaba Group Holding Ltd. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.alibaba.csp.sentinel.slots.block.flow; - -import static org.junit.Assert.*; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import org.junit.Ignore; -import org.junit.Test; - -import com.alibaba.csp.sentinel.context.Context; -import com.alibaba.csp.sentinel.node.ClusterNode; -import com.alibaba.csp.sentinel.node.DefaultNode; -import com.alibaba.csp.sentinel.slots.block.RuleConstant; -import com.alibaba.csp.sentinel.slots.block.flow.controller.DefaultController; - -/** - * @author jialiang.linjl - */ -@Ignore("Deprecated test for legacy FlowRule") -public class FlowRuleTest { - - @Test - public void testFlowRule_grade() { - - FlowRule flowRule = new FlowRule(); - flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); - flowRule.setCount(1); - flowRule.setLimitApp("default"); - flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT); - - DefaultController defaultController = new DefaultController(1, flowRule.getGrade()); - flowRule.setRater(defaultController); - - Context context = mock(Context.class); - DefaultNode node = mock(DefaultNode.class); - ClusterNode cn = mock(ClusterNode.class); - - when(context.getOrigin()).thenReturn(""); - when(node.getClusterNode()).thenReturn(cn); - when(cn.passQps()).thenReturn(1d); - - assertFalse(flowRule.passCheck(context, node, 1)); - - flowRule.setGrade(RuleConstant.FLOW_GRADE_THREAD); - defaultController = new DefaultController(1, flowRule.getGrade()); - flowRule.setRater(defaultController); - when(cn.curThreadNum()).thenReturn(1); - assertTrue(!flowRule.passCheck(context, node, 1)); - } - - @Test - public void testFlowRule_strategy() { - - FlowRule flowRule = new FlowRule(); - flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); - flowRule.setCount(1); - flowRule.setLimitApp("default"); - flowRule.setStrategy(RuleConstant.STRATEGY_CHAIN); - DefaultController defaultController = new DefaultController(1, flowRule.getGrade()); - flowRule.setRater(defaultController); - flowRule.setRefResource("entry1"); - - Context context = mock(Context.class); - DefaultNode dn = mock(DefaultNode.class); - - when(context.getName()).thenReturn("entry1"); - when(dn.passQps()).thenReturn(1d); - assertFalse(flowRule.passCheck(context, dn, 1)); - - when(context.getName()).thenReturn("entry2"); - assertTrue(flowRule.passCheck(context, dn, 1)); - - // Strategy == relate - flowRule.setStrategy(RuleConstant.STRATEGY_CHAIN); - ClusterNode cn = mock(ClusterNode.class); - assertTrue(flowRule.passCheck(context, dn, 1)); - } - - @Test - public void testOrigin() { - FlowRule flowRule = new FlowRule(); - flowRule.setGrade(RuleConstant.FLOW_GRADE_QPS); - flowRule.setCount(1); - flowRule.setLimitApp("default"); - flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT); - DefaultController defaultController = new DefaultController(1, flowRule.getGrade()); - flowRule.setRater(defaultController); - flowRule.setRefResource("entry1"); - - Context context = mock(Context.class); - DefaultNode dn = mock(DefaultNode.class); - when(context.getOrigin()).thenReturn("origin1"); - when(dn.passQps()).thenReturn(1d); - when(context.getOriginNode()).thenReturn(dn); - - /* - * first scenario, limit app as default - * - */ - ClusterNode cn = mock(ClusterNode.class); - when(dn.getClusterNode()).thenReturn(cn); - when(cn.passQps()).thenReturn(1d); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0]) == false); - when(cn.passQps()).thenReturn(0d); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0])); - - flowRule.setStrategy(RuleConstant.STRATEGY_CHAIN); - flowRule.setResource("entry1"); - when(context.getName()).thenReturn("entry1"); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0]) == false); - when(context.getName()).thenReturn("entry2"); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0])); - - // relate node - flowRule.setStrategy(RuleConstant.STRATEGY_RELATE); - flowRule.setResource("worong"); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0])); - - /* - * second scenario test a context with the same origin1 - * - */ - flowRule.setLimitApp("origin1"); - when(context.getName()).thenReturn("entry1"); - // direct node - flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0]) == false); - - // chain node - flowRule.setResource("entry1"); - flowRule.setStrategy(RuleConstant.STRATEGY_CHAIN); - when(context.getName()).thenReturn("entry1"); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0]) == false); - when(context.getName()).thenReturn("entry2"); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0])); - - // relate node - flowRule.setStrategy(RuleConstant.STRATEGY_RELATE); - flowRule.setResource("not exits"); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0])); - - when(context.getOrigin()).thenReturn("origin2"); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0])); - - /* - * limit app= other - */ - flowRule.setLimitApp("other"); - flowRule.setResource("hello world"); - - flowRule.setStrategy(RuleConstant.STRATEGY_DIRECT); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0]) == false); - - flowRule.setStrategy(RuleConstant.STRATEGY_CHAIN); - flowRule.setResource("entry1"); - when(context.getName()).thenReturn("entry1"); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0]) == false); - - when(context.getName()).thenReturn("entry2"); - assertTrue(flowRule.passCheck(context, dn, 1, new Object[0])); - } - -} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java index 66026e2bb7..1a29e27383 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/FlowSlotTest.java @@ -29,7 +29,6 @@ import org.junit.Before; import org.junit.Test; -import static org.junit.Assert.*; import static org.mockito.Mockito.*; /** diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterControllerTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterControllerTest.java index adec543bbd..0b83f5c059 100755 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterControllerTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/block/flow/controller/RateLimiterControllerTest.java @@ -61,7 +61,7 @@ public void testPaceController_timeout() throws InterruptedException { public void run() { boolean pass = paceController.canPass(node, 1); - if (pass == true) { + if (pass) { passcount.incrementAndGet(); } else { blockcount.incrementAndGet(); diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtilTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtilTest.java index f9e7dd7029..8a48bc7c7d 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtilTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/slots/logger/EagleEyeLogUtilTest.java @@ -21,7 +21,7 @@ public class EagleEyeLogUtilTest { public void testWriteLog() throws Exception { EagleEyeLogUtil.log("resourceName", "BlockException", "app1", "origin", 1); - final File file = new File(RecordLog.getLogBaseDir() + EagleEyeLogUtil.FILE_NAME); + final File file = new File(LogBase.getLogBaseDir() + EagleEyeLogUtil.FILE_NAME); await().timeout(2, TimeUnit.SECONDS) .until(new Callable() { @Override @@ -42,7 +42,7 @@ public void testChangeLogBase() throws Exception { EagleEyeLogUtil.log("resourceName", "BlockException", "app1", "origin", 1); - final File file = new File(RecordLog.getLogBaseDir() + EagleEyeLogUtil.FILE_NAME); + final File file = new File(LogBase.getLogBaseDir() + EagleEyeLogUtil.FILE_NAME); await().timeout(2, TimeUnit.SECONDS) .until(new Callable() { @Override @@ -51,7 +51,7 @@ public File call() throws Exception { } }, FileMatchers.anExistingFile()); Assert.assertTrue(file.getAbsolutePath().startsWith(newLogBase)); - deleteLogDir(new File(RecordLog.getLogBaseDir())); + deleteLogDir(new File(LogBase.getLogBaseDir())); } private void deleteLogDir(File logDirFile) { diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/test/AbstractTimeBasedTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/test/AbstractTimeBasedTest.java index 2c9f8fe4dd..e22d1a645f 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/test/AbstractTimeBasedTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/test/AbstractTimeBasedTest.java @@ -15,11 +15,17 @@ */ package com.alibaba.csp.sentinel.test; +import java.util.concurrent.ThreadLocalRandom; + import org.junit.runner.RunWith; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import com.alibaba.csp.sentinel.Entry; +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.util.TimeUtil; /** @@ -55,4 +61,39 @@ protected final void sleep(int t) { protected final void sleepSecond(int timeSec) { sleep(timeSec * 1000); } + + protected final boolean entryAndSleepFor(String res, int sleepMs) { + Entry entry = null; + try { + entry = SphU.entry(res); + sleep(sleepMs); + } catch (BlockException ex) { + return false; + } catch (Exception ex) { + Tracer.traceEntry(ex, entry); + } finally { + if (entry != null) { + entry.exit(); + } + } + return true; + } + + protected final boolean entryWithErrorIfPresent(String res, Exception ex) { + Entry entry = null; + try { + entry = SphU.entry(res); + if (ex != null) { + Tracer.traceEntry(ex, entry); + } + sleep(ThreadLocalRandom.current().nextInt(5, 10)); + } catch (BlockException b) { + return false; + } finally { + if (entry != null) { + entry.exit(); + } + } + return true; + } } diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/SpiLoaderTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/SpiLoaderTest.java new file mode 100644 index 0000000000..622f7fd249 --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/SpiLoaderTest.java @@ -0,0 +1,163 @@ +/* + * Copyright 1999-2018 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.util; + +import com.alibaba.csp.sentinel.slotchain.ProcessorSlot; +import com.alibaba.csp.sentinel.slotchain.SlotChainBuilder; +import com.alibaba.csp.sentinel.slots.DefaultSlotChainBuilder; +import com.alibaba.csp.sentinel.slots.block.authority.AuthoritySlot; +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeSlot; +import com.alibaba.csp.sentinel.slots.block.flow.FlowSlot; +import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot; +import com.alibaba.csp.sentinel.slots.logger.LogSlot; +import com.alibaba.csp.sentinel.slots.nodeselector.NodeSelectorSlot; +import com.alibaba.csp.sentinel.slots.statistic.StatisticSlot; +import com.alibaba.csp.sentinel.slots.system.SystemSlot; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.*; + +/** + * Test cases for {@link SpiLoader}. + * + * @author cdfive + */ +public class SpiLoaderTest { + + @Test + public void testLoadFirstInstance() { + ProcessorSlot processorSlot = SpiLoader.loadFirstInstance(ProcessorSlot.class); + assertNotNull(processorSlot); + + ProcessorSlot processorSlot2 = SpiLoader.loadFirstInstance(ProcessorSlot.class); + // As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances + assertSame(processorSlot, processorSlot2); + + SlotChainBuilder slotChainBuilder = SpiLoader.loadFirstInstance(SlotChainBuilder.class); + assertNotNull(slotChainBuilder); + assertTrue(slotChainBuilder instanceof DefaultSlotChainBuilder); + + SlotChainBuilder slotChainBuilder2 = SpiLoader.loadFirstInstance(SlotChainBuilder.class); + // As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances + assertSame(slotChainBuilder, slotChainBuilder2); + } + + @Test + public void testLoadHighestPriorityInstance() { + ProcessorSlot processorSlot = SpiLoader.loadHighestPriorityInstance(ProcessorSlot.class); + assertNotNull(processorSlot); + + // NodeSelectorSlot is highest order with @SpiOrder(-10000), among all slots + assertTrue(processorSlot instanceof NodeSelectorSlot); + + ProcessorSlot processorSlot2 = SpiLoader.loadHighestPriorityInstance(ProcessorSlot.class); + // As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances + assertSame(processorSlot, processorSlot2); + } + + @Test + public void testLoadInstanceList() { + List slots = SpiLoader.loadInstanceList(ProcessorSlot.class); + assertNotNull(slots); + + // Total 8 default slot in sentinel-core + assertEquals(8, slots.size()); + + // Get the first slot of slots + ProcessorSlot firstSlot = slots.get(0); + + // Call loadInstanceList again + List slots2 = SpiLoader.loadInstanceList(ProcessorSlot.class); + // Note: the return list are different, and the item instances in list are same + assertNotSame(slots, slots2); + + // Get the first slot of slots2 + ProcessorSlot firstSlot2 = slots2.get(0); + + // As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances + assertSame(firstSlot, firstSlot2); + } + + @Test + public void testLoadInstanceListSorted() { + List sortedSlots = SpiLoader.loadInstanceListSorted(ProcessorSlot.class); + assertNotNull(sortedSlots); + + // Total 8 default slot in sentinel-core + assertEquals(8, sortedSlots.size()); + + // Verify the order of slot + int index = 0; + assertTrue(sortedSlots.get(index++) instanceof NodeSelectorSlot); + assertTrue(sortedSlots.get(index++) instanceof ClusterBuilderSlot); + assertTrue(sortedSlots.get(index++) instanceof LogSlot); + assertTrue(sortedSlots.get(index++) instanceof StatisticSlot); + assertTrue(sortedSlots.get(index++) instanceof AuthoritySlot); + assertTrue(sortedSlots.get(index++) instanceof SystemSlot); + assertTrue(sortedSlots.get(index++) instanceof FlowSlot); + assertTrue(sortedSlots.get(index++) instanceof DegradeSlot); + + // Verify each call return different instances + // Note: the return list are different, and the item instances in list are same + List sortedSlots2 = SpiLoader.loadInstanceListSorted(ProcessorSlot.class); + assertNotSame(sortedSlots, sortedSlots2); + assertEquals(sortedSlots.size(), sortedSlots2.size()); + for (int i = 0; i < sortedSlots.size(); i++) { + ProcessorSlot slot = sortedSlots.get(i); + ProcessorSlot slot2 = sortedSlots2.get(i); + assertEquals(slot.getClass(), slot2.getClass()); + + // As SERVICE_LOADER_MAP in SpiLoader cached the instance, so they're same instances + assertSame(slot, slot2); + } + } + + @Test + public void testLoadPrototypeInstanceListSorted() { + List sortedSlots = SpiLoader.loadInstanceListSorted(ProcessorSlot.class); + assertNotNull(sortedSlots); + + // Total 8 default slot in sentinel-core + assertEquals(8, sortedSlots.size()); + + // Verify the order of slot + int index = 0; + assertTrue(sortedSlots.get(index++) instanceof NodeSelectorSlot); + assertTrue(sortedSlots.get(index++) instanceof ClusterBuilderSlot); + assertTrue(sortedSlots.get(index++) instanceof LogSlot); + assertTrue(sortedSlots.get(index++) instanceof StatisticSlot); + assertTrue(sortedSlots.get(index++) instanceof AuthoritySlot); + assertTrue(sortedSlots.get(index++) instanceof SystemSlot); + assertTrue(sortedSlots.get(index++) instanceof FlowSlot); + assertTrue(sortedSlots.get(index++) instanceof DegradeSlot); + + // Verify each call return new instances + List sortedSlots2 = SpiLoader.loadPrototypeInstanceListSorted(ProcessorSlot.class); + assertNotSame(sortedSlots, sortedSlots2); + assertEquals(sortedSlots.size(), sortedSlots2.size()); + for (int i = 0; i < sortedSlots.size(); i++) { + ProcessorSlot slot = sortedSlots.get(i); + ProcessorSlot slot2 = sortedSlots2.get(i); + assertEquals(slot.getClass(), slot2.getClass()); + + // Verify the instances are different + assertNotSame(slot, slot2); + assertNotEquals(slot, slot2); + } + } +} diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/VersionUtilTest.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/VersionUtilTest.java index bdccc1c4c3..1b620defc7 100644 --- a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/VersionUtilTest.java +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/VersionUtilTest.java @@ -15,6 +15,8 @@ */ package com.alibaba.csp.sentinel.util; +import static org.junit.Assert.*; + import org.junit.Assert; import org.junit.Test; @@ -27,4 +29,21 @@ public void testGetDefaultVersion() { // Manifest cannot be load before package. Assert.assertEquals(defaultVersion, version); } + + @Test + public void testFromVersionString() { + assertEquals(0x01020300, VersionUtil.fromVersionString("1.2.3")); + assertEquals(0x01020304, VersionUtil.fromVersionString("1.2.3.4")); + assertEquals(0x0102ff04, VersionUtil.fromVersionString("1.2.255.4")); + assertEquals(0xffffffff, VersionUtil.fromVersionString("255.255.255.255")); + assertEquals(0, VersionUtil.fromVersionString("1.255.256.0")); + assertEquals(0x01020000, VersionUtil.fromVersionString("1.2.")); + assertEquals(0x01000000, VersionUtil.fromVersionString("1")); + assertEquals(0x01020000, VersionUtil.fromVersionString("1.2")); + assertEquals(0, VersionUtil.fromVersionString("test")); + assertEquals(0x01020300, VersionUtil.fromVersionString("1.2.3-")); + assertEquals(0x01020300, VersionUtil.fromVersionString("1.2.3b")); + assertEquals(0x01023c00, VersionUtil.fromVersionString("1.2.60.sec9")); + assertEquals(0x01023c00, VersionUtil.fromVersionString("1.2.60-internal")); + } } diff --git a/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/function/Tuple2Test.java b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/function/Tuple2Test.java new file mode 100644 index 0000000000..7a53cfb6ef --- /dev/null +++ b/sentinel-core/src/test/java/com/alibaba/csp/sentinel/util/function/Tuple2Test.java @@ -0,0 +1,29 @@ +/* + * Copyright 1999-2019 Alibaba Group Holding Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.alibaba.csp.sentinel.util.function; + +import org.junit.Assert; +import org.junit.Test; + +public class Tuple2Test { + + @Test + public void testSwap() { + Tuple2 tupleOld = new Tuple2(1, 2); + Tuple2 tupleNew = tupleOld.swap(); + Assert.assertEquals(new Tuple2(2, 1), tupleNew); + } +} diff --git a/sentinel-dashboard/README.md b/sentinel-dashboard/README.md index 63de667248..ad8b91f966 100755 --- a/sentinel-dashboard/README.md +++ b/sentinel-dashboard/README.md @@ -33,7 +33,7 @@ java -Dserver.port=8080 \ | 参数 | 作用 | |--------|--------| -|`Dcsp.sentinel.dashboard.server=localhost:8080`|向 Sentinel 接入端指定控制台的地址| +|`-Dcsp.sentinel.dashboard.server=localhost:8080`|向 Sentinel 接入端指定控制台的地址| |`-Dproject.name=sentinel-dashboard`|向 Sentinel 指定应用名称,比如上面对应的应用名称就为 `sentinel-dashboard`| 全部的配置项可以参考 [启动配置项文档](https://github.com/alibaba/Sentinel/wiki/%E5%90%AF%E5%8A%A8%E9%85%8D%E7%BD%AE%E9%A1%B9)。 diff --git a/sentinel-dashboard/pom.xml b/sentinel-dashboard/pom.xml index fcddca45af..9b0f504d04 100755 --- a/sentinel-dashboard/pom.xml +++ b/sentinel-dashboard/pom.xml @@ -6,7 +6,7 @@ com.alibaba.csp sentinel-parent - 1.7.2-SNAPSHOT + 1.8.1-SNAPSHOT sentinel-dashboard diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DegradeController.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DegradeController.java index 61aaee68aa..b5a72b067d 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DegradeController.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DegradeController.java @@ -22,40 +22,46 @@ import com.alibaba.csp.sentinel.dashboard.client.SentinelApiClient; import com.alibaba.csp.sentinel.dashboard.discovery.MachineInfo; import com.alibaba.csp.sentinel.dashboard.auth.AuthService.PrivilegeType; +import com.alibaba.csp.sentinel.dashboard.repository.rule.RuleRepository; import com.alibaba.csp.sentinel.slots.block.RuleConstant; +import com.alibaba.csp.sentinel.slots.block.degrade.circuitbreaker.CircuitBreakerStrategy; import com.alibaba.csp.sentinel.util.StringUtil; import com.alibaba.csp.sentinel.dashboard.datasource.entity.rule.DegradeRuleEntity; import com.alibaba.csp.sentinel.dashboard.domain.Result; -import com.alibaba.csp.sentinel.dashboard.repository.rule.InMemDegradeRuleStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.MediaType; -import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; /** - * @author leyou + * Controller regarding APIs of degrade rules. Refactored since 1.8.0. + * + * @author Carpenter Lee + * @author Eric Zhao */ -@Controller -@RequestMapping(value = "/degrade", produces = MediaType.APPLICATION_JSON_VALUE) +@RestController +@RequestMapping("/degrade") public class DegradeController { private final Logger logger = LoggerFactory.getLogger(DegradeController.class); @Autowired - private InMemDegradeRuleStore repository; + private RuleRepository repository; @Autowired private SentinelApiClient sentinelApiClient; - @ResponseBody - @RequestMapping("/rules.json") + @GetMapping("/rules.json") @AuthAction(PrivilegeType.READ_RULE) - public Result> queryMachineRules(String app, String ip, Integer port) { - + public Result> apiQueryMachineRules(String app, String ip, Integer port) { if (StringUtil.isEmpty(app)) { return Result.ofFail(-1, "app can't be null or empty"); } @@ -75,117 +81,65 @@ public Result> queryMachineRules(String app, String ip, } } - @ResponseBody - @RequestMapping("/new.json") + @PostMapping("/rule") @AuthAction(PrivilegeType.WRITE_RULE) - public Result add(String app, String ip, Integer port, String limitApp, String resource, - Double count, Integer timeWindow, Integer grade) { - if (StringUtil.isBlank(app)) { - return Result.ofFail(-1, "app can't be null or empty"); - } - if (StringUtil.isBlank(ip)) { - return Result.ofFail(-1, "ip can't be null or empty"); - } - if (port == null) { - return Result.ofFail(-1, "port can't be null"); - } - if (StringUtil.isBlank(limitApp)) { - return Result.ofFail(-1, "limitApp can't be null or empty"); - } - if (StringUtil.isBlank(resource)) { - return Result.ofFail(-1, "resource can't be null or empty"); - } - if (count == null) { - return Result.ofFail(-1, "count can't be null"); - } - if (timeWindow == null) { - return Result.ofFail(-1, "timeWindow can't be null"); + public Result apiAddRule(@RequestBody DegradeRuleEntity entity) { + Result checkResult = checkEntityInternal(entity); + if (checkResult != null) { + return checkResult; } - if (grade == null) { - return Result.ofFail(-1, "grade can't be null"); - } - if (grade < RuleConstant.DEGRADE_GRADE_RT || grade > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) { - return Result.ofFail(-1, "Invalid grade: " + grade); - } - DegradeRuleEntity entity = new DegradeRuleEntity(); - entity.setApp(app.trim()); - entity.setIp(ip.trim()); - entity.setPort(port); - entity.setLimitApp(limitApp.trim()); - entity.setResource(resource.trim()); - entity.setCount(count); - entity.setTimeWindow(timeWindow); - entity.setGrade(grade); Date date = new Date(); entity.setGmtCreate(date); entity.setGmtModified(date); try { entity = repository.save(entity); - } catch (Throwable throwable) { - logger.error("add error:", throwable); - return Result.ofThrowable(-1, throwable); + } catch (Throwable t) { + logger.error("Failed to add new degrade rule, app={}, ip={}", entity.getApp(), entity.getIp(), t); + return Result.ofThrowable(-1, t); } - if (!publishRules(app, ip, port)) { - logger.info("publish degrade rules fail after rule add"); + if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) { + logger.warn("Publish degrade rules failed, app={}", entity.getApp()); } return Result.ofSuccess(entity); } - @ResponseBody - @RequestMapping("/save.json") + @PutMapping("/rule/{id}") @AuthAction(PrivilegeType.WRITE_RULE) - public Result updateIfNotNull(Long id, String app, String limitApp, String resource, - Double count, Integer timeWindow, Integer grade) { - if (id == null) { - return Result.ofFail(-1, "id can't be null"); + public Result apiUpdateRule(@PathVariable("id") Long id, + @RequestBody DegradeRuleEntity entity) { + if (id == null || id <= 0) { + return Result.ofFail(-1, "id can't be null or negative"); } - if (grade != null) { - if (grade < RuleConstant.DEGRADE_GRADE_RT || grade > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) { - return Result.ofFail(-1, "Invalid grade: " + grade); - } - } - DegradeRuleEntity entity = repository.findById(id); - if (entity == null) { - return Result.ofFail(-1, "id " + id + " dose not exist"); + DegradeRuleEntity oldEntity = repository.findById(id); + if (oldEntity == null) { + return Result.ofFail(-1, "Degrade rule does not exist, id=" + id); } - - if (StringUtil.isNotBlank(app)) { - entity.setApp(app.trim()); + entity.setApp(oldEntity.getApp()); + entity.setIp(oldEntity.getIp()); + entity.setPort(oldEntity.getPort()); + entity.setId(oldEntity.getId()); + Result checkResult = checkEntityInternal(entity); + if (checkResult != null) { + return checkResult; } - if (StringUtil.isNotBlank(limitApp)) { - entity.setLimitApp(limitApp.trim()); - } - if (StringUtil.isNotBlank(resource)) { - entity.setResource(resource.trim()); - } - if (count != null) { - entity.setCount(count); - } - if (timeWindow != null) { - entity.setTimeWindow(timeWindow); - } - if (grade != null) { - entity.setGrade(grade); - } - Date date = new Date(); - entity.setGmtModified(date); + entity.setGmtCreate(oldEntity.getGmtCreate()); + entity.setGmtModified(new Date()); try { entity = repository.save(entity); - } catch (Throwable throwable) { - logger.error("save error:", throwable); - return Result.ofThrowable(-1, throwable); + } catch (Throwable t) { + logger.error("Failed to save degrade rule, id={}, rule={}", id, entity, t); + return Result.ofThrowable(-1, t); } if (!publishRules(entity.getApp(), entity.getIp(), entity.getPort())) { - logger.info("publish degrade rules fail after rule update"); + logger.warn("Publish degrade rules failed, app={}", entity.getApp()); } return Result.ofSuccess(entity); } - @ResponseBody - @RequestMapping("/delete.json") + @DeleteMapping("/rule/{id}") @AuthAction(PrivilegeType.DELETE_RULE) - public Result delete(Long id) { + public Result delete(@PathVariable("id") Long id) { if (id == null) { return Result.ofFail(-1, "id can't be null"); } @@ -198,11 +152,11 @@ public Result delete(Long id) { try { repository.delete(id); } catch (Throwable throwable) { - logger.error("delete error:", throwable); + logger.error("Failed to delete degrade rule, id={}", id, throwable); return Result.ofThrowable(-1, throwable); } if (!publishRules(oldEntity.getApp(), oldEntity.getIp(), oldEntity.getPort())) { - logger.info("publish degrade rules fail after rule delete"); + logger.warn("Publish degrade rules failed, app={}", oldEntity.getApp()); } return Result.ofSuccess(id); } @@ -211,4 +165,57 @@ private boolean publishRules(String app, String ip, Integer port) { List rules = repository.findAllByMachine(MachineInfo.of(app, ip, port)); return sentinelApiClient.setDegradeRuleOfMachine(app, ip, port, rules); } + + private Result checkEntityInternal(DegradeRuleEntity entity) { + if (StringUtil.isBlank(entity.getApp())) { + return Result.ofFail(-1, "app can't be blank"); + } + if (StringUtil.isBlank(entity.getIp())) { + return Result.ofFail(-1, "ip can't be null or empty"); + } + if (entity.getPort() == null || entity.getPort() <= 0) { + return Result.ofFail(-1, "invalid port: " + entity.getPort()); + } + if (StringUtil.isBlank(entity.getLimitApp())) { + return Result.ofFail(-1, "limitApp can't be null or empty"); + } + if (StringUtil.isBlank(entity.getResource())) { + return Result.ofFail(-1, "resource can't be null or empty"); + } + Double threshold = entity.getCount(); + if (threshold == null || threshold < 0) { + return Result.ofFail(-1, "invalid threshold: " + threshold); + } + Integer recoveryTimeoutSec = entity.getTimeWindow(); + if (recoveryTimeoutSec == null || recoveryTimeoutSec <= 0) { + return Result.ofFail(-1, "recoveryTimeout should be positive"); + } + Integer strategy = entity.getGrade(); + if (strategy == null) { + return Result.ofFail(-1, "circuit breaker strategy cannot be null"); + } + if (strategy < CircuitBreakerStrategy.SLOW_REQUEST_RATIO.getType() + || strategy > RuleConstant.DEGRADE_GRADE_EXCEPTION_COUNT) { + return Result.ofFail(-1, "Invalid circuit breaker strategy: " + strategy); + } + if (entity.getMinRequestAmount() == null || entity.getMinRequestAmount() <= 0) { + return Result.ofFail(-1, "Invalid minRequestAmount"); + } + if (entity.getStatIntervalMs() == null || entity.getStatIntervalMs() <= 0) { + return Result.ofFail(-1, "Invalid statInterval"); + } + if (strategy == RuleConstant.DEGRADE_GRADE_RT) { + Double slowRatio = entity.getSlowRatioThreshold(); + if (slowRatio == null) { + return Result.ofFail(-1, "SlowRatioThreshold is required for slow request ratio strategy"); + } else if (slowRatio < 0 || slowRatio > 1) { + return Result.ofFail(-1, "SlowRatioThreshold should be in range: [0.0, 1.0]"); + } + } else if (strategy == RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) { + if (threshold > 1) { + return Result.ofFail(-1, "Ratio threshold should be in range: [0.0, 1.0]"); + } + } + return null; + } } diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DemoController.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DemoController.java index 91ae671225..cb4fb67a9f 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DemoController.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/DemoController.java @@ -108,7 +108,7 @@ public void run() { try { e1 = SphU.entry(name); - if (slow == true) { + if (slow) { TimeUnit.MILLISECONDS.sleep(3000); } diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleController.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleController.java index 0189163781..b0a53e7330 100644 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleController.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/controller/gateway/GatewayFlowRuleController.java @@ -146,18 +146,17 @@ public Result addFlowRule(@RequestBody AddFlowRuleReqVo r return Result.ofFail(-1, "fieldName can't be null or empty"); } itemEntity.setFieldName(paramItem.getFieldName()); + } - String pattern = paramItem.getPattern(); - // 如果匹配串不为空,验证匹配模式 - if (StringUtil.isNotEmpty(pattern)) { - itemEntity.setPattern(pattern); - - Integer matchStrategy = paramItem.getMatchStrategy(); - if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { - return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); - } - itemEntity.setMatchStrategy(matchStrategy); + String pattern = paramItem.getPattern(); + // 如果匹配串不为空,验证匹配模式 + if (StringUtil.isNotEmpty(pattern)) { + itemEntity.setPattern(pattern); + Integer matchStrategy = paramItem.getMatchStrategy(); + if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { + return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); } + itemEntity.setMatchStrategy(matchStrategy); } } @@ -292,18 +291,17 @@ public Result updateFlowRule(@RequestBody UpdateFlowRuleR return Result.ofFail(-1, "fieldName can't be null or empty"); } itemEntity.setFieldName(paramItem.getFieldName()); + } - String pattern = paramItem.getPattern(); - // 如果匹配串不为空,验证匹配模式 - if (StringUtil.isNotEmpty(pattern)) { - itemEntity.setPattern(pattern); - - Integer matchStrategy = paramItem.getMatchStrategy(); - if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { - return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); - } - itemEntity.setMatchStrategy(matchStrategy); + String pattern = paramItem.getPattern(); + // 如果匹配串不为空,验证匹配模式 + if (StringUtil.isNotEmpty(pattern)) { + itemEntity.setPattern(pattern); + Integer matchStrategy = paramItem.getMatchStrategy(); + if (!Arrays.asList(PARAM_MATCH_STRATEGY_EXACT, PARAM_MATCH_STRATEGY_CONTAINS, PARAM_MATCH_STRATEGY_REGEX).contains(matchStrategy)) { + return Result.ofFail(-1, "invalid matchStrategy: " + matchStrategy); } + itemEntity.setMatchStrategy(matchStrategy); } } else { entity.setParamItem(null); diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/MetricEntity.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/MetricEntity.java index f08a41a693..94513cb1db 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/MetricEntity.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/MetricEntity.java @@ -61,7 +61,6 @@ public static MetricEntity copyOf(MetricEntity oldEntity) { entity.setExceptionQps(oldEntity.getExceptionQps()); entity.setRt(oldEntity.getRt()); entity.setCount(oldEntity.getCount()); - entity.setResource(oldEntity.getResource()); return entity; } diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java index 2b86a373a5..4fd8b719a5 100755 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/datasource/entity/rule/DegradeRuleEntity.java @@ -23,18 +23,22 @@ * @author leyou */ public class DegradeRuleEntity implements RuleEntity { + private Long id; private String app; + private String ip; private Integer port; + private String resource; private String limitApp; private Double count; private Integer timeWindow; - /** - * 0 rt 限流; 1为异常; - */ private Integer grade; + private Integer minRequestAmount; + private Double slowRatioThreshold; + private Integer statIntervalMs; + private Date gmtCreate; private Date gmtModified; @@ -48,6 +52,9 @@ public static DegradeRuleEntity fromDegradeRule(String app, String ip, Integer p entity.setCount(rule.getCount()); entity.setTimeWindow(rule.getTimeWindow()); entity.setGrade(rule.getGrade()); + entity.setMinRequestAmount(rule.getMinRequestAmount()); + entity.setSlowRatioThreshold(rule.getSlowRatioThreshold()); + entity.setStatIntervalMs(rule.getStatIntervalMs()); return entity; } @@ -128,6 +135,33 @@ public void setGrade(Integer grade) { this.grade = grade; } + public Integer getMinRequestAmount() { + return minRequestAmount; + } + + public DegradeRuleEntity setMinRequestAmount(Integer minRequestAmount) { + this.minRequestAmount = minRequestAmount; + return this; + } + + public Double getSlowRatioThreshold() { + return slowRatioThreshold; + } + + public DegradeRuleEntity setSlowRatioThreshold(Double slowRatioThreshold) { + this.slowRatioThreshold = slowRatioThreshold; + return this; + } + + public Integer getStatIntervalMs() { + return statIntervalMs; + } + + public DegradeRuleEntity setStatIntervalMs(Integer statIntervalMs) { + this.statIntervalMs = statIntervalMs; + return this; + } + @Override public Date getGmtCreate() { return gmtCreate; @@ -153,6 +187,16 @@ public DegradeRule toRule() { rule.setCount(count); rule.setTimeWindow(timeWindow); rule.setGrade(grade); + if (minRequestAmount != null) { + rule.setMinRequestAmount(minRequestAmount); + } + if (slowRatioThreshold != null) { + rule.setSlowRatioThreshold(slowRatioThreshold); + } + if (statIntervalMs != null) { + rule.setStatIntervalMs(statIntervalMs); + } + return rule; } } diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/metric/InMemoryMetricsRepository.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/metric/InMemoryMetricsRepository.java index bed4ea1c73..4afeb0ccb7 100644 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/metric/InMemoryMetricsRepository.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/repository/metric/InMemoryMetricsRepository.java @@ -15,6 +15,11 @@ */ package com.alibaba.csp.sentinel.dashboard.repository.metric; +import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity; +import com.alibaba.csp.sentinel.util.StringUtil; +import com.alibaba.csp.sentinel.util.TimeUtil; +import org.springframework.stereotype.Component; + import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -22,14 +27,9 @@ import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.stream.Collectors; -import com.alibaba.csp.sentinel.dashboard.datasource.entity.MetricEntity; -import com.alibaba.csp.sentinel.util.StringUtil; - -import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap; -import org.springframework.stereotype.Component; - /** * Caches metrics data in a period of time in memory. * @@ -44,54 +44,71 @@ public class InMemoryMetricsRepository implements MetricsRepository resource -> timestamp -> metric} */ - private Map>> allMetrics = new ConcurrentHashMap<>(); + private Map>> allMetrics = new ConcurrentHashMap<>(); + private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(); @Override - public synchronized void save(MetricEntity entity) { + public void save(MetricEntity entity) { if (entity == null || StringUtil.isBlank(entity.getApp())) { return; } - allMetrics.computeIfAbsent(entity.getApp(), e -> new ConcurrentHashMap<>(16)) - .computeIfAbsent(entity.getResource(), e -> new ConcurrentLinkedHashMap.Builder() - .maximumWeightedCapacity(MAX_METRIC_LIVE_TIME_MS).weigher((key, value) -> { - // Metric older than {@link #MAX_METRIC_LIVE_TIME_MS} will be removed. - int weight = (int)(System.currentTimeMillis() - key); - // weight must be a number greater than or equal to one - return Math.max(weight, 1); - }).build()).put(entity.getTimestamp().getTime(), entity); + readWriteLock.writeLock().lock(); + try { + allMetrics.computeIfAbsent(entity.getApp(), e -> new HashMap<>(16)) + .computeIfAbsent(entity.getResource(), e -> new LinkedHashMap() { + @Override + protected boolean removeEldestEntry(Entry eldest) { + // Metric older than {@link #MAX_METRIC_LIVE_TIME_MS} will be removed. + return eldest.getKey() < TimeUtil.currentTimeMillis() - MAX_METRIC_LIVE_TIME_MS; + } + }).put(entity.getTimestamp().getTime(), entity); + } finally { + readWriteLock.writeLock().unlock(); + } + } @Override - public synchronized void saveAll(Iterable metrics) { + public void saveAll(Iterable metrics) { if (metrics == null) { return; } - metrics.forEach(this::save); + readWriteLock.writeLock().lock(); + try { + metrics.forEach(this::save); + } finally { + readWriteLock.writeLock().unlock(); + } } @Override - public synchronized List queryByAppAndResourceBetween(String app, String resource, - long startTime, long endTime) { + public List queryByAppAndResourceBetween(String app, String resource, + long startTime, long endTime) { List results = new ArrayList<>(); if (StringUtil.isBlank(app)) { return results; } - Map> resourceMap = allMetrics.get(app); + Map> resourceMap = allMetrics.get(app); if (resourceMap == null) { return results; } - ConcurrentLinkedHashMap metricsMap = resourceMap.get(resource); + LinkedHashMap metricsMap = resourceMap.get(resource); if (metricsMap == null) { return results; } - for (Entry entry : metricsMap.entrySet()) { - if (entry.getKey() >= startTime && entry.getKey() <= endTime) { - results.add(entry.getValue()); + readWriteLock.readLock().lock(); + try { + for (Entry entry : metricsMap.entrySet()) { + if (entry.getKey() >= startTime && entry.getKey() <= endTime) { + results.add(entry.getValue()); + } } + return results; + } finally { + readWriteLock.readLock().unlock(); } - return results; } @Override @@ -101,44 +118,49 @@ public List listResourcesOfApp(String app) { return results; } // resource -> timestamp -> metric - Map> resourceMap = allMetrics.get(app); + Map> resourceMap = allMetrics.get(app); if (resourceMap == null) { return results; } final long minTimeMs = System.currentTimeMillis() - 1000 * 60; Map resourceCount = new ConcurrentHashMap<>(32); - for (Entry> resourceMetrics : resourceMap.entrySet()) { - for (Entry metrics : resourceMetrics.getValue().entrySet()) { - if (metrics.getKey() < minTimeMs) { - continue; - } - MetricEntity newEntity = metrics.getValue(); - if (resourceCount.containsKey(resourceMetrics.getKey())) { - MetricEntity oldEntity = resourceCount.get(resourceMetrics.getKey()); - oldEntity.addPassQps(newEntity.getPassQps()); - oldEntity.addRtAndSuccessQps(newEntity.getRt(), newEntity.getSuccessQps()); - oldEntity.addBlockQps(newEntity.getBlockQps()); - oldEntity.addExceptionQps(newEntity.getExceptionQps()); - oldEntity.addCount(1); - } else { - resourceCount.put(resourceMetrics.getKey(), MetricEntity.copyOf(newEntity)); + readWriteLock.readLock().lock(); + try { + for (Entry> resourceMetrics : resourceMap.entrySet()) { + for (Entry metrics : resourceMetrics.getValue().entrySet()) { + if (metrics.getKey() < minTimeMs) { + continue; + } + MetricEntity newEntity = metrics.getValue(); + if (resourceCount.containsKey(resourceMetrics.getKey())) { + MetricEntity oldEntity = resourceCount.get(resourceMetrics.getKey()); + oldEntity.addPassQps(newEntity.getPassQps()); + oldEntity.addRtAndSuccessQps(newEntity.getRt(), newEntity.getSuccessQps()); + oldEntity.addBlockQps(newEntity.getBlockQps()); + oldEntity.addExceptionQps(newEntity.getExceptionQps()); + oldEntity.addCount(1); + } else { + resourceCount.put(resourceMetrics.getKey(), MetricEntity.copyOf(newEntity)); + } } } + // Order by last minute b_qps DESC. + return resourceCount.entrySet() + .stream() + .sorted((o1, o2) -> { + MetricEntity e1 = o1.getValue(); + MetricEntity e2 = o2.getValue(); + int t = e2.getBlockQps().compareTo(e1.getBlockQps()); + if (t != 0) { + return t; + } + return e2.getPassQps().compareTo(e1.getPassQps()); + }) + .map(Entry::getKey) + .collect(Collectors.toList()); + } finally { + readWriteLock.readLock().unlock(); } - // Order by last minute b_qps DESC. - return resourceCount.entrySet() - .stream() - .sorted((o1, o2) -> { - MetricEntity e1 = o1.getValue(); - MetricEntity e2 = o2.getValue(); - int t = e2.getBlockQps().compareTo(e1.getBlockQps()); - if (t != 0) { - return t; - } - return e2.getPassQps().compareTo(e1.getPassQps()); - }) - .map(Entry::getKey) - .collect(Collectors.toList()); } } diff --git a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtils.java b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtils.java index cb013a1249..71b9ac2750 100644 --- a/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtils.java +++ b/sentinel-dashboard/src/main/java/com/alibaba/csp/sentinel/dashboard/util/VersionUtils.java @@ -32,16 +32,16 @@ public final class VersionUtils { /** * Parse version of Sentinel from raw string. * - * @param versionFull version string + * @param verStr version string * @return parsed {@link SentinelVersion} if the version is valid; empty if * there is something wrong with the format */ - public static Optional parseVersion(String s) { - if (StringUtil.isBlank(s)) { + public static Optional parseVersion(String verStr) { + if (StringUtil.isBlank(verStr)) { return Optional.empty(); } try { - String versionFull = s; + String versionFull = verStr; SentinelVersion version = new SentinelVersion(); // postfix diff --git a/sentinel-dashboard/src/main/resources/application.properties b/sentinel-dashboard/src/main/resources/application.properties index a2f84dec89..b9e026c96c 100755 --- a/sentinel-dashboard/src/main/resources/application.properties +++ b/sentinel-dashboard/src/main/resources/application.properties @@ -3,6 +3,9 @@ spring.http.encoding.force=true spring.http.encoding.charset=UTF-8 spring.http.encoding.enabled=true +#cookie name setting +server.servlet.session.cookie.name=sentinel_dashboard_cookie + #logging settings logging.level.org.springframework.web=INFO logging.file=${user.home}/logs/csp/sentinel-dashboard.log diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js index bc3747af77..ff15e9a105 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/app.js @@ -38,6 +38,9 @@ angular return response; }, 'request' : function(config) { + // Resolved resource loading failure after configuring ContextPath + var baseUrl = $window.document.getElementsByTagName('base')[0].href; + config.url = baseUrl + config.url; return config; }, 'requestError' : function(config){ diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/degrade.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/degrade.js index 8a05323e4c..d2eac6da40 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/degrade.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/degrade.js @@ -66,7 +66,9 @@ app.controller('DegradeCtl', ['$scope', '$stateParams', 'DegradeService', 'ngDia app: $scope.app, ip: mac[0], port: mac[1], - limitApp: 'default' + limitApp: 'default', + minRequestAmount: 5, + statIntervalMs: 1000, }; $scope.degradeRuleDialog = { title: '新增降级规则', @@ -95,7 +97,7 @@ app.controller('DegradeCtl', ['$scope', '$stateParams', 'DegradeService', 'ngDia function parseDegradeMode(grade) { switch (grade) { case 0: - return 'RT'; + return '慢调用比例'; case 1: return '异常比例'; case 2: @@ -137,7 +139,7 @@ app.controller('DegradeCtl', ['$scope', '$stateParams', 'DegradeService', 'ngDia getMachineRules(); confirmDialog.close(); } else { - alert('失败!'); + alert('失败:' + data.msg); } }); }; @@ -148,7 +150,7 @@ app.controller('DegradeCtl', ['$scope', '$stateParams', 'DegradeService', 'ngDia getMachineRules(); degradeRuleDialog.close(); } else { - alert('失败!'); + alert('失败:' + data.msg); } }); }; @@ -163,7 +165,7 @@ app.controller('DegradeCtl', ['$scope', '$stateParams', 'DegradeService', 'ngDia confirmDialog.close(); } } else { - alert('失败!'); + alert('失败:' + data.msg); } }); } diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js index f8116be78e..2a14eb1e96 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/controllers/identity.js @@ -132,6 +132,8 @@ app.controller('IdentityCtl', ['$scope', '$stateParams', 'IdentityService', strategy: 0, resource: resource, limitApp: 'default', + minRequestAmount: 5, + statIntervalMs: 1000, app: $scope.app, ip: mac[0], port: mac[1] diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/header/header.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/header/header.js index b10c672d2d..4e6c8f2cf2 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/header/header.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/directives/header/header.js @@ -27,7 +27,15 @@ angular.module('sentinelDashboardApp') } }); } else { - handleLogout($scope, JSON.parse($window.localStorage.getItem("session_sentinel_admin")).id) + try { + var id = JSON.parse($window.localStorage.getItem("session_sentinel_admin")).id; + handleLogout($scope, id); + } catch (e) { + // Historical version compatibility processing, fixes issue-1449 + // If error happens while parsing, remove item in localStorage and redirect to login page. + $window.localStorage.removeItem("session_sentinel_admin"); + $state.go('login'); + } } function handleLogout($scope, id) { diff --git a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/degradeservice.js b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/degrade_service.js similarity index 50% rename from sentinel-dashboard/src/main/webapp/resources/app/scripts/services/degradeservice.js rename to sentinel-dashboard/src/main/webapp/resources/app/scripts/services/degrade_service.js index 146d841d70..a242b22a4c 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/degradeservice.js +++ b/sentinel-dashboard/src/main/webapp/resources/app/scripts/services/degrade_service.js @@ -15,21 +15,10 @@ app.service('DegradeService', ['$http', function ($http) { }; this.newRule = function (rule) { - var param = { - id: rule.id, - resource: rule.resource, - limitApp: rule.limitApp, - count: rule.count, - timeWindow: rule.timeWindow, - grade: rule.grade, - app: rule.app, - ip: rule.ip, - port: rule.port - }; return $http({ - url: '/degrade/new.json', - params: param, - method: 'GET' + url: '/degrade/rule', + data: rule, + method: 'POST' }); }; @@ -41,24 +30,22 @@ app.service('DegradeService', ['$http', function ($http) { grade: rule.grade, count: rule.count, timeWindow: rule.timeWindow, + statIntervalMs: rule.statIntervalMs, + minRequestAmount: rule.minRequestAmount, + slowRatioThreshold: rule.slowRatioThreshold, }; return $http({ - url: '/degrade/save.json', - params: param, - method: 'GET' + url: '/degrade/rule/' + rule.id, + data: param, + method: 'PUT' }); }; this.deleteRule = function (rule) { - var param = { - id: rule.id, - app: rule.app - }; - return $http({ - url: '/degrade/delete.json', - params: param, - method: 'GET' - }); + return $http({ + url: '/degrade/rule/' + rule.id, + method: 'DELETE' + }); }; this.checkRuleValid = function (rule) { @@ -74,8 +61,20 @@ app.service('DegradeService', ['$http', function ($http) { alert('降级阈值不能为空或小于 0'); return false; } - if (rule.timeWindow === undefined || rule.timeWindow === '' || rule.timeWindow <= 0) { - alert('降级时间窗口必须大于 0'); + if (rule.timeWindow == undefined || rule.timeWindow === '' || rule.timeWindow <= 0) { + alert('熔断时长必须大于 0s'); + return false; + } + if (rule.minRequestAmount == undefined || rule.minRequestAmount <= 0) { + alert('最小请求数目需大于 0'); + return false; + } + if (rule.statIntervalMs == undefined || rule.statIntervalMs <= 0) { + alert('统计窗口时长需大于 0s'); + return false; + } + if (rule.statIntervalMs !== undefined && rule.statIntervalMs > 60 * 1000 * 2) { + alert('统计窗口时长不能超过 120 分钟'); return false; } // 异常比率类型. @@ -83,6 +82,16 @@ app.service('DegradeService', ['$http', function ($http) { alert('异常比率超出范围:[0.0 - 1.0]'); return false; } + if (rule.grade == 0) { + if (rule.slowRatioThreshold == undefined) { + alert('慢调用比率不能为空'); + return false; + } + if (rule.slowRatioThreshold < 0 || rule.slowRatioThreshold > 1) { + alert('慢调用比率超出范围:[0.0 - 1.0]'); + return false; + } + } return true; }; }]); diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/degrade.html b/sentinel-dashboard/src/main/webapp/resources/app/views/degrade.html index 1ed7458f62..c2b67528a8 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/degrade.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/degrade.html @@ -33,21 +33,15 @@ 资源名 - - - - 降级模式 + 降级策略 阈值 - 时间窗口(s) + 熔断时长(s) - - - 操作 @@ -59,9 +53,9 @@ {{rule.resource}} - RT - 异常比例 - 异常数 + 慢调用比例 + 异常比例 + 异常数 {{rule.count}} diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/degrade-rule-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/degrade-rule-dialog.html index 7dddbbb829..b8fae0c3fd 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/degrade-rule-dialog.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/degrade-rule-dialog.html @@ -22,30 +22,63 @@
    - +
    -  RT   -  异常比例   -  异常数 +  慢调用比例   +  异常比例   +  异常数
    - - + + -
    - - +
    + +
    - +
    + +
    + +
    +
    +
    + +
    + +
    +
    + + s +
    +
    + + +
    + +
    +
    + +
    +
    - +
    + + ms +
    +
    diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html index f832ce3ec6..e68fb6b4a5 100755 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/flow-rule-dialog.html @@ -78,7 +78,7 @@ -
    +
    @@ -103,7 +103,7 @@
    -
    +
    @@ -130,7 +130,7 @@
    -
    + diff --git a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/flow-rule-dialog.html b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/flow-rule-dialog.html index 7dca16d719..ea744a7111 100644 --- a/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/flow-rule-dialog.html +++ b/sentinel-dashboard/src/main/webapp/resources/app/views/dialog/gateway/flow-rule-dialog.html @@ -70,7 +70,7 @@
    -
    +