This commit is contained in:
shuhongfan
2023-09-04 16:40:17 +08:00
commit cf5ac25c14
8267 changed files with 1305066 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# Sentinel SOFARPC Adapter
Sentinel SOFARPC Adapter provides service provider filter and consumer filter
for [SOFARPC](https://www.sofastack.tech/projects/sofa-rpc) services.
**Note: This adapter supports SOFARPC 5.4.x version and above, and 5.6.x is officially recommended.**
To use Sentinel SOFARPC Adapter, you can simply add the following dependency to your `pom.xml`:
```xml
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-sofa-rpc-adapter</artifactId>
<version>x.y.z</version>
</dependency>
```
The Sentinel filters are **enabled by default**. Once you add the dependency,
the SOFARPC 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-sofa-rpc](https://github.com/alibaba/Sentinel/tree/master/sentinel-demo/sentinel-demo-sofa-rpc).
If you don't want the filters enabled, you can manually disable them. For example:
```java
providerConfig.setParameter("sofa.rpc.sentinel.enabled", "false");
consumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false");
```
or add setting in `rpc-config.json` file, and its priority is lower than above.
```json
{
"sofa.rpc.sentinel.enabled": true
}
```
For more details of SOFARPC filter, see [here](https://www.sofastack.tech/projects/sofa-rpc/custom-filter/).
## SOFARPC resources
The resource for SOFARPC services has two granularities: service interface and service method.
- Service interfaceresourceName format is `interfaceName`e.g. `com.alibaba.csp.sentinel.demo.sofa.rpc.DemoService`
- Service methodresourceName format is `interfaceName#methodSignature`e.g. `com.alibaba.csp.sentinel.demo.sofa.rpc.DemoService#sayHello(java.lang.Integer,java.lang.String,int)`
## Flow control based on caller
In many circumstances, it's also significant to control traffic flow based on the **caller**.
For example, assuming that there are two services A and B, both of them initiate remote call requests to the service provider.
If we want to limit the calls from service B only, we can set the `limitApp` of flow rule as the identifier of service B (e.g. service name).
Sentinel SOFARPC Adapter will automatically resolve the SOFARPC consumer's *application name* as the caller's name (`origin`),
and will bring the caller's name when doing resource protection.
If `limitApp` of flow rules is not configured (`default`), flow control will take effects on all callers.
If `limitApp` of a flow rule is configured with a caller, then the corresponding flow rule will only take effect on the specific caller.
## Global fallback
Sentinel SOFARPC 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 `SofaRpcFallback` interface
and then register to `SofaRpcFallbackRegistry`. If no fallback is configured, Sentinel will wrap the `BlockException`
then directly throw it out.

View File

@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>sentinel-adapter</artifactId>
<groupId>com.alibaba.csp</groupId>
<version>1.8.3</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>sentinel-sofa-rpc-adapter</artifactId>
<properties>
<sofa-rpc-all.version>5.6.4</sofa-rpc-all.version>
</properties>
<dependencies>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-core</artifactId>
</dependency>
<dependency>
<groupId>com.alipay.sofa</groupId>
<artifactId>sofa-rpc-all</artifactId>
<version>${sofa-rpc-all.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -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.adapter.sofa.rpc;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.Tracer;
import com.alipay.sofa.rpc.common.RpcConfigs;
import com.alipay.sofa.rpc.common.utils.StringUtils;
import com.alipay.sofa.rpc.config.AbstractInterfaceConfig;
import com.alipay.sofa.rpc.core.exception.RpcErrorType;
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
import com.alipay.sofa.rpc.core.response.SofaResponse;
import com.alipay.sofa.rpc.filter.Filter;
import com.alipay.sofa.rpc.filter.FilterInvoker;
/**
* @author cdfive
*/
abstract class AbstractSofaRpcFilter extends Filter {
@Override
public boolean needToLoad(FilterInvoker invoker) {
AbstractInterfaceConfig<?, ?> config = invoker.getConfig();
String enabled = config.getParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED);
if (StringUtils.isNotBlank(enabled)) {
return Boolean.parseBoolean(enabled);
}
return RpcConfigs.getOrDefaultValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, true);
}
protected void traceResponseException(SofaResponse response, Entry interfaceEntry, Entry methodEntry) {
if (response.isError()) {
SofaRpcException rpcException = new SofaRpcException(RpcErrorType.SERVER_FILTER, response.getErrorMsg());
Tracer.traceEntry(rpcException, interfaceEntry);
Tracer.traceEntry(rpcException, methodEntry);
} else {
Object appResponse = response.getAppResponse();
if (appResponse instanceof Throwable) {
Tracer.traceEntry((Throwable) appResponse, interfaceEntry);
Tracer.traceEntry((Throwable) appResponse, methodEntry);
}
}
}
protected SofaRpcException traceOtherException(Throwable t, Entry interfaceEntry, Entry methodEntry) {
SofaRpcException rpcException;
if (t instanceof SofaRpcException) {
rpcException = (SofaRpcException) t;
} else {
rpcException = new SofaRpcException(RpcErrorType.SERVER_FILTER, t);
}
Tracer.traceEntry(rpcException, interfaceEntry);
Tracer.traceEntry(rpcException, methodEntry);
return rpcException;
}
}

View File

@@ -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.sofa.rpc;
/**
* @author cdfive
* @since 1.7.2
*/
public final class SentinelConstants {
public static final String SOFA_RPC_SENTINEL_ENABLED = "sofa.rpc.sentinel.enabled";
private SentinelConstants() {}
}

View File

@@ -0,0 +1,84 @@
/*
* 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.sofa.rpc;
import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback.SofaRpcFallbackRegistry;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alipay.sofa.rpc.common.RpcConstants;
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
import com.alipay.sofa.rpc.core.request.SofaRequest;
import com.alipay.sofa.rpc.core.response.SofaResponse;
import com.alipay.sofa.rpc.ext.Extension;
import com.alipay.sofa.rpc.filter.AutoActive;
import com.alipay.sofa.rpc.filter.FilterInvoker;
import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getInterfaceResourceName;
import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getMethodResourceName;
import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getMethodArguments;
/**
* SOFARPC service consumer filter for Sentinel, auto activated by default.
*
* If you want to disable the consumer filter, you can configure:
* <pre>ConsumerConfig.setParameter("sofa.rpc.sentinel.enabled", "false");</pre>
*
* or add setting in rpc-config.json:
* <pre>"sofa.rpc.sentinel.enabled": false </pre>
*
* @author cdfive
*/
@Extension(value = "consumerSentinel", order = -1000)
@AutoActive(consumerSide = true)
public class SentinelSofaRpcConsumerFilter extends AbstractSofaRpcFilter {
@Override
public SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException {
// Now only support sync invoke.
if (request.getInvokeType() != null && !RpcConstants.INVOKER_TYPE_SYNC.equals(request.getInvokeType())) {
return invoker.invoke(request);
}
String interfaceResourceName = getInterfaceResourceName(request);
String methodResourceName = getMethodResourceName(request);
Entry interfaceEntry = null;
Entry methodEntry = null;
try {
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.OUT);
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC,
EntryType.OUT, getMethodArguments(request));
SofaResponse response = invoker.invoke(request);
traceResponseException(response, interfaceEntry, methodEntry);
return response;
} catch (BlockException e) {
return SofaRpcFallbackRegistry.getConsumerFallback().handle(invoker, request, e);
} catch (Throwable t) {
throw traceOtherException(t, interfaceEntry, methodEntry);
} finally {
if (methodEntry != null) {
methodEntry.exit(1, getMethodArguments(request));
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
}
}
}

