diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml b/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml index 386cebd1..3fce7e2c 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/pom.xml @@ -15,7 +15,7 @@ 1.8 1.8 - 2.7.1 + 2.7.3 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 new file mode 100644 index 00000000..9ea01e59 --- /dev/null +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/BaseSentinelDubboFilter.java @@ -0,0 +1,77 @@ +/* + * 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.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.Invocation; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.ListenableFilter; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcContext; + +/** + * Base Class of the {@link SentinelDubboProviderFilter} and {@link SentinelDubboConsumerFilter}. + * + * @author Zechao Zheng + */ + +public abstract class BaseSentinelDubboFilter extends ListenableFilter { + public BaseSentinelDubboFilter() { + this.listener = new SentinelDubboListener(); + } + + static class SentinelDubboListener implements Listener { + + @Override + public void onResponse(Result appResponse, Invoker invoker, Invocation invocation) { + 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/DubboAppContextFilter.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java index 97ae51ba..789bb96e 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/DubboAppContextFilter.java @@ -15,7 +15,7 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo; -import org.apache.dubbo.common.Constants; +import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.rpc.Filter; import org.apache.dubbo.rpc.Invocation; @@ -34,7 +34,7 @@ public class DubboAppContextFilter implements Filter { @Override public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { - String application = invoker.getUrl().getParameter(Constants.APPLICATION_KEY); + String application = invoker.getUrl().getParameter(CommonConstants.APPLICATION_KEY); if (application != null) { RpcContext.getContext().setAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, application); } 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 362925d2..d2c325fa 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,6 +15,7 @@ */ package com.alibaba.csp.sentinel.adapter.dubbo; +import com.alibaba.csp.sentinel.adapter.dubbo.config.DubboConfig; import com.alibaba.csp.sentinel.util.StringUtil; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; @@ -25,6 +26,8 @@ import org.apache.dubbo.rpc.Invoker; 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) { @@ -33,12 +36,17 @@ public final class DubboUtils { return invocation.getAttachment(SENTINEL_DUBBO_APPLICATION_KEY, defaultValue); } - public static String getResourceName(Invoker invoker, Invocation invocation) { + public static String getResourceName(Invoker invoker, Invocation invocation){ + return getResourceName(invoker, invocation, false); + } + + public static String getResourceName(Invoker invoker, Invocation invocation, Boolean useGroupAndVersion) { StringBuilder buf = new StringBuilder(64); - buf.append(invoker.getInterface().getName()) - .append(":") - .append(invocation.getMethodName()) - .append("("); + String interfaceResource = useGroupAndVersion ? invoker.getUrl().getColonSeparatedKey() : invoker.getInterface().getName(); + buf.append(interfaceResource) + .append(":") + .append(invocation.getMethodName()) + .append("("); boolean isFirst = true; for (Class clazz : invocation.getParameterTypes()) { if (!isFirst) { @@ -55,13 +63,12 @@ public final class DubboUtils { if (StringUtil.isNotBlank(prefix)) { return new StringBuilder(64) .append(prefix) - .append(getResourceName(invoker, invocation)) + .append(getResourceName(invoker, invocation, DubboConfig.getDubboInterfaceGroupAndVersionEnabled())) .toString(); } else { - return getResourceName(invoker, invocation); + return getResourceName(invoker, invocation, DubboConfig.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 old mode 100755 new mode 100644 index ef312bc0..8b317e90 --- 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 @@ -19,17 +19,18 @@ 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.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 org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.rpc.Filter; 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.support.RpcUtils; /** *

Dubbo service consumer filter for Sentinel. Auto activated by default.

@@ -43,7 +44,7 @@ import org.apache.dubbo.rpc.RpcException; * @author Eric Zhao */ @Activate(group = "consumer") -public class SentinelDubboConsumerFilter implements Filter { +public class SentinelDubboConsumerFilter extends BaseSentinelDubboFilter { public SentinelDubboConsumerFilter() { RecordLog.info("Sentinel Apache Dubbo consumer filter initialized"); @@ -53,33 +54,29 @@ public class SentinelDubboConsumerFilter implements Filter { public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { Entry interfaceEntry = null; Entry methodEntry = null; + RpcContext rpcContext = RpcContext.getContext(); try { - String resourceName = DubboUtils.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 methodResourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboConsumerPrefix()); + String interfaceResourceName = DubboConfig.getDubboInterfaceGroupAndVersionEnabled() ? invoker.getUrl().getColonSeparatedKey() + : invoker.getInterface().getName(); + InvokeMode invokeMode = RpcUtils.getInvokeMode(invoker.getUrl(), invocation); - Result result = invoker.invoke(invocation); - if (result.hasException()) { - Throwable e = result.getException(); - // Record common exception. - Tracer.traceEntry(e, interfaceEntry); - Tracer.traceEntry(e, methodEntry); + 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()); } - return result; + rpcContext.set(DubboUtils.DUBBO_METHOD_ENTRY_KEY, methodEntry); + return invoker.invoke(invocation); } catch (BlockException e) { return DubboFallbackRegistry.getConsumerFallback().handle(invoker, invocation, e); - } catch (RpcException e) { - Tracer.traceEntry(e, interfaceEntry); - Tracer.traceEntry(e, methodEntry); - throw e; - } finally { - if (methodEntry != null) { - methodEntry.exit(); - } - if (interfaceEntry != null) { - interfaceEntry.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 old mode 100755 new mode 100644 index d5a2745e..479b0954 --- 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 @@ -19,17 +19,16 @@ 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.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; import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.rpc.Filter; 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; /** @@ -45,7 +44,7 @@ import org.apache.dubbo.rpc.RpcException; * @author Eric Zhao */ @Activate(group = "provider") -public class SentinelDubboProviderFilter implements Filter { +public class SentinelDubboProviderFilter extends BaseSentinelDubboFilter { public SentinelDubboProviderFilter() { RecordLog.info("Sentinel Apache Dubbo provider filter initialized"); @@ -55,41 +54,26 @@ public class SentinelDubboProviderFilter implements Filter { public Result invoke(Invoker invoker, Invocation invocation) throws RpcException { // Get origin caller. String application = DubboUtils.getApplication(invocation, ""); - + RpcContext rpcContext = RpcContext.getContext(); Entry interfaceEntry = null; Entry methodEntry = null; try { - String resourceName = DubboUtils.getResourceName(invoker, invocation, DubboConfig.getDubboProviderPrefix()); - String interfaceName = invoker.getInterface().getName(); + 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(resourceName, application); - interfaceEntry = SphU.entry(interfaceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN); - methodEntry = SphU.entry(resourceName, ResourceTypeConstants.COMMON_RPC, - EntryType.IN, invocation.getArguments()); - - Result result = invoker.invoke(invocation); - if (result.hasException()) { - Throwable e = result.getException(); - // Record common exception. - Tracer.traceEntry(e, interfaceEntry); - Tracer.traceEntry(e, methodEntry); - } - return result; + ContextUtil.enter(methodResourceName, application); + 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); } catch (BlockException e) { return DubboFallbackRegistry.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/DubboConfig.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/main/java/com/alibaba/csp/sentinel/adapter/dubbo/config/DubboConfig.java index 515bca3e..9b2b0218 100644 --- 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 @@ -37,8 +37,12 @@ public final class DubboConfig { 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 boolean isUsePrefix(){ + 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)); } @@ -58,5 +62,16 @@ public final class DubboConfig { 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/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 a61f3b4c..7793a656 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,73 @@ */ 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.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 - * + *

* Note: Only for test. DO NOT USE IN PRODUCTION! * * @author cdfive */ 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() { - RpcContext.removeContext(); - ClusterBuilderSlot.getClusterNodeMap().clear(); - CtSph.resetChainMap(); + 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<>()); + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } } } \ No newline at end of file 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 95933fcf..ece46fde 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 @@ -18,6 +18,8 @@ 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 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.junit.After; @@ -29,7 +31,9 @@ import java.util.HashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.fail; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * @author cdfive @@ -41,6 +45,7 @@ public class DubboUtilsTest { 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"); } @@ -49,6 +54,7 @@ public class DubboUtilsTest { 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"); } @@ -78,27 +84,49 @@ public class DubboUtilsTest { } @Test - public void testGetResourceName() { + public void testGetResourceName() throws NoSuchMethodException { Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); - Method method = DemoService.class.getMethods()[0]; + Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); String resourceName = DubboUtils.getResourceName(invoker, invocation); assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", resourceName); + } @Test - public void testGetResourceNameWithPrefix() { + public void testGetResourceNameWithGroupAndVersion() throws NoSuchMethodException { + Invoker 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 invocation = mock(Invocation.class); + Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class); + when(invocation.getMethodName()).thenReturn(method.getName()); + when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); + + String resourceNameUseGroupAndVersion = DubboUtils.getResourceName(invoker, invocation, true); + + assertEquals("com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:1.0.0:grp1:sayHello(java.lang.String,int)", resourceNameUseGroupAndVersion); + } + + + @Test + public void testGetResourceNameWithPrefix() throws NoSuchMethodException { Invoker invoker = mock(Invoker.class); when(invoker.getInterface()).thenReturn(DemoService.class); Invocation invocation = mock(Invocation.class); - Method method = DemoService.class.getMethods()[0]; + Method method = DemoService.class.getDeclaredMethod("sayHello", String.class, int.class); when(invocation.getMethodName()).thenReturn(method.getName()); when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); @@ -118,4 +146,4 @@ public class DubboUtilsTest { assertEquals("my:dubbo:consumer:com.alibaba.csp.sentinel.adapter.dubbo.provider.DemoService:sayHello(java.lang.String,int)", 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 55b87a59..31318cb2 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,9 +16,11 @@ package com.alibaba.csp.sentinel.adapter.dubbo; import com.alibaba.csp.sentinel.BaseTest; -import com.alibaba.csp.sentinel.Constants; 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.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; @@ -27,20 +29,40 @@ import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; 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.DegradeRule; +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.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 static org.junit.Assert.*; -import static org.mockito.Mockito.*; +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; /** * @author cdfive @@ -49,9 +71,12 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { private SentinelDubboConsumerFilter filter = new SentinelDubboConsumerFilter(); + @Before public void setUp() { cleanUpAll(); + initFallback(); + constructInvokerAndInvocation(); } @After @@ -59,18 +84,177 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { cleanUpAll(); } - @Test - public void testInvoke() { - final Invoker invoker = mock(Invoker.class); - when(invoker.getInterface()).thenReturn(DemoService.class); + 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); + } - final Invocation invocation = mock(Invocation.class); - Method method = DemoService.class.getMethods()[0]; - when(invocation.getMethodName()).thenReturn(method.getName()); - when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); + 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 { + when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); + initFlowRule(invoker.getUrl().getColonSeparatedKey()); + Result result1 = responseBack(requestGo(false, invocation)); + assertEquals("normal", result1.getValue()); + // should fallback because the qps > 1 + Result result2 = responseBack(requestGo(false, invocation)); + assertEquals("fallback", result2.getValue()); + // sleeping 1000 ms to reset qps + Thread.sleep(1000); + Result result3 = responseBack(requestGo(false, invocation)); + assertEquals("normal", result3.getValue()); + + verifyInvocationStructureForCallFinish(); + } + + @Test + public void testDegradeAsync() throws InterruptedException { + when(invocation.getAttachment(ASYNC_KEY)).thenReturn(Boolean.TRUE.toString()); + + initDegradeRule(invoker.getUrl().getColonSeparatedKey()); + Result result = requestGo(false, invocation); + verifyInvocationStructureForAsyncCall(invoker, invocation); + responseBack(result); + 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(); + } + Result result2 = responseBack(requestGo(false, invocation)); + assertEquals("fallback", result2.getValue()); + // sleeping 1000 ms to reset exception + Thread.sleep(1000); + + Result result3 = responseBack(requestGo(false, invocation)); + assertEquals("normal", result3.getValue()); + + Context context = ContextUtil.getContext(); + assertNull(context); + } + + @Test + public void testDegradeSync() throws InterruptedException { + + initDegradeRule(invoker.getUrl().getColonSeparatedKey()); + Result result = requestGo(false, invocation); + verifyInvocationStructure(invoker, invocation); + responseBack(result); + 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(); + } + Result result2 = responseBack(requestGo(false, invocation)); + assertEquals("fallback", result2.getValue()); + // sleeping 1000 ms to reset exception + Thread.sleep(1000); + + Result result3 = responseBack(requestGo(false, 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 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(); + 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); + 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); + } + + public Result responseBack(Result result) { + filter.listener().onResponse(result, invoker, invocation); + return result; + } + + + @Test + public void testInvokeAsync() throws InterruptedException { + + 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); + verify(invoker).invoke(invocation); + + Context context = ContextUtil.getContext(); + assertNotNull(context); + } + + @Test + public void testInvokeSync() { + + final Result result = mock(Result.class); + when(result.hasException()).thenReturn(false); + when(result.getException()).thenReturn(new Exception()); when(invoker.invoke(invocation)).thenAnswer(invocationOnMock -> { verifyInvocationStructure(invoker, invocation); return result; @@ -79,6 +263,7 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); + filter.listener().onResponse(result, invoker, invocation); Context context = ContextUtil.getContext(); assertNull(context); } @@ -92,31 +277,32 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { 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 // If consumer is on the top of Dubbo RPC invocation chain, use default context - String resourceName = DubboUtils.getResourceName(invoker, invocation); - assertEquals(Constants.CONTEXT_DEFAULT_NAME, context.getName()); + String resourceName = DubboUtils.getResourceName(invoker, invocation, true); + assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); assertEquals("", context.getOrigin()); DefaultNode entranceNode = context.getEntranceNode(); ResourceWrapper entranceResource = entranceNode.getId(); - assertEquals(Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName()); + + assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName()); assertSame(EntryType.IN, entranceResource.getEntryType()); // As SphU.entry(interfaceName, EntryType.OUT); Set childList = entranceNode.getChildList(); assertEquals(1, childList.size()); - DefaultNode interfaceNode = (DefaultNode) childList.iterator().next(); + DefaultNode interfaceNode = getNode(invoker.getUrl().getColonSeparatedKey(), entranceNode); ResourceWrapper interfaceResource = interfaceNode.getId(); - assertEquals(DemoService.class.getName(), interfaceResource.getName()); + + assertEquals(invoker.getUrl().getColonSeparatedKey(), interfaceResource.getName()); assertSame(EntryType.OUT, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.OUT); childList = interfaceNode.getChildList(); assertEquals(1, childList.size()); - DefaultNode methodNode = (DefaultNode) childList.iterator().next(); + DefaultNode methodNode = getNode(resourceName, entranceNode); ResourceWrapper methodResource = methodNode.getId(); assertEquals(resourceName, methodResource.getName()); assertSame(EntryType.OUT, methodResource.getEntryType()); @@ -139,4 +325,82 @@ public class SentinelDubboConsumerFilterTest extends BaseTest { Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); assertEquals(0, interfaceOriginCountMap.size()); } + + private void verifyInvocationStructureForAsyncCall(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 + // If consumer is on the top of Dubbo RPC invocation chain, use default context + String resourceName = DubboUtils.getResourceName(invoker, invocation, true); + assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, context.getName()); + assertEquals("", context.getOrigin()); + + DefaultNode entranceNode = context.getEntranceNode(); + ResourceWrapper entranceResource = entranceNode.getId(); + assertEquals(com.alibaba.csp.sentinel.Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName()); + assertSame(EntryType.IN, entranceResource.getEntryType()); + + // As SphU.entry(interfaceName, EntryType.OUT); + Set childList = entranceNode.getChildList(); + assertEquals(2, childList.size()); + DefaultNode interfaceNode = getNode(invoker.getUrl().getColonSeparatedKey(), entranceNode); + ResourceWrapper interfaceResource = interfaceNode.getId(); + assertEquals(invoker.getUrl().getColonSeparatedKey(), interfaceResource.getName()); + assertSame(EntryType.OUT, interfaceResource.getEntryType()); + + // As SphU.entry(resourceName, EntryType.OUT); + childList = interfaceNode.getChildList(); + assertEquals(0, childList.size()); + DefaultNode methodNode = getNode(resourceName, entranceNode); + ResourceWrapper methodResource = methodNode.getId(); + assertEquals(resourceName, methodResource.getName()); + assertSame(EntryType.OUT, methodResource.getEntryType()); + + // Verify curEntry + // nothing will bind to local context when use the AsyncEntry + Entry curEntry = context.getCurEntry(); + assertNull(curEntry); + + // Verify clusterNode + ClusterNode methodClusterNode = methodNode.getClusterNode(); + ClusterNode interfaceClusterNode = interfaceNode.getClusterNode(); + 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(); + assertEquals(0, methodOriginCountMap.size()); + + Map interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap(); + assertEquals(0, interfaceOriginCountMap.size()); + } + + + private void verifyInvocationStructureForCallFinish() { + 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); + } + + + public DefaultNode getNode(String resourceName, DefaultNode root) { + + Queue queue = new LinkedList<>(); + queue.offer(root); + while (!queue.isEmpty()) { + DefaultNode temp = queue.poll(); + if (temp.getId().getName().equals(resourceName)) { + return temp; + } + for (Node node : temp.getChildList()) { + queue.offer((DefaultNode) node); + } + } + return null; + } + } 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 d8a6f00d..1a7000f1 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 @@ -26,7 +26,8 @@ import com.alibaba.csp.sentinel.node.DefaultNode; import com.alibaba.csp.sentinel.node.Node; import com.alibaba.csp.sentinel.node.StatisticNode; import com.alibaba.csp.sentinel.slotchain.ResourceWrapper; - +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.Result; @@ -38,8 +39,15 @@ import java.lang.reflect.Method; import java.util.Map; import java.util.Set; -import static org.junit.Assert.*; -import static org.mockito.Mockito.*; +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; /** * @author cdfive @@ -50,6 +58,7 @@ public class SentinelDubboProviderFilterTest extends BaseTest { @Before public void setUp() { + constructInvokerAndInvocation(); cleanUpAll(); } @@ -62,18 +71,16 @@ public class SentinelDubboProviderFilterTest extends BaseTest { public void testInvoke() { final String originApplication = "consumerA"; - final Invoker invoker = mock(Invoker.class); - when(invoker.getInterface()).thenReturn(DemoService.class); + URL url = invoker.getUrl() + .addParameter(CommonConstants.SIDE_KEY, CommonConstants.PROVIDER_SIDE); + when(invoker.getUrl()).thenReturn(url); - final Invocation invocation = mock(Invocation.class); - Method method = DemoService.class.getMethods()[0]; - when(invocation.getMethodName()).thenReturn(method.getName()); - when(invocation.getParameterTypes()).thenReturn(method.getParameterTypes()); when(invocation.getAttachment(DubboUtils.SENTINEL_DUBBO_APPLICATION_KEY, "")) - .thenReturn(originApplication); + .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; @@ -82,6 +89,7 @@ public class SentinelDubboProviderFilterTest extends BaseTest { filter.invoke(invoker, invocation); verify(invoker).invoke(invocation); + filter.listener().onResponse(result, invoker, invocation); Context context = ContextUtil.getContext(); assertNull(context); } @@ -97,7 +105,7 @@ public class SentinelDubboProviderFilterTest extends BaseTest { assertNotNull(context); // As ContextUtil.enter(resourceName, application) in SentinelDubboProviderFilter - String resourceName = DubboUtils.getResourceName(invoker, invocation); + String resourceName = DubboUtils.getResourceName(invoker, invocation, true); assertEquals(resourceName, context.getName()); assertEquals(originApplication, context.getOrigin()); @@ -111,7 +119,8 @@ public class SentinelDubboProviderFilterTest extends BaseTest { assertEquals(1, childList.size()); DefaultNode interfaceNode = (DefaultNode) childList.iterator().next(); ResourceWrapper interfaceResource = interfaceNode.getId(); - assertEquals(DemoService.class.getName(), interfaceResource.getName()); + + assertEquals(invoker.getUrl().getColonSeparatedKey(), interfaceResource.getName()); assertSame(EntryType.IN, interfaceResource.getEntryType()); // As SphU.entry(resourceName, EntryType.IN, 1, invocation.getArguments()); 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 bc06ebf1..ce5019e1 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 @@ -18,9 +18,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 com.alibaba.csp.sentinel.slots.block.flow.FlowException; - +import org.apache.dubbo.rpc.AsyncRpcResult; import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.RpcResult; import org.junit.Assert; import org.junit.Test; @@ -41,7 +40,7 @@ public class DubboFallbackRegistryTest { public void testCustomFallback() { BlockException ex = new FlowException("xxx"); DubboFallbackRegistry.setConsumerFallback( - (invoker, invocation, e) -> new RpcResult("Error: " + e.getClass().getName())); + (invoker, invocation, e) -> AsyncRpcResult.newDefaultAsyncResult("Error: " + e.getClass().getName(), invocation)); Result result = DubboFallbackRegistry.getConsumerFallback() .handle(null, null, ex); Assert.assertFalse("The invocation should not fail", result.hasException()); diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java index 2a024cc9..26e84479 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/DemoService.java @@ -20,4 +20,5 @@ package com.alibaba.csp.sentinel.adapter.dubbo.provider; */ public interface DemoService { String sayHello(String name, int n); + String sayHi(String name,int n); } diff --git a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java index f804c2c4..b09e08be 100644 --- a/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java +++ b/sentinel-adapter/sentinel-apache-dubbo-adapter/src/test/java/com/alibaba/csp/sentinel/adapter/dubbo/provider/impl/DemoServiceImpl.java @@ -24,4 +24,9 @@ public class DemoServiceImpl implements DemoService { public String sayHello(String name, int n) { return "Hello " + name + ", " + n; } + + @Override + public String sayHi(String name, int n) { + return "Hi " + name + ", " + n; + } } diff --git a/sentinel-demo/sentinel-demo-apache-dubbo/pom.xml b/sentinel-demo/sentinel-demo-apache-dubbo/pom.xml index 3d9b1053..b65359ff 100644 --- a/sentinel-demo/sentinel-demo-apache-dubbo/pom.xml +++ b/sentinel-demo/sentinel-demo-apache-dubbo/pom.xml @@ -15,7 +15,7 @@ org.apache.dubbo dubbo - 2.7.1 + 2.7.3 diff --git a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooConsumerBootstrap.java b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooConsumerBootstrap.java index dfe1dbcb..38de4892 100644 --- a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooConsumerBootstrap.java +++ b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooConsumerBootstrap.java @@ -15,12 +15,22 @@ */ package com.alibaba.csp.sentinel.demo.apache.dubbo; +import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry; import com.alibaba.csp.sentinel.demo.apache.dubbo.consumer.ConsumerConfiguration; import com.alibaba.csp.sentinel.demo.apache.dubbo.consumer.FooServiceConsumer; +import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.SentinelRpcException; - +import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule; +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.springframework.context.annotation.AnnotationConfigApplicationContext; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + /** * Please add the following VM arguments: *

@@ -33,10 +43,14 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
  */
 public class FooConsumerBootstrap {
 
-    public static void main(String[] args) {
+    private static final String INTERFACE_RES_KEY = FooService.class.getName();
+    private static final String RES_KEY = INTERFACE_RES_KEY + ":sayHello(java.lang.String)";
+
+    public static void main(String[] args) throws InterruptedException {
         AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext();
         consumerContext.register(ConsumerConfiguration.class);
         consumerContext.refresh();
+        initFlowRule(10, false);
 
         FooServiceConsumer service = consumerContext.getBean(FooServiceConsumer.class);
 
@@ -50,5 +64,97 @@ public class FooConsumerBootstrap {
                 ex.printStackTrace();
             }
         }
+
+        // method flowcontrol
+        Thread.sleep(1000);
+        initFlowRule(20, true);
+        for (int i = 0; i < 10; i++) {
+            try {
+                String message = service.sayHello("Eric");
+                System.out.println("Success: " + message);
+            } catch (SentinelRpcException ex) {
+                System.out.println("Blocked");
+                System.out.println("fallback:" + service.doAnother());
+
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+
+        // fallback to result
+        Thread.sleep(1000);
+        registryCustomFallback();
+
+        for (int i = 0; i < 10; i++) {
+            try {
+                String message = service.sayHello("Eric");
+                System.out.println("Result: " + message);
+            } catch (SentinelRpcException ex) {
+                System.out.println("Blocked");
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+        // fallback to exception
+        Thread.sleep(1000);
+        registryCustomFallbackForCustomException();
+
+        for (int i = 0; i < 10; i++) {
+            try {
+                String message = service.sayHello("Eric");
+                System.out.println("Result: " + message);
+            } catch (SentinelRpcException ex) {
+                System.out.println("Blocked");
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+
+        Thread.sleep(1000);
+        registryCustomFallbackWhenFallbackError();
+        for (int i = 0; i < 10; i++) {
+            try {
+                String message = service.sayHello("Eric");
+                System.out.println("Result: " + message);
+            } catch (SentinelRpcException ex) {
+                System.out.println("Blocked");
+            } catch (Exception ex) {
+                ex.printStackTrace();
+            }
+        }
+    }
+
+    public static void registryCustomFallback() {
+        DubboFallbackRegistry.setConsumerFallback(
+                (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult("fallback", invocation));
+
+    }
+
+    public static void registryCustomFallbackForCustomException() {
+        DubboFallbackRegistry.setConsumerFallback(
+                (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult(new RuntimeException("fallback"), invocation));
+    }
+
+    public static void registryCustomFallbackWhenFallbackError() {
+        DubboFallbackRegistry.setConsumerFallback(
+                (invoker, invocation, ex) -> {
+                    throw new RuntimeException("fallback");
+                });
+    }
+
+
+    private static void initFlowRule(int interfaceFlowLimit, boolean method) {
+        FlowRule flowRule = new FlowRule(INTERFACE_RES_KEY)
+                .setCount(interfaceFlowLimit)
+                .setGrade(RuleConstant.FLOW_GRADE_QPS);
+        List list = new ArrayList<>();
+        if (method) {
+            FlowRule flowRule1 = new FlowRule(RES_KEY)
+                    .setCount(5)
+                    .setGrade(RuleConstant.FLOW_GRADE_QPS);
+            list.add(flowRule1);
+        }
+        list.add(flowRule);
+        FlowRuleManager.loadRules(list);
     }
 }
diff --git a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooConsumerExceptionDegradeBootstrap.java b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooConsumerExceptionDegradeBootstrap.java
new file mode 100644
index 00000000..2df72d84
--- /dev/null
+++ b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooConsumerExceptionDegradeBootstrap.java
@@ -0,0 +1,119 @@
+/*
+ * 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.demo.apache.dubbo;
+
+import com.alibaba.csp.sentinel.adapter.dubbo.fallback.DubboFallbackRegistry;
+import com.alibaba.csp.sentinel.demo.apache.dubbo.consumer.ConsumerConfiguration;
+import com.alibaba.csp.sentinel.demo.apache.dubbo.consumer.FooServiceConsumer;
+import com.alibaba.csp.sentinel.slots.block.RuleConstant;
+import com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRule;
+import com.alibaba.csp.sentinel.slots.block.degrade.DegradeRuleManager;
+import org.apache.dubbo.rpc.AsyncRpcResult;
+import org.apache.dubbo.rpc.RpcContext;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+
+import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * Please add the following VM arguments:
+ * 
+ * -Djava.net.preferIPv4Stack=true
+ * -Dcsp.sentinel.api.port=8721
+ * -Dproject.name=dubbo-consumer-demo
+ * 
+ * + * @author Zechao zheng + */ +public class FooConsumerExceptionDegradeBootstrap { + + private static final String INTERFACE_RES_KEY = FooService.class.getName(); + private static final String RES_KEY = INTERFACE_RES_KEY + ":sayHello(java.lang.String)"; + + public static void main(String[] args) throws InterruptedException, ExecutionException { + AnnotationConfigApplicationContext consumerContext = new AnnotationConfigApplicationContext(); + consumerContext.register(ConsumerConfiguration.class); + consumerContext.refresh(); + + FooServiceConsumer service = consumerContext.getBean(FooServiceConsumer.class); + initExceptionFallback(3); + registryCustomFallback(); + for (int i = 0; i < 10; i++) { + try { + String message = service.exceptionTest(true, false); + System.out.println("Result: " + message); + } catch (SentinelRpcException ex) { + System.out.println("Blocked"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + // sleep 3s to skip the time window + initExceptionFallback(3); + Thread.sleep(3000); + for (int i = 0; i < 10; i++) { + try { + String message = service.exceptionTest(false, true); + System.out.println("Result: " + message); + } catch (SentinelRpcException ex) { + System.out.println("Blocked"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + initExceptionFallback(3); + Thread.sleep(3000); + + try { + // timeout to trigger the fallback + CompletableFuture completableFuture = RpcContext.getContext().asyncCall(() -> service.exceptionTest(false, true)); + System.out.println("Result: " + completableFuture.get()); + } catch (Exception e) { + e.printStackTrace(); + } + + for (int i = 0; i < 10; i++) { + try { + CompletableFuture result = RpcContext.getContext().asyncCall(() -> service.exceptionTest(false, true)); + System.out.println("Result: " + result.get()); + } catch (SentinelRpcException ex) { + System.out.println("Blocked"); + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + } + + public static void registryCustomFallback() { + DubboFallbackRegistry.setConsumerFallback( + (invoker, invocation, ex) -> AsyncRpcResult.newDefaultAsyncResult("fallback", invocation)); + + } + + public static void initExceptionFallback(int timewindow) { + DegradeRule degradeRule = new DegradeRule(INTERFACE_RES_KEY) + .setCount(0.5) + .setGrade(RuleConstant.DEGRADE_GRADE_EXCEPTION_RATIO) + .setTimeWindow(timewindow) + .setMinRequestAmount(1); + DegradeRuleManager.loadRules(Collections.singletonList(degradeRule)); + + } +} diff --git a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooProviderBootstrap.java b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooProviderBootstrap.java index a23c0ed0..1e4619a9 100644 --- a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooProviderBootstrap.java +++ b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooProviderBootstrap.java @@ -37,15 +37,11 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext */ public class FooProviderBootstrap { - private static final String INTERFACE_RES_KEY = FooService.class.getName(); - private static final String RES_KEY = INTERFACE_RES_KEY + ":sayHello(java.lang.String)"; - public static void main(String[] args) { // Users don't need to manually call this method. // Only for eager initialization. InitExecutor.doInit(); - initFlowRule(); AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(); context.register(ProviderConfiguration.class); @@ -54,10 +50,4 @@ public class FooProviderBootstrap { System.out.println("Service provider is ready"); } - private static void initFlowRule() { - FlowRule flowRule = new FlowRule(INTERFACE_RES_KEY) - .setCount(10) - .setGrade(RuleConstant.FLOW_GRADE_QPS); - FlowRuleManager.loadRules(Collections.singletonList(flowRule)); - } } diff --git a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooService.java b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooService.java index dcdabc8c..1ccff084 100644 --- a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooService.java +++ b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/FooService.java @@ -23,4 +23,6 @@ public interface FooService { String sayHello(String name); String doAnother(); + + String exceptionTest(boolean biz, boolean timeout); } diff --git a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/consumer/FooServiceConsumer.java b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/consumer/FooServiceConsumer.java index f83d98a0..5a224d5b 100644 --- a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/consumer/FooServiceConsumer.java +++ b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/consumer/FooServiceConsumer.java @@ -16,7 +16,6 @@ package com.alibaba.csp.sentinel.demo.apache.dubbo.consumer; import com.alibaba.csp.sentinel.demo.apache.dubbo.FooService; - import org.apache.dubbo.config.annotation.Reference; /** @@ -24,7 +23,7 @@ import org.apache.dubbo.config.annotation.Reference; */ public class FooServiceConsumer { - @Reference(url = "dubbo://127.0.0.1:25758", timeout = 3000) + @Reference(url = "dubbo://127.0.0.1:25758", timeout = 500) private FooService fooService; public String sayHello(String name) { @@ -34,4 +33,8 @@ public class FooServiceConsumer { public String doAnother() { return fooService.doAnother(); } + + public String exceptionTest(boolean biz, boolean timeout) { + return fooService.exceptionTest(biz, timeout); + } } diff --git a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/provider/FooServiceImpl.java b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/provider/FooServiceImpl.java index bd92567a..f73184b0 100644 --- a/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/provider/FooServiceImpl.java +++ b/sentinel-demo/sentinel-demo-apache-dubbo/src/main/java/com/alibaba/csp/sentinel/demo/apache/dubbo/provider/FooServiceImpl.java @@ -15,12 +15,11 @@ */ package com.alibaba.csp.sentinel.demo.apache.dubbo.provider; -import java.time.LocalDateTime; - import com.alibaba.csp.sentinel.demo.apache.dubbo.FooService; - import org.apache.dubbo.config.annotation.Service; +import java.time.LocalDateTime; + /** * @author Eric Zhao */ @@ -36,4 +35,20 @@ public class FooServiceImpl implements FooService { public String doAnother() { return LocalDateTime.now().toString(); } + + @Override + public String exceptionTest(boolean biz, boolean timeout) { + if (biz) { + throw new RuntimeException("biz exception"); + } + if (timeout) { + try { + Thread.sleep(2000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + return "Success"; + } + }