View File

@@ -0,0 +1,93 @@
/*
* 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.sofa.rpc;
import com.alibaba.csp.sentinel.*;
import com.alibaba.csp.sentinel.adapter.sofa.rpc.fallback.SofaRpcFallbackRegistry;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alipay.sofa.rpc.common.RpcConstants;
import com.alipay.sofa.rpc.core.exception.SofaRpcException;
import com.alipay.sofa.rpc.core.request.SofaRequest;
import com.alipay.sofa.rpc.core.response.SofaResponse;
import com.alipay.sofa.rpc.ext.Extension;
import com.alipay.sofa.rpc.filter.AutoActive;
import com.alipay.sofa.rpc.filter.FilterInvoker;
import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getApplicationName;
import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getInterfaceResourceName;
import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getMethodResourceName;
import static com.alibaba.csp.sentinel.adapter.sofa.rpc.SofaRpcUtils.getMethodArguments;
/**
* SOFARPC service provider filter for Sentinel, auto activated by default.
*
* If you want to disable the provider filter, you can configure:
* <pre>ProviderConfig.setParameter("sofa.rpc.sentinel.enabled", "false");</pre>
*
* or add setting in rpc-config.json file:
* <pre>
* {
* "sofa.rpc.sentinel.enabled": false
* }
* </pre>
*
* @author cdfive
*/
@Extension(value = "providerSentinel", order = -1000)
@AutoActive(providerSide = true)
public class SentinelSofaRpcProviderFilter extends AbstractSofaRpcFilter {
@Override
public SofaResponse invoke(FilterInvoker invoker, SofaRequest request) throws SofaRpcException {
// Now only support sync invoke.
if (request.getInvokeType() != null && !RpcConstants.INVOKER_TYPE_SYNC.equals(request.getInvokeType())) {
return invoker.invoke(request);
}
String callerApp = getApplicationName(request);
String interfaceResourceName = getInterfaceResourceName(request);
String methodResourceName = getMethodResourceName(request);
Entry interfaceEntry = null;
Entry methodEntry = null;
try {
ContextUtil.enter(methodResourceName, callerApp);
interfaceEntry = SphU.entry(interfaceResourceName, ResourceTypeConstants.COMMON_RPC, EntryType.IN);
methodEntry = SphU.entry(methodResourceName, ResourceTypeConstants.COMMON_RPC,
EntryType.IN, getMethodArguments(request));
SofaResponse response = invoker.invoke(request);
traceResponseException(response, interfaceEntry, methodEntry);
return response;
} catch (BlockException e) {
return SofaRpcFallbackRegistry.getProviderFallback().handle(invoker, request, e);
} catch (Throwable t) {
throw traceOtherException(t, interfaceEntry, methodEntry);
} finally {
if (methodEntry != null) {
methodEntry.exit(1, getMethodArguments(request));
}
if (interfaceEntry != null) {
interfaceEntry.exit();
}
ContextUtil.exit();
}
}
}

View File

@@ -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.sofa.rpc;
import com.alipay.sofa.rpc.common.RemotingConstants;
import com.alipay.sofa.rpc.core.request.SofaRequest;
/**
* @author cdfive
*/
public class SofaRpcUtils {
public static String getApplicationName(SofaRequest request) {
String appName = (String) request.getRequestProp(RemotingConstants.HEAD_APP_NAME);
return appName == null ? "" : appName;
}
public static String getInterfaceResourceName(SofaRequest request) {
return request.getInterfaceName();
}
public static String getMethodResourceName(SofaRequest request) {
StringBuilder buf = new StringBuilder(64);
buf.append(request.getInterfaceName())
.append("#")
.append(request.getMethodName())
.append("(");
boolean isFirst = true;
for (String methodArgSig : request.getMethodArgSigs()) {
if (!isFirst) {
buf.append(",");
} else {
isFirst = false;
}
buf.append(methodArgSig);
}
buf.append(")");
return buf.toString();
}
public static Object[] getMethodArguments(SofaRequest request) {
return request.getMethodArgs();
}
private SofaRpcUtils() {}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.sofa.rpc.fallback;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
import com.alipay.sofa.rpc.core.request.SofaRequest;
import com.alipay.sofa.rpc.core.response.SofaResponse;
import com.alipay.sofa.rpc.filter.FilterInvoker;
/**
* Default Sentinel fallback handler for SOFARPC services.
* Just wrap and throw the exception.
*
* @author cdfive
*/
public class DefaultSofaRpcFallback implements SofaRpcFallback {
@Override
public SofaResponse handle(FilterInvoker invoker, SofaRequest request, BlockException ex) {
// Just wrap and throw the exception.
throw new SentinelRpcException(ex);
}
}

View File

@@ -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.sofa.rpc.fallback;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alipay.sofa.rpc.core.request.SofaRequest;
import com.alipay.sofa.rpc.core.response.SofaResponse;
import com.alipay.sofa.rpc.filter.FilterInvoker;
/**
* Sentinel fallback handler for SOFARPC services.
*
* @author cdfive
*/
public interface SofaRpcFallback {
/**
* Handle the block exception and provide fallback result.
*
* @param invoker FilterInvoker
* @param request SofaRequest
* @param ex block exception
* @return fallback result
*/
SofaResponse handle(FilterInvoker invoker, SofaRequest request, BlockException ex);
}

View File

@@ -0,0 +1,50 @@
/*
* 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.sofa.rpc.fallback;
import com.alibaba.csp.sentinel.util.AssertUtil;
/**
* Global Sentinel fallback registry for SOFARPC services.
*
* @author cdfive
*/
public final class SofaRpcFallbackRegistry {
private static volatile SofaRpcFallback providerFallback = new DefaultSofaRpcFallback();
private static volatile SofaRpcFallback consumerFallback = new DefaultSofaRpcFallback();
public static SofaRpcFallback getProviderFallback() {
return providerFallback;
}
public static void setProviderFallback(SofaRpcFallback providerFallback) {
AssertUtil.notNull(providerFallback, "providerFallback cannot be null");
SofaRpcFallbackRegistry.providerFallback = providerFallback;
}
public static SofaRpcFallback getConsumerFallback() {
return consumerFallback;
}
public static void setConsumerFallback(SofaRpcFallback consumerFallback) {
AssertUtil.notNull(consumerFallback, "consumerFallback cannot be null");
SofaRpcFallbackRegistry.consumerFallback = consumerFallback;
}
private SofaRpcFallbackRegistry() {}
}

View File

@@ -0,0 +1,3 @@
# name # order
com.alibaba.csp.sentinel.adapter.sofa.rpc.SentinelSofaRpcProviderFilter # -1000
com.alibaba.csp.sentinel.adapter.sofa.rpc.SentinelSofaRpcConsumerFilter # -1000

View File

@@ -0,0 +1,108 @@
package com.alibaba.csp.sentinel.adapter.sofa.rpc;
import com.alipay.sofa.rpc.codec.Serializer;
import com.alipay.sofa.rpc.common.RpcConfigs;
import com.alipay.sofa.rpc.config.ConsumerConfig;
import com.alipay.sofa.rpc.config.ProviderConfig;
import com.alipay.sofa.rpc.filter.FilterInvoker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.lang.reflect.Method;
import static org.junit.Assert.*;
/**
* Test cases for {@link AbstractSofaRpcFilter}.
*
* @author cdfive
*/
public class AbstractSofaRpcFilterTest {
@Before
public void setUp() {
removeRpcConfig(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED);
}
@After
public void cleanUp() {
removeRpcConfig(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED);
}
@Test
public void testNeedToLoadProvider() {
SentinelSofaRpcProviderFilter providerFilter = new SentinelSofaRpcProviderFilter();
ProviderConfig providerConfig = new ProviderConfig();
providerConfig.setInterfaceId(Serializer.class.getName());
providerConfig.setId("AAA");
FilterInvoker invoker = new FilterInvoker(null, null, providerConfig);
assertTrue(providerFilter.needToLoad(invoker));
providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
assertFalse(providerFilter.needToLoad(invoker));
providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "");
assertTrue(providerFilter.needToLoad(invoker));
RpcConfigs.putValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
assertFalse(providerFilter.needToLoad(invoker));
}
@Test
public void testNeedToLoadConsumer() {
SentinelSofaRpcConsumerFilter consumerFilter = new SentinelSofaRpcConsumerFilter();
ConsumerConfig consumerConfig = new ConsumerConfig();
consumerConfig.setInterfaceId(Serializer.class.getName());
consumerConfig.setId("BBB");
FilterInvoker invoker = new FilterInvoker(null, null, consumerConfig);
assertTrue(consumerFilter.needToLoad(invoker));
consumerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
assertFalse(consumerFilter.needToLoad(invoker));
consumerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "");
assertTrue(consumerFilter.needToLoad(invoker));
RpcConfigs.putValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
assertFalse(consumerFilter.needToLoad(invoker));
}
@Test
public void testNeedToLoadProviderAndConsumer() {
SentinelSofaRpcProviderFilter providerFilter = new SentinelSofaRpcProviderFilter();
ProviderConfig providerConfig = new ProviderConfig();
providerConfig.setInterfaceId(Serializer.class.getName());
providerConfig.setId("AAA");
FilterInvoker providerInvoker = new FilterInvoker(null, null, providerConfig);
assertTrue(providerFilter.needToLoad(providerInvoker));
SentinelSofaRpcConsumerFilter consumerFilter = new SentinelSofaRpcConsumerFilter();
ConsumerConfig consumerConfig = new ConsumerConfig();
consumerConfig.setInterfaceId(Serializer.class.getName());
consumerConfig.setId("BBB");
FilterInvoker consumerInvoker = new FilterInvoker(null, null, consumerConfig);
assertTrue(consumerFilter.needToLoad(consumerInvoker));
providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
assertFalse(providerFilter.needToLoad(providerInvoker));
assertTrue(consumerFilter.needToLoad(consumerInvoker));
providerConfig.setParameter(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "");
assertTrue(providerFilter.needToLoad(providerInvoker));
RpcConfigs.putValue(SentinelConstants.SOFA_RPC_SENTINEL_ENABLED, "false");
assertFalse(providerFilter.needToLoad(providerInvoker));
assertFalse(consumerFilter.needToLoad(consumerInvoker));
}
private void removeRpcConfig(String key) {
try {
Method removeValueMethod = RpcConfigs.class.getDeclaredMethod("removeValue", String.class);
removeValueMethod.setAccessible(true);
removeValueMethod.invoke(null, key);
} catch (Exception e) {
// Empty
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* 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.sofa.rpc;
import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.CtSph;
import com.alibaba.csp.sentinel.context.Context;
import com.alibaba.csp.sentinel.context.ContextUtil;
import com.alibaba.csp.sentinel.slots.clusterbuilder.ClusterBuilderSlot;
import java.lang.reflect.Method;
/**
* Base test class, provide common methods for sub test class.
*
* Note: Only for test. DO NOT USE IN PRODUCTION!
*
* @author cdfive
*/
public class BaseTest {
/**
* Clean up resources.
*/
protected static void cleanUpAll() {
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
}
}
}

View File

@@ -0,0 +1,155 @@
/*
* 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.sofa.rpc;
import com.alibaba.csp.sentinel.Constants;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
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.DefaultNode;
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.node.StatisticNode;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alipay.sofa.rpc.common.RpcConstants;
import com.alipay.sofa.rpc.core.request.SofaRequest;
import com.alipay.sofa.rpc.core.response.SofaResponse;
import com.alipay.sofa.rpc.filter.FilterInvoker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Test cases for {@link SentinelSofaRpcConsumerFilter}.
*
* @author cdfive
*/
public class SentinelSofaRpcConsumerFilterTest extends BaseTest {
@Before
public void setUp() {
cleanUpAll();
}
@After
public void cleanUp() {
cleanUpAll();
}
@Test
public void testInvokeSentinelWorks() {
SentinelSofaRpcConsumerFilter filter = new SentinelSofaRpcConsumerFilter();
final String interfaceResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService";
final String methodResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService#sayHello(java.lang.String,int)";
SofaRequest request = mock(SofaRequest.class);
when(request.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_SYNC);
when(request.getInterfaceName()).thenReturn(interfaceResourceName);
when(request.getMethodName()).thenReturn("sayHello");
when(request.getMethodArgSigs()).thenReturn(new String[]{"java.lang.String", "int"});
when(request.getMethodArgs()).thenReturn(new Object[]{"Sentinel", 2020});
FilterInvoker filterInvoker = mock(FilterInvoker.class);
when(filterInvoker.invoke(request)).thenAnswer(new Answer<SofaResponse>() {
@Override
public SofaResponse answer(InvocationOnMock invocationOnMock) throws Throwable {
verifyInvocationStructure(interfaceResourceName, methodResourceName);
SofaResponse response = new SofaResponse();
response.setAppResponse("Hello Sentinel 2020");
return response;
}
});
// Before invoke
assertNull(ContextUtil.getContext());
// Do invoke
SofaResponse response = filter.invoke(filterInvoker, request);
assertEquals("Hello Sentinel 2020", response.getAppResponse());
verify(filterInvoker).invoke(request);
// After invoke, make sure exit context
assertNull(ContextUtil.getContext());
}
/**
* Verify Sentinel invocation structure in memory:
* EntranceNode(defaultContextName)
* --InterfaceNode(interfaceName)
* ----MethodNode(resourceName)
*/
private void verifyInvocationStructure(String interfaceResourceName, String methodResourceName) {
Context context = ContextUtil.getContext();
assertNotNull(context);
// As not call ContextUtil.enter(methodResourceName, applicationName) in SentinelSofaRpcConsumerFilter, use default context
// In actual project, a consumer is usually also a provider, the context will be created by SentinelSofaRpcProviderFilter
// If consumer is on the top of SOFARPC invocation chain, use default context
assertEquals(Constants.CONTEXT_DEFAULT_NAME, context.getName());
assertEquals("", context.getOrigin());
DefaultNode entranceNode = context.getEntranceNode();
ResourceWrapper entranceResource = entranceNode.getId();
assertEquals(Constants.CONTEXT_DEFAULT_NAME, entranceResource.getName());
assertSame(EntryType.IN, entranceResource.getEntryType());
// As SphU.entry(interfaceResourceName, EntryType.OUT);
Set<Node> childList = entranceNode.getChildList();
assertEquals(1, childList.size());
DefaultNode interfaceNode = (DefaultNode) childList.iterator().next();
ResourceWrapper interfaceResource = interfaceNode.getId();
assertEquals(interfaceResourceName, interfaceResource.getName());
assertSame(EntryType.OUT, interfaceResource.getEntryType());
// As SphU.entry(methodResourceName, EntryType.OUT);
childList = interfaceNode.getChildList();
assertEquals(1, childList.size());
DefaultNode methodNode = (DefaultNode) childList.iterator().next();
ResourceWrapper methodResource = methodNode.getId();
assertEquals(methodResourceName, methodResource.getName());
assertSame(EntryType.OUT, methodResource.getEntryType());
// Verify curEntry
Entry curEntry = context.getCurEntry();
assertSame(methodNode, curEntry.getCurNode());
assertSame(interfaceNode, curEntry.getLastNode());
// As context origin is not "", no originNode should be created in curEntry
assertNull(curEntry.getOriginNode());
// Verify clusterNode
ClusterNode methodClusterNode = methodNode.getClusterNode();
ClusterNode interfaceClusterNode = interfaceNode.getClusterNode();
// Different resource->Different ProcessorSlot->Different ClusterNode
assertNotSame(methodClusterNode, interfaceClusterNode);
// As context origin is "", the StatisticNode should not be created in originCountMap of ClusterNode
Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap();
assertEquals(0, methodOriginCountMap.size());
Map<String, StatisticNode> interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
assertEquals(0, interfaceOriginCountMap.size());
}
}

View File

@@ -0,0 +1,154 @@
/*
* 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.sofa.rpc;
import com.alibaba.csp.sentinel.Entry;
import com.alibaba.csp.sentinel.EntryType;
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.DefaultNode;
import com.alibaba.csp.sentinel.node.Node;
import com.alibaba.csp.sentinel.node.StatisticNode;
import com.alibaba.csp.sentinel.slotchain.ResourceWrapper;
import com.alipay.sofa.rpc.common.RpcConstants;
import com.alipay.sofa.rpc.core.request.SofaRequest;
import com.alipay.sofa.rpc.core.response.SofaResponse;
import com.alipay.sofa.rpc.filter.FilterInvoker;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Map;
import java.util.Set;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Test cases for {@link SentinelSofaRpcProviderFilter}.
*
* @author cdfive
*/
public class SentinelSofaRpcProviderFilterTest extends BaseTest {
@Before
public void setUp() {
cleanUpAll();
}
@After
public void cleanUp() {
cleanUpAll();
}
@Test
public void testInvokeSentinelWorks() {
SentinelSofaRpcProviderFilter filter = new SentinelSofaRpcProviderFilter();
final String applicationName = "demo-provider";
final String interfaceResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService";
final String methodResourceName = "com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService#sayHello(java.lang.String,int)";
SofaRequest request = mock(SofaRequest.class);
when(request.getRequestProp("app")).thenReturn(applicationName);
when(request.getInvokeType()).thenReturn(RpcConstants.INVOKER_TYPE_SYNC);
when(request.getInterfaceName()).thenReturn(interfaceResourceName);
when(request.getMethodName()).thenReturn("sayHello");
when(request.getMethodArgSigs()).thenReturn(new String[]{"java.lang.String", "int"});
when(request.getMethodArgs()).thenReturn(new Object[]{"Sentinel", 2020});
FilterInvoker filterInvoker = mock(FilterInvoker.class);
when(filterInvoker.invoke(request)).thenAnswer(new Answer<SofaResponse>() {
@Override
public SofaResponse answer(InvocationOnMock invocationOnMock) throws Throwable {
verifyInvocationStructure(applicationName, interfaceResourceName, methodResourceName);
SofaResponse response = new SofaResponse();
response.setAppResponse("Hello Sentinel 2020");
return response;
}
});
// Before invoke
assertNull(ContextUtil.getContext());
// Do invoke
SofaResponse response = filter.invoke(filterInvoker, request);
assertEquals("Hello Sentinel 2020", response.getAppResponse());
verify(filterInvoker).invoke(request);
// After invoke, make sure exit context
assertNull(ContextUtil.getContext());
}
/**
* Verify Sentinel invocation structure in memory:
* EntranceNode(methodResourceName)
* --InterfaceNode(interfaceResourceName)
* ----MethodNode(methodResourceName)
*/
private void verifyInvocationStructure(String applicationName, String interfaceResourceName, String methodResourceName) {
Context context = ContextUtil.getContext();
assertNotNull(context);
assertEquals(methodResourceName, context.getName());
assertEquals(applicationName, context.getOrigin());
DefaultNode entranceNode = context.getEntranceNode();
ResourceWrapper entranceResource = entranceNode.getId();
assertEquals(methodResourceName, entranceResource.getName());
assertSame(EntryType.IN, entranceResource.getEntryType());
// As SphU.entry(interfaceResourceName, EntryType.IN);
Set<Node> childList = entranceNode.getChildList();
assertEquals(1, childList.size());
DefaultNode interfaceNode = (DefaultNode) childList.iterator().next();
ResourceWrapper interfaceResource = interfaceNode.getId();
assertEquals(interfaceResourceName, interfaceResource.getName());
assertSame(EntryType.IN, interfaceResource.getEntryType());
// As SphU.entry(methodResourceName, EntryType.IN, 1, methodArguments);
childList = interfaceNode.getChildList();
assertEquals(1, childList.size());
DefaultNode methodNode = (DefaultNode) childList.iterator().next();
ResourceWrapper methodResource = methodNode.getId();
assertEquals(methodResourceName, methodResource.getName());
assertSame(EntryType.IN, methodResource.getEntryType());
// Verify curEntry
Entry curEntry = context.getCurEntry();
assertSame(methodNode, curEntry.getCurNode());
assertSame(interfaceNode, curEntry.getLastNode());
// As context origin is not "", originNode should be created
assertNotNull(curEntry.getOriginNode());
// Verify clusterNode
ClusterNode methodClusterNode = methodNode.getClusterNode();
ClusterNode interfaceClusterNode = interfaceNode.getClusterNode();
// Different resource->Different ProcessorSlot->Different ClusterNode
assertNotSame(methodClusterNode, interfaceClusterNode);
// As context origin is not "", the StatisticNode should be created in originCountMap of ClusterNode
Map<String, StatisticNode> methodOriginCountMap = methodClusterNode.getOriginCountMap();
assertEquals(1, methodOriginCountMap.size());
assertTrue(methodOriginCountMap.containsKey(applicationName));
Map<String, StatisticNode> interfaceOriginCountMap = interfaceClusterNode.getOriginCountMap();
assertEquals(1, interfaceOriginCountMap.size());
assertTrue(interfaceOriginCountMap.containsKey(applicationName));
}
}

View File

@@ -0,0 +1,68 @@
/*
* 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.sofa.rpc;
import com.alipay.sofa.rpc.core.request.SofaRequest;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test cases for {@link SofaRpcUtils}.
*
* @author cdfive
*/
public class SofaRpcUtilsTest {
@Test
public void testGetApplicationName() {
SofaRequest request = new SofaRequest();
String applicationName = SofaRpcUtils.getApplicationName(request);
assertEquals("", applicationName);
request.addRequestProp("app", "test-app");
applicationName = SofaRpcUtils.getApplicationName(request);
assertEquals("test-app", applicationName);
}
@Test
public void testGetInterfaceResourceName() {
SofaRequest request = new SofaRequest();
request.setInterfaceName("com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService");
String interfaceResourceName = SofaRpcUtils.getInterfaceResourceName(request);
assertEquals("com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService", interfaceResourceName);
}
@Test
public void testGetMethodResourceName() {
SofaRequest request = new SofaRequest();
request.setInterfaceName("com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService");
request.setMethodName("sayHello");
request.setMethodArgSigs(new String[]{"java.lang.String", "int"});
String methodResourceName = SofaRpcUtils.getMethodResourceName(request);
assertEquals("com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService#sayHello(java.lang.String,int)", methodResourceName);
}
@Test
public void testGetMethodArguments() {
SofaRequest request = new SofaRequest();
request.setMethodArgs(new Object[]{"Sentinel", 2020});
Object[] arguments = SofaRpcUtils.getMethodArguments(request);
assertEquals(arguments.length, 2);
assertEquals("Sentinel", arguments[0]);
assertEquals(2020, arguments[1]);
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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.sofa.rpc.fallback;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.csp.sentinel.slots.block.SentinelRpcException;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
/**
* Test cases for {@link DefaultSofaRpcFallback}.
*
* @author cdfive
*/
public class DefaultSofaRpcFallbackTest {
@Test
public void testHandle() {
SofaRpcFallback sofaRpcFallback = new DefaultSofaRpcFallback();
BlockException blockException = mock(BlockException.class);
boolean throwSentinelRpcException = false;
boolean causeIsBlockException = false;
try {
sofaRpcFallback.handle(null, null, blockException);
} catch (Exception e) {
throwSentinelRpcException = e instanceof SentinelRpcException;
causeIsBlockException = e.getCause() instanceof BlockException;
}
assertTrue(throwSentinelRpcException);
assertTrue(causeIsBlockException);
}
}

View File

@@ -0,0 +1,74 @@
/*
* 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.sofa.rpc.fallback;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alipay.sofa.rpc.core.request.SofaRequest;
import com.alipay.sofa.rpc.core.response.SofaResponse;
import com.alipay.sofa.rpc.filter.FilterInvoker;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Test cases for {@link SofaRpcFallbackRegistry}.
*
* @author cdfive
*/
public class SofaRpcFallbackRegistryTest {
@Test
public void testDefaultfallback() {
// Test get default provider fallback
SofaRpcFallback providerFallback = SofaRpcFallbackRegistry.getProviderFallback();
assertNotNull(providerFallback);
assertTrue(providerFallback instanceof DefaultSofaRpcFallback);
// Test get default consumer fallback
SofaRpcFallback consumerFallback = SofaRpcFallbackRegistry.getConsumerFallback();
assertNotNull(consumerFallback);
assertTrue(consumerFallback instanceof DefaultSofaRpcFallback);
}
@Test
public void testCustomFallback() {
// Test invoke custom provider fallback
SofaRpcFallbackRegistry.setProviderFallback(new SofaRpcFallback() {
@Override
public SofaResponse handle(FilterInvoker invoker, SofaRequest request, BlockException ex) {
SofaResponse response = new SofaResponse();
response.setAppResponse("test provider response");
return response;
}
});
SofaResponse providerResponse = SofaRpcFallbackRegistry.getProviderFallback().handle(null, null, null);
assertNotNull(providerResponse);
assertEquals("test provider response", providerResponse.getAppResponse());
// Test invoke custom consumer fallback
SofaRpcFallbackRegistry.setConsumerFallback(new SofaRpcFallback() {
@Override
public SofaResponse handle(FilterInvoker invoker, SofaRequest request, BlockException ex) {
SofaResponse response = new SofaResponse();
response.setAppResponse("test consumer response");
return response;
}
});
SofaResponse consumerResponse = SofaRpcFallbackRegistry.getConsumerFallback().handle(null, null, null);
assertNotNull(consumerResponse);
assertEquals("test consumer response", consumerResponse.getAppResponse());
}
}

View File

@@ -0,0 +1,24 @@
/*
* 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.sofa.rpc.service;
/**
* @author cdfive
*/
public interface DemoService {
String sayHello(String name, int year);
}

View File

@@ -0,0 +1,29 @@
/*
* 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.sofa.rpc.service.impl;
import com.alibaba.csp.sentinel.adapter.sofa.rpc.service.DemoService;
/**
* @author cdfive
*/
public class DemoServiceImpl implements DemoService {
@Override
public String sayHello(String name, int year) {
return "Hello " + name + " " + year;
}
}