diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/registry/AddressListener.java b/dubbo-cluster/src/main/java/org/apache/dubbo/registry/AddressListener.java index ff7780032be..684cb647324 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/registry/AddressListener.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/registry/AddressListener.java @@ -28,10 +28,14 @@ public interface AddressListener { /** * processing when receiving the address list * - * @param addresses provider address list + * @param addresses provider address list * @param consumerUrl * @param registryDirectory */ List notify(List addresses, URL consumerUrl, Directory registryDirectory); + default void destroy(URL consumerUrl, Directory registryDirectory) { + + } + } \ No newline at end of file diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Router.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Router.java index fadefdeae0f..c412ff8f238 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Router.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/Router.java @@ -89,6 +89,10 @@ default void notify(List> invokers) { */ int getPriority(); + default void stop() { + //do nothing by default + } + @Override default int compareTo(Router o) { if (o == null) { diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterChain.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterChain.java index e69a90ad201..a16b2aa2564 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterChain.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/RouterChain.java @@ -18,6 +18,8 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.rpc.Invocation; import org.apache.dubbo.rpc.Invoker; @@ -31,6 +33,7 @@ * Router chain */ public class RouterChain { + public static final Logger LOGGER = LoggerFactory.getLogger(RouterChain.class); // full list of addresses from registry, classified by method name. private List> invokers = Collections.emptyList(); @@ -113,4 +116,17 @@ public void setInvokers(List> invokers) { this.invokers = (invokers == null ? Collections.emptyList() : invokers); routers.forEach(router -> router.notify(this.invokers)); } + + public void destroy() { + invokers = Collections.emptyList(); + for (Router router : routers) { + try { + router.stop(); + } catch (Exception e) { + LOGGER.error("Error trying to stop router " + router.getClass(), e); + } + } + routers = Collections.emptyList(); + builtinRouters = Collections.emptyList(); + } } diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java index a3514c1dcc9..9336c069b78 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/condition/config/ListenableRouter.java @@ -57,8 +57,8 @@ public ListenableRouter(URL url, String ruleKey) { @Override public synchronized void process(ConfigChangedEvent event) { - if (logger.isInfoEnabled()) { - logger.info("Notification of condition rule, change type is: " + event.getChangeType() + + if (logger.isDebugEnabled()) { + logger.debug("Notification of condition rule, change type is: " + event.getChangeType() + ", raw rule is:\n " + event.getContent()); } diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshAppRuleListener.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshAppRuleListener.java index 3946ba4016f..838c7ef7533 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshAppRuleListener.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshAppRuleListener.java @@ -25,6 +25,7 @@ import org.apache.dubbo.rpc.cluster.router.mesh.rule.destination.DestinationRule; import org.apache.dubbo.rpc.cluster.router.mesh.rule.virtualservice.VirtualServiceRule; import org.apache.dubbo.rpc.cluster.router.mesh.util.VsDestinationGroupRuleDispatcher; + import org.yaml.snakeyaml.Yaml; import java.text.MessageFormat; @@ -37,7 +38,7 @@ public class MeshAppRuleListener implements ConfigurationListener { private final VsDestinationGroupRuleDispatcher vsDestinationGroupRuleDispatcher = new VsDestinationGroupRuleDispatcher(); - private String appName; + private final String appName; private VsDestinationGroup vsDestinationGroupHolder; @@ -46,8 +47,10 @@ public MeshAppRuleListener(String appName) { } public void receiveConfigInfo(String configInfo) { - logger.info(MessageFormat.format("[MeshAppRule] Received rule for app [{0}]: {1}.", - appName, configInfo)); + if(logger.isDebugEnabled()) { + logger.debug(MessageFormat.format("[MeshAppRule] Received rule for app [{0}]: {1}.", + appName, configInfo)); + } try { VsDestinationGroup vsDestinationGroup = new VsDestinationGroup(); @@ -55,15 +58,15 @@ public void receiveConfigInfo(String configInfo) { Yaml yaml = new Yaml(); Yaml yaml2 = new Yaml(); - Iterable objectIterable = yaml.loadAll(configInfo); + Iterable objectIterable = yaml.loadAll(configInfo); for (Object result : objectIterable) { Map resultMap = (Map) result; - if (resultMap.get("kind").equals("DestinationRule")) { + if ("DestinationRule".equals(resultMap.get("kind"))) { DestinationRule destinationRule = yaml2.loadAs(yaml2.dump(result), DestinationRule.class); vsDestinationGroup.getDestinationRuleList().add(destinationRule); - } else if (resultMap.get("kind").equals("VirtualService")) { + } else if ("VirtualService".equals(resultMap.get("kind"))) { VirtualServiceRule virtualServiceRule = yaml2.loadAs(yaml2.dump(result), VirtualServiceRule.class); vsDestinationGroup.getVirtualServiceRuleList().add(virtualServiceRule); } @@ -86,7 +89,7 @@ public void register(MeshRuleRouter subscriber) { vsDestinationGroupRuleDispatcher.register(subscriber); } - // + public void unregister(MeshRuleRouter sub) { vsDestinationGroupRuleDispatcher.unregister(sub); } diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleAddressListenerInterceptor.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleAddressListenerInterceptor.java index d4cf551af6a..e1074563142 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleAddressListenerInterceptor.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleAddressListenerInterceptor.java @@ -28,18 +28,18 @@ @Activate(order = 670) public class MeshRuleAddressListenerInterceptor implements AddressListener { - private static final Object mark = new Object(); - private static ConcurrentHashMap appMap = new ConcurrentHashMap(); + private static final Object MARK = new Object(); + private static final ConcurrentHashMap APP_MAP = new ConcurrentHashMap<>(); @Override public List notify(List addresses, URL consumerUrl, Directory registryDirectory) { if (addresses != null && !addresses.isEmpty()) { - for (URL serviceURL : addresses) { + for (URL url : addresses) { - String app = serviceURL.getRemoteApplication(); + String app = url.getRemoteApplication(); if (app != null && !app.isEmpty()) { - if (appMap.putIfAbsent(app, mark) == null) { + if (APP_MAP.putIfAbsent(app, MARK) == null) { MeshRuleManager.subscribeAppRule(app); } } diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleManager.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleManager.java index aa1630f13cd..2b48943e9a2 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleManager.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleManager.java @@ -33,7 +33,7 @@ public final class MeshRuleManager { private static final String MESH_RULE_DATA_ID_SUFFIX = ".MESHAPPRULE"; private static final String GROUP = "DEFAULT_GROUP"; - private static ConcurrentHashMap appRuleListeners = new ConcurrentHashMap<>(); + private static final ConcurrentHashMap APP_RULE_LISTENERS = new ConcurrentHashMap<>(); public synchronized static void subscribeAppRule(String app) { @@ -57,11 +57,11 @@ public synchronized static void subscribeAppRule(String app) { } configuration.addListener(appRuleDataId, GROUP, meshAppRuleListener); - appRuleListeners.put(app, meshAppRuleListener); + APP_RULE_LISTENERS.put(app, meshAppRuleListener); } public static void register(String app, MeshRuleRouter subscriber) { - MeshAppRuleListener meshAppRuleListener = appRuleListeners.get(app); + MeshAppRuleListener meshAppRuleListener = APP_RULE_LISTENERS.get(app); if (meshAppRuleListener == null) { logger.warn("appRuleListener can't find when Router register"); return; @@ -70,7 +70,7 @@ public static void register(String app, MeshRuleRouter subscriber) { } public static void unregister(MeshRuleRouter subscriber) { - Collection listeners = appRuleListeners.values(); + Collection listeners = APP_RULE_LISTENERS.values(); for (MeshAppRuleListener listener : listeners) { listener.unregister(subscriber); } diff --git a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleRouter.java b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleRouter.java index 34b9e7f65ad..cd09996ed50 100644 --- a/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleRouter.java +++ b/dubbo-cluster/src/main/java/org/apache/dubbo/rpc/cluster/router/mesh/route/MeshRuleRouter.java @@ -53,7 +53,7 @@ public class MeshRuleRouter implements Router, VsDestinationGroupRuleListener { private VsDestinationGroup vsDestinationGroup; - private Map sourcesLables = new HashMap<>(); + private Map sourcesLabels = new HashMap<>(); protected List> invokerList = new ArrayList<>(); @@ -63,7 +63,7 @@ public class MeshRuleRouter implements Router, VsDestinationGroupRuleListener { public MeshRuleRouter(URL url) { this.url = url; - sourcesLables.putAll(url.getParameters()); + sourcesLabels.putAll(url.getParameters()); } @Override @@ -146,6 +146,7 @@ private void registerAppRule(List> invokers) { } + @Override public void onRuleChange(VsDestinationGroup vsDestinationGroup) { this.vsDestinationGroup = vsDestinationGroup; computeSubset(); @@ -236,7 +237,7 @@ protected DubboRouteDetail findMatchDubboRouteDetail(List dubb //FIXME to deal with headers for (DubboMatchRequest dubboMatchRequest : matchRequestList) { if (!DubboMatchRequest.isMatch(dubboMatchRequest, methodName, parameterTypeList, parameters, - sourcesLables, + sourcesLabels, new HashMap<>(), invocation.getAttachments(), new HashMap<>())) { match = false; @@ -320,22 +321,34 @@ protected boolean containMapKeyValue(Map originMap, Map sourcesLables) { - this.sourcesLables = sourcesLables; + /** + * just for test + * @param sourcesLabels + */ + protected void setSourcesLabels(Map sourcesLabels) { + this.sourcesLabels = sourcesLabels; } - // just for test + /** + * just for test + * @param invokerList + */ protected void setInvokerList(List> invokerList) { this.invokerList = invokerList; } - // just for test + /** + * just for test + * @param subsetMap + */ protected void setSubsetMap(Map>> subsetMap) { this.subsetMap = subsetMap; } @@ -345,8 +358,8 @@ public VsDestinationGroup getVsDestinationGroup() { return vsDestinationGroup; } - public Map getSourcesLables() { - return sourcesLables; + public Map getSourcesLabels() { + return sourcesLabels; } public List> getInvokerList() { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/BaseServiceMetadata.java b/dubbo-common/src/main/java/org/apache/dubbo/common/BaseServiceMetadata.java index 223fc978014..27893cd774a 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/BaseServiceMetadata.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/BaseServiceMetadata.java @@ -30,7 +30,11 @@ public class BaseServiceMetadata { protected volatile String group; public static String buildServiceKey(String path, String group, String version) { - StringBuilder buf = new StringBuilder(); + int length = path == null ? 0 : path.length(); + length += group == null ? 0 : group.length(); + length += version == null ? 0 : version.length(); + length += 3; + StringBuilder buf = new StringBuilder(length); if (group != null && group.length() > 0) { buf.append(group).append("/"); } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java index 485ce7c91fd..96383df57aa 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/URL.java @@ -177,7 +177,7 @@ public URL(String protocol, } this.urlAddress = new PathURLAddress(protocol, username, password, path, host, port); - this.urlParam = new URLParam(parameters); + this.urlParam = URLParam.parse(parameters); } protected URL(String protocol, @@ -194,7 +194,7 @@ protected URL(String protocol, } this.urlAddress = new PathURLAddress(protocol, username, password, path, host, port); - this.urlParam = new URLParam(parameters); + this.urlParam = URLParam.parse(parameters); } public static URL cacheableValueOf(String url) { @@ -469,10 +469,6 @@ public Map getParameters(Predicate nameToSelect) { return Collections.unmodifiableMap(selectedParameters); } - public Map> getMethodParameters() { - return urlParam.getMethodParameters(); - } - public String getParameterAndDecoded(String key) { return getParameterAndDecoded(key, null); } @@ -732,24 +728,11 @@ public String getMethodParameterAndDecoded(String method, String key, String def } public String getMethodParameter(String method, String key) { - Map keyMap = getMethodParameters().get(method); - String value = null; - if (keyMap != null) { - value = keyMap.get(key); - } - if (StringUtils.isEmpty(value)) { - value = urlParam.getParameter(key); - } - return value; + return urlParam.getMethodParameter(method, key); } public String getMethodParameterStrict(String method, String key) { - Map keyMap = getMethodParameters().get(method); - String value = null; - if (keyMap != null) { - value = keyMap.get(key); - } - return value; + return urlParam.getMethodParameterStrict(method, key); } public String getMethodParameter(String method, String key, String defaultValue) { @@ -936,20 +919,11 @@ public boolean hasMethodParameter(String method, String key) { } public String getAnyMethodParameter(String key) { - String suffix = "." + key; - for (String fullKey : getParameters().keySet()) { - if (fullKey.endsWith(suffix)) { - return getParameter(fullKey); - } - } - return null; + return urlParam.getAnyMethodParameter(key); } public boolean hasMethodParameter(String method) { - if (method == null) { - return false; - } - return getMethodParameters().containsKey(method); + return urlParam.hasMethodParameter(method); } public boolean isLocalHost() { diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceUnregisteredEvent.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/ConfigItem.java similarity index 52% rename from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceUnregisteredEvent.java rename to dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/ConfigItem.java index ef92db46f64..61d93055311 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceUnregisteredEvent.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/ConfigItem.java @@ -14,22 +14,45 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.registry.client.event; - -import org.apache.dubbo.registry.client.ServiceDiscovery; -import org.apache.dubbo.registry.client.ServiceInstance; +package org.apache.dubbo.common.config.configcenter; /** - * An event raised after a {@link ServiceInstance service instance} - * {@link ServiceDiscovery#unregister(ServiceInstance) unregistered} - * - * @see ServiceInstanceEvent - * @since 2.7.5 + * Hold content and version information */ -public class ServiceInstanceUnregisteredEvent extends ServiceInstanceEvent { +public class ConfigItem { + + /** + * configure item content. + */ + private String content; + /** + * version information corresponding to the current configure item. + */ + private Object stat; + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public Object getStat() { + return stat; + } + + public void setStat(Object stat) { + this.stat = stat; + } + + public ConfigItem(String content, Object stat) { + this.content = content; + this.stat = stat; + } + + public ConfigItem() { - public ServiceInstanceUnregisteredEvent(Object source, ServiceInstance serviceInstance) { - super(source, serviceInstance); } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java index 0b573966a1b..6e31d7fe6f2 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/config/configcenter/DynamicConfiguration.java @@ -100,6 +100,17 @@ default String getConfig(String key, String group) { return getConfig(key, group, getDefaultTimeout()); } + /** + * get configItem which contains content and stat info. + * @param key + * @param group + * @return + */ + default ConfigItem getConfigItem(String key, String group) { + String content = getConfig(key, group); + return new ConfigItem(content, null); + } + /** * Get the configuration mapped to the given key and the given group. If the * configuration fails to fetch after timeout exceeds, IllegalStateException will be thrown. @@ -156,6 +167,19 @@ default boolean publishConfig(String key, String group, String content) throws U return false; } + /** + * publish config mapped to this given key and given group with stat. + * @param key + * @param group + * @param content + * @param stat + * @return + * @throws UnsupportedOperationException + */ + default boolean publishConfigCas(String key, String group, String content, Object stat) throws UnsupportedOperationException { + return false; + } + /** * Get the config keys by the specified group * @@ -242,4 +266,13 @@ static String getRuleKey(URL url) { default boolean removeConfig(String key, String group) { return true; } + + /** + * support cas or not. + * + * @return + */ + default boolean hasSupportCas() { + return false; + } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java index 953b11b0b3a..02a97702340 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/constants/CommonConstants.java @@ -225,11 +225,17 @@ public interface CommonConstants { String $INVOKE_ASYNC = "$invokeAsync"; String GENERIC_PARAMETER_DESC = "Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/Object;"; + /** + * echo call + */ + String $ECHO = "$echo"; /** * package version in the manifest */ String RELEASE_KEY = "release"; + String PROTOBUF_MESSAGE_CLASS_NAME = "com.google.protobuf.Message"; + int MAX_PROXY_COUNT = 65535; String MONITOR_KEY = "monitor"; @@ -382,4 +388,5 @@ public interface CommonConstants { String CLUSTER_INTERCEPTOR_COMPATIBLE_KEY = "dubbo.application.cluster.interceptor.compatible"; + String UTF8ENCODE = "UTF-8"; } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java index 26489e0957a..629eaef77b9 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/extension/ExtensionLoader.java @@ -42,6 +42,8 @@ import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; @@ -97,7 +99,9 @@ public class ExtensionLoader { private final Holder>> cachedClasses = new Holder<>(); - private final Map cachedActivates = new ConcurrentHashMap<>(); + private final Map cachedActivates = Collections.synchronizedMap(new LinkedHashMap<>()); + private final Map> cachedActivateGroups = Collections.synchronizedMap(new LinkedHashMap<>()); + private final Map cachedActivateValues = Collections.synchronizedMap(new LinkedHashMap<>()); private final ConcurrentMap> cachedInstances = new ConcurrentHashMap<>(); private final Holder cachedAdaptiveInstance = new Holder<>(); private volatile Class cachedAdaptiveClass = null; @@ -261,29 +265,40 @@ public List getActivateExtension(URL url, String[] values, String group) { List activateExtensions = new ArrayList<>(); List names = values == null ? new ArrayList<>(0) : asList(values); if (!names.contains(REMOVE_VALUE_PREFIX + DEFAULT_KEY)) { - getExtensionClasses(); - for (Map.Entry entry : cachedActivates.entrySet()) { - String name = entry.getKey(); - Object activate = entry.getValue(); - - String[] activateGroup, activateValue; - - if (activate instanceof Activate) { - activateGroup = ((Activate) activate).group(); - activateValue = ((Activate) activate).value(); - } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) { - activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group(); - activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value(); - } else { - continue; + if (cachedActivateGroups.size() == 0) { + synchronized (cachedActivateGroups) { + if (cachedActivateGroups.size() == 0) { + getExtensionClasses(); + for (Map.Entry entry : cachedActivates.entrySet()) { + String name = entry.getKey(); + Object activate = entry.getValue(); + + String[] activateGroup, activateValue; + + if (activate instanceof Activate) { + activateGroup = ((Activate) activate).group(); + activateValue = ((Activate) activate).value(); + } else if (activate instanceof com.alibaba.dubbo.common.extension.Activate) { + activateGroup = ((com.alibaba.dubbo.common.extension.Activate) activate).group(); + activateValue = ((com.alibaba.dubbo.common.extension.Activate) activate).value(); + } else { + continue; + } + cachedActivateGroups.put(name, new HashSet<>(Arrays.asList(activateGroup))); + cachedActivateValues.put(name, activateValue); + } + } } + } + + cachedActivateGroups.forEach((name, activateGroup)->{ if (isMatchGroup(group, activateGroup) && !names.contains(name) && !names.contains(REMOVE_VALUE_PREFIX + name) - && isActive(activateValue, url)) { + && isActive(cachedActivateValues.get(name), url)) { activateExtensions.add(getExtension(name)); } - } + }); activateExtensions.sort(ActivateComparator.COMPARATOR); } List loadedExtensions = new ArrayList<>(); @@ -322,16 +337,12 @@ public List getActivateExtensions() { return activateExtensions; } - private boolean isMatchGroup(String group, String[] groups) { + private boolean isMatchGroup(String group, Set groups) { if (StringUtils.isEmpty(group)) { return true; } - if (groups != null && groups.length > 0) { - for (String g : groups) { - if (group.equals(g)) { - return true; - } - } + if(CollectionUtils.isNotEmpty(groups)){ + return groups.contains(group); } return false; } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableFunction.java b/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableFunction.java index 1c29cf674fa..53a0a69a1ed 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableFunction.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/function/ThrowableFunction.java @@ -51,7 +51,7 @@ default R execute(T t) throws RuntimeException { try { result = apply(t); } catch (Throwable e) { - throw new RuntimeException(e.getCause()); + throw new RuntimeException(e); } return result; } diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreUnregisteredEvent.java b/dubbo-common/src/main/java/org/apache/dubbo/common/stream/StreamObserver.java similarity index 59% rename from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreUnregisteredEvent.java rename to dubbo-common/src/main/java/org/apache/dubbo/common/stream/StreamObserver.java index e52baf9dfd9..debafaa2407 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreUnregisteredEvent.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/stream/StreamObserver.java @@ -14,21 +14,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.registry.client.event; -import org.apache.dubbo.registry.client.ServiceDiscovery; -import org.apache.dubbo.registry.client.ServiceInstance; +package org.apache.dubbo.common.stream; +public interface StreamObserver { + /** + * onNext + * @param data + */ + void onNext(T data); -/** - * An event raised before a {@link ServiceInstance service instance} - * {@link ServiceDiscovery#unregister(ServiceInstance) unregistered} - * - * @since 2.7.5 - */ -public class ServiceInstancePreUnregisteredEvent extends ServiceInstanceEvent { + /** + * onError + * @param throwable + */ + void onError(Throwable throwable); - public ServiceInstancePreUnregisteredEvent(Object source, ServiceInstance serviceInstance) { - super(source, serviceInstance); - } + /** + * onCompleted + */ + void onCompleted(); } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceConfigURL.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceConfigURL.java index 2bbb0620dd9..8702f5ccb88 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceConfigURL.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/ServiceConfigURL.java @@ -45,7 +45,7 @@ public ServiceConfigURL(String protocol, int port, String path, Map parameters) { - this(new PathURLAddress(protocol, username, password, path, host, port), new URLParam(parameters), null); + this(new PathURLAddress(protocol, username, password, path, host, port), URLParam.parse(parameters), null); } public ServiceConfigURL(String protocol, @@ -56,7 +56,7 @@ public ServiceConfigURL(String protocol, String path, Map parameters, Map attributes) { - this(new PathURLAddress(protocol, username, password, path, host, port), new URLParam(parameters), attributes); + this(new PathURLAddress(protocol, username, password, path, host, port), URLParam.parse(parameters), attributes); } protected T newURL(URLAddress urlAddress, URLParam urlParam) { diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLParam.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLParam.java index cea92cb3966..4c333eabe6e 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLParam.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/URLParam.java @@ -18,47 +18,196 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.URLStrParser; +import org.apache.dubbo.common.url.component.param.DynamicParamTable; import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.common.utils.StringUtils; -import org.eclipse.collections.impl.map.mutable.UnifiedMap; - import java.io.Serializable; +import java.util.Arrays; +import java.util.BitSet; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Objects; -import java.util.TreeMap; +import java.util.Set; +import java.util.StringJoiner; import static org.apache.dubbo.common.constants.CommonConstants.DEFAULT_KEY_PREFIX; import static org.apache.dubbo.common.constants.CommonConstants.METHODS_KEY; +/** + * A class which store parameters for {@link URL} + *
+ * Using {@link DynamicParamTable} to compress common keys (i.e. side, version) + *
+ * {@link DynamicParamTable} allow to use only two integer value named `key` and + * `value-offset` to find a unique string to string key-pair. Also, `value-offset` + * is not required if the real value is the default value. + *
+ * URLParam should operate as Copy-On-Write, each modify actions will return a new Object + * + * @since 3.0 + */ public class URLParam implements Serializable { private static final long serialVersionUID = -1985165475234910535L; + /** + * Maximum size of key-pairs requested using array moving to add into URLParam. + * If user request like addParameter for only one key-pair, adding value into a array + * on moving is more efficient. However when add more than ADD_PARAMETER_ON_MOVE_THRESHOLD + * size of key-pairs, recover compressed array back to map can reduce operation count + * when putting objects. + */ + private static final int ADD_PARAMETER_ON_MOVE_THRESHOLD = 1; + + /** + * the original parameters string, empty if parameters have been modified or init by {@link Map} + */ private final String rawParam; - private final Map params; - //cache - private transient Map> methodParameters; + /** + * using bit to save if index exist even if value is default value + */ + private final BitSet KEY; + + /** + * using bit to save if value is default value (reduce VALUE size) + */ + private final BitSet DEFAULT_KEY; + + /** + * a compressed (those key not exist or value is default value are bring removed in this array) array which contains value-offset + */ + private final Integer[] VALUE; + + /** + * store extra parameters which key not match in {@link DynamicParamTable} + */ + private final Map EXTRA_PARAMS; + + /** + * store method related parameters + *

+ * K - key + * V - + * K - method + * V - value + *

+ * e.g. method1.mock=true => ( mock, (method1, true) ) + */ + private final Map> METHOD_PARAMETERS; + private transient long timestamp; - public URLParam(Map params) { - this(params, null); + private final static URLParam EMPTY_PARAM = new URLParam(new BitSet(0), Collections.emptyMap(), Collections.emptyMap(), Collections.emptyMap(), ""); + + private URLParam() { + this.rawParam = null; + this.KEY = null; + this.DEFAULT_KEY = null; + this.VALUE = null; + this.EXTRA_PARAMS = null; + this.METHOD_PARAMETERS = null; } - public URLParam(Map params, String rawParam) { - this.params = Collections.unmodifiableMap((params == null ? new HashMap<>() : new HashMap<>(params))); + private URLParam(BitSet key, Map value, Map extraParams, Map> methodParameters, String rawParam) { + this.KEY = key; + this.DEFAULT_KEY = new BitSet(KEY.size()); + this.VALUE = new Integer[value.size()]; + + // compress VALUE + for (int i = key.nextSetBit(0), offset = 0; i >= 0; i = key.nextSetBit(i + 1)) { + if (value.containsKey(i)) { + VALUE[offset++] = value.get(i); + } else { + DEFAULT_KEY.set(i); + } + } + + this.EXTRA_PARAMS = Collections.unmodifiableMap((extraParams == null ? new HashMap<>() : new HashMap<>(extraParams))); + this.METHOD_PARAMETERS = Collections.unmodifiableMap((methodParameters == null) ? Collections.emptyMap() : new LinkedHashMap<>(methodParameters)); this.rawParam = rawParam; this.timestamp = System.currentTimeMillis(); } - public Map> getMethodParameters() { - if (methodParameters == null) { - methodParameters = Collections.unmodifiableMap(initMethodParameters(this.params)); + private URLParam(BitSet key, BitSet defaultKey, Integer[] value, Map extraParams, Map> methodParameters, String rawParam) { + this.KEY = key; + this.DEFAULT_KEY = defaultKey; + + this.VALUE = value; + + this.EXTRA_PARAMS = Collections.unmodifiableMap((extraParams == null ? new HashMap<>() : new HashMap<>(extraParams))); + this.METHOD_PARAMETERS = Collections.unmodifiableMap((methodParameters == null) ? Collections.emptyMap() : new LinkedHashMap<>(methodParameters)); + this.rawParam = rawParam; + + this.timestamp = System.currentTimeMillis(); + } + + /** + * Weather there contains some parameter match method + * + * @param method method name + * @return contains or not + */ + public boolean hasMethodParameter(String method) { + if (method == null) { + return false; + } + + String methodsString = getParameter(METHODS_KEY); + if (StringUtils.isNotEmpty(methodsString)) { + if (!methodsString.contains(method)) { + return false; + } + } + + for (Map.Entry> methods : METHOD_PARAMETERS.entrySet()) { + if (methods.getValue().containsKey(method)) { + return true; + } + } + return false; + } + + /** + * Get method related parameter. If not contains, use getParameter(key) instead. + * Specially, in some situation like `method1.1.callback=true`, key is `1.callback`. + * + * @param method method name + * @param key key + * @return value + */ + public String getMethodParameter(String method, String key) { + String strictResult = getMethodParameterStrict(method, key); + return StringUtils.isNotEmpty(strictResult) ? strictResult : getParameter(key); + } + + /** + * Get method related parameter. If not contains, return null. + * Specially, in some situation like `method1.1.callback=true`, key is `1.callback`. + * + * @param method method name + * @param key key + * @return value + */ + public String getMethodParameterStrict(String method, String key) { + String methodsString = getParameter(METHODS_KEY); + if (StringUtils.isNotEmpty(methodsString)) { + if (!methodsString.contains(method)) { + return null; + } + } + + Map methodMap = METHOD_PARAMETERS.get(key); + if (CollectionUtils.isNotEmptyMap(methodMap)) { + return methodMap.get(method); + } else { + return null; } - return methodParameters; } public static Map> initMethodParameters(Map parameters) { @@ -94,45 +243,266 @@ public static Map> initMethodParameters(Map + * copy-on-write mode, urlParam reference will be changed after modify actions. + * If wishes to get the result after modify, please use {@link URLParamMap#getUrlParam()} + */ + public static class URLParamMap implements Map { + private URLParam urlParam; + + public URLParamMap(URLParam urlParam) { + this.urlParam = urlParam; + } + + public static class Node implements Map.Entry { + private final String key; + private String value; + + public Node(String key, String value) { + this.key = key; + this.value = value; + } + + @Override + public String getKey() { + return key; + } + + @Override + public String getValue() { + return value; + } + + @Override + public String setValue(String value) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Node node = (Node) o; + return Objects.equals(key, node.key) && Objects.equals(value, node.value); + } + + @Override + public int hashCode() { + return Objects.hash(key, value); + } + } + + @Override + public int size() { + return urlParam.KEY.cardinality() + urlParam.EXTRA_PARAMS.size(); + } + + @Override + public boolean isEmpty() { + return size() == 0; + } + + @Override + public boolean containsKey(Object key) { + if (key instanceof String) { + return urlParam.hasParameter((String) key); + } else { + return false; + } + } + + @Override + public boolean containsValue(Object value) { + return values().contains(value); + } + + @Override + public String get(Object key) { + if (key instanceof String) { + return urlParam.getParameter((String) key); + } else { + return null; + } + } + + @Override + public String put(String key, String value) { + String previous = urlParam.getParameter(key); + urlParam = urlParam.addParameter(key, value); + return previous; + } + + @Override + public String remove(Object key) { + if (key instanceof String) { + String previous = urlParam.getParameter((String) key); + urlParam = urlParam.removeParameters((String) key); + return previous; + } else { + return null; + } + } + + @Override + public void putAll(Map m) { + urlParam = urlParam.addParameters((Map) m); + } + + @Override + public void clear() { + urlParam = urlParam.clearParameters(); + } + + @Override + public Set keySet() { + Set set = new LinkedHashSet<>((int) ((urlParam.VALUE.length + urlParam.EXTRA_PARAMS.size()) / 0.75) + 1); + for (int i = urlParam.KEY.nextSetBit(0); i >= 0; i = urlParam.KEY.nextSetBit(i + 1)) { + set.add(DynamicParamTable.getKey(i)); + } + for (Entry entry : urlParam.EXTRA_PARAMS.entrySet()) { + set.add(entry.getKey()); + } + return Collections.unmodifiableSet(set); + } + + @Override + public Collection values() { + Set set = new LinkedHashSet<>((int) ((urlParam.VALUE.length + urlParam.EXTRA_PARAMS.size()) / 0.75) + 1); + for (int i = urlParam.KEY.nextSetBit(0); i >= 0; i = urlParam.KEY.nextSetBit(i + 1)) { + String value; + if (urlParam.DEFAULT_KEY.get(i)) { + value = DynamicParamTable.getDefaultValue(i); + } else { + Integer offset = urlParam.keyIndexToOffset(i); + value = DynamicParamTable.getValue(i, offset); + } + set.add(value); + } + + for (Entry entry : urlParam.EXTRA_PARAMS.entrySet()) { + set.add(entry.getValue()); + } + return Collections.unmodifiableSet(set); + } + + @Override + public Set> entrySet() { + Set> set = new LinkedHashSet<>((int) ((urlParam.KEY.cardinality() + urlParam.EXTRA_PARAMS.size()) / 0.75) + 1); + for (int i = urlParam.KEY.nextSetBit(0); i >= 0; i = urlParam.KEY.nextSetBit(i + 1)) { + String value; + if (urlParam.DEFAULT_KEY.get(i)) { + value = DynamicParamTable.getDefaultValue(i); + } else { + Integer offset = urlParam.keyIndexToOffset(i); + value = DynamicParamTable.getValue(i, offset); + } + set.add(new Node(DynamicParamTable.getKey(i), value)); + } + + for (Entry entry : urlParam.EXTRA_PARAMS.entrySet()) { + set.add(new Node(entry.getKey(), entry.getValue())); + } + return Collections.unmodifiableSet(set); + } + + public URLParam getUrlParam() { + return urlParam; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + URLParamMap that = (URLParamMap) o; + return Objects.equals(urlParam, that.urlParam); + } + + @Override + public int hashCode() { + return Objects.hash(urlParam); + } + } + + /** + * Get a Map like URLParam + * + * @return a {@link URLParamMap} adapt to URLParam + */ public Map getParameters() { - return params; + return new URLParamMap(this); } - public URLParam addParameter(String key, String value) { - if (StringUtils.isEmpty(key) - || StringUtils.isEmpty(value)) { - return this; + /** + * Get any method related parameter which match key + * + * @param key key + * @return result ( if any, random choose one ) + */ + public String getAnyMethodParameter(String key) { + Map methodMap = METHOD_PARAMETERS.get(key); + if (CollectionUtils.isNotEmptyMap(methodMap)) { + String methods = getParameter(METHODS_KEY); + if (StringUtils.isNotEmpty(methods)) { + for (String method : methods.split(",")) { + String value = methodMap.get(method); + if (StringUtils.isNotEmpty(value)) { + return value; + } + } + } else { + return methodMap.values().iterator().next(); + } } - // if value doesn't change, return immediately - if (value.equals(getParameters().get(key))) { // value != null + return null; + } + + /** + * Add parameters to a new URLParam. + * + * @param key key + * @param value value + * @return A new URLParam + */ + public URLParam addParameter(String key, String value) { + if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) { return this; } - - Map map = new HashMap<>(getParameters()); - map.put(key, value); - return new URLParam(map, rawParam); + return addParameters(Collections.singletonMap(key, value)); } + /** + * Add absent parameters to a new URLParam. + * + * @param key key + * @param value value + * @return A new URLParam + */ public URLParam addParameterIfAbsent(String key, String value) { - if (StringUtils.isEmpty(key) - || StringUtils.isEmpty(value)) { + if (StringUtils.isEmpty(key) || StringUtils.isEmpty(value)) { return this; } if (hasParameter(key)) { return this; } - - Map map = new HashMap<>(getParameters()); - map.put(key, value); - - return new URLParam(map, rawParam); + return addParametersIfAbsent(Collections.singletonMap(key, value)); } /** - * Add parameters to a new url. + * Add parameters to a new URLParam. + * If key-pair is present, this will cover it. * * @param parameters parameters in key-value pairs - * @return A new URL + * @return A new URLParam */ public URLParam addParameters(Map parameters) { if (CollectionUtils.isEmptyMap(parameters)) { @@ -159,53 +529,311 @@ public URLParam addParameters(Map parameters) { return this; } - Map map = new HashMap<>((int)(getParameters().size() + parameters.size() / 0.75f) + 1); - map.putAll(getParameters()); - map.putAll(parameters); - return new URLParam(map, rawParam); + return doAddParameters(parameters, false); } + /** + * Add absent parameters to a new URLParam. + * + * @param parameters parameters in key-value pairs + * @return A new URL + */ public URLParam addParametersIfAbsent(Map parameters) { if (CollectionUtils.isEmptyMap(parameters)) { return this; } - Map map = new HashMap<>((int)(getParameters().size() + parameters.size() / 0.75f) + 1); - map.putAll(parameters); - map.putAll(getParameters()); - return new URLParam(map, rawParam); + return doAddParameters(parameters, true); } + private URLParam doAddParameters(Map parameters, boolean skipIfPresent) { + // lazy init, null if no modify + BitSet newKey = null; + BitSet defaultKey = null; + Integer[] newValueArray = null; + Map newValueMap = null; + Map newExtraParams = null; + Map> newMethodParams = null; + for (Map.Entry entry : parameters.entrySet()) { + if (skipIfPresent && hasParameter(entry.getKey())) { + continue; + } + Integer keyIndex = DynamicParamTable.getKeyIndex(entry.getKey()); + if (keyIndex == null) { + // entry key is not present in DynamicParamTable, add it to EXTRA_PARAMS + if (newExtraParams == null) { + newExtraParams = new HashMap<>(EXTRA_PARAMS); + } + newExtraParams.put(entry.getKey(), entry.getValue()); + String[] methodSplit = entry.getKey().split("\\."); + if (methodSplit.length == 2) { + if (newMethodParams == null) { + newMethodParams = new HashMap<>(METHOD_PARAMETERS); + } + Map methodMap = newMethodParams.computeIfAbsent(methodSplit[1], (k) -> new HashMap<>()); + methodMap.put(methodSplit[0], entry.getValue()); + } + } else { + if (newKey == null) { + newKey = (BitSet) KEY.clone(); + } + newKey.set(keyIndex); + + if (parameters.size() > ADD_PARAMETER_ON_MOVE_THRESHOLD) { + // recover VALUE back to Map + if (newValueMap == null) { + newValueMap = recoverCompressedValue(); + } + newValueMap.put(keyIndex, DynamicParamTable.getValueIndex(entry.getKey(), entry.getValue())); + } else if (!DynamicParamTable.isDefaultValue(entry.getKey(), entry.getValue())) { + // add parameter by moving array, only support for adding once + newValueArray = addByMove(VALUE, keyIndexToCompressIndex(newKey, DEFAULT_KEY, keyIndex), DynamicParamTable.getValueIndex(entry.getKey(), entry.getValue())); + } else { + // value is default value, add to defaultKey directly + if (defaultKey == null) { + defaultKey = (BitSet) DEFAULT_KEY.clone(); + } + defaultKey.set(keyIndex); + } + } + } + if (newKey == null) { + newKey = KEY; + } + if (defaultKey == null) { + defaultKey = DEFAULT_KEY; + } + if (newValueArray == null && newValueMap == null) { + newValueArray = VALUE; + } + if (newExtraParams == null) { + newExtraParams = EXTRA_PARAMS; + } + if (newMethodParams == null) { + newMethodParams = METHOD_PARAMETERS; + } + if (newValueMap == null) { + return new URLParam(newKey, defaultKey, newValueArray, newExtraParams, newMethodParams, null); + } else { + return new URLParam(newKey, newValueMap, newExtraParams, newMethodParams, null); + } + } + + private Map recoverCompressedValue() { + Map map = new HashMap<>((int) (KEY.size() / 0.75) + 1); + for (int i = KEY.nextSetBit(0), offset = 0; i >= 0; i = KEY.nextSetBit(i + 1)) { + if (!DEFAULT_KEY.get(i)) { + map.put(i, VALUE[offset++]); + } + } + return map; + } + + private Integer[] addByMove(Integer[] array, int index, Integer value) { + if (index < 0 || index > array.length) { + throw new IllegalArgumentException(); + } + // copy-on-write + Integer[] result = new Integer[array.length + 1]; + + System.arraycopy(array, 0, result, 0, index); + result[index] = value; + System.arraycopy(array, index, result, index + 1, array.length - index); + + return result; + } + + /** + * remove specified parameters in URLParam + * + * @param keys keys to being removed + * @return A new URLParam + */ public URLParam removeParameters(String... keys) { if (keys == null || keys.length == 0) { return this; } - - Map map = new HashMap<>(getParameters()); + // lazy init, null if no modify + BitSet newKey = null; + BitSet defaultKey = null; + Integer[] newValueArray = null; + Map newExtraParams = null; + Map> newMethodParams = null; for (String key : keys) { - map.remove(key); + Integer keyIndex = DynamicParamTable.getKeyIndex(key); + if (keyIndex != null && KEY.get(keyIndex)) { + if (newKey == null) { + newKey = (BitSet) KEY.clone(); + } + newKey.clear(keyIndex); + if (DEFAULT_KEY.get(keyIndex)) { + // is default value, remove in DEFAULT_KEY + if (defaultKey == null) { + defaultKey = (BitSet) DEFAULT_KEY.clone(); + } + defaultKey.clear(keyIndex); + } else { + // which offset is in VALUE array, set value as -1, compress in the end + if (newValueArray == null) { + newValueArray = new Integer[VALUE.length]; + System.arraycopy(VALUE, 0, newValueArray, 0, VALUE.length); + } + // KEY is immutable + newValueArray[keyIndexToCompressIndex(KEY, DEFAULT_KEY, keyIndex)] = -1; + } + } + if (EXTRA_PARAMS.containsKey(key)) { + if (newExtraParams == null) { + newExtraParams = new HashMap<>(EXTRA_PARAMS); + } + newExtraParams.remove(key); + + String[] methodSplit = key.split("\\."); + if (methodSplit.length == 2) { + if (newMethodParams == null) { + newMethodParams = new HashMap<>(METHOD_PARAMETERS); + } + Map methodMap = newMethodParams.get(methodSplit[1]); + if (CollectionUtils.isNotEmptyMap(methodMap)) { + methodMap.remove(methodSplit[0]); + } + } + } + // ignore if key is absent } - if (map.size() == getParameters().size()) { - return this; + if (newKey == null) { + newKey = KEY; + } + if (defaultKey == null) { + defaultKey = DEFAULT_KEY; + } + if (newValueArray == null) { + newValueArray = VALUE; + } else { + // remove -1 value + newValueArray = compressArray(newValueArray); + } + if (newExtraParams == null) { + newExtraParams = EXTRA_PARAMS; + } + if (newMethodParams == null) { + newMethodParams = METHOD_PARAMETERS; + } + if (newKey.cardinality() + newExtraParams.size() == 0) { + // empty, directly return cache + return EMPTY_PARAM; + } else { + return new URLParam(newKey, defaultKey, newValueArray, newExtraParams, newMethodParams, null); } - return new URLParam(map, rawParam); } + private Integer[] compressArray(Integer[] array) { + int total = 0; + for (int i : array) { + if (i > -1) { + total++; + } + } + if (total == 0) { + return new Integer[0]; + } + + Integer[] result = new Integer[total]; + for (int i = 0, offset = 0; i < array.length; i++) { + // skip if value if less than 0 + if (array[i] > -1) { + result[offset++] = array[i]; + } + } + return result; + } + + /** + * remove all of the parameters in URLParam + * + * @return An empty URLParam + */ public URLParam clearParameters() { - return new URLParam(new HashMap<>()); + return EMPTY_PARAM; } + /** + * check if specified key is present in URLParam + * + * @param key specified key + * @return present or not + */ public boolean hasParameter(String key) { - String value = getParameter(key); - return value != null && value.length() > 0; + Integer keyIndex = DynamicParamTable.getKeyIndex(key); + if (keyIndex == null) { + return EXTRA_PARAMS.containsKey(key); + } + return KEY.get(keyIndex); } + /** + * get value of specified key in URLParam + * + * @param key specified key + * @return value, null if key is absent + */ public String getParameter(String key) { - return params.get(key); + Integer keyIndex = DynamicParamTable.getKeyIndex(key); + if (keyIndex == null) { + if (EXTRA_PARAMS.containsKey(key)) { + return EXTRA_PARAMS.get(key); + } + return null; + } + if (KEY.get(keyIndex)) { + String value; + if (DEFAULT_KEY.get(keyIndex)) { + value = DynamicParamTable.getDefaultValue(keyIndex); + } else { + Integer offset = keyIndexToOffset(keyIndex); + value = DynamicParamTable.getValue(keyIndex, offset); + } + if (StringUtils.isEmpty(value)) { + // Forward compatible, make sure key dynamic increment can work. + // In that case, some values which are proceed before increment will set in EXTRA_PARAMS. + return EXTRA_PARAMS.get(key); + } else { + return value; + } + } + return null; + } + + + private int keyIndexToCompressIndex(BitSet key, BitSet defaultKey, int keyIndex) { + int index = 0; + for (int i = 0; i < keyIndex; i++) { + if (key.get(i)) { + if (!defaultKey.get(i)) { + index++; + } + } + } + return index; } + private Integer keyIndexToOffset(int keyIndex) { + int arrayOffset = keyIndexToCompressIndex(KEY, DEFAULT_KEY, keyIndex); + return VALUE[arrayOffset]; + } + + /** + * get raw string like parameters + * + * @return raw string like parameters + */ public String getRawParam() { - return rawParam; + if (StringUtils.isNotEmpty(rawParam)) { + return rawParam; + } else { + // empty if parameters have been modified or init by Map + return toString(); + } } public long getTimestamp() { @@ -217,16 +845,33 @@ public void setTimestamp(long timestamp) { } @Override - public int hashCode() { - return Objects.hash(params); + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + URLParam urlParam = (URLParam) o; + return Objects.equals(KEY, urlParam.KEY) + && Objects.equals(DEFAULT_KEY, urlParam.DEFAULT_KEY) + && Arrays.equals(VALUE, urlParam.VALUE) + && Objects.equals(EXTRA_PARAMS, urlParam.EXTRA_PARAMS); } + private int hashCodeCache = -1; + @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (!(obj instanceof URLParam)) return false; - URLParam that = (URLParam) obj; - return Objects.equals(this.getParameters(), that.getParameters()); + public int hashCode() { + if (hashCodeCache == -1) { + hashCodeCache = EXTRA_PARAMS.hashCode(); + for (Integer value : VALUE) { + hashCodeCache = hashCodeCache * 31 + value; + } + hashCodeCache = hashCodeCache * 31 + ((KEY == null) ? 0 : KEY.hashCode()); + hashCodeCache = hashCodeCache * 31 + ((DEFAULT_KEY == null) ? 0 : DEFAULT_KEY.hashCode()); + } + return hashCodeCache; } @Override @@ -234,38 +879,72 @@ public String toString() { if (StringUtils.isNotEmpty(rawParam)) { return rawParam; } - if (params == null) { + if ((KEY.cardinality() + EXTRA_PARAMS.size()) == 0) { return ""; } - StringBuilder buf = new StringBuilder(); - boolean first = true; - for (Map.Entry entry : new TreeMap<>(params).entrySet()) { - if (StringUtils.isNotEmpty(entry.getKey())) { - if (first) { - first = false; - } else { - buf.append("&"); - } - buf.append(entry.getKey()); - buf.append("="); - buf.append(entry.getValue() == null ? "" : entry.getValue().trim()); - } + StringJoiner stringJoiner = new StringJoiner("&"); + for (int i = KEY.nextSetBit(0); i >= 0; i = KEY.nextSetBit(i + 1)) { + String key = DynamicParamTable.getKey(i); + String value = DEFAULT_KEY.get(i) ? + DynamicParamTable.getDefaultValue(i) : DynamicParamTable.getValue(i, keyIndexToOffset(i)); + value = value == null ? "" : value.trim(); + stringJoiner.add(String.format("%s=%s", key, value)); + } + for (Map.Entry entry : EXTRA_PARAMS.entrySet()) { + String key = entry.getKey(); + String value = entry.getValue(); + value = value == null ? "" : value.trim(); + stringJoiner.add(String.format("%s=%s", key, value)); } - return buf.toString(); + + return stringJoiner.toString(); + } + + /** + * Parse URLParam + * Init URLParam by constructor is not allowed + * rawParam field in result will be null while {@link URLParam#getRawParam()} will automatically create it + * + * @param params params map added into URLParam + * @return a new URLParam + */ + public static URLParam parse(Map params) { + return parse(params, null); } + /** + * Parse URLParam + * Init URLParam by constructor is not allowed + * + * @param rawParam original rawParam string + * @param encoded if parameters are URL encoded + * @param extraParameters extra parameters to add into URLParam + * @return a new URLParam + */ public static URLParam parse(String rawParam, boolean encoded, Map extraParameters) { Map parameters = URLStrParser.parseParams(rawParam, encoded); if (CollectionUtils.isNotEmptyMap(extraParameters)) { parameters.putAll(extraParameters); } - return new URLParam(parameters, rawParam); + return parse(parameters, rawParam); } + /** + * Parse URLParam + * Init URLParam by constructor is not allowed + * + * @param rawParam original rawParam string + * @return a new URLParam + */ public static URLParam parse(String rawParam) { String[] parts = rawParam.split("&"); - Map parameters = new UnifiedMap<>((int) (parts.length/.75f) + 1); + + BitSet keyBit = new BitSet((int) (parts.length / .75f) + 1); + Map valueMap = new HashMap<>((int) (parts.length / .75f) + 1); + Map extraParam = new HashMap<>((int) (parts.length / .75f) + 1); + Map> methodParameters = new HashMap<>((int) (parts.length / .75f) + 1); + for (String part : parts) { part = part.trim(); if (part.length() > 0) { @@ -273,16 +952,75 @@ public static URLParam parse(String rawParam) { if (j >= 0) { String key = part.substring(0, j); String value = part.substring(j + 1); - parameters.put(key, value); + addParameter(keyBit, valueMap, extraParam, methodParameters, key, value, false); // compatible with lower versions registering "default." keys if (key.startsWith(DEFAULT_KEY_PREFIX)) { - parameters.putIfAbsent(key.substring(DEFAULT_KEY_PREFIX.length()), value); + addParameter(keyBit, valueMap, extraParam, methodParameters, key.substring(DEFAULT_KEY_PREFIX.length()), value, true); } } else { - parameters.put(part, part); + addParameter(keyBit, valueMap, extraParam, methodParameters, part, part, false); + } + } + } + return new URLParam(keyBit, valueMap, extraParam, methodParameters, rawParam); + } + + /** + * Parse URLParam + * Init URLParam by constructor is not allowed + * + * @param params params map added into URLParam + * @param rawParam original rawParam string, directly add to rawParam field, + * will not effect real key-pairs store in URLParam. + * Please make sure it can correspond with params or will + * cause unexpected result when calling {@link URLParam#getRawParam()} + * and {@link URLParam#toString()} ()}. If you not sure, you can call + * {@link URLParam#parse(String)} to init. + * @return a new URLParam + */ + public static URLParam parse(Map params, String rawParam) { + if (CollectionUtils.isNotEmptyMap(params)) { + BitSet keyBit = new BitSet((int) (params.size() / .75f) + 1); + Map valueMap = new HashMap<>((int) (params.size() / .75f) + 1); + Map extraParam = new HashMap<>((int) (params.size() / .75f) + 1); + Map> methodParameters = new HashMap<>((int) (params.size() / .75f) + 1); + + for (Map.Entry entry : params.entrySet()) { + addParameter(keyBit, valueMap, extraParam, methodParameters, entry.getKey(), entry.getValue(), false); + } + return new URLParam(keyBit, valueMap, extraParam, methodParameters, rawParam); + } else { + return EMPTY_PARAM; + } + } + + private static void addParameter(BitSet keyBit, Map valueMap, Map extraParam, + Map> methodParameters, String key, String value, boolean skipIfPresent) { + Integer keyIndex = DynamicParamTable.getKeyIndex(key); + if (skipIfPresent) { + if (keyIndex == null) { + if (extraParam.containsKey(key)) { + return; } + } else { + if (keyBit.get(keyIndex)) { + return; + } + } + } + + if (keyIndex == null) { + extraParam.put(key, value); + String[] methodSplit = key.split("\\.", 2); + if (methodSplit.length == 2) { + Map methodMap = methodParameters.computeIfAbsent(methodSplit[1], (k) -> new HashMap<>()); + methodMap.put(methodSplit[0], value); + } + } else { + if (!DynamicParamTable.isDefaultValue(key, value)) { + valueMap.put(keyIndex, DynamicParamTable.getValueIndex(key, value)); } + keyBit.set(keyIndex); } - return new URLParam(parameters, rawParam); } } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/DynamicParamSource.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/DynamicParamSource.java new file mode 100644 index 00000000000..46f901e0e71 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/DynamicParamSource.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.common.url.component.param; + +import org.apache.dubbo.common.extension.SPI; + +import java.util.List; +@SPI +public interface DynamicParamSource { + + void init(List KEYS, List VALUES); +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/DynamicParamTable.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/DynamicParamTable.java new file mode 100644 index 00000000000..fd1e9eac7d0 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/DynamicParamTable.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.common.url.component.param; + +import org.apache.dubbo.common.extension.ExtensionLoader; + +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Global Param Cache Table + * Not support method parameters + */ +public final class DynamicParamTable { + private static final List KEYS = new CopyOnWriteArrayList<>(); + private static final List VALUES = new CopyOnWriteArrayList<>(); + private static final Map KEY2INDEX = new HashMap<>(64); + + private DynamicParamTable() { + throw new IllegalStateException(); + } + + static { + init(); + } + + public static Integer getKeyIndex(String key) { + return KEY2INDEX.get(key); + } + + public static Integer getValueIndex(String key, String value) { + Integer idx = getKeyIndex(key); + if (idx == null) { + throw new IllegalArgumentException("Cannot found key in url param:" + key); + } + ParamValue paramValue = VALUES.get(idx); + return paramValue.getIndex(value); + } + + public static String getKey(int offset) { + return KEYS.get(offset); + } + + public static boolean isDefaultValue(String key, String value) { + return Objects.equals(value, VALUES.get(getKeyIndex(key)).defaultVal()); + } + + public static String getValue(int vi, Integer offset) { + return VALUES.get(vi).getN(offset); + } + + public static String getDefaultValue(int vi) { + return VALUES.get(vi).defaultVal(); + } + + public static void init() { + List keys = new LinkedList<>(); + List values = new LinkedList<>(); + Map key2Index = new HashMap<>(64); + keys.add(""); + values.add(new DynamicValues(null)); + + // Cache key and defaultValue + keys.add("version"); + values.add(new DynamicValues(null)); + + keys.add("side"); + values.add(new FixedParamValue("consumer", "provider")); + + KEYS.addAll(keys); + VALUES.addAll(values); + + ExtensionLoader.getExtensionLoader(DynamicParamSource.class) + .getSupportedExtensionInstances().forEach(source -> source.init(KEYS, VALUES)); + + for (int i = 0; i < KEYS.size(); i++) { + if (!KEYS.get(i).isEmpty()) { + key2Index.put(KEYS.get(i), i); + } + } + KEY2INDEX.putAll(key2Index); + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/DynamicValues.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/DynamicValues.java new file mode 100644 index 00000000000..6b025e9d29a --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/DynamicValues.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.common.url.component.param; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class DynamicValues implements ParamValue { + private final Map index2Value = new ConcurrentHashMap<>(); + private final Map value2Index = new ConcurrentHashMap<>(); + private int indexSeq = 0; + + public DynamicValues(String defaultVal) { + if (defaultVal == null) { + indexSeq += 1; + } else { + add(defaultVal); + } + } + + public int add(String value) { + Integer index = value2Index.get(value); + if (index != null) { + return index; + } else { + synchronized (this) { + // thread safe + if (!value2Index.containsKey(value)) { + value2Index.put(value, indexSeq); + index2Value.put(indexSeq, value); + indexSeq += 1; + } + } + } + return value2Index.get(value); + } + + @Override + public String getN(Integer n) { + return index2Value.get(n); + } + + @Override + public Integer getIndex(String value) { + Integer index = value2Index.get(value); + if (index == null) { + return add(value); + } + return index; + } + + @Override + public String defaultVal() { + return getN(0); + } +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/FixedParamValue.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/FixedParamValue.java new file mode 100644 index 00000000000..d9a3bec9003 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/FixedParamValue.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.common.url.component.param; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * In lower case + */ +public class FixedParamValue implements ParamValue { + private final String[] values; + private final Map val2Index; + + public FixedParamValue(String... values) { + if (values.length == 0) { + throw new IllegalArgumentException("the array size of values should be larger than 0"); + } + this.values = values; + Map valueMap = new HashMap<>(values.length); + for (int i = 0; i < values.length; i++) { + if (values[i] != null) { + valueMap.put(values[i].toLowerCase(Locale.ROOT), i); + } + } + val2Index = Collections.unmodifiableMap(valueMap); + } + + /** + * DEFAULT value will be returned if n = 0 + * @param n + */ + @Override + public String getN(Integer n) { + return values[n]; + } + + @Override + public Integer getIndex(String value) { + Integer offset = val2Index.get(value.toLowerCase(Locale.ROOT)); + if (offset == null) { + throw new IllegalArgumentException("unrecognized value " + value + + " , please check if value is illegal. " + + "Permitted values: " + Arrays.asList(values)); + } + return offset; + } + + @Override + public String defaultVal() { + return values[0]; + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceRegisteredEvent.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/IgnoredParam.java similarity index 60% rename from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceRegisteredEvent.java rename to dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/IgnoredParam.java index a6bd8460c2e..cfc768f3b31 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstanceRegisteredEvent.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/IgnoredParam.java @@ -14,21 +14,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.registry.client.event; +package org.apache.dubbo.common.url.component.param; -import org.apache.dubbo.registry.client.ServiceDiscovery; -import org.apache.dubbo.registry.client.ServiceInstance; +import java.util.HashSet; +public class IgnoredParam { + private static final HashSet IGNORED = new HashSet<>(); -/** - * An event raised after a {@link ServiceInstance service instance} - * {@link ServiceDiscovery#register(ServiceInstance) registered} - * - * @since 2.7.5 - */ -public class ServiceInstanceRegisteredEvent extends ServiceInstanceEvent { + static { + IGNORED.add("timestamp"); + } - public ServiceInstanceRegisteredEvent(Object source, ServiceInstance serviceInstance) { - super(source, serviceInstance); + static boolean ignore(String key) { + return IGNORED.contains(key); } + } diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/ParamValue.java b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/ParamValue.java new file mode 100644 index 00000000000..54271cccf61 --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/url/component/param/ParamValue.java @@ -0,0 +1,43 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.common.url.component.param; + +public interface ParamValue { + /** + * get value at the specified index. + * + * @param n the nth value + * @return the value stored at index = n + */ + String getN(Integer n); + + + /** + * max index is 2^31 - 1 + * + * @param value the stored value + * @return the index of value + */ + Integer getIndex(String value); + + /** + * get default value + * + * @return the default value stored at index = 0 + */ + String defaultVal(); +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java new file mode 100644 index 00000000000..92b520d72fc --- /dev/null +++ b/dubbo-common/src/main/java/org/apache/dubbo/common/utils/MD5Utils.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.common.utils; + +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * MD5 util. + */ +public class MD5Utils { + + private static final Logger logger = LoggerFactory.getLogger(MD5Utils.class); + + private static final char[] hexDigits = { + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' + }; + + private static MessageDigest mdInst; + + static { + try { + mdInst = MessageDigest.getInstance("MD5"); + } catch (NoSuchAlgorithmException e) { + logger.error("Failed to obtain md5", e); + } + } + + public static String getMd5(String value) { + mdInst.update(value.getBytes(UTF_8)); + byte[] md5 = mdInst.digest(); + + int j = md5.length; + char str[] = new char[j * 2]; + int k = 0; + for (int i = 0; i < j; i++) { + byte byte0 = md5[i]; + str[k++] = hexDigits[byte0 >>> 4 & 0xf]; + str[k++] = hexDigits[byte0 & 0xf]; + } + return new String(str); + } + + +} diff --git a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java index b4a80b24bb2..e14166822ad 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/config/AbstractInterfaceConfig.java @@ -466,7 +466,9 @@ public void setApplication(ApplicationConfig application) { this.application = application; if (application != null) { ConfigManager configManager = ApplicationModel.getConfigManager(); - if (!configManager.getApplication().isPresent()) { + // FIXME, revert this change after spring bean initialization process fixed. + if (!configManager.getApplication().isPresent() + || !configManager.getApplication().get().getName().equalsIgnoreCase(application.getName())) { configManager.setApplication(application); } else { application.refresh(); diff --git a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java index f53767f2551..241ab30d81a 100644 --- a/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java +++ b/dubbo-common/src/main/java/org/apache/dubbo/rpc/model/MethodDescriptor.java @@ -16,14 +16,19 @@ */ package org.apache.dubbo.rpc.model; +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.common.utils.ReflectUtils; import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.stream.Stream; +import static org.apache.dubbo.common.constants.CommonConstants.$ECHO; import static org.apache.dubbo.common.constants.CommonConstants.$INVOKE; import static org.apache.dubbo.common.constants.CommonConstants.$INVOKE_ASYNC; +import static org.apache.dubbo.common.constants.CommonConstants.PROTOBUF_MESSAGE_CLASS_NAME; /** * @@ -31,7 +36,7 @@ public class MethodDescriptor { private final Method method; // private final boolean isCallBack; -// private final boolean isFuture; + // private final boolean isFuture; private final String paramDesc; // duplicate filed as paramDesc, but with different format. private final String[] compatibleParamSignatures; @@ -40,11 +45,30 @@ public class MethodDescriptor { private final Type[] returnTypes; private final String methodName; private final boolean generic; + private final RpcType rpcType; public MethodDescriptor(Method method) { this.method = method; - this.parameterClasses = method.getParameterTypes(); - this.returnClass = method.getReturnType(); + Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes.length == 1 && isStreamType(parameterTypes[0])) { + this.parameterClasses = new Class[]{ + (Class) ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments()[0]}; + this.returnClass = (Class) ((ParameterizedType) method.getGenericParameterTypes()[0]) + .getActualTypeArguments()[0]; + if (needWrap()) { + rpcType = RpcType.STREAM_WRAP; + } else { + rpcType = RpcType.STREAM_UNWRAP; + } + } else { + this.parameterClasses = method.getParameterTypes(); + this.returnClass = method.getReturnType(); + if (needWrap()) { + rpcType = RpcType.UNARY_WRAP; + } else { + rpcType = RpcType.UNARY_UNWRAP; + } + } this.returnTypes = ReflectUtils.getReturnTypes(method); this.paramDesc = ReflectUtils.getDesc(parameterClasses); this.compatibleParamSignatures = Stream.of(parameterClasses) @@ -54,7 +78,51 @@ public MethodDescriptor(Method method) { this.generic = (methodName.equals($INVOKE) || methodName.equals($INVOKE_ASYNC)) && parameterClasses.length == 3; } - public boolean matchParams (String params) { + private static boolean isStreamType(Class clz) { + return StreamObserver.class.isAssignableFrom(clz); + } + + public boolean isStream() { + return rpcType.equals(RpcType.STREAM_WRAP) || rpcType.equals(RpcType.STREAM_UNWRAP); + } + + public boolean isUnary() { + return rpcType.equals(RpcType.UNARY_WRAP) || rpcType.equals(RpcType.UNARY_UNWRAP); + } + + public boolean isNeedWrap() { + return rpcType.equals(RpcType.UNARY_WRAP) || rpcType.equals(RpcType.STREAM_WRAP); + } + + private boolean needWrap() { + if (CommonConstants.$INVOKE.equals(methodName) || CommonConstants.$INVOKE_ASYNC.equals(methodName)) { + return true; + } else if ($ECHO.equals(methodName)) { + return true; + } else { + if (parameterClasses.length != 1 || parameterClasses[0] == null) { + return true; + } + + Class clazz = parameterClasses[0]; + while (clazz != Object.class && clazz != null) { + Class[] interfaces = clazz.getInterfaces(); + if (interfaces.length > 0) { + for (Class clazzInterface : interfaces) { + if (PROTOBUF_MESSAGE_CLASS_NAME.equalsIgnoreCase(clazzInterface.getName())) { + return false; + } + } + } + + clazz = clazz.getSuperclass(); + } + + return true; + } + } + + public boolean matchParams(String params) { return paramDesc.equalsIgnoreCase(params); } @@ -90,4 +158,11 @@ public boolean isGeneric() { return generic; } + public enum RpcType { + UNARY_WRAP, + UNARY_UNWRAP, + STREAM_WRAP, + STREAM_UNWRAP; + } + } diff --git a/dubbo-common/src/test/java/org/apache/dubbo/common/url/URLParamTest.java b/dubbo-common/src/test/java/org/apache/dubbo/common/url/URLParamTest.java new file mode 100644 index 00000000000..c7ea41c836d --- /dev/null +++ b/dubbo-common/src/test/java/org/apache/dubbo/common/url/URLParamTest.java @@ -0,0 +1,273 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.common.url; + +import org.apache.dubbo.common.url.component.URLParam; +import org.apache.dubbo.common.utils.CollectionUtils; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class URLParamTest { + @Test + public void testParseWithRawParam() { + URLParam urlParam1 = URLParam.parse("aaa=aaa&bbb&version=1.0&default.ccc=123"); + Assertions.assertEquals("aaa", urlParam1.getParameter("aaa")); + Assertions.assertEquals("bbb", urlParam1.getParameter("bbb")); + Assertions.assertEquals("1.0", urlParam1.getParameter("version")); + Assertions.assertEquals("123", urlParam1.getParameter("default.ccc")); + Assertions.assertEquals("123", urlParam1.getParameter("ccc")); + Assertions.assertEquals(urlParam1, URLParam.parse(urlParam1.getRawParam())); + Assertions.assertEquals(urlParam1, URLParam.parse(urlParam1.toString())); + + URLParam urlParam2 = URLParam.parse("aaa%3dtest", true, null); + Assertions.assertEquals("test", urlParam2.getParameter("aaa")); + + Map overrideMap = Collections.singletonMap("aaa", "bbb"); + URLParam urlParam3 = URLParam.parse("aaa%3dtest", true, overrideMap); + Assertions.assertEquals("bbb", urlParam3.getParameter("aaa")); + + URLParam urlParam4 = URLParam.parse("ccc=456&&default.ccc=123"); + Assertions.assertEquals("456", urlParam4.getParameter("ccc")); + + URLParam urlParam5 = URLParam.parse("version=2.0&&default.version=1.0"); + Assertions.assertEquals("2.0", urlParam5.getParameter("version")); + } + + @Test + public void testParseWithMap() { + Map map = new HashMap<>(); + map.put("aaa", "aaa"); + map.put("bbb", "bbb"); + map.put("version", "2.0"); + map.put("side", "consumer"); + + URLParam urlParam1 = URLParam.parse(map); + Assertions.assertEquals("aaa", urlParam1.getParameter("aaa")); + Assertions.assertEquals("bbb", urlParam1.getParameter("bbb")); + Assertions.assertEquals("2.0", urlParam1.getParameter("version")); + Assertions.assertEquals("consumer", urlParam1.getParameter("side")); + Assertions.assertEquals(urlParam1, URLParam.parse(urlParam1.getRawParam())); + + map.put("bbb", "ccc"); + + Assertions.assertEquals("bbb", urlParam1.getParameter("bbb")); + + URLParam urlParam2 = URLParam.parse(map); + Assertions.assertEquals("ccc", urlParam2.getParameter("bbb")); + + URLParam urlParam3 = URLParam.parse(null, null); + Assertions.assertFalse(urlParam3.hasParameter("aaa")); + Assertions.assertEquals(urlParam3, URLParam.parse(urlParam3.getRawParam())); + } + + @Test + public void testGetParameter() { + URLParam urlParam1 = URLParam.parse("aaa=aaa&bbb&version=1.0&default.ccc=123"); + Assertions.assertNull(urlParam1.getParameter("abcde")); + + URLParam urlParam2 = URLParam.parse("aaa=aaa&bbb&default.ccc=123"); + Assertions.assertNull(urlParam2.getParameter("version")); + + URLParam urlParam3 = URLParam.parse("aaa=aaa&side=consumer"); + Assertions.assertEquals("consumer", urlParam3.getParameter("side")); + + URLParam urlParam4 = URLParam.parse("aaa=aaa&side=provider"); + Assertions.assertEquals("provider", urlParam4.getParameter("side")); + + } + + @Test + public void testHasParameter() { + URLParam urlParam1 = URLParam.parse("aaa=aaa&side=provider"); + Assertions.assertTrue(urlParam1.hasParameter("aaa")); + Assertions.assertFalse(urlParam1.hasParameter("bbb")); + Assertions.assertTrue(urlParam1.hasParameter("side")); + Assertions.assertFalse(urlParam1.hasParameter("version")); + } + + @Test + public void testRemoveParameters() { + URLParam urlParam1 = URLParam.parse("aaa=aaa&side=provider&version=1.0"); + Assertions.assertTrue(urlParam1.hasParameter("aaa")); + Assertions.assertTrue(urlParam1.hasParameter("side")); + Assertions.assertTrue(urlParam1.hasParameter("version")); + + URLParam urlParam2 = urlParam1.removeParameters("side"); + Assertions.assertFalse(urlParam2.hasParameter("side")); + + URLParam urlParam3 = urlParam1.removeParameters("aaa", "version"); + Assertions.assertFalse(urlParam3.hasParameter("aaa")); + Assertions.assertFalse(urlParam3.hasParameter("version")); + + URLParam urlParam4 = urlParam1.removeParameters(); + Assertions.assertTrue(urlParam4.hasParameter("aaa")); + Assertions.assertTrue(urlParam4.hasParameter("side")); + Assertions.assertTrue(urlParam4.hasParameter("version")); + + URLParam urlParam5 = urlParam1.clearParameters(); + Assertions.assertFalse(urlParam5.hasParameter("aaa")); + Assertions.assertFalse(urlParam5.hasParameter("side")); + Assertions.assertFalse(urlParam5.hasParameter("version")); + + URLParam urlParam6 = urlParam1.removeParameters("aaa"); + Assertions.assertFalse(urlParam6.hasParameter("aaa")); + + URLParam urlParam7 = URLParam.parse("side=consumer").removeParameters("side"); + Assertions.assertFalse(urlParam7.hasParameter("side")); + } + + @Test + public void testAddParameters() { + URLParam urlParam1 = URLParam.parse("aaa=aaa&side=provider"); + Assertions.assertTrue(urlParam1.hasParameter("aaa")); + Assertions.assertTrue(urlParam1.hasParameter("side")); + + URLParam urlParam2 = urlParam1.addParameter("bbb", "bbb"); + Assertions.assertEquals("aaa", urlParam2.getParameter("aaa")); + Assertions.assertEquals("bbb", urlParam2.getParameter("bbb")); + + URLParam urlParam3 = urlParam1.addParameter("aaa", "ccc"); + Assertions.assertEquals("aaa", urlParam1.getParameter("aaa")); + Assertions.assertEquals("ccc", urlParam3.getParameter("aaa")); + + URLParam urlParam4 = urlParam1.addParameter("aaa", "aaa"); + Assertions.assertEquals("aaa", urlParam4.getParameter("aaa")); + + URLParam urlParam5 = urlParam1.addParameter("version", "0.1"); + Assertions.assertEquals("0.1", urlParam5.getParameter("version")); + + URLParam urlParam6 = urlParam5.addParameterIfAbsent("version", "0.2"); + Assertions.assertEquals("0.1", urlParam6.getParameter("version")); + + URLParam urlParam7 = urlParam1.addParameterIfAbsent("version", "0.2"); + Assertions.assertEquals("0.2", urlParam7.getParameter("version")); + + Map map = new HashMap<>(); + map.put("version", "1.0"); + map.put("side", "provider"); + + URLParam urlParam8 = urlParam1.addParameters(map); + Assertions.assertEquals("1.0", urlParam8.getParameter("version")); + Assertions.assertEquals("provider", urlParam8.getParameter("side")); + + map.put("side", "consumer"); + + Assertions.assertEquals("provider", urlParam8.getParameter("side")); + + URLParam urlParam9 = urlParam8.addParameters(map); + Assertions.assertEquals("consumer", urlParam9.getParameter("side")); + + URLParam urlParam10 = urlParam8.addParametersIfAbsent(map); + Assertions.assertEquals("provider", urlParam10.getParameter("side")); + + Assertions.assertThrows(IllegalArgumentException.class, + () -> urlParam1.addParameter("side", "unrecognized")); + } + + @Test + public void testURLParamMap() { + URLParam urlParam1 = URLParam.parse(""); + Assertions.assertTrue(urlParam1.getParameters().isEmpty()); + Assertions.assertEquals(0, urlParam1.getParameters().size()); + Assertions.assertFalse(urlParam1.getParameters().containsKey("aaa")); + Assertions.assertFalse(urlParam1.getParameters().containsKey("version")); + Assertions.assertFalse(urlParam1.getParameters().containsKey(new Object())); + + URLParam urlParam2 = URLParam.parse("aaa=aaa&version=1.0"); + URLParam.URLParamMap urlParam2Map = (URLParam.URLParamMap) urlParam2.getParameters(); + Assertions.assertTrue(urlParam2Map.containsKey("version")); + Assertions.assertFalse(urlParam2Map.containsKey("side")); + + Assertions.assertTrue(urlParam2Map.containsValue("1.0")); + Assertions.assertFalse(urlParam2Map.containsValue("2.0")); + + Assertions.assertEquals("1.0", urlParam2Map.get("version")); + Assertions.assertEquals("aaa", urlParam2Map.get("aaa")); + Assertions.assertNull(urlParam2Map.get(new Object())); + + Assertions.assertEquals(urlParam2, urlParam2Map.getUrlParam()); + + urlParam2Map.put("version", "1.0"); + Assertions.assertEquals(urlParam2, urlParam2Map.getUrlParam()); + + urlParam2Map.putAll(Collections.singletonMap("version", "1.0")); + Assertions.assertEquals(urlParam2, urlParam2Map.getUrlParam()); + + urlParam2Map.put("side", "consumer"); + Assertions.assertNotEquals(urlParam2, urlParam2Map.getUrlParam()); + + urlParam2Map = (URLParam.URLParamMap) urlParam2.getParameters(); + Assertions.assertEquals(urlParam2, urlParam2Map.getUrlParam()); + + urlParam2Map.remove("version"); + Assertions.assertNotEquals(urlParam2, urlParam2Map.getUrlParam()); + Assertions.assertFalse(urlParam2Map.containsValue("version")); + Assertions.assertNull(urlParam2Map.getUrlParam().getParameter("version")); + + urlParam2Map = (URLParam.URLParamMap) urlParam2.getParameters(); + Assertions.assertEquals(urlParam2, urlParam2Map.getUrlParam()); + + urlParam2Map.clear(); + Assertions.assertTrue(urlParam2Map.isEmpty()); + Assertions.assertEquals(0, urlParam2Map.size()); + Assertions.assertNull(urlParam2Map.getUrlParam().getParameter("aaa")); + Assertions.assertNull(urlParam2Map.getUrlParam().getParameter("version")); + + urlParam2Map = (URLParam.URLParamMap) urlParam2.getParameters(); + Assertions.assertEquals(urlParam2, urlParam2Map.getUrlParam()); + + URLParam urlParam3 = URLParam.parse("aaa=aaa&version=1.0"); + Assertions.assertTrue(CollectionUtils.mapEquals(urlParam2Map, urlParam3.getParameters())); + Assertions.assertTrue(CollectionUtils.equals(urlParam2Map.entrySet(), urlParam3.getParameters().entrySet())); + Assertions.assertTrue(CollectionUtils.equals(urlParam2Map.keySet(), urlParam3.getParameters().keySet())); + Assertions.assertTrue(CollectionUtils.equals(urlParam2Map.values(), urlParam3.getParameters().values())); + + URLParam urlParam4 = URLParam.parse("aaa=aaa&version=1.0&side=consumer"); + Assertions.assertFalse(CollectionUtils.mapEquals(urlParam2Map, urlParam4.getParameters())); + Assertions.assertFalse(CollectionUtils.equals(urlParam2Map.entrySet(), urlParam4.getParameters().entrySet())); + Assertions.assertFalse(CollectionUtils.equals(urlParam2Map.keySet(), urlParam4.getParameters().keySet())); + Assertions.assertFalse(CollectionUtils.equals(urlParam2Map.values(), urlParam4.getParameters().values())); + + Set> set = new HashSet<>(); + + set.add(urlParam2Map); + set.add(urlParam3.getParameters()); + Assertions.assertEquals(1,set.size()); + + set.add(urlParam4.getParameters()); + Assertions.assertEquals(2,set.size()); + } + + @Test + public void testMethodParameters() { + URLParam urlParam1 = URLParam.parse("aaa.method1=aaa&bbb.method2=bbb"); + Assertions.assertEquals("aaa",urlParam1.getAnyMethodParameter("method1")); + Assertions.assertEquals("bbb",urlParam1.getAnyMethodParameter("method2")); + + + URLParam urlParam2 = URLParam.parse("methods=aaa&aaa.method1=aaa&bbb.method2=bbb"); + Assertions.assertEquals("aaa",urlParam2.getAnyMethodParameter("method1")); + Assertions.assertNull(urlParam2.getAnyMethodParameter("method2")); + } +} diff --git a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java index 44f77309224..341d0a5702b 100644 --- a/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java +++ b/dubbo-config/dubbo-config-api/src/main/java/org/apache/dubbo/config/ServiceConfig.java @@ -39,6 +39,7 @@ import org.apache.dubbo.event.EventDispatcher; import org.apache.dubbo.metadata.MetadataService; import org.apache.dubbo.metadata.ServiceNameMapping; +import org.apache.dubbo.metadata.ServiceNameMappingHandler; import org.apache.dubbo.registry.client.metadata.MetadataUtils; import org.apache.dubbo.rpc.Exporter; import org.apache.dubbo.rpc.Invoker; @@ -218,7 +219,8 @@ public void exported() { List exportedURLs = this.getExportedUrls(); exportedURLs.forEach(url -> { Map parameters = getApplication().getParameters(); - ServiceNameMapping.getExtension(parameters != null ? parameters.get(MAPPING_KEY) : null).map(url); + ServiceNameMapping serviceNameMapping = ServiceNameMapping.getExtension(parameters != null ? parameters.get(MAPPING_KEY) : null); + ServiceNameMappingHandler.map(serviceNameMapping, url); }); // dispatch a ServiceConfigExportedEvent since 2.7.4 dispatch(new ServiceConfigExportedEvent(this)); @@ -579,7 +581,9 @@ private String findConfigedHosts(ProtocolConfig protocolConfig, if (isInvalidLocalHost(hostToBind)) { anyhost = true; try { - logger.info("No valid ip found from environment, try to find valid host from DNS."); + if(logger.isDebugEnabled()) { + logger.info("No valid ip found from environment, try to find valid host from DNS."); + } hostToBind = InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { logger.warn(e.getMessage(), e); diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java index eccf786bb39..ce81e98e96e 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/DubboBootstrapTest.java @@ -109,17 +109,15 @@ public void testLoadRegistries() { interfaceConfig.checkRegistry(); ApplicationModel.getEnvironment().setDynamicConfiguration(new CompositeDynamicConfiguration()); List urls = ConfigValidationUtils.loadRegistries(interfaceConfig, true); - Assertions.assertEquals(2, urls.size()); - Assertions.assertEquals("service-discovery-registry", urls.get(0).getProtocol()); - Assertions.assertEquals("registry", urls.get(1).getProtocol()); - for (URL url : urls) { - Assertions.assertEquals("addr1:9090", url.getAddress()); - Assertions.assertEquals(RegistryService.class.getName(), url.getPath()); - Assertions.assertTrue(url.getParameters().containsKey("timestamp")); - Assertions.assertTrue(url.getParameters().containsKey("pid")); - Assertions.assertTrue(url.getParameters().containsKey("registry")); - Assertions.assertTrue(url.getParameters().containsKey("dubbo")); - } + Assertions.assertEquals(1, urls.size()); + URL url = urls.get(0); + Assertions.assertEquals("registry", url.getProtocol()); + Assertions.assertEquals("addr1:9090", url.getAddress()); + Assertions.assertEquals(RegistryService.class.getName(), url.getPath()); + Assertions.assertTrue(url.getParameters().containsKey("timestamp")); + Assertions.assertTrue(url.getParameters().containsKey("pid")); + Assertions.assertTrue(url.getParameters().containsKey("registry")); + Assertions.assertTrue(url.getParameters().containsKey("dubbo")); } diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/AbstractInterfaceBuilderTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/AbstractInterfaceBuilderTest.java index c3bb9cff566..2e9857ab315 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/AbstractInterfaceBuilderTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/bootstrap/builders/AbstractInterfaceBuilderTest.java @@ -247,6 +247,7 @@ void scope() { void build() { MonitorConfig monitorConfig = new MonitorConfig("123"); ApplicationConfig applicationConfig = new ApplicationConfig(); + applicationConfig.setName("appName"); ModuleConfig moduleConfig = new ModuleConfig(); RegistryConfig registryConfig = new RegistryConfig(); MetadataReportConfig metadataReportConfig = new MetadataReportConfig(); diff --git a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java index 3190f34b580..0022aac7bb8 100644 --- a/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java +++ b/dubbo-config/dubbo-config-api/src/test/java/org/apache/dubbo/config/event/listener/PublishingServiceDefinitionListenerTest.java @@ -17,6 +17,7 @@ package org.apache.dubbo.config.event.listener; import org.apache.dubbo.config.ApplicationConfig; +import org.apache.dubbo.config.ProtocolConfig; import org.apache.dubbo.config.RegistryConfig; import org.apache.dubbo.config.ServiceConfig; import org.apache.dubbo.config.bootstrap.EchoService; @@ -65,8 +66,12 @@ public void reset() { */ @Test public void testOnServiceConfigExportedEvent() { + ProtocolConfig protocolConfig = new ProtocolConfig("dubbo"); + protocolConfig.setPort(-1); + ServiceConfig serviceConfig = new ServiceConfig<>(); serviceConfig.setInterface(EchoService.class); + serviceConfig.setProtocol(protocolConfig); serviceConfig.setRef(new EchoServiceImpl()); serviceConfig.setRegistry(new RegistryConfig("N/A")); serviceConfig.export(); diff --git a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java index cca467f7bf2..ac46c7e2879 100644 --- a/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-nacos/src/main/java/org/apache/dubbo/configcenter/support/nacos/NacosDynamicConfiguration.java @@ -20,10 +20,12 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.config.configcenter.ConfigChangeType; import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent; +import org.apache.dubbo.common.config.configcenter.ConfigItem; import org.apache.dubbo.common.config.configcenter.ConfigurationListener; import org.apache.dubbo.common.config.configcenter.DynamicConfiguration; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.MD5Utils; import org.apache.dubbo.common.utils.StringUtils; import com.alibaba.fastjson.JSON; @@ -35,11 +37,8 @@ import com.alibaba.nacos.api.config.listener.AbstractSharedListener; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.client.config.http.HttpAgent; -import com.alibaba.nacos.client.config.impl.HttpSimpleClient; -import java.io.IOException; import java.lang.reflect.Field; -import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; @@ -51,12 +50,9 @@ import java.util.concurrent.Executor; import java.util.stream.Stream; -import static com.alibaba.nacos.api.PropertyKeyConst.ENCODE; import static com.alibaba.nacos.api.PropertyKeyConst.NAMING_LOAD_CACHE_AT_START; import static com.alibaba.nacos.api.PropertyKeyConst.SERVER_ADDR; import static com.alibaba.nacos.client.naming.utils.UtilAndComs.NACOS_NAMING_LOG_NAME; -import static java.util.Arrays.asList; -import static java.util.Collections.emptyList; import static org.apache.dubbo.common.constants.RemotingConstants.BACKUP_KEY; import static org.apache.dubbo.common.utils.StringConstantFieldValuePredicate.of; import static org.apache.dubbo.common.utils.StringUtils.HYPHEN_CHAR; @@ -188,7 +184,8 @@ private NacosConfigListener createTargetListener(String key, String group) { public void addListener(String key, String group, ConfigurationListener listener) { String resolvedGroup = resolveGroup(group); String listenerKey = buildListenerKey(key, group); - NacosConfigListener nacosConfigListener = watchListenerMap.computeIfAbsent(listenerKey, k -> createTargetListener(key, resolvedGroup)); + NacosConfigListener nacosConfigListener = + watchListenerMap.computeIfAbsent(listenerKey, k -> createTargetListener(key, resolvedGroup)); nacosConfigListener.addListener(listener); try { configService.addListener(key, resolvedGroup, nacosConfigListener); @@ -221,6 +218,16 @@ public String getConfig(String key, String group, long timeout) throws IllegalSt return null; } + @Override + public ConfigItem getConfigItem(String key, String group) { + String content = getConfig(key, group); + String casMd5 = ""; + if (StringUtils.isNotEmpty(content)) { + casMd5 = MD5Utils.getMd5(content); + } + return new ConfigItem(content, casMd5); + } + @Override public Object getInternalProperty(String key) { try { @@ -243,6 +250,21 @@ public boolean publishConfig(String key, String group, String content) { return published; } + @Override + public boolean publishConfigCas(String key, String group, String content, Object stat) { + boolean published = false; + String resolvedGroup = resolveGroup(group); + try { + if (!(null != stat && stat instanceof String)) { + throw new IllegalArgumentException("nacos publishConfigCas requires stat of string type"); + } + published = configService.publishConfigCas(key, resolvedGroup, content, (String) stat); + } catch (NacosException e) { + logger.error(e.getErrMsg(), e); + } + return published; + } + @Override public long getDefaultTimeout() { return DEFAULT_TIMEOUT; @@ -258,23 +280,6 @@ public long getDefaultTimeout() { public SortedSet getConfigKeys(String group) { // TODO use Nacos Client API to replace HTTP Open API SortedSet keys = new TreeSet<>(); - try { - List paramsValues = asList( - "search", "accurate", - "dataId", "", - "group", resolveGroup(group), - "pageNo", "1", - "pageSize", String.valueOf(Integer.MAX_VALUE) - ); - String encoding = getProperty(ENCODE, "UTF-8"); - HttpSimpleClient.HttpResult result = httpAgent.httpGet(GET_CONFIG_KEYS_PATH, emptyList(), paramsValues, encoding, 5 * 1000); - Stream keysStream = toKeysStream(result.content); - keysStream.forEach(keys::add); - } catch (IOException e) { - if (logger.isErrorEnabled()) { - logger.error(e.getMessage(), e); - } - } return keys; } @@ -362,4 +367,9 @@ protected String buildListenerKey(String key, String group) { protected String resolveGroup(String group) { return isBlank(group) ? group : group.replace(SLASH_CHAR, HYPHEN_CHAR); } + + @Override + public boolean hasSupportCas() { + return true; + } } diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java index fbdbf749eaa..a4c35110b46 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/main/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfiguration.java @@ -17,6 +17,7 @@ package org.apache.dubbo.configcenter.support.zookeeper; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.configcenter.ConfigItem; import org.apache.dubbo.common.config.configcenter.ConfigurationListener; import org.apache.dubbo.common.config.configcenter.TreePathDynamicConfiguration; import org.apache.dubbo.common.utils.NamedThreadFactory; @@ -88,11 +89,24 @@ protected boolean doPublishConfig(String pathKey, String content) throws Excepti return true; } + @Override + public boolean publishConfigCas(String key, String group, String content, Object stat) { + String pathKey = buildPathKey(group, key); + zkClient.createOrUpdate(pathKey, content, false, stat); + return true; + } + @Override protected String doGetConfig(String pathKey) throws Exception { return zkClient.getContent(pathKey); } + @Override + public ConfigItem getConfigItem(String key, String group) { + String pathKey = buildPathKey(group, key); + return zkClient.getConfigItem(pathKey); + } + @Override protected boolean doRemoveConfig(String pathKey) throws Exception { zkClient.delete(pathKey); @@ -113,4 +127,9 @@ protected void doAddListener(String pathKey, ConfigurationListener listener) { protected void doRemoveListener(String pathKey, ConfigurationListener listener) { cacheListener.removeListener(pathKey, listener); } + + @Override + public boolean hasSupportCas() { + return true; + } } diff --git a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java index 9d4a0c36d3f..e40e231b4e6 100644 --- a/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java +++ b/dubbo-configcenter/dubbo-configcenter-zookeeper/src/test/java/org/apache/dubbo/configcenter/support/zookeeper/ZookeeperDynamicConfigurationTest.java @@ -18,6 +18,7 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.config.configcenter.ConfigChangedEvent; +import org.apache.dubbo.common.config.configcenter.ConfigItem; import org.apache.dubbo.common.config.configcenter.ConfigurationListener; import org.apache.dubbo.common.config.configcenter.DynamicConfiguration; import org.apache.dubbo.common.config.configcenter.DynamicConfigurationFactory; @@ -75,7 +76,8 @@ public static void setUp() throws Exception { configUrl = URL.valueOf("zookeeper://127.0.0.1:" + zkServerPort); - configuration = ExtensionLoader.getExtensionLoader(DynamicConfigurationFactory.class).getExtension(configUrl.getProtocol()).getDynamicConfiguration(configUrl); + configuration = ExtensionLoader.getExtensionLoader(DynamicConfigurationFactory.class).getExtension(configUrl.getProtocol()) + .getDynamicConfiguration(configUrl); } @AfterAll @@ -137,6 +139,24 @@ public void testPublishConfig() { assertEquals("test", configuration.getProperties(key, group)); } + @Test + public void testPublishConfigCas() { + String key = "user-service-cas"; + String group = "org.apache.dubbo.service.UserService"; + String content = "test"; + ConfigItem configItem = configuration.getConfigItem(key, group); + assertTrue(configuration.publishConfigCas(key, group, content, configItem.getStat())); + configItem = configuration.getConfigItem(key, group); + assertEquals("test", configItem.getContent()); + assertTrue(configuration.publishConfigCas(key, group, "newtest", configItem.getStat())); + try { + configuration.publishConfigCas(key, group, "newtest2", configItem.getStat()); + } catch (Exception e) { + assertTrue(e.getMessage().contains("KeeperErrorCode = BadVersion")); + } + assertEquals("newtest", configuration.getConfigItem(key, group).getContent()); + } + @Test public void testGetConfigKeysAndContents() { diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml index 1f83b3f2ca0..675b7c74ecb 100644 --- a/dubbo-dependencies-bom/pom.xml +++ b/dubbo-dependencies-bom/pom.xml @@ -132,7 +132,7 @@ 3.0.19.Final 8.5.31 0.5.3 - 1.3.1 + 2.0.0 1.31.1 1.7.25 diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java index b38b0323c77..bac064ad837 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/DynamicConfigurationServiceNameMapping.java @@ -17,9 +17,12 @@ package org.apache.dubbo.metadata; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.configcenter.ConfigItem; import org.apache.dubbo.common.config.configcenter.DynamicConfiguration; +import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.utils.StringUtils; import java.util.Collections; import java.util.LinkedHashSet; @@ -28,6 +31,8 @@ import static java.lang.String.valueOf; import static java.util.Arrays.asList; +import static org.apache.dubbo.common.constants.CommonConstants.COMMA_SEPARATOR; +import static org.apache.dubbo.common.utils.CollectionUtils.ofSet; import static org.apache.dubbo.rpc.model.ApplicationModel.getName; /** @@ -41,6 +46,8 @@ public class DynamicConfigurationServiceNameMapping implements ServiceNameMappin private final Logger logger = LoggerFactory.getLogger(getClass()); + private static final int PUBLISH_CONFIG_RETRY_TIMES = 6; + @Override public void map(URL url) { String serviceInterface = url.getServiceInterface(); @@ -62,13 +69,24 @@ public void map(URL url) { execute(() -> { dynamicConfiguration.publishConfig(key, ServiceNameMapping.buildGroup(serviceInterface, group, version, protocol), content); - if (logger.isInfoEnabled()) { + if (logger.isDebugEnabled()) { logger.info(String.format("Dubbo service[%s] mapped to interface name[%s].", group, serviceInterface, group)); } }); } + @Override + public void mapWithCas(URL url) { + String serviceInterface = url.getServiceInterface(); + if (IGNORED_SERVICE_INTERFACES.contains(serviceInterface)) { + return; + } + execute(() -> { + publishConfigCas(serviceInterface, DEFAULT_MAPPING_GROUP, getName()); + }); + } + @Override public Set getAndListen(URL url, MappingListener mappingListener) { String serviceInterface = url.getServiceInterface(); @@ -86,6 +104,50 @@ public Set getAndListen(URL url, MappingListener mappingListener) { return Collections.unmodifiableSet(serviceNames); } + @Override + public Set getAndListenWithNewStore(URL url, MappingListener mappingListener) { + String serviceInterface = url.getServiceInterface(); + DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration(); + Set serviceNames = new LinkedHashSet<>(); + execute(() -> { + String configContent = dynamicConfiguration.getConfig(serviceInterface, DEFAULT_MAPPING_GROUP); + if (null != configContent) { + String[] split = StringUtils.split(configContent, CommonConstants.COMMA_SEPARATOR_CHAR); + serviceNames.addAll(ofSet(split)); + } + }); + return Collections.unmodifiableSet(serviceNames); + } + + /** + * publish config with cas. + * + * @param key + * @param group + * @param appName + * @return + */ + private boolean publishConfigCas(String key, String group, String appName) { + int currentRetryTimes = 1; + boolean result = false; + DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration(); + String newConfigContent = appName; + do { + ConfigItem configItem = dynamicConfiguration.getConfigItem(key, group); + String oldConfigContent = configItem.getContent(); + if (StringUtils.isNotEmpty(oldConfigContent)) { + boolean contains = StringUtils.isContains(configItem.getContent(), appName); + if (contains) { + return true; + } + newConfigContent = oldConfigContent + COMMA_SEPARATOR + appName; + } + result = dynamicConfiguration.publishConfigCas(key, group, newConfigContent, configItem.getStat()); + } while (!result && currentRetryTimes++ <= PUBLISH_CONFIG_RETRY_TIMES); + + return result; + } + private void execute(Runnable runnable) { try { runnable.run(); diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java index 4106ad29a90..afd1ec92aad 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/MetadataInfo.java @@ -448,12 +448,16 @@ public boolean equals(Object obj) { } ServiceInfo serviceInfo = (ServiceInfo) obj; - return this.getMatchKey().equals(serviceInfo.getMatchKey()) && this.getParams().equals(serviceInfo.getParams()); +// return this.getMatchKey().equals(serviceInfo.getMatchKey()) && this.getParams().equals(serviceInfo.getParams()); + // Please check ServiceInstancesChangedListener.localServiceToRevisions before change this behaviour. + return this.getMatchKey().equals(serviceInfo.getMatchKey()); } @Override public int hashCode() { - return Objects.hash(getMatchKey(), getParams()); +// return Objects.hash(getMatchKey(), getParams()); + return Objects.hash(getMatchKey()); + } @Override diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java index 3b3af2be909..58bca71a59e 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/RevisionResolver.java @@ -16,44 +16,16 @@ */ package org.apache.dubbo.metadata; -import org.apache.dubbo.common.logger.Logger; -import org.apache.dubbo.common.logger.LoggerFactory; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; - -import static java.nio.charset.StandardCharsets.UTF_8; +import org.apache.dubbo.common.utils.MD5Utils; public class RevisionResolver { - private static final Logger logger = LoggerFactory.getLogger(RevisionResolver.class); + public static final String EMPTY_REVISION = "0"; - private static final char[] hexDigits = { - '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' - }; - - private static MessageDigest mdInst; - - static { - try { - mdInst = MessageDigest.getInstance("MD5"); - } catch (NoSuchAlgorithmException e) { - logger.error("Failed to calculate metadata revision", e); - } - } + public static String calRevision(String metadata) { - mdInst.update(metadata.getBytes(UTF_8)); - byte[] md5 = mdInst.digest(); - - int j = md5.length; - char str[] = new char[j * 2]; - int k = 0; - for (int i = 0; i < j; i++) { - byte byte0 = md5[i]; - str[k++] = hexDigits[byte0 >>> 4 & 0xf]; - str[k++] = hexDigits[byte0 & 0xf]; - } - return new String(str); + return MD5Utils.getMd5(metadata); } public static String getEmptyRevision(String app) { diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java index 5282deac481..a233910473f 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMapping.java @@ -19,6 +19,7 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.SPI; +import java.util.Collections; import java.util.Set; import static org.apache.dubbo.common.constants.CommonConstants.CONFIG_MAPPING_TYPE; @@ -41,6 +42,15 @@ public interface ServiceNameMapping { */ void map(URL url); + /** + * Map the specified Dubbo service interface, group, version and protocol to current Dubbo service name with cas. + * + * @param url + */ + default void mapWithCas(URL url) { + + } + /** * Get the service names from the specified Dubbo service interface, group, version and protocol * @@ -48,6 +58,19 @@ public interface ServiceNameMapping { */ Set getAndListen(URL url, MappingListener mappingListener); + /** + * service name mapping new store structure. + * interface(key) + * -- mapping(group) + * --appName1,appName2,appName3(content) + * @param url + * @param mappingListener + * @return + */ + default Set getAndListenWithNewStore(URL url, MappingListener mappingListener){ + return Collections.emptySet(); + }; + default Set get(URL url) { return getAndListen(url, null); } @@ -83,5 +106,4 @@ static String buildGroup(String serviceInterface, String group, String version, // return groupBuilder.toString(); return DEFAULT_MAPPING_GROUP + SLASH + serviceInterface; } - } diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMappingHandler.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMappingHandler.java new file mode 100644 index 00000000000..12479f266ba --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMappingHandler.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.metadata; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.configcenter.DynamicConfiguration; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; + +public class ServiceNameMappingHandler { + public static final String DUBBO_SERVICENAME_STORE = "dubbo.application.service-name.store"; + private static final Logger logger = LoggerFactory.getLogger(ServiceNameMappingHandler.class); + private static final ServiceNameMappingStoreEnum DEFAULT_STORE_TYPE = ServiceNameMappingStoreEnum.BOTH_STORAGE; + + private final ServiceNameMapping serviceNameMapping; + private final URL url; + + public ServiceNameMappingHandler(ServiceNameMapping serviceNameMapping, URL url) { + this.serviceNameMapping = serviceNameMapping; + this.url = url; + } + + public static void map(ServiceNameMapping serviceNameMapping, URL url) { + new ServiceNameMappingHandler(serviceNameMapping, url).init(); + } + + public void init() { + DynamicConfiguration dynamicConfiguration = DynamicConfiguration.getDynamicConfiguration(); + + ServiceNameMappingStoreEnum storeType = DEFAULT_STORE_TYPE; + boolean hasSupportCas = dynamicConfiguration.hasSupportCas(); + if (!hasSupportCas) { + storeType = ServiceNameMappingStoreEnum.APPLICANT_INTERFACE_STORAGE; + } + doMap(storeType); + } + + public void doMap(ServiceNameMappingStoreEnum storeType) { + if (null == storeType) { + throw new IllegalStateException("storeType of serviceNameMapping cannot be null"); + } + switch (storeType) { + case INTERFACE_APPLICATION_STORAGE: + serviceNameMapping.mapWithCas(url); + break; + case APPLICANT_INTERFACE_STORAGE: + serviceNameMapping.map(url); + break; + case BOTH_STORAGE: + serviceNameMapping.map(url); + serviceNameMapping.mapWithCas(url); + break; + default: + serviceNameMapping.map(url); + } + } +} diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMappingStoreEnum.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMappingStoreEnum.java new file mode 100644 index 00000000000..2aa5b9d4f47 --- /dev/null +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/ServiceNameMappingStoreEnum.java @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.metadata; + +import java.util.HashMap; +import java.util.Map; + +public enum ServiceNameMappingStoreEnum { + + INTERFACE_APPLICATION_STORAGE("interface_application_storage"), + APPLICANT_INTERFACE_STORAGE("applicant_interface_storage"), + BOTH_STORAGE("both_storage"); + + private String storeType; + + private static final Map storeMap = new HashMap<>(); + + static { + for (ServiceNameMappingStoreEnum serviceNameMappingStoreEnum : values()) { + storeMap.put(serviceNameMappingStoreEnum.getStoreType(), serviceNameMappingStoreEnum); + } + } + + + ServiceNameMappingStoreEnum(String storeType) { + this.storeType = storeType; + } + + public String getStoreType() { + return this.storeType; + } + + public static ServiceNameMappingStoreEnum getStoreEnumByStoreType(String storeType) { + return storeMap.get(storeType); + } +} diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java index 84a7b73d2fc..1782ef7788c 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java +++ b/dubbo-metadata/dubbo-metadata-api/src/main/java/org/apache/dubbo/metadata/WritableMetadataService.java @@ -21,7 +21,6 @@ import org.apache.dubbo.common.extension.SPI; import org.apache.dubbo.rpc.model.ApplicationModel; -import java.util.Map; import java.util.Set; import static org.apache.dubbo.common.extension.ExtensionLoader.getExtensionLoader; @@ -88,8 +87,6 @@ default URL getMetadataServiceURL() { void putCachedMapping(String serviceKey, Set apps); - Map> getCachedMapping(); - Set getCachedMapping(String mappingKey); Set getCachedMapping(URL consumerURL); diff --git a/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataParamsFilter b/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataParamsFilter index 6754126774a..0d903f39b02 100644 --- a/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataParamsFilter +++ b/dubbo-metadata/dubbo-metadata-api/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.metadata.MetadataParamsFilter @@ -1 +1 @@ -default=org.apache.dubbo.metadata.DefaultMetadataParamsFilter \ No newline at end of file +dubbo=org.apache.dubbo.metadata.DefaultMetadataParamsFilter \ No newline at end of file diff --git a/dubbo-metadata/dubbo-metadata-processor/src/test/java/org/apache/dubbo/metadata/annotation/processing/util/TypeUtilsTest.java b/dubbo-metadata/dubbo-metadata-processor/src/test/java/org/apache/dubbo/metadata/annotation/processing/util/TypeUtilsTest.java index 55d866ebaac..fc5995b7f32 100644 --- a/dubbo-metadata/dubbo-metadata-processor/src/test/java/org/apache/dubbo/metadata/annotation/processing/util/TypeUtilsTest.java +++ b/dubbo-metadata/dubbo-metadata-processor/src/test/java/org/apache/dubbo/metadata/annotation/processing/util/TypeUtilsTest.java @@ -25,6 +25,7 @@ import org.apache.dubbo.metadata.tools.GenericTestService; import org.apache.dubbo.metadata.tools.TestServiceImpl; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import javax.lang.model.element.Element; @@ -450,6 +451,7 @@ public void testListTypeElements() { } @Test + @Disabled public void testGetResource() throws URISyntaxException { URL resource = getResource(processingEnv, testType); assertNotNull(resource); diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceDiscoveryFactory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceDiscoveryFactory.java index 150b7a7eac9..780222ee611 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceDiscoveryFactory.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/DefaultServiceDiscoveryFactory.java @@ -29,7 +29,6 @@ * extension name by which the {@link ServiceDiscovery} instance is loaded. * * @see AbstractServiceDiscoveryFactory - * @see EventPublishingServiceDiscovery * @since 2.7.5 */ public class DefaultServiceDiscoveryFactory extends AbstractServiceDiscoveryFactory { diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscovery.java deleted file mode 100644 index f9c80198ab0..00000000000 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/EventPublishingServiceDiscovery.java +++ /dev/null @@ -1,330 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.dubbo.registry.client; - -import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.function.ThrowableAction; -import org.apache.dubbo.common.logger.Logger; -import org.apache.dubbo.common.logger.LoggerFactory; -import org.apache.dubbo.common.utils.Page; -import org.apache.dubbo.event.Event; -import org.apache.dubbo.event.EventDispatcher; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryDestroyedEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryDestroyingEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryExceptionEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryInitializedEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryInitializingEvent; -import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstancePreUnregisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstanceRegisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstanceUnregisteredEvent; -import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.concurrent.atomic.AtomicBoolean; - -import static java.util.Optional.empty; -import static java.util.Optional.of; - -/** - * The decorating implementation of {@link ServiceDiscovery} to published the {@link Event Dubbo event} when some actions are - * executing, including: - *

    - *
  • Lifecycle actions:
  • - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    ActionbeforeAfter
    {@link #INITIALIZE_ACTION start}{@link ServiceDiscoveryInitializingEvent}{@link ServiceDiscoveryInitializedEvent}
    {@link #DESTROY_ACTION stop}{@link ServiceDiscoveryDestroyingEvent}{@link ServiceDiscoveryDestroyedEvent}
    - *
  • Registration actions:
  • - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    ActionbeforeAfter
    {@link #REGISTER_ACTION register}{@link ServiceInstancePreRegisteredEvent}{@link ServiceInstanceRegisteredEvent}
    {@link #UPDATE_ACTION update}N/AN/A
    {@link #UNREGISTER_ACTION unregister}N/AN/A
    - *
- * - * @see ServiceDiscovery - * @see ServiceDiscoveryInitializingEvent - * @see ServiceDiscoveryInitializedEvent - * @see ServiceInstancePreRegisteredEvent - * @see ServiceInstanceRegisteredEvent - * @see ServiceDiscoveryDestroyingEvent - * @see ServiceDiscoveryDestroyedEvent - * @since 2.7.5 - */ -final class EventPublishingServiceDiscovery implements ServiceDiscovery { - - /** - * @see ServiceInstancePreRegisteredEvent - * @see ServiceInstanceRegisteredEvent - */ - protected static final String REGISTER_ACTION = "register"; - - protected static final String UPDATE_ACTION = "update"; - - protected static final String UNREGISTER_ACTION = "unregister"; - - /** - * @see ServiceDiscoveryInitializingEvent - * @see ServiceDiscoveryInitializedEvent - */ - protected static final String INITIALIZE_ACTION = "initialize"; - - /** - * @see ServiceDiscoveryDestroyingEvent - * @see ServiceDiscoveryDestroyedEvent - */ - protected static final String DESTROY_ACTION = "destroy"; - - protected final EventDispatcher eventDispatcher = EventDispatcher.getDefaultExtension(); - - protected final AtomicBoolean initialized = new AtomicBoolean(false); - - protected final AtomicBoolean destroyed = new AtomicBoolean(false); - - protected final Logger logger = LoggerFactory.getLogger(getClass()); - - private final ServiceDiscovery serviceDiscovery; - - protected EventPublishingServiceDiscovery(ServiceDiscovery serviceDiscovery) { - if (serviceDiscovery == null) { - throw new NullPointerException("The ServiceDiscovery argument must not be null!"); - } - this.serviceDiscovery = serviceDiscovery; - } - - @Override - public final void register(ServiceInstance serviceInstance) throws RuntimeException { - - assertDestroyed(REGISTER_ACTION); - assertInitialized(REGISTER_ACTION); - - executeWithEvents( - of(new ServiceInstancePreRegisteredEvent(serviceDiscovery, serviceInstance)), - () -> serviceDiscovery.register(serviceInstance), - of(new ServiceInstanceRegisteredEvent(serviceDiscovery, serviceInstance)) - ); - } - - @Override - public final void update(ServiceInstance serviceInstance) throws RuntimeException { - - assertDestroyed(UPDATE_ACTION); - assertInitialized(UPDATE_ACTION); - - executeWithEvents( - empty(), - () -> serviceDiscovery.update(serviceInstance), - empty() - ); - } - - @Override - public final void unregister(ServiceInstance serviceInstance) throws RuntimeException { - - assertDestroyed(UNREGISTER_ACTION); - assertInitialized(UNREGISTER_ACTION); - - executeWithEvents( - of(new ServiceInstancePreUnregisteredEvent(this, serviceInstance)), - () -> serviceDiscovery.unregister(serviceInstance), - of(new ServiceInstanceUnregisteredEvent(this, serviceInstance)) - ); - } - - @Override - public Set getServices() { - return serviceDiscovery.getServices(); - } - - @Override - public List getInstances(String serviceName) throws NullPointerException { - return serviceDiscovery.getInstances(serviceName); - } - - @Override - public Page getInstances(String serviceName, int offset, int pageSize) throws NullPointerException, IllegalArgumentException { - return serviceDiscovery.getInstances(serviceName, offset, pageSize); - } - - @Override - public Page getInstances(String serviceName, int offset, int pageSize, boolean healthyOnly) throws NullPointerException, IllegalArgumentException { - return serviceDiscovery.getInstances(serviceName, offset, pageSize, healthyOnly); - } - - @Override - public Map> getInstances(Iterable serviceNames, int offset, int requestSize) throws NullPointerException, IllegalArgumentException { - return serviceDiscovery.getInstances(serviceNames, offset, requestSize); - } - - @Override - public String toString() { - return serviceDiscovery.toString(); - } - - @Override - public void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws NullPointerException, IllegalArgumentException { - serviceDiscovery.addServiceInstancesChangedListener(listener); - eventDispatcher.addEventListener(listener); - } - - @Override - public ServiceInstancesChangedListener createListener(Set serviceNames) { - return serviceDiscovery.createListener(serviceNames); - } - - @Override - public void removeServiceInstancesChangedListener(ServiceInstancesChangedListener listener) throws IllegalArgumentException { - serviceDiscovery.removeServiceInstancesChangedListener(listener); - } - - @Override - public long getDelay() { - return serviceDiscovery.getDelay(); - } - - @Override - public URL getUrl() { - return serviceDiscovery.getUrl(); - } - - @Override - public ServiceInstance getLocalInstance() { - return serviceDiscovery.getLocalInstance(); - } - - @Override - public void initialize(URL registryURL) { - - assertInitialized(INITIALIZE_ACTION); - - if (isInitialized()) { - if (logger.isWarnEnabled()) { - logger.warn("It's ignored to start current ServiceDiscovery, because it has been started."); - } - return; - } - - executeWithEvents( - of(new ServiceDiscoveryInitializingEvent(this, serviceDiscovery)), - () -> serviceDiscovery.initialize(registryURL), - of(new ServiceDiscoveryInitializedEvent(this, serviceDiscovery)) - ); - - // doesn't start -> started - initialized.compareAndSet(false, true); - } - - @Override - public void destroy() { - - assertDestroyed(DESTROY_ACTION); - - if (isDestroyed()) { - if (logger.isWarnEnabled()) { - logger.warn("It's ignored to stop current ServiceDiscovery, because it has been stopped."); - } - return; - } - - executeWithEvents( - of(new ServiceDiscoveryDestroyingEvent(this, serviceDiscovery)), - serviceDiscovery::destroy, - of(new ServiceDiscoveryDestroyedEvent(this, serviceDiscovery)) - ); - - // doesn't stop -> stopped - destroyed.compareAndSet(false, true); - } - - protected final void executeWithEvents(Optional beforeEvent, - ThrowableAction action, - Optional afterEvent) { - beforeEvent.ifPresent(this::dispatchEvent); - try { - action.execute(); - } catch (Throwable e) { - dispatchEvent(new ServiceDiscoveryExceptionEvent(this, serviceDiscovery, e)); - } - afterEvent.ifPresent(this::dispatchEvent); - } - - private void dispatchEvent(Event event) { - eventDispatcher.dispatch(event); - } - - public final boolean isInitialized() { - return initialized.get(); - } - - public final boolean isDestroyed() { - return destroyed.get(); - } - - protected void assertDestroyed(String action) throws IllegalStateException { - if (!isInitialized()) { - throw new IllegalStateException("The action[" + action + "] is rejected, because the ServiceDiscovery is not initialized yet."); - } - } - - protected void assertInitialized(String action) throws IllegalStateException { - if (isDestroyed()) { - throw new IllegalStateException("The action[" + action + "] is rejected, because the ServiceDiscovery is destroyed already."); - } - } -} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java index 7537090be01..d33040addd3 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/SelfHostMetaServiceDiscovery.java @@ -137,7 +137,6 @@ private void updateMetadata(ServiceInstance serviceInstance) { // check if metadata updated if (!metadataRevision.equalsIgnoreCase(lastMetadataRevision)) { - logger.info("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString); if (logger.isDebugEnabled()) { logger.debug("Update Service Instance Metadata of DNS registry. Newer metadata: " + metadataString); } @@ -221,7 +220,9 @@ public final void fillServiceInstance(DefaultServiceInstance serviceInstance) { String consumerId = ApplicationModel.getName() + NetUtils.getLocalHost(); String metadata = metadataService.getAndListenInstanceMetadata( consumerId, metadataString -> { - logger.info("Receive callback: " + metadataString + serviceInstance); + if(logger.isDebugEnabled()) { + logger.debug("Receive callback: " + metadataString + serviceInstance); + } if (StringUtils.isEmpty(metadataString)) { // provider is shutdown metadataMap.remove(hostId); diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java index aa318d4bdbb..3636c2f4e08 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscovery.java @@ -210,7 +210,6 @@ default Map> getInstances(Iterable service * @param listener an instance of {@link ServiceInstancesChangedListener} * @throws NullPointerException * @throws IllegalArgumentException - * @see EventPublishingServiceDiscovery * @see EventDispatcher */ default void addServiceInstancesChangedListener(ServiceInstancesChangedListener listener) diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java index 31ae6f967b1..42d4c3ef4c4 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistry.java @@ -112,8 +112,7 @@ public ServiceDiscovery getServiceDiscovery() { * @return non-null */ protected ServiceDiscovery createServiceDiscovery(URL registryURL) { - ServiceDiscovery originalServiceDiscovery = getServiceDiscovery(registryURL); - ServiceDiscovery serviceDiscovery = enhanceEventPublishing(originalServiceDiscovery); + ServiceDiscovery serviceDiscovery = getServiceDiscovery(registryURL); execute(() -> { serviceDiscovery.initialize(registryURL.addParameter(INTERFACE_KEY, ServiceDiscovery.class.getName()) .removeParameter(REGISTRY_TYPE_KEY)); @@ -138,16 +137,6 @@ private ServiceDiscovery getServiceDiscovery(URL registryURL) { return factory.getServiceDiscovery(registryURL); } - /** - * Enhance the original {@link ServiceDiscovery} with event publishing feature - * - * @param original the original {@link ServiceDiscovery} - * @return {@link EventPublishingServiceDiscovery} instance - */ - private ServiceDiscovery enhanceEventPublishing(ServiceDiscovery original) { - return new EventPublishingServiceDiscovery(original); - } - protected boolean shouldRegister(URL providerURL) { String side = providerURL.getSide(); @@ -256,8 +245,14 @@ public void doUnsubscribe(URL url, NotifyListener listener) { String protocolServiceKey = url.getServiceKey() + GROUP_CHAR_SEPARATOR + url.getParameter(PROTOCOL_KEY, DUBBO); Set serviceNames = writableMetadataService.getCachedMapping(url); if (CollectionUtils.isNotEmpty(serviceNames)) { - ServiceInstancesChangedListener instancesChangedListener = serviceListeners.get(toStringKeys(serviceNames)); - instancesChangedListener.removeListener(protocolServiceKey); + String serviceNamesKey = toStringKeys(serviceNames); + ServiceInstancesChangedListener instancesChangedListener = serviceListeners.get(serviceNamesKey); + if (instancesChangedListener != null) { + instancesChangedListener.removeListener(protocolServiceKey); + if (!instancesChangedListener.hasListeners()) { + serviceListeners.remove(serviceNamesKey); + } + } } } diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java index f1af5bdf736..505142e7ebe 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceDiscoveryRegistryDirectory.java @@ -77,6 +77,9 @@ public boolean isAvailable() { @Override public synchronized void notify(List instanceUrls) { + if (isDestroyed()) { + return; + } // Set the context of the address notification thread. RpcContext.setRpcContext(getConsumerUrl()); @@ -106,7 +109,7 @@ public boolean isServiceDiscovery() { */ @Override public boolean isNotificationReceived() { - return serviceListener.isDestroyed() + return serviceListener == null || serviceListener.isDestroyed() || serviceListener.getAllInstances().size() == serviceListener.getServiceNames().size(); } @@ -237,7 +240,9 @@ protected void destroyAllInvokers() { } localUrlInvokerMap.clear(); } - invokers = null; + + this.urlInvokerMap = null; + this.invokers = null; } /** diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java index 11f2c2841c1..99cffc122ef 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/ServiceInstanceCustomizer.java @@ -18,10 +18,9 @@ import org.apache.dubbo.common.extension.SPI; import org.apache.dubbo.common.lang.Prioritized; -import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; /** - * The interface to customize {@link ServiceInstance the service instance} on {@link ServiceInstancePreRegisteredEvent} + * The interface to customize {@link ServiceInstance the service instance} * * @see ServiceInstance#getMetadata() * @since 2.7.5 diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListener.java deleted file mode 100644 index 91fb07d7149..00000000000 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListener.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.dubbo.registry.client.event.listener; - -import org.apache.dubbo.common.logger.Logger; -import org.apache.dubbo.common.logger.LoggerFactory; -import org.apache.dubbo.event.Event; -import org.apache.dubbo.event.GenericEventListener; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryDestroyedEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryDestroyingEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryInitializedEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryInitializingEvent; -import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstancePreUnregisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstanceRegisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstanceUnregisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent; - -import static java.lang.String.format; - -/** - * A listener for logging the {@link Event Dubbo event} - * - * @since 2.7.5 - */ -public class LoggingEventListener extends GenericEventListener { - - private final Logger logger = LoggerFactory.getLogger(getClass()); - - public void onEvent(ServiceDiscoveryInitializingEvent event) { - info("%s is initializing...", event.getServiceDiscovery()); - } - - public void onEvent(ServiceDiscoveryInitializedEvent event) { - info("%s is initialized.", event.getServiceDiscovery()); - } - - public void onEvent(ServiceInstancePreRegisteredEvent event) { - info("%s is registering into %s...", event.getServiceInstance(), event.getSource()); - } - - public void onEvent(ServiceInstanceRegisteredEvent event) { - info("%s has been registered into %s.", event.getServiceInstance(), event.getSource()); - } - - public void onEvent(ServiceInstancesChangedEvent event) { - info("The services'[name : %s] instances[size : %s] has been changed.", event.getServiceName(), event.getServiceInstances().size()); - } - - public void onEvent(ServiceInstancePreUnregisteredEvent event) { - info("%s is registering from %s...", event.getServiceInstance(), event.getSource()); - } - - public void onEvent(ServiceInstanceUnregisteredEvent event) { - info("%s has been unregistered from %s.", event.getServiceInstance(), event.getSource()); - } - - public void onEvent(ServiceDiscoveryDestroyingEvent event) { - info("%s is stopping...", event.getServiceDiscovery()); - } - - public void onEvent(ServiceDiscoveryDestroyedEvent event) { - info("%s is stopped.", event.getServiceDiscovery()); - } - - private void info(String pattern, Object... args) { - if (logger.isInfoEnabled()) { - logger.info(format(pattern, args)); - } - } -} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java index 57aadd5cf49..1517ed7dbda 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/listener/ServiceInstancesChangedListener.java @@ -47,6 +47,7 @@ import java.util.Objects; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.Semaphore; @@ -88,7 +89,7 @@ public class ServiceInstancesChangedListener implements ConditionalEventListener public ServiceInstancesChangedListener(Set serviceNames, ServiceDiscovery serviceDiscovery) { this.serviceNames = serviceNames; this.serviceDiscovery = serviceDiscovery; - this.listeners = new HashMap<>(); + this.listeners = new ConcurrentHashMap<>(); this.allInstances = new HashMap<>(); this.serviceUrls = new HashMap<>(); this.revisionToMetadata = new HashMap<>(); @@ -101,6 +102,9 @@ public ServiceInstancesChangedListener(Set serviceNames, ServiceDiscover * @param event {@link ServiceInstancesChangedEvent} */ public synchronized void onEvent(ServiceInstancesChangedEvent event) { + if (destroyed.get()) { + return; + } if (this.isRetryAndExpired(event)) { return; } @@ -120,7 +124,9 @@ public synchronized void onEvent(ServiceInstancesChangedEvent event) { for (ServiceInstance instance : instances) { String revision = getExportedServicesRevision(instance); if (EMPTY_REVISION.equals(revision)) { - logger.info("Find instance without valid service metadata: " + instance.getAddress()); + if(logger.isDebugEnabled()) { + logger.debug("Find instance without valid service metadata: " + instance.getAddress()); + } continue; } List subInstances = revisionToInstances.computeIfAbsent(revision, r -> new LinkedList<>()); @@ -134,7 +140,9 @@ public synchronized void onEvent(ServiceInstancesChangedEvent event) { } } - logger.info(newRevisionToMetadata.size() + " unique revisions: " + newRevisionToMetadata.keySet()); + if(logger.isDebugEnabled()) { + logger.debug(newRevisionToMetadata.size() + " unique revisions: " + newRevisionToMetadata.keySet()); + } if (hasEmptyMetadata(newRevisionToMetadata)) {// retry every 10 seconds if (retryPermission.tryAcquire()) { @@ -184,6 +192,10 @@ public void removeListener(String serviceKey) { } } + public boolean hasListeners() { + return CollectionUtils.isNotEmptyMap(listeners); + } + /** * Get the correlative service name * @@ -262,9 +274,16 @@ protected boolean hasEmptyMetadata(Map revisionToMetadata) protected MetadataInfo getRemoteMetadata(ServiceInstance instance, String revision, Map> localServiceToRevisions, List subInstances) { MetadataInfo metadata = revisionToMetadata.get(revision); + if (metadata != null && metadata != MetadataInfo.EMPTY) { + if (logger.isDebugEnabled()) { + logger.debug("MetadataInfo for instance " + instance.getAddress() + "?revision=" + revision + "&cluster=" + instance.getRegistryCluster() + ", " + metadata); + } + } + if (metadata == null || (metadata == MetadataInfo.EMPTY && (failureCounter.get() < 3 || (System.currentTimeMillis() - lastFailureTime > 10000)))) { metadata = getMetadataInfo(instance); + if (metadata != MetadataInfo.EMPTY) { failureCounter.set(0); revisionToMetadata.putIfAbsent(revision, metadata); @@ -301,7 +320,7 @@ protected MetadataInfo getMetadataInfo(ServiceInstance instance) { MetadataInfo metadataInfo; try { if (logger.isDebugEnabled()) { - logger.info("Instance " + instance.getAddress() + " is using metadata type " + metadataType); + logger.debug("Instance " + instance.getAddress() + " is using metadata type " + metadataType); } if (REMOTE_METADATA_STORAGE_TYPE.equals(metadataType)) { RemoteMetadataServiceImpl remoteMetadataService = MetadataUtils.getRemoteMetadataService(); @@ -310,7 +329,6 @@ protected MetadataInfo getMetadataInfo(ServiceInstance instance) { MetadataService metadataServiceProxy = MetadataUtils.getMetadataServiceProxy(instance, serviceDiscovery); metadataInfo = metadataServiceProxy.getMetadataInfo(ServiceInstanceMetadataUtils.getExportedServicesRevision(instance)); } - logger.info("Metadata " + metadataInfo); } catch (Exception e) { logger.error("Failed to load service metadata, meta type is " + metadataType, e); metadataInfo = null; @@ -332,7 +350,7 @@ protected Object getServiceUrlsCache(Map> revision DefaultServiceInstance.Endpoint endpoint = ServiceInstanceMetadataUtils.getEndpoint(i, protocol); if (endpoint != null && !endpoint.getPort().equals(i.getPort())) { urls.add(((DefaultServiceInstance) i).copy(endpoint).toURL()); - break; + continue; } } urls.add(i.toURL()); @@ -364,14 +382,16 @@ protected List toUrlsWithEmpty(List urls) { /** * Since this listener is shared among interfaces, destroy this listener only when all interface listener are unsubscribed */ - public void destroy() { - if (destroyed.compareAndSet(false, true)) { + public synchronized void destroy() { + if (!destroyed.get()) { if (CollectionUtils.isEmptyMap(listeners)) { - allInstances.clear(); - serviceUrls.clear(); - revisionToMetadata.clear(); - if (retryFuture != null && !retryFuture.isDone()) { - retryFuture.cancel(true); + if (destroyed.compareAndSet(false, true)) { + allInstances.clear(); + serviceUrls.clear(); + revisionToMetadata.clear(); + if (retryFuture != null && !retryFuture.isDone()) { + retryFuture.cancel(true); + } } } } diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java index 01949a1c808..aeda3b0b02d 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/ServiceInstanceMetadataUtils.java @@ -258,14 +258,17 @@ public static void refreshMetadataAndInstance(ServiceInstance serviceInstance) { remoteMetadataService.publishMetadata(ApplicationModel.getName()); AbstractRegistryFactory.getServiceDiscoveries().forEach(serviceDiscovery -> { - ServiceInstance instance = serviceDiscovery.getLocalInstance() == null ? serviceInstance : serviceDiscovery.getLocalInstance(); + ServiceInstance instance = serviceDiscovery.getLocalInstance(); if (instance == null) { - LOGGER.error("Error refreshing service instance, instance not registered yet."); + LOGGER.warn("Refreshing of service instance started, but instance hasn't been registered yet."); + instance = serviceInstance; } calInstanceRevision(serviceDiscovery, instance); customizeInstance(instance); - // update service instance revision - serviceDiscovery.update(instance); + if (serviceInstance.getPort() > 0) { + // update service instance revision + serviceDiscovery.update(instance); + } }); } diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java index fdecb76ee47..f7354abb78c 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/metadata/store/InMemoryWritableMetadataService.java @@ -297,12 +297,7 @@ public URL getMetadataServiceURL() { @Override public void putCachedMapping(String serviceKey, Set apps) { - serviceToAppsMapping.put(serviceKey, apps); - } - - @Override - public Map> getCachedMapping() { - return serviceToAppsMapping; + serviceToAppsMapping.put(serviceKey, new TreeSet<>(apps)); } @Override diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationInvoker.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationInvoker.java index 6befc9bc089..85e216a2295 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationInvoker.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationInvoker.java @@ -37,6 +37,8 @@ import org.apache.dubbo.rpc.model.ConsumerModel; import java.util.Set; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import static org.apache.dubbo.rpc.cluster.Constants.REFER_KEY; @@ -55,6 +57,10 @@ public class MigrationInvoker implements MigrationClusterInvoker { private volatile ClusterInvoker currentAvailableInvoker; private volatile MigrationStep step; private volatile MigrationRule rule; + private volatile boolean migrated; + + + private static final ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); public MigrationInvoker(RegistryProtocol registryProtocol, Cluster cluster, @@ -135,7 +141,7 @@ private void doReSubscribe(ClusterInvoker invoker, URL newSubscribeUrl) { public void fallbackToInterfaceInvoker() { refreshInterfaceInvoker(); setListener(invoker, () -> { - this.destroyServiceDiscoveryInvoker(this.serviceDiscoveryInvoker, true); + this.destroyServiceDiscoveryInvoker(); }); } @@ -157,7 +163,7 @@ public void migrateToServiceDiscoveryInvoker(boolean forceMigrate) { } else { refreshServiceDiscoveryInvoker(); setListener(serviceDiscoveryInvoker, () -> { - this.destroyInterfaceInvoker(this.invoker, true); + this.destroyInterfaceInvoker(); }); } } @@ -175,7 +181,7 @@ private boolean checkMigratingConditionMatch(URL consumerUrl) { public void refreshServiceDiscoveryInvokerOnMappingCallback(boolean forceMigrate) { if (this.serviceDiscoveryInvoker != null) { DynamicDirectory dynamicDirectory = (DynamicDirectory) this.serviceDiscoveryInvoker.getDirectory(); - dynamicDirectory.subscribe(dynamicDirectory.getOriginalConsumerUrl()); + dynamicDirectory.subscribe(dynamicDirectory.getSubscribeUrl()); } else { migrateToServiceDiscoveryInvoker(forceMigrate); } @@ -305,30 +311,51 @@ public boolean invokersChanged() { */ private synchronized void compareAddresses(ClusterInvoker serviceDiscoveryInvoker, ClusterInvoker invoker) { this.invokersChanged = true; + if (migrated) { + return; + } + if (serviceDiscoveryInvoker == null || serviceDiscoveryInvoker.isDestroyed()) { + currentAvailableInvoker = invoker; + return; + } else if (invoker == null || invoker.isDestroyed()) { + currentAvailableInvoker = serviceDiscoveryInvoker; + return; + } + Set detectors = ExtensionLoader.getExtensionLoader(MigrationAddressComparator.class).getSupportedExtensionInstances(); if (detectors != null && detectors.stream().allMatch(migrationDetector -> migrationDetector.shouldMigrate(serviceDiscoveryInvoker, invoker, rule))) { logger.info("serviceKey:" + invoker.getUrl().getServiceKey() + " switch to APP Level address"); - destroyInterfaceInvoker(invoker, false); + scheduler.submit(() -> { + if (invoker.getDirectory().isNotificationReceived()) { + destroyInterfaceInvoker(); + migrated = true; + } + }); } else { - logger.info("serviceKey:" + invoker.getUrl().getServiceKey() + " switch to Service Level address"); - destroyServiceDiscoveryInvoker(serviceDiscoveryInvoker, false); + logger.info("serviceKey:" + serviceDiscoveryInvoker.getUrl().getServiceKey() + " switch to Service Level address"); + scheduler.submit(() -> { + if (serviceDiscoveryInvoker.getDirectory().isNotificationReceived()) { + destroyServiceDiscoveryInvoker(); + migrated = true; + } + }); } } - protected synchronized void destroyServiceDiscoveryInvoker(ClusterInvoker serviceDiscoveryInvoker, boolean force) { + protected void destroyServiceDiscoveryInvoker() { if (this.invoker != null) { this.currentAvailableInvoker = this.invoker; -// clearListener(this.serviceDiscoveryInvoker); - updateConsumerModel(currentAvailableInvoker, serviceDiscoveryInvoker); } if (serviceDiscoveryInvoker != null && !serviceDiscoveryInvoker.isDestroyed()) { - if (force || serviceDiscoveryInvoker.getDirectory().isNotificationReceived()) { - if (logger.isInfoEnabled()) { - logger.info("Destroying instance address invokers, will not listen for address changes until re-subscribed, " + type.getName()); - } - serviceDiscoveryInvoker.destroy(); + if (logger.isInfoEnabled()) { + logger.info("Destroying instance address invokers, will not listen for address changes until re-subscribed, " + type.getName()); } + serviceDiscoveryInvoker.destroy(); + serviceDiscoveryInvoker = null; } + + updateConsumerModel(currentAvailableInvoker, serviceDiscoveryInvoker); + migrated = true; } // protected synchronized void discardServiceDiscoveryInvokerAddress(ClusterInvoker serviceDiscoveryInvoker) { @@ -367,20 +394,20 @@ protected void refreshInterfaceInvoker() { } } - protected synchronized void destroyInterfaceInvoker(ClusterInvoker invoker, boolean force) { + protected void destroyInterfaceInvoker() { if (this.serviceDiscoveryInvoker != null) { this.currentAvailableInvoker = this.serviceDiscoveryInvoker; -// clearListener(this.serviceDiscoveryInvoker); - updateConsumerModel(currentAvailableInvoker, invoker); } if (invoker != null && !invoker.isDestroyed()) { - if (force || invoker.getDirectory().isNotificationReceived()) { - if (logger.isInfoEnabled()) { - logger.info("Destroying interface address invokers, will not listen for address changes until re-subscribed, " + type.getName()); - } - invoker.destroy(); + if (logger.isInfoEnabled()) { + logger.info("Destroying interface address invokers, will not listen for address changes until re-subscribed, " + type.getName()); } + invoker.destroy(); + invoker = null; } + + updateConsumerModel(currentAvailableInvoker, invoker); + migrated = true; } // diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java index 85c7349043f..355ab6360f1 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/migration/MigrationRuleListener.java @@ -36,6 +36,7 @@ import org.apache.dubbo.rpc.cluster.ClusterInvoker; import org.apache.dubbo.rpc.model.ApplicationModel; +import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -181,7 +182,11 @@ protected Set getServices(URL registryURL, URL subscribedURL, MigrationR } protected Set findMappedServices(URL registryURL, URL subscribedURL, MappingListener listener) { - return ServiceNameMapping.getExtension(registryURL.getParameter(MAPPING_KEY)).getAndListen(subscribedURL, listener); + Set result = new LinkedHashSet<>(); + ServiceNameMapping serviceNameMapping = ServiceNameMapping.getExtension(registryURL.getParameter(MAPPING_KEY)); + result.addAll(serviceNameMapping.getAndListen(subscribedURL, listener)); + result.addAll(serviceNameMapping.getAndListenWithNewStore(subscribedURL, listener)); + return result; } public static Set parseServices(String literalServices) { @@ -206,7 +211,9 @@ public DefaultMappingListener(URL subscribedURL, Set serviceNames, Migra @Override public void onEvent(MappingChangedEvent event) { - logger.info("Received mapping notification from meta server, " + event); + if(logger.isDebugEnabled()) { + logger.debug("Received mapping notification from meta server, " + event); + } Set newApps = event.getApps(); Set tempOldApps = oldApps; oldApps = newApps; diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/DynamicDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/DynamicDirectory.java index c7b0daaa63c..8085ed47f96 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/DynamicDirectory.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/DynamicDirectory.java @@ -22,6 +22,7 @@ import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.NetUtils; +import org.apache.dubbo.registry.AddressListener; import org.apache.dubbo.registry.NotifyListener; import org.apache.dubbo.registry.Registry; import org.apache.dubbo.registry.client.event.listener.ServiceInstancesChangedListener; @@ -71,7 +72,7 @@ public abstract class DynamicDirectory extends AbstractDirectory implement protected boolean shouldSimplified; protected volatile URL overrideDirectoryUrl; // Initialization at construction time, assertion not null, and always assign non null value - + protected volatile URL subscribeUrl; protected volatile URL registeredConsumerUrl; /** @@ -128,12 +129,12 @@ public boolean isShouldRegister() { } public void subscribe(URL url) { - setConsumerUrl(url); + setSubscribeUrl(url); registry.subscribe(url, this); } public void unSubscribe(URL url) { - setConsumerUrl(null); + setSubscribeUrl(null); registry.unsubscribe(url, this); } @@ -172,19 +173,31 @@ public List> getAllInvokers() { return invokers; } + // The currently effective consumer url @Override public URL getConsumerUrl() { return this.overrideDirectoryUrl; } + // The original consumer url public URL getOriginalConsumerUrl() { - return this.consumerUrl; + return this.overrideDirectoryUrl; } + // The url registered to registry or metadata center public URL getRegisteredConsumerUrl() { return registeredConsumerUrl; } + // The url used to subscribe from registry + public URL getSubscribeUrl() { + return subscribeUrl; + } + + public void setSubscribeUrl(URL subscribeUrl) { + this.subscribeUrl = subscribeUrl; + } + public void setRegisteredConsumerUrl(URL url) { if (!shouldSimplified) { this.registeredConsumerUrl = url.addParameters(CATEGORY_KEY, CONSUMERS_CATEGORY, CHECK_KEY, @@ -220,19 +233,32 @@ public void destroy() { // unsubscribe. try { if (getConsumerUrl() != null && registry != null && registry.isAvailable()) { - registry.unsubscribe(getConsumerUrl(), this); + registry.unsubscribe(getSubscribeUrl(), this); } } catch (Throwable t) { logger.warn("unexpected error when unsubscribe service " + serviceKey + "from registry" + registry.getUrl(), t); } - super.destroy(); // must be executed after unsubscribing - try { - destroyAllInvokers(); - } catch (Throwable t) { - logger.warn("Failed to destroy service " + serviceKey, t); + + ExtensionLoader addressListenerExtensionLoader = ExtensionLoader.getExtensionLoader(AddressListener.class); + List supportedListeners = addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null); + if (supportedListeners != null && !supportedListeners.isEmpty()) { + for (AddressListener addressListener : supportedListeners) { + addressListener.destroy(getConsumerUrl(), this); + } } - invokersChangedListener = null; + synchronized (this) { + try { + destroyAllInvokers(); + } catch (Throwable t) { + logger.warn("Failed to destroy service " + serviceKey, t); + } + routerChain.destroy(); + invokersChangedListener = null; + serviceListener = null; + + super.destroy(); // must be executed after unsubscribing + } } @Override diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java index 16303cb4b3f..efce5d0b592 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/integration/RegistryDirectory.java @@ -118,6 +118,10 @@ public void unSubscribe(URL url) { @Override public synchronized void notify(List urls) { + if (isDestroyed()) { + return; + } + Map> categoryUrls = urls.stream() .filter(Objects::nonNull) .filter(this::isValidCategory) diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/CacheableFailbackRegistry.java b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/CacheableFailbackRegistry.java index 9f677c81a96..16e0f899924 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/CacheableFailbackRegistry.java +++ b/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/support/CacheableFailbackRegistry.java @@ -31,6 +31,7 @@ import org.apache.dubbo.common.utils.CollectionUtils; import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.common.utils.UrlUtils; +import org.apache.dubbo.registry.NotifyListener; import java.util.ArrayList; import java.util.Collection; @@ -55,9 +56,7 @@ import static org.apache.dubbo.common.constants.CommonConstants.PROTOCOL_SEPARATOR_ENCODED; import static org.apache.dubbo.common.constants.RegistryConstants.CATEGORY_KEY; import static org.apache.dubbo.common.constants.RegistryConstants.EMPTY_PROTOCOL; -import static org.apache.dubbo.common.constants.RegistryConstants.OVERRIDE_PROTOCOL; -import static org.apache.dubbo.common.constants.RegistryConstants.ROUTE_PROTOCOL; -import static org.apache.dubbo.common.constants.RegistryConstants.ROUTE_SCRIPT_PROTOCOL; +import static org.apache.dubbo.common.constants.RegistryConstants.PROVIDERS_CATEGORY; import static org.apache.dubbo.common.url.component.DubboServiceAddressURL.PROVIDER_FIRST_KEYS; /** @@ -81,8 +80,8 @@ public abstract class CacheableFailbackRegistry extends FailbackRegistry { static { ExecutorRepository executorRepository = ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension(); cacheRemovalScheduler = executorRepository.nextScheduledExecutor(); - cacheRemovalTaskIntervalInMillis = getIntConfig(CACHE_CLEAR_TASK_INTERVAL, 10 * 60 * 1000); - cacheClearWaitingThresholdInMillis = getIntConfig(CACHE_CLEAR_WAITING_THRESHOLD, 30 * 60 * 1000); + cacheRemovalTaskIntervalInMillis = getIntConfig(CACHE_CLEAR_TASK_INTERVAL, 2 * 60 * 1000); + cacheClearWaitingThresholdInMillis = getIntConfig(CACHE_CLEAR_WAITING_THRESHOLD, 5 * 60 * 1000); } public CacheableFailbackRegistry(URL url) { @@ -104,6 +103,31 @@ protected static int getIntConfig(String key, int def) { return result; } + @Override + public void doUnsubscribe(URL url, NotifyListener listener) { + this.evictURLCache(url); + } + + protected void evictURLCache(URL url) { + Map oldURLs = stringUrls.remove(url); + try { + if (oldURLs != null && oldURLs.size() > 0) { + logger.info("Evicting urls for service " + url.getServiceKey() + ", size " + oldURLs.size()); + Long currentTimestamp = System.currentTimeMillis(); + for (Map.Entry entry : oldURLs.entrySet()) { + waitForRemove.put(entry.getValue(), currentTimestamp); + } + if (CollectionUtils.isNotEmptyMap(waitForRemove)) { + if (semaphore.tryAcquire()) { + cacheRemovalScheduler.schedule(new RemovalTask(), cacheRemovalTaskIntervalInMillis, TimeUnit.MILLISECONDS); + } + } + } + } catch (Exception e) { + logger.warn("Failed to evict url for " + url.getServiceKey(), e); + } + } + protected List toUrlsWithoutEmpty(URL consumer, Collection providers) { // keep old urls Map oldURLs = stringUrls.get(consumer); @@ -138,42 +162,28 @@ protected List toUrlsWithoutEmpty(URL consumer, Collection provider } } + evictURLCache(consumer); stringUrls.put(consumer, newURLs); - // destroy used urls - try { - if (oldURLs != null && oldURLs.size() > 0) { - Long currentTimestamp = System.currentTimeMillis(); - for (Map.Entry entry : oldURLs.entrySet()) { - waitForRemove.put(entry.getValue(), currentTimestamp); - } - if (CollectionUtils.isNotEmptyMap(waitForRemove)) { - if (semaphore.tryAcquire()) { - cacheRemovalScheduler.schedule(new RemovalTask(), cacheRemovalTaskIntervalInMillis, TimeUnit.MILLISECONDS); - } - } - } - } catch (Exception e) { - logger.warn("Failed to evict url for " + consumer, e); - } - return new ArrayList<>(newURLs.values()); } protected List toUrlsWithEmpty(URL consumer, String path, Collection providers) { - List urls; - if (CollectionUtils.isEmpty(providers)) { - urls = new ArrayList<>(1); - // clear cache on empty notification: unsubscribe or provider offline - stringUrls.remove(consumer); + List urls = new ArrayList<>(1); + boolean isProviderPath = path.endsWith(PROVIDERS_CATEGORY); + if (isProviderPath) { + if (CollectionUtils.isNotEmpty(providers)) { + urls = toUrlsWithoutEmpty(consumer, providers); + } else { + // clear cache on empty notification: unsubscribe or provider offline + evictURLCache(consumer); + } } else { - String rawProvider = providers.iterator().next(); - if (rawProvider.startsWith(OVERRIDE_PROTOCOL) || rawProvider.startsWith(ROUTE_PROTOCOL) || rawProvider.startsWith(ROUTE_SCRIPT_PROTOCOL)) { + if (CollectionUtils.isNotEmpty(providers)) { urls = toConfiguratorsWithoutEmpty(consumer, providers); - } else { - urls = toUrlsWithoutEmpty(consumer, providers); } } + if (urls.isEmpty()) { int i = path.lastIndexOf(PATH_SEPARATOR); String category = i < 0 ? path : path.substring(i + 1); @@ -183,6 +193,7 @@ protected List toUrlsWithEmpty(URL consumer, String path, Collection() { - @Override - public void onEvent(ServiceDiscoveryInitializingEvent event) { - assertNotNull(event.getServiceDiscovery()); - } - }); - - // ServiceDiscoveryStartedEvent - eventDispatcher.addEventListener(new EventListener() { - @Override - public void onEvent(ServiceDiscoveryInitializedEvent event) { - assertNotNull(event.getServiceDiscovery()); - } - }); - - // ServiceInstancePreRegisteredEvent - eventDispatcher.addEventListener(new EventListener() { - @Override - public void onEvent(ServiceInstancePreRegisteredEvent event) { - assertNotNull(event.getServiceInstance()); - } - }); - - // ServiceInstanceRegisteredEvent - eventDispatcher.addEventListener(new EventListener() { - @Override - public void onEvent(ServiceInstanceRegisteredEvent event) { - assertNotNull(event.getServiceInstance()); - } - }); - - assertFalse(serviceDiscovery.isInitialized()); - assertFalse(serviceDiscovery.isDestroyed()); - - // test start() - serviceDiscoveryTest.init(); - - assertTrue(serviceDiscovery.isInitialized()); - assertFalse(serviceDiscovery.isDestroyed()); - } - - @AfterEach - public void destroy() throws Exception { - - // ServiceDiscoveryStoppingEvent - eventDispatcher.addEventListener(new EventListener() { - @Override - public void onEvent(ServiceDiscoveryDestroyingEvent event) { - assertNotNull(event.getServiceDiscovery()); - } - }); - - // ServiceDiscoveryStoppedEvent - eventDispatcher.addEventListener(new EventListener() { - @Override - public void onEvent(ServiceDiscoveryDestroyedEvent event) { - assertNotNull(event.getServiceDiscovery()); - } - }); - - assertTrue(serviceDiscovery.isInitialized()); - assertFalse(serviceDiscovery.isDestroyed()); - - // test stop() - serviceDiscoveryTest.destroy(); - - assertTrue(serviceDiscovery.isInitialized()); - assertTrue(serviceDiscovery.isDestroyed()); - } - - @Test - public void testToString() { - serviceDiscoveryTest.testToString(); - } - - @Test - public void testRegisterAndUpdateAndUnregister() { - serviceDiscoveryTest.testRegisterAndUpdateAndUnregister(); - } - - @Test - public void testGetServices() { - serviceDiscoveryTest.testGetServices(); - } - - @Test - public void testGetInstances() { - serviceDiscoveryTest.testGetInstances(); - } - - @Test - public void testGetInstancesWithHealthy() { - serviceDiscoveryTest.testGetInstancesWithHealthy(); - } -} \ No newline at end of file diff --git a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListenerTest.java b/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListenerTest.java deleted file mode 100644 index 7eda41a9c0b..00000000000 --- a/dubbo-registry/dubbo-registry-api/src/test/java/org/apache/dubbo/registry/client/event/listener/LoggingEventListenerTest.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You 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 org.apache.dubbo.registry.client.event.listener; - -import org.apache.dubbo.common.URL; -import org.apache.dubbo.registry.client.FileSystemServiceDiscovery; -import org.apache.dubbo.registry.client.ServiceDiscovery; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryDestroyedEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryDestroyingEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryInitializedEvent; -import org.apache.dubbo.registry.client.event.ServiceDiscoveryInitializingEvent; -import org.apache.dubbo.registry.client.event.ServiceInstancePreRegisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstancePreUnregisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstanceRegisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstanceUnregisteredEvent; -import org.apache.dubbo.registry.client.event.ServiceInstancesChangedEvent; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; - -import static org.apache.dubbo.registry.client.DefaultServiceInstanceTest.createInstance; - -/** - * {@link LoggingEventListener} Test - * - * @since 2.7.5 - */ -public class LoggingEventListenerTest { - - private LoggingEventListener listener; - - @BeforeEach - public void init() { - listener = new LoggingEventListener(); - } - - @Test - public void testOnEvent() throws Exception { - - URL connectionURL = URL.valueOf("file:///Users/Home"); - - ServiceDiscovery serviceDiscovery = new FileSystemServiceDiscovery(); - - serviceDiscovery.initialize(connectionURL); - - // ServiceDiscoveryStartingEvent - listener.onEvent(new ServiceDiscoveryInitializingEvent(serviceDiscovery, serviceDiscovery)); - - // ServiceDiscoveryStartedEvent - listener.onEvent(new ServiceDiscoveryInitializedEvent(serviceDiscovery, serviceDiscovery)); - - // ServiceInstancePreRegisteredEvent - listener.onEvent(new ServiceInstancePreRegisteredEvent(serviceDiscovery, createInstance())); - - // ServiceInstanceRegisteredEvent - listener.onEvent(new ServiceInstanceRegisteredEvent(serviceDiscovery, createInstance())); - - // ServiceInstancesChangedEvent - listener.onEvent(new ServiceInstancesChangedEvent("test", Arrays.asList(createInstance()))); - - // ServiceInstancePreUnregisteredEvent - listener.onEvent(new ServiceInstancePreUnregisteredEvent(serviceDiscovery, createInstance())); - - // ServiceInstanceUnregisteredEvent - listener.onEvent(new ServiceInstanceUnregisteredEvent(serviceDiscovery, createInstance())); - - // ServiceDiscoveryStoppingEvent - listener.onEvent(new ServiceDiscoveryDestroyingEvent(serviceDiscovery, serviceDiscovery)); - - // ServiceDiscoveryStoppedEvent - listener.onEvent(new ServiceDiscoveryDestroyedEvent(serviceDiscovery, serviceDiscovery)); - } -} diff --git a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java index c472793ee2c..abebd64bafd 100644 --- a/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java +++ b/dubbo-registry/dubbo-registry-nacos/src/main/java/org/apache/dubbo/registry/nacos/NacosRegistry.java @@ -327,7 +327,7 @@ private String getLegacySubscribedServiceName(URL url) { private void appendIfPresent(StringBuilder target, URL url, String parameterName) { String parameterValue = url.getParameter(parameterName); - if (!org.apache.commons.lang3.StringUtils.isBlank(parameterValue)) { + if (!StringUtils.isBlank(parameterValue)) { target.append(SERVICE_NAME_SEPARATOR).append(parameterValue); } } diff --git a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java index ddfb48a69f4..6dc11c592bc 100644 --- a/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java +++ b/dubbo-registry/dubbo-registry-zookeeper/src/main/java/org/apache/dubbo/registry/zookeeper/ZookeeperRegistry.java @@ -188,6 +188,7 @@ public void doSubscribe(final URL url, final NotifyListener listener) { @Override public void doUnsubscribe(URL url, NotifyListener listener) { + super.doUnsubscribe(url, listener); ConcurrentMap listeners = zkListeners.get(url); if (listeners != null) { org.apache.dubbo.remoting.zookeeper.ChildListener zkListener = listeners.get(listener); diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Connection.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Connection.java index 5b7d5d32110..3f058292d03 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Connection.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/api/Connection.java @@ -168,6 +168,7 @@ public boolean isClosed() { return closed.get(); } + //TODO replace channelFuture with intermediate future public ChannelFuture write(Object request) throws RemotingException { if (!isAvailable()) { throw new RemotingException(null, null, "Failed to send request " + request + ", cause: The channel to " + remote + " is closed!"); diff --git a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture2.java b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture2.java index fdb90f6f10a..45702f9f2a6 100644 --- a/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture2.java +++ b/dubbo-remoting/dubbo-remoting-api/src/main/java/org/apache/dubbo/remoting/exchange/support/DefaultFuture2.java @@ -30,8 +30,6 @@ import org.apache.dubbo.remoting.exchange.Request; import org.apache.dubbo.remoting.exchange.Response; -import io.netty.channel.Channel; - import java.net.InetSocketAddress; import java.text.SimpleDateFormat; import java.util.Date; @@ -51,12 +49,10 @@ public class DefaultFuture2 extends CompletableFuture { 30, TimeUnit.MILLISECONDS); private static final Logger logger = LoggerFactory.getLogger(DefaultFuture2.class); - private static final Map CONNECTIONS = new ConcurrentHashMap<>(); private static final Map FUTURES = new ConcurrentHashMap<>(); // invoke id. - private final Long id; - private final Connection connection; private final Request request; + private final Connection connection; private final int timeout; private final long start = System.currentTimeMillis(); private volatile long sent; @@ -67,11 +63,9 @@ public class DefaultFuture2 extends CompletableFuture { private DefaultFuture2(Connection client2, Request request, int timeout) { this.connection = client2; this.request = request; - this.id = request.getId(); this.timeout = timeout; // put into waiting map. - FUTURES.put(id, this); - CONNECTIONS.put(id, connection); + FUTURES.put(request.getId(), this); } /** @@ -87,13 +81,13 @@ private static void timeoutCheck(DefaultFuture2 future) { * 1.init a DefaultFuture * 2.timeout check * - * @param channel channel - * @param request the request - * @param timeout timeout + * @param connection connection + * @param request the request + * @param timeout timeout * @return a new DefaultFuture */ - public static DefaultFuture2 newFuture(Connection channel, Request request, int timeout, ExecutorService executor) { - final DefaultFuture2 future = new DefaultFuture2(channel, request, timeout); + public static DefaultFuture2 newFuture(Connection connection, Request request, int timeout, ExecutorService executor) { + final DefaultFuture2 future = new DefaultFuture2(connection, request, timeout); future.setExecutor(executor); // ThreadlessExecutor needs to hold the waiting future in case of circuit return. if (executor instanceof ThreadlessExecutor) { @@ -108,10 +102,6 @@ public static DefaultFuture2 getFuture(long id) { return FUTURES.get(id); } - public static boolean hasFuture(Channel channel) { - return CONNECTIONS.containsValue(channel); - } - public static void sent(Request request) { DefaultFuture2 future = FUTURES.get(request.getId()); if (future != null) { @@ -119,57 +109,25 @@ public static void sent(Request request) { } } - /** - * close a channel when a channel is inactive - * directly return the unfinished requests. - * - * @param connection channel to close - */ - public static void closeChannel(Connection connection) { - for (Map.Entry entry : CONNECTIONS.entrySet()) { - if (connection.equals(entry.getValue())) { - DefaultFuture2 future = getFuture(entry.getKey()); - if (future != null && !future.isDone()) { - ExecutorService futureExecutor = future.getExecutor(); - if (futureExecutor != null && !futureExecutor.isTerminated()) { - futureExecutor.shutdownNow(); - } - - Response disconnectResponse = new Response(future.getId()); - disconnectResponse.setStatus(Response.CHANNEL_INACTIVE); - disconnectResponse.setErrorMessage("Channel " + - connection + - " is inactive. Directly return the unFinished request : " + - future.getRequest()); - DefaultFuture2.received(connection, disconnectResponse); - } - } - } - } - public static void received(Connection connection, Response response) { received(connection, response, false); } public static void received(Connection connection, Response response, boolean timeout) { - try { - DefaultFuture2 future = FUTURES.remove(response.getId()); - if (future != null) { - Timeout t = future.timeoutCheckTask; - if (!timeout) { - // decrease Time - t.cancel(); - } - future.doReceived(response); - } else { - logger.warn("The timeout response finally returned at " - + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())) - + ", response status is " + response.getStatus() - + (connection == null ? "" : ", channel: " + connection.getChannel().localAddress() - + " -> " + connection.getRemote()) + ", please check provider side for detailed result."); + DefaultFuture2 future = FUTURES.remove(response.getId()); + if (future != null) { + Timeout t = future.timeoutCheckTask; + if (!timeout) { + // decrease Time + t.cancel(); } - } finally { - CONNECTIONS.remove(response.getId()); + future.doReceived(response); + } else { + logger.warn("The timeout response finally returned at " + + (new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date())) + + ", response status is " + response.getStatus() + + (connection == null ? "" : ", channel: " + connection.getChannel().localAddress() + + " -> " + connection.getRemote()) + ", please check provider side for detailed result."); } } @@ -183,12 +141,11 @@ public void setExecutor(ExecutorService executor) { @Override public boolean cancel(boolean mayInterruptIfRunning) { - Response errorResult = new Response(id); + Response errorResult = new Response(request.getId()); errorResult.setStatus(Response.CLIENT_ERROR); errorResult.setErrorMessage("request future has been canceled."); this.doReceived(errorResult); - FUTURES.remove(id); - CONNECTIONS.remove(id); + FUTURES.remove(request.getId()); return true; } @@ -225,7 +182,7 @@ private void doReceived(Response res) { } private long getId() { - return id; + return request.getId(); } private Connection getConnection() { @@ -236,9 +193,6 @@ private boolean isSent() { return sent > 0; } - public Request getRequest() { - return request; - } private int getTimeout() { return timeout; diff --git a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java index 775d2910c71..3b960e29019 100644 --- a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java +++ b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/ZookeeperClient.java @@ -17,6 +17,7 @@ package org.apache.dubbo.remoting.zookeeper; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.configcenter.ConfigItem; import java.util.List; import java.util.concurrent.Executor; @@ -60,8 +61,12 @@ public interface ZookeeperClient { void create(String path, String content, boolean ephemeral); + void createOrUpdate(String path, String content, boolean ephemeral, Object stat); + String getContent(String path); + ConfigItem getConfigItem(String path); + boolean checkExists(String path); } diff --git a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java index 625f00176d4..a5d45e1831e 100644 --- a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java +++ b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/curator/CuratorZookeeperClient.java @@ -17,6 +17,7 @@ package org.apache.dubbo.remoting.zookeeper.curator; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.configcenter.ConfigItem; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.remoting.zookeeper.ChildListener; @@ -39,6 +40,7 @@ import org.apache.zookeeper.KeeperException.NodeExistsException; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; +import org.apache.zookeeper.data.Stat; import java.nio.charset.Charset; import java.util.List; @@ -49,7 +51,8 @@ import static org.apache.dubbo.common.constants.CommonConstants.TIMEOUT_KEY; -public class CuratorZookeeperClient extends AbstractZookeeperClient { +public class CuratorZookeeperClient + extends AbstractZookeeperClient { protected static final Logger logger = LoggerFactory.getLogger(CuratorZookeeperClient.class); private static final String ZK_SESSION_EXPIRE_KEY = "zk.session.expire"; @@ -144,6 +147,47 @@ protected void createEphemeral(String path, String data) { } } + @Override + protected void update(String path, String data, Object stat) { + byte[] dataBytes = data.getBytes(CHARSET); + try { + if (null == stat || !(stat instanceof Stat)) { + throw new IllegalArgumentException("unable to get the version information of zookeeper data"); + } + client.setData().withVersion(((Stat) stat).getVersion()).forPath(path, dataBytes); + } catch (NoNodeException e) { + logger.warn("ZNode " + path + "does not exists.", e); + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + @Override + protected void createOrUpdatePersistent(String path, String data, Object stat) { + try { + if (checkExists(path)) { + update(path, data, stat); + } else { + createPersistent(path, data); + } + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + + @Override + protected void createOrUpdateEphemeral(String path, String data, Object stat) { + try { + if (checkExists(path)) { + update(path, data, stat); + } else { + createEphemeral(path, data); + } + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + } + @Override protected void deletePath(String path) { try { @@ -194,6 +238,22 @@ public String doGetContent(String path) { return null; } + @Override + public ConfigItem doGetConfigItem(String path) { + String content = null; + Stat stat = null; + try { + stat = new Stat(); + byte[] dataBytes = client.getData().storingStatIn(stat).forPath(path); + content = (dataBytes == null || dataBytes.length == 0) ? null : new String(dataBytes, CHARSET); + } catch (NoNodeException e) { + // ignore NoNode Exception. + } catch (Exception e) { + throw new IllegalStateException(e.getMessage(), e); + } + return new ConfigItem(content, stat); + } + @Override public void doClose() { client.close(); diff --git a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/support/AbstractZookeeperClient.java b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/support/AbstractZookeeperClient.java index 42fe5c925c8..61219696af3 100644 --- a/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/support/AbstractZookeeperClient.java +++ b/dubbo-remoting/dubbo-remoting-zookeeper/src/main/java/org/apache/dubbo/remoting/zookeeper/support/AbstractZookeeperClient.java @@ -17,6 +17,7 @@ package org.apache.dubbo.remoting.zookeeper.support; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.config.configcenter.ConfigItem; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.utils.ConcurrentHashSet; @@ -43,13 +44,15 @@ public abstract class AbstractZookeeperClient stateListeners = new CopyOnWriteArraySet(); - private final ConcurrentMap> childListeners = new ConcurrentHashMap>(); + private final ConcurrentMap> childListeners = + new ConcurrentHashMap>(); - private final ConcurrentMap> listeners = new ConcurrentHashMap>(); + private final ConcurrentMap> listeners = + new ConcurrentHashMap>(); private volatile boolean closed = false; - private final Set persistentExistNodePath = new ConcurrentHashSet<>(); + private final Set persistentExistNodePath = new ConcurrentHashSet<>(); public AbstractZookeeperClient(URL url) { this.url = url; @@ -61,7 +64,7 @@ public URL getUrl() { } @Override - public void delete(String path){ + public void delete(String path) { //never mind if ephemeral persistentExistNodePath.remove(path); deletePath(path); @@ -71,7 +74,7 @@ public void delete(String path){ @Override public void create(String path, boolean ephemeral) { if (!ephemeral) { - if(persistentExistNodePath.contains(path)){ + if (persistentExistNodePath.contains(path)) { return; } if (checkExists(path)) { @@ -125,11 +128,11 @@ public void addDataListener(String path, DataListener listener, Executor executo } @Override - public void removeDataListener(String path, DataListener listener ){ + public void removeDataListener(String path, DataListener listener) { ConcurrentMap dataListenerMap = listeners.get(path); if (dataListenerMap != null) { TargetDataListener targetListener = dataListenerMap.remove(listener); - if(targetListener != null){ + if (targetListener != null) { removeTargetDataListener(path, targetListener); } } @@ -181,6 +184,19 @@ public void create(String path, String content, boolean ephemeral) { } } + @Override + public void createOrUpdate(String path, String content, boolean ephemeral, Object stat) { + int i = path.lastIndexOf('/'); + if (i > 0) { + create(path.substring(0, i), false); + } + if (ephemeral) { + createOrUpdateEphemeral(path, content, stat); + } else { + createOrUpdatePersistent(path, content, stat); + } + } + @Override public String getContent(String path) { if (!checkExists(path)) { @@ -189,6 +205,14 @@ public String getContent(String path) { return doGetContent(path); } + @Override + public ConfigItem getConfigItem(String path) { + if (!checkExists(path)) { + return new ConfigItem(); + } + return doGetConfigItem(path); + } + protected abstract void doClose(); protected abstract void createPersistent(String path); @@ -199,6 +223,13 @@ public String getContent(String path) { protected abstract void createEphemeral(String path, String data); + protected abstract void update(String path, String data, Object stat); + + protected abstract void createOrUpdatePersistent(String path, String data, Object stat); + + protected abstract void createOrUpdateEphemeral(String path, String data, Object stat); + + @Override public abstract boolean checkExists(String path); protected abstract TargetChildListener createTargetChildListener(String path, ChildListener listener); @@ -217,8 +248,11 @@ public String getContent(String path) { protected abstract String doGetContent(String path); + protected abstract ConfigItem doGetConfigItem(String path); + /** * we invoke the zookeeper client to delete the node + * * @param path the node path */ protected abstract void deletePath(String path); diff --git a/dubbo-rpc/dubbo-rpc-triple/pom.xml b/dubbo-rpc/dubbo-rpc-triple/pom.xml index 8ffe0274be5..6ab905200ea 100644 --- a/dubbo-rpc/dubbo-rpc-triple/pom.xml +++ b/dubbo-rpc/dubbo-rpc-triple/pom.xml @@ -1,18 +1,18 @@ diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractClientStream.java new file mode 100644 index 00000000000..ab7c155cfe2 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractClientStream.java @@ -0,0 +1,188 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.remoting.api.Connection; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.model.ConsumerModel; +import org.apache.dubbo.triple.TripleWrapper; + +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpHeaderValues; + +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.RejectedExecutionException; + +public abstract class AbstractClientStream extends AbstractStream implements Stream { + private ConsumerModel consumerModel; + private Connection connection; + + protected AbstractClientStream(URL url) { + super(url); + } + + protected AbstractClientStream(URL url, Executor executor) { + super(url, executor); + } + + public static UnaryClientStream unary(URL url, Executor executor) { + return new UnaryClientStream(url, executor); + } + + public static AbstractClientStream stream(URL url) { + return new ClientStream(url); + } + + public AbstractClientStream service(ConsumerModel model) { + this.consumerModel = model; + return this; + } + + public ConsumerModel getConsumerModel() { + return consumerModel; + } + + public AbstractClientStream connection(Connection connection) { + this.connection = connection; + return this; + } + + public Connection getConnection() { + return connection; + } + + @Override + public void execute(Runnable runnable) { + try { + super.execute(runnable); + } catch (RejectedExecutionException e) { + LOGGER.error("Consumer's thread pool is full", e); + getStreamSubscriber().onError(GrpcStatus.fromCode(GrpcStatus.Code.RESOURCE_EXHAUSTED) + .withDescription("Consumer's thread pool is full").asException()); + } catch (Throwable t) { + LOGGER.error("Consumer submit request to thread pool error ", t); + getStreamSubscriber().onError(GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withCause(t) + .withDescription("Consumer's error") + .asException()); + } + } + + protected byte[] encodeRequest(Object value) { + final byte[] out; + final Object obj; + + if (getMethodDescriptor().isNeedWrap()) { + obj = getRequestWrapper(value); + } else { + obj = getRequestValue(value); + } + out = TripleUtil.pack(obj); + + return out; + } + + private TripleWrapper.TripleRequestWrapper getRequestWrapper(Object value) { + if (getMethodDescriptor().isStream()) { + String type = getMethodDescriptor().getParameterClasses()[0].getName(); + return TripleUtil.wrapReq(getUrl(), getSerializeType(), value, type, getMultipleSerialization()); + } else { + RpcInvocation invocation = (RpcInvocation) value; + return TripleUtil.wrapReq(getUrl(), invocation, getMultipleSerialization()); + } + } + + private Object getRequestValue(Object value) { + if (getMethodDescriptor().isUnary()) { + RpcInvocation invocation = (RpcInvocation) value; + return invocation.getArguments()[0]; + } + + return value; + } + + protected Object deserializeResponse(byte[] data) { + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + try { + if (getConsumerModel() != null) { + ClassLoadUtil.switchContextLoader(getConsumerModel().getServiceInterfaceClass().getClassLoader()); + } + if (getMethodDescriptor().isNeedWrap()) { + final TripleWrapper.TripleResponseWrapper wrapper = TripleUtil.unpack(data, + TripleWrapper.TripleResponseWrapper.class); + serialize(wrapper.getSerializeType()); + return TripleUtil.unwrapResp(getUrl(), wrapper, getMultipleSerialization()); + } else { + return TripleUtil.unpack(data, getMethodDescriptor().getReturnClass()); + } + } finally { + ClassLoadUtil.switchContextLoader(tccl); + } + } + + protected Metadata createRequestMeta(RpcInvocation inv) { + Metadata metadata = new DefaultMetadata(); + metadata.put(TripleConstant.PATH_KEY, "/" + inv.getObjectAttachment(CommonConstants.PATH_KEY) + "/" + inv.getMethodName()) + .put(TripleConstant.AUTHORITY_KEY, getUrl().getAddress()) + .put(TripleConstant.CONTENT_TYPE_KEY, TripleConstant.CONTENT_PROTO) + .put(TripleConstant.TIMEOUT, inv.get(CommonConstants.TIMEOUT_KEY) + "m") + .put(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS); + + metadata.putIfNotNull(TripleConstant.SERVICE_VERSION, inv.getInvoker().getUrl().getVersion()) + .putIfNotNull(TripleConstant.CONSUMER_APP_NAME_KEY, + (String) inv.getObjectAttachments().remove(CommonConstants.APPLICATION_KEY)) + .putIfNotNull(TripleConstant.CONSUMER_APP_NAME_KEY, + (String) inv.getObjectAttachments().remove(CommonConstants.REMOTE_APPLICATION_KEY)) + .putIfNotNull(TripleConstant.SERVICE_GROUP, inv.getInvoker().getUrl().getGroup()); + inv.getObjectAttachments().remove(CommonConstants.GROUP_KEY); + inv.getObjectAttachments().remove(CommonConstants.INTERFACE_KEY); + inv.getObjectAttachments().remove(CommonConstants.PATH_KEY); + metadata.forEach(e -> metadata.put(e.getKey(), e.getValue())); + final Map attachments = inv.getObjectAttachments(); + if (attachments != null) { + convertAttachment(metadata, attachments); + } + return metadata; + } + + protected class ClientStreamObserver implements StreamObserver { + @Override + public void onNext(Object data) { + RpcInvocation invocation = (RpcInvocation) data; + final Metadata metadata = createRequestMeta(invocation); + getTransportSubscriber().tryOnMetadata(metadata, false); + final byte[] bytes = encodeRequest(invocation); + getTransportSubscriber().tryOnData(bytes, false); + } + + @Override + public void onError(Throwable throwable) { + + } + + @Override + public void onCompleted() { + getTransportSubscriber().tryOnComplete(); + } + } + +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractServerStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractServerStream.java new file mode 100644 index 00000000000..41dfabf17f1 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractServerStream.java @@ -0,0 +1,211 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.constants.CommonConstants; +import org.apache.dubbo.common.extension.ExtensionLoader; +import org.apache.dubbo.common.threadpool.manager.ExecutorRepository; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.ProviderModel; +import org.apache.dubbo.rpc.model.ServiceDescriptor; +import org.apache.dubbo.rpc.model.ServiceRepository; +import org.apache.dubbo.triple.TripleWrapper; + +import com.google.protobuf.Message; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.RejectedExecutionException; + +public abstract class AbstractServerStream extends AbstractStream implements Stream { + + protected static final ExecutorRepository EXECUTOR_REPOSITORY = + ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension(); + private final ProviderModel providerModel; + private List methodDescriptors; + private Invoker invoker; + + protected AbstractServerStream(URL url) { + this(url, lookupProviderModel(url)); + } + + protected AbstractServerStream(URL url, ProviderModel providerModel) { + this(url, lookupExecutor(url, providerModel), providerModel); + } + + protected AbstractServerStream(URL url, Executor executor, ProviderModel providerModel) { + super(url, executor); + this.providerModel = providerModel; + } + + private static Executor lookupExecutor(URL url, ProviderModel providerModel) { + ExecutorService executor = null; + if (providerModel != null) { + executor = (ExecutorService) providerModel.getServiceMetadata() + .getAttribute(CommonConstants.THREADPOOL_KEY); + } + if (executor == null) { + executor = EXECUTOR_REPOSITORY.getExecutor(url); + } + if (executor == null) { + executor = EXECUTOR_REPOSITORY.createExecutorIfAbsent(url); + } + return executor; + } + + public static AbstractServerStream unary(URL url) { + return new UnaryServerStream(url); + } + + public static AbstractServerStream stream(URL url) { + return new ServerStream(url); + } + + private static ProviderModel lookupProviderModel(URL url) { + ServiceRepository repo = ApplicationModel.getServiceRepository(); + final ProviderModel model = repo.lookupExportedService(url.getServiceKey()); + if (model != null) { + ClassLoadUtil.switchContextLoader(model.getServiceInterfaceClass().getClassLoader()); + } + return model; + } + + public List getMethodDescriptors() { + return methodDescriptors; + } + + public AbstractServerStream methods(List methods) { + this.methodDescriptors = methods; + return this; + } + + public Invoker getInvoker() { + return invoker; + } + + public ProviderModel getProviderModel() { + return providerModel; + } + + protected RpcInvocation buildInvocation(Metadata metadata) { + RpcInvocation inv = new RpcInvocation(); + inv.setServiceName(getServiceDescriptor().getServiceName()); + inv.setTargetServiceUniqueName(getUrl().getServiceKey()); + inv.setMethodName(getMethodDescriptor().getMethodName()); + inv.setParameterTypes(getMethodDescriptor().getParameterClasses()); + inv.setReturnTypes(getMethodDescriptor().getReturnTypes()); + + final Map attachments = parseMetadataToMap(metadata); + attachments.remove("interface"); + attachments.remove("serialization"); + attachments.remove("te"); + attachments.remove("path"); + attachments.remove(TripleConstant.CONTENT_TYPE_KEY); + attachments.remove(TripleConstant.SERVICE_GROUP); + attachments.remove(TripleConstant.SERVICE_VERSION); + attachments.remove(TripleConstant.MESSAGE_KEY); + attachments.remove(TripleConstant.STATUS_KEY); + attachments.remove(TripleConstant.TIMEOUT); + inv.setObjectAttachments(attachments); + + return inv; + } + + protected Object[] deserializeRequest(byte[] data) { + ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + try { + if (getProviderModel() != null) { + ClassLoadUtil.switchContextLoader(getProviderModel().getServiceInterfaceClass().getClassLoader()); + } + if (getMethodDescriptor() == null || getMethodDescriptor().isNeedWrap()) { + final TripleWrapper.TripleRequestWrapper wrapper = TripleUtil.unpack(data, + TripleWrapper.TripleRequestWrapper.class); + serialize(wrapper.getSerializeType()); + if (getMethodDescriptor() == null) { + final String[] paramTypes = wrapper.getArgTypesList().toArray(new String[wrapper.getArgsCount()]); + + for (MethodDescriptor descriptor : getMethodDescriptors()) { + if (Arrays.equals(descriptor.getCompatibleParamSignatures(), paramTypes)) { + method(descriptor); + break; + } + } + if (getMethodDescriptor() == null) { + transportError(GrpcStatus.fromCode(GrpcStatus.Code.UNIMPLEMENTED) + .withDescription("Method :" + getMethodName() + "[" + Arrays.toString(paramTypes) + "] " + + "not found of service:" + getServiceDescriptor().getServiceName())); + + return null; + } + } + + return TripleUtil.unwrapReq(getUrl(), wrapper, getMultipleSerialization()); + } else { + return new Object[]{TripleUtil.unpack(data, getMethodDescriptor().getParameterClasses()[0])}; + } + + } finally { + ClassLoadUtil.switchContextLoader(tccl); + } + } + + protected byte[] encodeResponse(Object value) { + final com.google.protobuf.Message message; + if (getMethodDescriptor().isNeedWrap()) { + message = TripleUtil.wrapResp(getUrl(), getSerializeType(), value, getMethodDescriptor(), + getMultipleSerialization()); + } else { + message = (Message) value; + } + return TripleUtil.pack(message); + } + + @Override + public void execute(Runnable runnable) { + try { + super.execute(runnable); + } catch (RejectedExecutionException e) { + LOGGER.error("Provider's thread pool is full", e); + transportError(GrpcStatus.fromCode(GrpcStatus.Code.RESOURCE_EXHAUSTED) + .withDescription("Provider's thread pool is full")); + } catch (Throwable t) { + LOGGER.error("Provider submit request to thread pool error ", t); + transportError(GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withCause(t) + .withDescription("Provider's error")); + } + } + + public AbstractServerStream service(ServiceDescriptor sd) { + setServiceDescriptor(sd); + return this; + } + + public AbstractServerStream invoker(Invoker invoker) { + this.invoker = invoker; + return this; + } + +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java index 964d0cd2614..2fd9524e8e4 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java @@ -14,130 +14,214 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.rpc.protocol.tri; +package org.apache.dubbo.rpc.protocol.tri; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.ExtensionLoader; -import org.apache.dubbo.common.logger.Logger; -import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.common.serialize.MultipleSerialization; +import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.common.threadlocal.NamedInternalThreadFactory; import org.apache.dubbo.common.utils.ConfigUtils; import org.apache.dubbo.config.Constants; +import org.apache.dubbo.remoting.exchange.Request; +import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.ServiceDescriptor; +import org.apache.dubbo.rpc.protocol.tri.GrpcStatus.Code; -import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http2.Http2Headers; import java.io.IOException; -import java.io.InputStream; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Locale; import java.util.Map; - -import static org.apache.dubbo.rpc.protocol.tri.TripleUtil.responseErr; +import java.util.concurrent.Executor; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadLocalRandom; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; public abstract class AbstractStream implements Stream { - public static final boolean ENABLE_ATTACHMENT_WRAP = Boolean.parseBoolean(ConfigUtils.getProperty("triple.attachment", "false")); - private static final Logger LOGGER = LoggerFactory.getLogger(AbstractStream.class); - private static final GrpcStatus TOO_MANY_DATA = GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) - .withDescription("Too many data"); - private final ChannelHandlerContext ctx; + public static final boolean ENABLE_ATTACHMENT_WRAP = Boolean.parseBoolean( + ConfigUtils.getProperty("triple.attachment", "false")); + protected static final String DUPLICATED_DATA = "Duplicated data"; + private static final List CALLBACK_EXECUTORS = new ArrayList<>(4); + + static { + ThreadFactory tripleTF = new NamedInternalThreadFactory("tri-callbcak", true); + for (int i = 0; i < 4; i++) { + final ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 0, TimeUnit.DAYS, new LinkedBlockingQueue<>(1024), + tripleTF, new ThreadPoolExecutor.AbortPolicy()); + CALLBACK_EXECUTORS.add(tp); + } + + } + private final URL url; - private MultipleSerialization multipleSerialization; - private Http2Headers headers; - private Http2Headers te; - private boolean needWrap; - private InputStream data; + private final MultipleSerialization multipleSerialization; + private final StreamObserver streamObserver; + private final TransportObserver transportObserver; + private final Executor executor; + private ServiceDescriptor serviceDescriptor; + private MethodDescriptor methodDescriptor; + private String methodName; + private Request request; private String serializeType; + private StreamObserver streamSubscriber; + private TransportObserver transportSubscriber; - protected AbstractStream(URL url, ChannelHandlerContext ctx) { - this(url, ctx, false); + protected AbstractStream(URL url) { + this(url, allocateCallbackExecutor()); } - protected AbstractStream(URL url, ChannelHandlerContext ctx, boolean needWrap) { - this.ctx = ctx; + protected AbstractStream(URL url, Executor executor) { this.url = url; - this.needWrap = needWrap; - if (needWrap) { - loadFromURL(url); - } + this.executor = executor; + final String value = url.getParameter(Constants.MULTI_SERIALIZATION_KEY, CommonConstants.DEFAULT_KEY); + this.multipleSerialization = ExtensionLoader.getExtensionLoader(MultipleSerialization.class) + .getExtension(value); + this.streamObserver = createStreamObserver(); + this.transportObserver = createTransportObserver(); } - protected void loadFromURL(URL url) { - final String value = url.getParameter(Constants.MULTI_SERIALIZATION_KEY, "default"); - this.multipleSerialization = ExtensionLoader.getExtensionLoader(MultipleSerialization.class).getExtension(value); + private static Executor allocateCallbackExecutor() { + return CALLBACK_EXECUTORS.get(ThreadLocalRandom.current().nextInt(4)); } - public URL getUrl() { - return url; + public Request getRequest() { + return request; } + public AbstractStream request(Request request) { + this.request = request; + return this; + } + + public Executor getExecutor() { + return executor; + } + + @Override + public void execute(Runnable runnable) { + executor.execute(runnable); + } + + public String getMethodName() { + return methodName; + } + + public AbstractStream methodName(String methodName) { + this.methodName = methodName; + return this; + } + + public AbstractStream method(MethodDescriptor md) { + this.methodDescriptor = md; + return this; + } + + protected abstract StreamObserver createStreamObserver(); + + protected abstract TransportObserver createTransportObserver(); + public String getSerializeType() { return serializeType; } - protected void setSerializeType(String serializeType) { + public AbstractStream serialize(String serializeType) { + if (serializeType.equals("hessian4")) { + serializeType = "hessian2"; + } this.serializeType = serializeType; + return this; } - protected boolean isNeedWrap() { - return needWrap; + public MultipleSerialization getMultipleSerialization() { + return multipleSerialization; } - protected void setNeedWrap(boolean needWrap) { - this.needWrap = needWrap; + public StreamObserver getStreamSubscriber() { + return streamSubscriber; } - public ChannelHandlerContext getCtx() { - return ctx; + public TransportObserver getTransportSubscriber() { + return transportSubscriber; } - public Http2Headers getHeaders() { - return headers; + public MethodDescriptor getMethodDescriptor() { + return methodDescriptor; } - public Http2Headers getTe() { - return te; + public ServiceDescriptor getServiceDescriptor() { + return serviceDescriptor; } - public InputStream getData() { - return data; + public void setServiceDescriptor(ServiceDescriptor serviceDescriptor) { + this.serviceDescriptor = serviceDescriptor; } - public MultipleSerialization getMultipleSerialization() { - return multipleSerialization; + public URL getUrl() { + return url; } @Override - public void onData(InputStream in) { - if (data != null) { - responseErr(ctx, TOO_MANY_DATA); - return; - } + public void subscribe(StreamObserver observer) { + this.streamSubscriber = observer; + } + + @Override + public void subscribe(TransportObserver observer) { + this.transportSubscriber = observer; + } + + @Override + public StreamObserver asStreamObserver() { + return streamObserver; + } - this.data = in; + @Override + public TransportObserver asTransportObserver() { + return transportObserver; + } + + protected void transportError(GrpcStatus status) { + Metadata metadata = new DefaultMetadata(); + metadata.put(TripleConstant.STATUS_KEY, Integer.toString(status.code.code)); + metadata.put(TripleConstant.MESSAGE_KEY, status.toMessage()); + getTransportSubscriber().tryOnMetadata(metadata, true); + if (LOGGER.isErrorEnabled()) { + LOGGER.error("[Triple-Server-Error] " + status.toMessage()); + } } - public void onHeaders(Http2Headers headers) { - if (this.headers == null) { - this.headers = headers; - } else if (te == null) { - this.te = headers; + protected void transportError(Throwable throwable) { + Metadata metadata = new DefaultMetadata(); + metadata.put(TripleConstant.STATUS_KEY, Integer.toString(Code.UNKNOWN.code)); + metadata.put(TripleConstant.MESSAGE_KEY, throwable.getMessage()); + getTransportSubscriber().tryOnMetadata(metadata, true); + if (LOGGER.isErrorEnabled()) { + LOGGER.error("[Triple-Server-Error] service=" + getServiceDescriptor().getServiceName() + + " method=" + getMethodName(), throwable); } } - protected Map parseHeadersToMap(Http2Headers headers) { + protected Map parseMetadataToMap(Metadata metadata) { Map attachments = new HashMap<>(); - for (Map.Entry header : headers) { + for (Map.Entry header : metadata) { String key = header.getKey().toString(); - if(Http2Headers.PseudoHeaderName.isPseudoHeader(key)){ + if (Http2Headers.PseudoHeaderName.isPseudoHeader(key)) { continue; } if (ENABLE_ATTACHMENT_WRAP) { if (key.endsWith("-tw-bin") && key.length() > 7) { try { - attachments.put(key.substring(0, key.length() - 7), TripleUtil.decodeObjFromHeader(getUrl(), header.getValue(), getMultipleSerialization())); + attachments.put(key.substring(0, key.length() - 7), + TripleUtil.decodeObjFromHeader(url, header.getValue(), multipleSerialization)); } catch (Exception e) { LOGGER.error("Failed to parse response attachment key=" + key, e); } @@ -156,27 +240,114 @@ protected Map parseHeadersToMap(Http2Headers headers) { return attachments; } - protected void convertAttachment(Http2Headers trailers, Map attachments) throws IOException { + protected void convertAttachment(Metadata metadata, Map attachments) { for (Map.Entry entry : attachments.entrySet()) { final String key = entry.getKey().toLowerCase(Locale.ROOT); - if(Http2Headers.PseudoHeaderName.isPseudoHeader(key)){ + if (Http2Headers.PseudoHeaderName.isPseudoHeader(key)) { continue; } final Object v = entry.getValue(); + convertSingleAttachment(metadata, key, v); + } + } + + private void convertSingleAttachment(Metadata metadata, String key, Object v) { + try { if (!ENABLE_ATTACHMENT_WRAP) { if (v instanceof String) { - trailers.addObject(key, v); + metadata.put(key, (String) v); } else if (v instanceof byte[]) { - trailers.add(key + "-bin", TripleUtil.encodeBase64ASCII((byte[]) v)); + metadata.put(key + "-bin", TripleUtil.encodeBase64ASCII((byte[]) v)); } } else { if (v instanceof String || serializeType == null) { - trailers.addObject(key, v); + metadata.put(key, v.toString()); } else { String encoded = TripleUtil.encodeWrapper(url, v, this.serializeType, getMultipleSerialization()); - trailers.add(key + "-tw-bin", encoded); + metadata.put(key + "-tw-bin", encoded); } } + } catch (IOException e) { + LOGGER.warn("Meet exception when convert single attachment key:" + key, e); + } + } + + protected static abstract class AbstractTransportObserver implements TransportObserver { + private Metadata headers; + private Metadata trailers; + + public Metadata getHeaders() { + return headers; + } + + public Metadata getTrailers() { + return trailers; + } + + @Override + public void onMetadata(Metadata metadata, boolean endStream, OperationHandler handler) { + if (headers == null) { + headers = metadata; + } else { + trailers = metadata; + } + } + + protected GrpcStatus extractStatusFromMeta(Metadata metadata) { + if (metadata.contains(TripleConstant.STATUS_KEY)) { + final int code = Integer.parseInt(metadata.get(TripleConstant.STATUS_KEY).toString()); + + if (!GrpcStatus.Code.isOk(code)) { + GrpcStatus status = GrpcStatus.fromCode(code); + if (metadata.contains(TripleConstant.MESSAGE_KEY)) { + final String raw = metadata.get(TripleConstant.MESSAGE_KEY).toString(); + status = status.withDescription(GrpcStatus.fromMessage(raw)); + } + return status; + } + return GrpcStatus.fromCode(Code.OK); + } + return GrpcStatus.fromCode(Code.OK); + } + + } + + protected abstract static class UnaryTransportObserver extends AbstractTransportObserver implements TransportObserver { + private byte[] data; + + public byte[] getData() { + return data; + } + + protected abstract void onError(GrpcStatus status); + + @Override + public void onComplete(OperationHandler handler) { + Metadata metadata; + if (getTrailers() == null) { + metadata = getHeaders(); + } else { + metadata = getTrailers(); + } + + final GrpcStatus status = extractStatusFromMeta(metadata); + if (GrpcStatus.Code.isOk(status.code.code)) { + doOnComplete(handler); + } else { + onError(status); + } + } + + protected abstract void doOnComplete(OperationHandler handler); + + @Override + public void onData(byte[] in, boolean endStream, OperationHandler handler) { + if (data == null) { + this.data = in; + } else { + handler.operationDone(OperationResult.FAILURE, GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withDescription(DUPLICATED_DATA).asException()); + } } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStream.java index 9114d0e47bd..7352bf14c8b 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStream.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientStream.java @@ -14,221 +14,72 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.dubbo.rpc.protocol.tri; import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.constants.CommonConstants; -import org.apache.dubbo.remoting.Constants; -import org.apache.dubbo.remoting.api.Connection; -import org.apache.dubbo.remoting.exchange.Request; -import org.apache.dubbo.remoting.exchange.Response; -import org.apache.dubbo.remoting.exchange.support.DefaultFuture2; -import org.apache.dubbo.rpc.AppResponse; -import org.apache.dubbo.rpc.Invocation; +import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.rpc.RpcInvocation; -import org.apache.dubbo.rpc.model.ApplicationModel; -import org.apache.dubbo.rpc.model.ConsumerModel; -import org.apache.dubbo.rpc.model.MethodDescriptor; -import org.apache.dubbo.rpc.model.ServiceRepository; -import org.apache.dubbo.triple.TripleWrapper; - -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.HttpHeaderValues; -import io.netty.handler.codec.http.HttpMethod; -import io.netty.handler.codec.http.HttpResponseStatus; -import io.netty.handler.codec.http2.DefaultHttp2DataFrame; -import io.netty.handler.codec.http2.DefaultHttp2Headers; -import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; -import io.netty.handler.codec.http2.Http2Headers; -import io.netty.handler.codec.http2.Http2NoMoreStreamIdsException; -import io.netty.handler.codec.http2.Http2StreamChannel; -import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; -import io.netty.util.AsciiString; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; -import java.util.concurrent.Executor; - -import static org.apache.dubbo.rpc.Constants.CONSUMER_MODEL; - -public class ClientStream extends AbstractStream implements Stream { - private static final GrpcStatus MISSING_RESP = GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) - .withDescription("Missing Response"); - private static final AsciiString SCHEME = AsciiString.of("http"); - private final String authority; - private final Request request; - private final RpcInvocation invocation; - private final Executor callback; - - public ClientStream(URL url, ChannelHandlerContext ctx, boolean needWrap, Request request, Executor callback) { - super(url, ctx, needWrap); - this.callback = callback; - if (needWrap) { - setSerializeType((String) ((RpcInvocation) (request.getData())).getObjectAttachment(Constants.SERIALIZATION_KEY)); - } - this.authority = url.getAddress(); - this.request = request; - this.invocation = (RpcInvocation) request.getData(); - } - public static ConsumerModel getConsumerModel(Invocation invocation) { - Object o = invocation.get(CONSUMER_MODEL); - if (o instanceof ConsumerModel) { - return (ConsumerModel) o; - } - String serviceKey = invocation.getInvoker().getUrl().getServiceKey(); - return ApplicationModel.getConsumerModel(serviceKey); - } +public class ClientStream extends AbstractClientStream implements Stream { - @Override - public void onError(GrpcStatus status) { - Response response = new Response(request.getId(), request.getVersion()); - if (status.description != null) { - response.setErrorMessage(status.description); - } else { - response.setErrorMessage(status.cause.getMessage()); - } - final byte code = GrpcStatus.toDubboStatus(status.code); - response.setStatus(code); - DefaultFuture2.received(Connection.getConnectionFromChannel(getCtx().channel()), response); + protected ClientStream(URL url) { + super(url); } @Override - public void write(Object obj, ChannelPromise promise) throws IOException { - final Http2StreamChannelBootstrap streamChannelBootstrap = new Http2StreamChannelBootstrap(getCtx().channel()); - final Http2StreamChannel streamChannel = streamChannelBootstrap.open().syncUninterruptibly().getNow(); - - Http2Headers headers = new DefaultHttp2Headers() - .authority(authority) - .scheme(SCHEME) - .method(HttpMethod.POST.asciiName()) - .path("/" + invocation.getObjectAttachment(CommonConstants.PATH_KEY) + "/" + invocation.getMethodName()) - .set(HttpHeaderNames.CONTENT_TYPE, TripleConstant.CONTENT_PROTO) - .set(TripleConstant.TIMEOUT, invocation.get(CommonConstants.TIMEOUT_KEY) + "m") - .set(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS); - - final String version = invocation.getInvoker().getUrl().getVersion(); - if (version != null) { - headers.set(TripleConstant.SERVICE_VERSION, version); - } - - final String app = (String) invocation.getObjectAttachment(CommonConstants.APPLICATION_KEY); - if (app != null) { - headers.set(TripleConstant.CONSUMER_APP_NAME_KEY, app); - invocation.getObjectAttachments().remove(CommonConstants.APPLICATION_KEY); - } - - final String group = invocation.getInvoker().getUrl().getGroup(); - if (group != null) { - headers.set(TripleConstant.SERVICE_GROUP, group); - invocation.getObjectAttachments().remove(CommonConstants.GROUP_KEY); - } - final Map attachments = invocation.getObjectAttachments(); - if (attachments != null) { - convertAttachment(headers, attachments); - } - headers.remove("path"); - headers.remove("interface"); - DefaultHttp2HeadersFrame frame = new DefaultHttp2HeadersFrame(headers); - final TripleHttp2ClientResponseHandler responseHandler = new TripleHttp2ClientResponseHandler(); - - - TripleUtil.setClientStream(streamChannel, this); - streamChannel.pipeline().addLast(responseHandler) - .addLast(new GrpcDataDecoder(Integer.MAX_VALUE)) - .addLast(new TripleClientInboundHandler()); - streamChannel.write(frame).addListener(future -> { - if (!future.isSuccess()) { - if (future.cause() instanceof Http2NoMoreStreamIdsException) { - getCtx().close(); + protected StreamObserver createStreamObserver() { + return new ClientStreamObserver() { + boolean metaSent; + + @Override + public void onNext(Object data) { + if (!metaSent) { + metaSent = true; + final Metadata metadata = createRequestMeta((RpcInvocation) getRequest().getData()); + getTransportSubscriber().tryOnMetadata(metadata, false); } - promise.setFailure(future.cause()); + final byte[] bytes = encodeRequest(data); + getTransportSubscriber().tryOnData(bytes, false); } - }); - final ByteBuf out; - ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - try { - final ConsumerModel model = getConsumerModel(invocation); - if (model != null) { - ClassLoadUtil.switchContextLoader(model.getClassLoader()); + @Override + public void onError(Throwable throwable) { + transportError(throwable); } - if (isNeedWrap()) { - final TripleWrapper.TripleRequestWrapper wrap = TripleUtil.wrapReq(getUrl(), invocation, getMultipleSerialization()); - out = TripleUtil.pack(getCtx(), wrap); - } else { - out = TripleUtil.pack(getCtx(), invocation.getArguments()[0]); - } - } finally { - ClassLoadUtil.switchContextLoader(tccl); - } - final DefaultHttp2DataFrame data = new DefaultHttp2DataFrame(out, true); - streamChannel.write(data).addListener(f -> { - if (f.isSuccess()) { - promise.trySuccess(); - } else { - promise.tryFailure(f.cause()); - } - }); + }; } - public void halfClose() { - final int httpCode = HttpResponseStatus.parseLine(getHeaders().status()).code(); - if (HttpResponseStatus.OK.code() != httpCode) { - final Integer code = getHeaders().getInt(TripleConstant.STATUS_KEY); - final GrpcStatus status = GrpcStatus.fromCode(code) - .withDescription(TripleUtil.percentDecode(getHeaders().get(TripleConstant.MESSAGE_KEY))); - onError(status); - return; - } - final Http2Headers te = getTe() == null ? getHeaders() : getTe(); - final Integer code = te.getInt(TripleConstant.STATUS_KEY); - if (!GrpcStatus.Code.isOk(code)) { - final GrpcStatus status = GrpcStatus.fromCode(code) - .withDescription(TripleUtil.percentDecode(getHeaders().get(TripleConstant.MESSAGE_KEY))); - onError(status); - return; - } - final InputStream data = getData(); - if (data == null) { - onError(MISSING_RESP); - return; - } - callback.execute(() -> { - final Invocation invocation = (Invocation) (request.getData()); - ServiceRepository repo = ApplicationModel.getServiceRepository(); - MethodDescriptor methodDescriptor = repo.lookupMethod(invocation.getServiceName(), invocation.getMethodName()); - ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - try { - final Object resp; - final ConsumerModel model = getConsumerModel(invocation); - if (model != null) { - ClassLoadUtil.switchContextLoader(model.getClassLoader()); - } - if (isNeedWrap()) { - final TripleWrapper.TripleResponseWrapper message = TripleUtil.unpack(data, TripleWrapper.TripleResponseWrapper.class); - resp = TripleUtil.unwrapResp(getUrl(), message, getMultipleSerialization()); - } else { - resp = TripleUtil.unpack(data, methodDescriptor.getReturnClass()); - } - Response response = new Response(request.getId(), request.getVersion()); - final AppResponse result = new AppResponse(resp); - result.setObjectAttachments(parseHeadersToMap(te)); - response.setResult(result); - DefaultFuture2.received(Connection.getConnectionFromChannel(getCtx().channel()), response); - } catch (Exception e) { - final GrpcStatus status = GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) - .withCause(e) - .withDescription("Failed to deserialize response"); - onError(status); - } finally { - ClassLoadUtil.switchContextLoader(tccl); + @Override + protected TransportObserver createTransportObserver() { + return new AbstractTransportObserver() { + + @Override + public void onData(byte[] data, boolean endStream, OperationHandler handler) { + execute(() -> { + final Object resp = deserializeResponse(data); + getStreamSubscriber().onNext(resp); + }); } - }); - } + @Override + public void onComplete(OperationHandler handler) { + execute(() -> { + Metadata metadata; + if (getTrailers() == null) { + metadata = getHeaders(); + } else { + metadata = getTrailers(); + } + final GrpcStatus status = extractStatusFromMeta(metadata); + + if (GrpcStatus.Code.isOk(status.code.code)) { + getStreamSubscriber().onCompleted(); + } else { + getStreamSubscriber().onError(status.asException()); + } + }); + } + }; + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientTransportObserver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientTransportObserver.java new file mode 100644 index 00000000000..a62d7754fed --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ClientTransportObserver.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPromise; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; +import io.netty.handler.codec.http2.Http2Headers; +import io.netty.handler.codec.http2.Http2StreamChannel; +import io.netty.handler.codec.http2.Http2StreamChannelBootstrap; +import io.netty.util.AsciiString; + +public class ClientTransportObserver implements TransportObserver { + private static final AsciiString SCHEME = AsciiString.of("http"); + private final ChannelHandlerContext ctx; + private final Http2StreamChannel streamChannel; + private final ChannelPromise promise; + private boolean headerSent = false; + private boolean endStreamSent = false; + + + public ClientTransportObserver(ChannelHandlerContext ctx, AbstractClientStream stream, ChannelPromise promise) { + this.ctx = ctx; + this.promise = promise; + + final Http2StreamChannelBootstrap streamChannelBootstrap = new Http2StreamChannelBootstrap(ctx.channel()); + streamChannel = streamChannelBootstrap.open().syncUninterruptibly().getNow(); + + final TripleHttp2ClientResponseHandler responseHandler = new TripleHttp2ClientResponseHandler(); + streamChannel.pipeline().addLast(responseHandler) + .addLast(new GrpcDataDecoder(Integer.MAX_VALUE)) + .addLast(new TripleClientInboundHandler()); + streamChannel.attr(TripleUtil.CLIENT_STREAM_KEY).set(stream); + } + + @Override + public void onMetadata(Metadata metadata, boolean endStream, Stream.OperationHandler handler) { + if (!headerSent) { + final Http2Headers headers = new DefaultHttp2Headers(true) + .path(metadata.get(TripleConstant.PATH_KEY)) + .authority(metadata.get(TripleConstant.AUTHORITY_KEY)) + .scheme(SCHEME) + .method(HttpMethod.POST.asciiName()); + metadata.forEach(e -> headers.set(e.getKey(), e.getValue())); + headerSent = true; + streamChannel.writeAndFlush(new DefaultHttp2HeadersFrame(headers, endStream)) + .addListener(future -> { + if (!future.isSuccess()) { + promise.tryFailure(future.cause()); + } + }); + } + } + + @Override + public void onData(byte[] data, boolean endStream, Stream.OperationHandler handler) { + ByteBuf buf = ctx.alloc().buffer(); + buf.writeByte(0); + buf.writeInt(data.length); + buf.writeBytes(data); + streamChannel.writeAndFlush(new DefaultHttp2DataFrame(buf, endStream)) + .addListener(future -> { + if (!future.isSuccess()) { + promise.tryFailure(future.cause()); + } + }); + } + + @Override + public void onComplete(Stream.OperationHandler handler) { + if (!endStreamSent) { + endStreamSent = true; + streamChannel.writeAndFlush(new DefaultHttp2DataFrame(true)) + .addListener(future -> { + if (future.isSuccess()) { + promise.trySuccess(); + } else { + promise.tryFailure(future.cause()); + } + }); + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DefaultMetadata.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DefaultMetadata.java new file mode 100644 index 00000000000..5873fd15964 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/DefaultMetadata.java @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Spliterator; +import java.util.function.Consumer; + +public class DefaultMetadata implements Metadata { + private final Map innerMap = new HashMap<>(); + + @Override + public CharSequence get(CharSequence key) { + return innerMap.get(key); + } + + @Override + public Metadata put(CharSequence key, CharSequence value) { + innerMap.put(key, value); + return this; + } + + @Override + public Iterator> iterator() { + return innerMap.entrySet().iterator(); + } + + @Override + public void forEach(Consumer> action) { + innerMap.entrySet().forEach(action); + } + + @Override + public Spliterator> spliterator() { + return innerMap.entrySet().spliterator(); + } + + public boolean contains(CharSequence key) { + return innerMap.containsKey(key); + } + +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GracefulShutdown.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GracefulShutdown.java index 1a5f83411d2..3743d7582ec 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GracefulShutdown.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GracefulShutdown.java @@ -33,8 +33,8 @@ public class GracefulShutdown { private static final long GRACEFUL_SHUTDOWN_PING_TIMEOUT_NANOS = TimeUnit.SECONDS.toNanos(10); private final ChannelHandlerContext ctx; private final ChannelPromise originPromise; - private boolean pingAckedOrTimeout; private final String goAwayMessage; + private boolean pingAckedOrTimeout; private Future pingFuture; public GracefulShutdown(ChannelHandlerContext ctx, String goAwayMessage, ChannelPromise originPromise) { @@ -66,7 +66,8 @@ void secondGoAwayAndClose(ChannelHandlerContext ctx) { pingFuture.cancel(false); try { - Http2GoAwayFrame goAwayFrame = new DefaultHttp2GoAwayFrame(Http2Error.NO_ERROR, ByteBufUtil.writeAscii(this.ctx.alloc(), this.goAwayMessage)); + Http2GoAwayFrame goAwayFrame = new DefaultHttp2GoAwayFrame(Http2Error.NO_ERROR, + ByteBufUtil.writeAscii(this.ctx.alloc(), this.goAwayMessage)); ctx.write(goAwayFrame); ctx.flush(); //TODO support customize graceful shutdown timeout mills diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcDataDecoder.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcDataDecoder.java index 2102a82d3e6..29d342a526c 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcDataDecoder.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcDataDecoder.java @@ -63,8 +63,9 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t } checkpoint(GrpcDecodeState.PAYLOAD); case PAYLOAD: - ByteBuf buf = in.readRetainedSlice(len); - out.add(buf); + byte[] dst = new byte[len]; + in.readBytes(dst); + out.add(dst); checkpoint(GrpcDecodeState.HEADER); break; default: @@ -73,6 +74,7 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) t } enum GrpcDecodeState { - HEADER, PAYLOAD + HEADER, + PAYLOAD } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatus.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatus.java index a3edc1e9b4d..669b90d5906 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatus.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatus.java @@ -16,8 +16,12 @@ */ package org.apache.dubbo.rpc.protocol.tri; +import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.exchange.Response; +import io.netty.handler.codec.http.QueryStringDecoder; +import io.netty.handler.codec.http.QueryStringEncoder; + /** * See https://github.com/grpc/grpc/blob/master/doc/statuscodes.md */ @@ -76,6 +80,21 @@ public static byte toDubboStatus(Code code) { return status; } + public static String limitSizeTo4KB(String desc) { + if (desc.length() < 4096) { + return desc; + } else { + return desc.substring(0, 4086); + } + } + + public static String fromMessage(String raw) { + if (raw == null || raw.isEmpty()) { + return ""; + } + return QueryStringDecoder.decodeComponent(raw); + } + public GrpcStatus withCause(Throwable cause) { return new GrpcStatus(this.code, cause, this.description); } @@ -88,6 +107,24 @@ public TripleRpcException asException() { return new TripleRpcException(this); } + public String toMessage() { + final String msg; + if (cause == null) { + msg = description; + } else { + String placeHolder = description == null ? "" : description; + msg = StringUtils.toString(placeHolder, cause); + } + if (msg == null) { + return ""; + } + String output = limitSizeTo4KB(msg); + QueryStringEncoder encoder = new QueryStringEncoder(""); + encoder.addParam("", output); + // ?= + return encoder.toString().substring(2); + } + enum Code { OK(0), CANCELLED(1), @@ -116,7 +153,6 @@ public static boolean isOk(Integer status) { return status == OK.code; } - public static Code fromCode(int code) { for (Code value : Code.values()) { if (value.code == code) { diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2HeaderMeta.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2HeaderMeta.java new file mode 100644 index 00000000000..8b069969fc2 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Http2HeaderMeta.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import io.netty.handler.codec.http2.Http2Headers; + +import java.util.Iterator; +import java.util.Map; + +public class Http2HeaderMeta implements Metadata { + private final Http2Headers headers; + + public Http2HeaderMeta(Http2Headers headers) { + this.headers = headers; + } + + @Override + public Metadata put(CharSequence key, CharSequence value) { + headers.set(key, value); + return this; + } + + @Override + public CharSequence get(CharSequence key) { + return headers.get(key); + } + + @Override + public boolean contains(CharSequence key) { + return headers.contains(key); + } + + @Override + public Iterator> iterator() { + return headers.iterator(); + } +} diff --git a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreRegisteredEvent.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Metadata.java similarity index 60% rename from dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreRegisteredEvent.java rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Metadata.java index c4363e6dcf1..cf578ec27de 100644 --- a/dubbo-registry/dubbo-registry-api/src/main/java/org/apache/dubbo/registry/client/event/ServiceInstancePreRegisteredEvent.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Metadata.java @@ -14,21 +14,24 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.registry.client.event; -import org.apache.dubbo.registry.client.ServiceDiscovery; -import org.apache.dubbo.registry.client.ServiceInstance; +package org.apache.dubbo.rpc.protocol.tri; +import java.util.Map; -/** - * An event raised before a {@link ServiceInstance service instance} - * {@link ServiceDiscovery#register(ServiceInstance) registered} - * - * @since 2.7.5 - */ -public class ServiceInstancePreRegisteredEvent extends ServiceInstanceEvent { +public interface Metadata extends Iterable> { + + Metadata put(CharSequence key, CharSequence value); - public ServiceInstancePreRegisteredEvent(Object source, ServiceInstance serviceInstance) { - super(source, serviceInstance); + default Metadata putIfNotNull(CharSequence key, CharSequence value) { + if (value != null) { + put(key, value); + } + return this; } -} + + CharSequence get(CharSequence key); + + boolean contains(CharSequence key); + +} \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerStream.java index 6f0f8317e4e..678c2d61005 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerStream.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerStream.java @@ -14,282 +14,92 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.dubbo.rpc.protocol.tri; -import org.apache.dubbo.common.constants.CommonConstants; -import org.apache.dubbo.common.extension.ExtensionLoader; -import org.apache.dubbo.common.logger.Logger; -import org.apache.dubbo.common.logger.LoggerFactory; -import org.apache.dubbo.common.threadpool.manager.ExecutorRepository; -import org.apache.dubbo.common.utils.ExecutorUtil; -import org.apache.dubbo.remoting.TimeoutException; -import org.apache.dubbo.rpc.AppResponse; -import org.apache.dubbo.rpc.Invocation; -import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.stream.StreamObserver; import org.apache.dubbo.rpc.Result; -import org.apache.dubbo.rpc.RpcContext; import org.apache.dubbo.rpc.RpcInvocation; -import org.apache.dubbo.rpc.model.ApplicationModel; -import org.apache.dubbo.rpc.model.MethodDescriptor; -import org.apache.dubbo.rpc.model.ProviderModel; -import org.apache.dubbo.rpc.model.ServiceDescriptor; -import org.apache.dubbo.rpc.model.ServiceRepository; -import org.apache.dubbo.rpc.protocol.tri.GrpcStatus.Code; -import org.apache.dubbo.rpc.service.EchoService; -import org.apache.dubbo.rpc.service.GenericService; -import org.apache.dubbo.triple.TripleWrapper; - -import com.google.protobuf.Message; -import io.netty.buffer.ByteBuf; -import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http2.DefaultHttp2DataFrame; -import io.netty.handler.codec.http2.DefaultHttp2Headers; -import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; -import io.netty.handler.codec.http2.Http2Headers; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.RejectedExecutionException; -import java.util.function.BiConsumer; -import java.util.function.Function; - -import static io.netty.handler.codec.http.HttpResponseStatus.OK; -import static org.apache.dubbo.rpc.protocol.tri.TripleUtil.responseErr; - -public class ServerStream extends AbstractStream implements Stream { - private static final Logger LOGGER = LoggerFactory.getLogger(ServerStream.class); - private static final String TOO_MANY_REQ = "Too many requests"; - private static final String MISSING_REQ = "Missing request"; - private static final ExecutorRepository EXECUTOR_REPOSITORY = - ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension(); - private final Invoker invoker; - private final ChannelHandlerContext ctx; - private final ServiceDescriptor serviceDescriptor; - private final ProviderModel providerModel; - private final String methodName; - private MethodDescriptor methodDescriptor; - - public ServerStream(Invoker invoker, ServiceDescriptor serviceDescriptor, String methodName, ChannelHandlerContext ctx) { - super(ExecutorUtil.setThreadName(invoker.getUrl(), "DubboPUServerHandler"), ctx); - this.invoker = invoker; - ServiceRepository repo = ApplicationModel.getServiceRepository(); - this.providerModel = repo.lookupExportedService(getUrl().getServiceKey()); - this.methodName = methodName; - this.serviceDescriptor = serviceDescriptor; - this.ctx = ctx; +public class ServerStream extends AbstractServerStream implements Stream { + protected ServerStream(URL url) { + super(url); } - @Override - public void onError(GrpcStatus status) { + protected StreamObserver createStreamObserver() { + return new ServerStreamObserver(); } @Override - public void write(Object obj, ChannelPromise promise) throws Exception { - + protected TransportObserver createTransportObserver() { + return new StreamTransportObserver(); } - public void halfClose() throws Exception { - if (getData() == null) { - responseErr(ctx, GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) - .withDescription(MISSING_REQ)); - return; - } - ExecutorService executor = null; - if (providerModel != null) { - executor = (ExecutorService) providerModel.getServiceMetadata().getAttribute(CommonConstants.THREADPOOL_KEY); - } - if (executor == null) { - executor = EXECUTOR_REPOSITORY.getExecutor(getUrl()); + private class ServerStreamObserver implements StreamObserver { + private boolean headersSent; + + @Override + public void onNext(Object data) { + if (!headersSent) { + getTransportSubscriber().tryOnMetadata(new DefaultMetadata(), false); + headersSent = true; + } + final byte[] bytes = encodeResponse(data); + getTransportSubscriber().tryOnData(bytes, false); } - if (executor == null) { - executor = EXECUTOR_REPOSITORY.createExecutorIfAbsent(getUrl()); + + @Override + public void onError(Throwable throwable) { + final GrpcStatus status = GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withCause(throwable) + .withDescription("Biz exception"); + transportError(status); } - try { - executor.execute(this::unaryInvoke); - } catch (RejectedExecutionException e) { - LOGGER.error("Provider's thread pool is full", e); - responseErr(ctx, GrpcStatus.fromCode(Code.RESOURCE_EXHAUSTED) - .withDescription("Provider's thread pool is full")); - } catch (Throwable t) { - LOGGER.error("Provider submit request to thread pool error ", t); - responseErr(ctx, GrpcStatus.fromCode(Code.INTERNAL) - .withCause(t) - .withDescription("Provider's error")); + @Override + public void onCompleted() { + Metadata metadata = new DefaultMetadata(); + metadata.put(TripleConstant.MESSAGE_KEY, "OK"); + metadata.put(TripleConstant.STATUS_KEY, Integer.toString(GrpcStatus.Code.OK.code)); + getTransportSubscriber().tryOnMetadata(metadata, true); } } - private void unaryInvoke() { + private class StreamTransportObserver extends AbstractTransportObserver implements TransportObserver { - Invocation invocation; - try { - invocation = buildInvocation(); - } catch (Throwable t) { - LOGGER.warn("Exception processing triple message", t); - responseErr(ctx, GrpcStatus.fromCode(Code.INTERNAL).withDescription("Decode request failed:" + t.getMessage())); - return; - } - if (invocation == null) { - return; + @Override + public void onMetadata(Metadata metadata, boolean endStream, OperationHandler handler) { + super.onMetadata(metadata, endStream, handler); + final RpcInvocation inv = buildInvocation(metadata); + inv.setArguments(new Object[]{asStreamObserver()}); + final Result result = getInvoker().invoke(inv); + try { + subscribe((StreamObserver) result.getValue()); + } catch (Throwable t) { + transportError(GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withDescription("Failed to create server's observer")); + } } - final Result result = this.invoker.invoke(invocation); - CompletionStage future = result.thenApply(Function.identity()); - - BiConsumer onComplete = (appResult, t) -> { + @Override + public void onData(byte[] in, boolean endStream, OperationHandler handler) { try { - if (t != null) { - if (t instanceof TimeoutException) { - responseErr(ctx, GrpcStatus.fromCode(Code.DEADLINE_EXCEEDED).withCause(t)); - } else { - responseErr(ctx, GrpcStatus.fromCode(GrpcStatus.Code.UNKNOWN).withCause(t)); - } - return; - } - AppResponse response = (AppResponse) appResult; - if (response.hasException()) { - final Throwable exception = response.getException(); - if (exception instanceof TripleRpcException) { - responseErr(ctx, ((TripleRpcException) exception).getStatus()); - } else { - responseErr(ctx, GrpcStatus.fromCode(GrpcStatus.Code.UNKNOWN) - .withCause(exception)); - } - return; + final Object[] arguments = deserializeRequest(in); + if (arguments != null) { + getStreamSubscriber().onNext(arguments[0]); } - Http2Headers http2Headers = new DefaultHttp2Headers() - .status(OK.codeAsText()) - .set(HttpHeaderNames.CONTENT_TYPE, TripleConstant.CONTENT_PROTO); - final Message message; - - ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - - final ByteBuf buf; - try { - ClassLoadUtil.switchContextLoader(providerModel.getServiceInterfaceClass().getClassLoader()); - if (isNeedWrap()) { - message = TripleUtil.wrapResp(getUrl(), getSerializeType(), response.getValue(), methodDescriptor, getMultipleSerialization()); - } else { - message = (Message) response.getValue(); - } - buf = TripleUtil.pack(ctx, message); - } finally { - ClassLoadUtil.switchContextLoader(tccl); - } - - final Http2Headers trailers = new DefaultHttp2Headers(); - final Map attachments = response.getObjectAttachments(); - if (attachments != null) { - convertAttachment(trailers, attachments); - } - trailers.setInt(TripleConstant.STATUS_KEY, GrpcStatus.Code.OK.code); - ctx.write(new DefaultHttp2HeadersFrame(http2Headers)); - final DefaultHttp2DataFrame data = new DefaultHttp2DataFrame(buf); - ctx.write(data); - ctx.writeAndFlush(new DefaultHttp2HeadersFrame(trailers, true)); - } catch (Throwable e) { - LOGGER.warn("Exception processing triple message", e); - if (e instanceof TripleRpcException) { - responseErr(ctx, ((TripleRpcException) e).getStatus()); - } else { - responseErr(ctx, GrpcStatus.fromCode(GrpcStatus.Code.UNKNOWN) - .withDescription("Exception occurred in provider's execution:" + e.getMessage()) - .withCause(e)); - } - } - }; - - future.whenComplete(onComplete); - RpcContext.removeContext(); - } - - - private Invocation buildInvocation() { - - RpcInvocation inv = new RpcInvocation(); - ClassLoader tccl = Thread.currentThread().getContextClassLoader(); - ServiceRepository repo = ApplicationModel.getServiceRepository(); - final List methods = serviceDescriptor.getMethods(methodName); - if (CommonConstants.$INVOKE.equals(methodName) || CommonConstants.$INVOKE_ASYNC.equals(methodName)) { - this.methodDescriptor = repo.lookupMethod(GenericService.class.getName(), methodName); - setNeedWrap(true); - } else if("$echo".equals(methodName)) { - this.methodDescriptor=repo.lookupMethod(EchoService.class.getName(),methodName); - setNeedWrap(true); - }else{ - if (methods == null || methods.isEmpty()) { - responseErr(ctx, GrpcStatus.fromCode(Code.UNIMPLEMENTED) - .withDescription("Method not found:" + methodName + " of service:" + serviceDescriptor.getServiceName())); - return null; + } catch (Throwable t) { + transportError(GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withDescription("Deserialize request failed") + .withCause(t)); } - if (methods.size() == 1) { - this.methodDescriptor = methods.get(0); - setNeedWrap(TripleUtil.needWrapper(this.methodDescriptor.getParameterClasses())); - } else { - // can not determine which one to invoke when same protobuf method name is used, force wrap it - setNeedWrap(true); - } - } - if (isNeedWrap()) { - loadFromURL(getUrl()); } - try { - if (providerModel != null) { - ClassLoadUtil.switchContextLoader(providerModel.getServiceInterfaceClass().getClassLoader()); - } - if (isNeedWrap()) { - final TripleWrapper.TripleRequestWrapper req = TripleUtil.unpack(getData(), TripleWrapper.TripleRequestWrapper.class); - setSerializeType(req.getSerializeType()); - if (this.methodDescriptor == null) { - String[] paramTypes = req.getArgTypesList().toArray(new String[req.getArgsCount()]); - for (MethodDescriptor method : methods) { - if (Arrays.equals(method.getCompatibleParamSignatures(), paramTypes)) { - this.methodDescriptor = method; - break; - } - } - if (this.methodDescriptor == null) { - responseErr(ctx, GrpcStatus.fromCode(Code.UNIMPLEMENTED) - .withDescription("Method not found:" + methodName + - " args:" + Arrays.toString(paramTypes) + " of service:" + serviceDescriptor.getServiceName())); - return null; - } - } - final Object[] arguments = TripleUtil.unwrapReq(getUrl(), req, getMultipleSerialization()); - inv.setArguments(arguments); - } else { - - final Object req = TripleUtil.unpack(getData(), methodDescriptor.getParameterClasses()[0]); - inv.setArguments(new Object[]{req}); - } - } finally { - ClassLoadUtil.switchContextLoader(tccl); + @Override + public void onComplete(OperationHandler handler) { + getStreamSubscriber().onCompleted(); } - inv.setMethodName(methodDescriptor.getMethodName()); - inv.setServiceName(serviceDescriptor.getServiceName()); - inv.setTargetServiceUniqueName(getUrl().getServiceKey()); - inv.setParameterTypes(methodDescriptor.getParameterClasses()); - inv.setReturnTypes(methodDescriptor.getReturnTypes()); - final Map attachments = parseHeadersToMap(getHeaders()); - attachments.remove("interface"); - attachments.remove("serialization"); - attachments.remove("te"); - attachments.remove("path"); - attachments.remove(TripleConstant.CONTENT_TYPE_KEY); - attachments.remove(TripleConstant.SERVICE_GROUP); - attachments.remove(TripleConstant.SERVICE_VERSION); - attachments.remove(TripleConstant.MESSAGE_KEY); - attachments.remove(TripleConstant.STATUS_KEY); - attachments.remove(TripleConstant.TIMEOUT); - inv.setObjectAttachments(attachments); - return inv; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerTransportObserver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerTransportObserver.java new file mode 100644 index 00000000000..38cf5d35843 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/ServerTransportObserver.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http2.DefaultHttp2DataFrame; +import io.netty.handler.codec.http2.DefaultHttp2Headers; +import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; + +import static io.netty.handler.codec.http.HttpResponseStatus.OK; + +public class ServerTransportObserver implements TransportObserver { + private final ChannelHandlerContext ctx; + private boolean headerSent = false; + + public ServerTransportObserver(ChannelHandlerContext ctx) { + this.ctx = ctx; + } + + @Override + public void onMetadata(Metadata metadata, boolean endStream, Stream.OperationHandler handler) { + final DefaultHttp2Headers headers = new DefaultHttp2Headers(true); + metadata.forEach(e -> { + headers.set(e.getKey(), e.getValue()); + }); + if (!headerSent) { + headerSent = true; + headers.status(OK.codeAsText()); + headers.set(TripleConstant.CONTENT_TYPE_KEY, TripleConstant.CONTENT_PROTO); + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(headers, endStream)); + } else { + ctx.writeAndFlush(new DefaultHttp2HeadersFrame(headers, endStream)); + } + } + + @Override + public void onData(byte[] data, boolean endStream, Stream.OperationHandler handler) { + ByteBuf buf = ctx.alloc().buffer(); + buf.writeByte(0); + buf.writeInt(data.length); + buf.writeBytes(data); + ctx.writeAndFlush(new DefaultHttp2DataFrame(buf, false)); + } + + @Override + public void onComplete(Stream.OperationHandler handler) { + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/SingleProtobufSerialization.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/SingleProtobufSerialization.java index 33982579a4f..75b2ac3d72a 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/SingleProtobufSerialization.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/SingleProtobufSerialization.java @@ -16,8 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri; - -import com.google.protobuf.CodedInputStream; import com.google.protobuf.ExtensionRegistryLite; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.Message; @@ -66,54 +64,26 @@ public Object deserialize(InputStream in, Class clz) throws IOException { } } - public int serialize(Object obj, OutputStream os) throws IOException { + public void serialize(Object obj, OutputStream os) throws IOException { final MessageLite msg = (MessageLite) obj; msg.writeTo(os); - return msg.getSerializedSize(); } private SingleMessageMarshaller getMarshaller(Class clz) { return marshallers.computeIfAbsent(clz, k -> new SingleMessageMarshaller(k)); } - private SingleMessageMarshaller getMarshaller(Object obj) { - return getMarshaller(obj.getClass()); - } - public static final class SingleMessageMarshaller { private final Parser parser; - private final T defaultInstance; - @SuppressWarnings("unchecked") SingleMessageMarshaller(Class clz) { - this.defaultInstance = (T) defaultInst(clz); - this.parser = (Parser) defaultInstance.getParserForType(); - } - - @SuppressWarnings("unchecked") - public Class getMessageClass() { - // Precisely T since protobuf doesn't let messages extend other messages. - return (Class) defaultInstance.getClass(); - } - - public T getMessagePrototype() { - return defaultInstance; + final T inst = (T) defaultInst(clz); + this.parser = (Parser) inst.getParserForType(); } public T parse(InputStream stream) throws InvalidProtocolBufferException { return parser.parseFrom(stream, globalRegistry); } - - private T parseFrom(CodedInputStream stream) throws InvalidProtocolBufferException { - T message = parser.parseFrom(stream, globalRegistry); - try { - stream.checkLastTagWas(0); - return message; - } catch (InvalidProtocolBufferException e) { - e.setUnfinishedMessage(message); - throw e; - } - } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Stream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Stream.java index 131c83ea153..4737fa68288 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Stream.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/Stream.java @@ -16,20 +16,36 @@ */ package org.apache.dubbo.rpc.protocol.tri; -import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http2.Http2Headers; - -import java.io.InputStream; +import org.apache.dubbo.common.logger.Logger; +import org.apache.dubbo.common.logger.LoggerFactory; +import org.apache.dubbo.common.stream.StreamObserver; public interface Stream { - void onHeaders(Http2Headers headers); + Logger LOGGER = LoggerFactory.getLogger(Stream.class); + + void subscribe(TransportObserver observer); + + TransportObserver asTransportObserver(); + + void subscribe(StreamObserver observer); + + StreamObserver asStreamObserver(); - void onData(InputStream in); + void execute(Runnable runnable); - void onError(GrpcStatus status); + enum OperationResult { + OK, + FAILURE, + NETWORK_FAIL + } - void write(Object obj, ChannelPromise promise) throws Exception; + interface OperationHandler { - void halfClose() throws Exception; + /** + * @param result operation's result + * @param cause null if the operation succeed + */ + void operationDone(OperationResult result, Throwable cause); + } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TransportObserver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TransportObserver.java new file mode 100644 index 00000000000..6e33c5c28c4 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TransportObserver.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +public interface TransportObserver { + Stream.OperationHandler EMPTY_HANDLER = (result, cause) -> { + }; + + default void tryOnMetadata(Metadata metadata, boolean endStream) { + onMetadata(metadata, endStream, EMPTY_HANDLER); + } + + default void tryOnData(byte[] data, boolean endStream) { + onData(data, endStream, EMPTY_HANDLER); + } + + default void tryOnComplete() { + onComplete(EMPTY_HANDLER); + } + + void onMetadata(Metadata metadata, boolean endStream, Stream.OperationHandler handler); + + void onData(byte[] data, boolean endStream, Stream.OperationHandler handler); + + void onComplete(Stream.OperationHandler handler); + +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientHandler.java index e99e3659a82..8c6282b2dd0 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientHandler.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientHandler.java @@ -17,9 +17,19 @@ package org.apache.dubbo.rpc.protocol.tri; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.remoting.Constants; +import org.apache.dubbo.remoting.api.Connection; import org.apache.dubbo.remoting.api.ConnectionHandler; import org.apache.dubbo.remoting.exchange.Request; +import org.apache.dubbo.remoting.exchange.Response; +import org.apache.dubbo.remoting.exchange.support.DefaultFuture2; +import org.apache.dubbo.rpc.AppResponse; import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.ConsumerModel; +import org.apache.dubbo.rpc.model.MethodDescriptor; +import org.apache.dubbo.rpc.model.ServiceRepository; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; @@ -28,7 +38,6 @@ import io.netty.handler.codec.http2.Http2SettingsFrame; import io.netty.util.ReferenceCountUtil; -import java.io.IOException; import java.util.concurrent.Executor; public class TripleClientHandler extends ChannelDuplexHandler { @@ -55,12 +64,40 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception } } - private void writeRequest(ChannelHandlerContext ctx, final Request req, ChannelPromise promise) throws IOException { + private void writeRequest(ChannelHandlerContext ctx, final Request req, final ChannelPromise promise) { final RpcInvocation inv = (RpcInvocation) req.getData(); - final boolean needWrapper = TripleUtil.needWrapper(inv.getParameterTypes()); final URL url = inv.getInvoker().getUrl(); - final Executor callback = (Executor) inv.getAttributes().remove("callback.executor"); - ClientStream clientStream = new ClientStream(url, ctx, needWrapper, req,callback); - clientStream.write(req, promise); + ServiceRepository repo = ApplicationModel.getServiceRepository(); + MethodDescriptor methodDescriptor = repo.lookupMethod(inv.getServiceName(), inv.getMethodName()); + final ConsumerModel service = repo.lookupReferredService(url.getServiceKey()); + if (service != null) { + ClassLoadUtil.switchContextLoader(service.getServiceInterfaceClass().getClassLoader()); + } + final Executor executor = (Executor) inv.getAttributes().remove("callback.executor"); + AbstractClientStream stream; + if (methodDescriptor.isUnary()) { + stream = AbstractClientStream.unary(url, executor); + } else { + stream = AbstractClientStream.stream(url); + } + stream.service(service) + .connection(Connection.getConnectionFromChannel(ctx.channel())) + .method(methodDescriptor) + .methodName(methodDescriptor.getMethodName()) + .request(req) + .serialize((String) inv.getObjectAttachment(Constants.SERIALIZATION_KEY)) + .subscribe(new ClientTransportObserver(ctx, stream, promise)); + + if (methodDescriptor.isUnary()) { + stream.asStreamObserver().onNext(inv); + stream.asStreamObserver().onCompleted(); + } else { + final StreamObserver streamObserver = (StreamObserver) inv.getArguments()[0]; + stream.subscribe(streamObserver); + Response response = new Response(req.getId(), req.getVersion()); + final AppResponse result = new AppResponse(stream.asStreamObserver()); + response.setResult(result); + DefaultFuture2.received(stream.getConnection(), response); + } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientInboundHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientInboundHandler.java index 2734014ad66..913e91c25a9 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientInboundHandler.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleClientInboundHandler.java @@ -16,18 +16,18 @@ */ package org.apache.dubbo.rpc.protocol.tri; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class TripleClientInboundHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - final ClientStream invoker = TripleUtil.getClientStream(ctx); - final ByteBuf buffer = (ByteBuf) msg; - if (invoker != null) { - invoker.onData(new ByteBufInputStream(buffer, buffer.readableBytes(),true)); + final AbstractClientStream clientStream = TripleUtil.getClientStream(ctx); + + final byte[] data = (byte[]) msg; + if (clientStream != null) { + clientStream.asTransportObserver() + .tryOnData(data, false); } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java index 1abe7e2ba0a..673897f3fa4 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleConstant.java @@ -17,6 +17,9 @@ package org.apache.dubbo.rpc.protocol.tri; public interface TripleConstant { + String AUTHORITY_KEY = ":authority"; + String PATH_KEY = ":path"; + String HTTP_STATUS_KEY = "http-status"; String STATUS_KEY = "grpc-status"; String MESSAGE_KEY = "grpc-message"; String TIMEOUT = "grpc-timeout"; @@ -29,5 +32,6 @@ public interface TripleConstant { String UNIT_INFO_KEY = "tri-unit-info"; String SERVICE_VERSION = "tri-service-version"; String SERVICE_GROUP = "tri-service-group"; + String TRI_VERSION = "1.0.0"; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2ClientResponseHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2ClientResponseHandler.java index c87b178705f..e215c599629 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2ClientResponseHandler.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2ClientResponseHandler.java @@ -23,11 +23,12 @@ import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http2.Http2DataFrame; import io.netty.handler.codec.http2.Http2GoAwayFrame; +import io.netty.handler.codec.http2.Http2Headers; import io.netty.handler.codec.http2.Http2HeadersFrame; import io.netty.handler.codec.http2.Http2StreamFrame; public final class TripleHttp2ClientResponseHandler extends SimpleChannelInboundHandler { - private static final Logger LOGGER = LoggerFactory.getLogger(TripleHttp2ClientResponseHandler.class); + private static final Logger logger = LoggerFactory.getLogger(TripleHttp2ClientResponseHandler.class); public TripleHttp2ClientResponseHandler() { super(false); @@ -37,7 +38,10 @@ public TripleHttp2ClientResponseHandler() { public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { super.userEventTriggered(ctx, evt); if (evt instanceof Http2GoAwayFrame) { + Http2GoAwayFrame event = (Http2GoAwayFrame) evt; ctx.close(); + logger.debug( + "Event triggered, event name is: " + event.name() + ", last stream id is: " + event.lastStreamId()); } } @@ -53,29 +57,35 @@ protected void channelRead0(ChannelHandlerContext ctx, Http2StreamFrame msg) thr } private void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame msg) { - TripleUtil.getClientStream(ctx).onHeaders(msg.headers()); + Http2Headers headers = msg.headers(); + AbstractClientStream clientStream = TripleUtil.getClientStream(ctx); + final TransportObserver observer = clientStream.asTransportObserver(); + observer.tryOnMetadata(new Http2HeaderMeta(headers), false); if (msg.isEndStream()) { - final ClientStream clientStream = TripleUtil.getClientStream(ctx); - clientStream.halfClose(); + observer.tryOnComplete(); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - final ClientStream clientStream = TripleUtil.getClientStream(ctx); + final AbstractClientStream clientStream = TripleUtil.getClientStream(ctx); final GrpcStatus status = GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) .withCause(cause); - clientStream.onError(status); + Metadata metadata = new DefaultMetadata(); + metadata.put(TripleConstant.STATUS_KEY, Integer.toString(status.code.code)); + metadata.put(TripleConstant.MESSAGE_KEY, status.toMessage()); + logger.warn("Meet Exception on ClientResponseHandler, status code is: " + status.code, cause); + clientStream.asStreamObserver().onError(status.asException()); ctx.close(); } public void onDataRead(ChannelHandlerContext ctx, Http2DataFrame msg) throws Exception { super.channelRead(ctx, msg.content()); if (msg.isEndStream()) { - final ClientStream clientStream = TripleUtil.getClientStream(ctx); + final AbstractClientStream clientStream = TripleUtil.getClientStream(ctx); // stream already closed; if (clientStream != null) { - clientStream.halfClose(); + clientStream.asTransportObserver().tryOnComplete(); } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2FrameServerHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2FrameServerHandler.java index 17fa7bba9f6..351c174c609 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2FrameServerHandler.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2FrameServerHandler.java @@ -17,14 +17,18 @@ package org.apache.dubbo.rpc.protocol.tri; import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.constants.CommonConstants; import org.apache.dubbo.common.extension.ExtensionLoader; import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.model.ApplicationModel; +import org.apache.dubbo.rpc.model.MethodDescriptor; import org.apache.dubbo.rpc.model.ServiceDescriptor; import org.apache.dubbo.rpc.model.ServiceRepository; import org.apache.dubbo.rpc.protocol.tri.GrpcStatus.Code; +import org.apache.dubbo.rpc.service.EchoService; +import org.apache.dubbo.rpc.service.GenericService; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; @@ -38,12 +42,16 @@ import io.netty.handler.codec.http2.Http2HeadersFrame; import io.netty.util.ReferenceCountUtil; +import java.util.List; + import static org.apache.dubbo.rpc.protocol.tri.TripleUtil.responseErr; import static org.apache.dubbo.rpc.protocol.tri.TripleUtil.responsePlainTextError; public class TripleHttp2FrameServerHandler extends ChannelDuplexHandler { private static final Logger LOGGER = LoggerFactory.getLogger(TripleHttp2FrameServerHandler.class); - private static final PathResolver PATH_RESOLVER = ExtensionLoader.getExtensionLoader(PathResolver.class).getDefaultExtension(); + private static final PathResolver PATH_RESOLVER = ExtensionLoader.getExtensionLoader(PathResolver.class) + .getDefaultExtension(); + @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { @@ -74,18 +82,20 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E public void onDataRead(ChannelHandlerContext ctx, Http2DataFrame msg) throws Exception { super.channelRead(ctx, msg.content()); + if (msg.isEndStream()) { - final ServerStream serverStream = TripleUtil.getServerStream(ctx); - // stream already closed; + final AbstractServerStream serverStream = TripleUtil.getServerStream(ctx); if (serverStream != null) { - serverStream.halfClose(); + serverStream.asTransportObserver().tryOnComplete(); } } } private Invoker getInvoker(Http2Headers headers, String serviceName) { - final String version = headers.contains(TripleConstant.SERVICE_VERSION) ? headers.get(TripleConstant.SERVICE_VERSION).toString() : null; - final String group = headers.contains(TripleConstant.SERVICE_GROUP) ? headers.get(TripleConstant.SERVICE_GROUP).toString() : null; + final String version = headers.contains(TripleConstant.SERVICE_VERSION) ? headers.get( + TripleConstant.SERVICE_VERSION).toString() : null; + final String group = headers.contains(TripleConstant.SERVICE_GROUP) ? headers.get(TripleConstant.SERVICE_GROUP) + .toString() : null; final String key = URL.buildKey(serviceName, group, version); Invoker invoker = PATH_RESOLVER.resolve(key); if (invoker == null) { @@ -98,34 +108,39 @@ public void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame msg) thro final Http2Headers headers = msg.headers(); if (!HttpMethod.POST.asciiName().contentEquals(headers.method())) { - responsePlainTextError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED.code(), GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) - .withDescription(String.format("Method '%s' is not supported", headers.method()))); + responsePlainTextError(ctx, HttpResponseStatus.METHOD_NOT_ALLOWED.code(), + GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withDescription(String.format("Method '%s' is not supported", headers.method()))); return; } if (headers.path() == null) { - responsePlainTextError(ctx, HttpResponseStatus.NOT_FOUND.code(), GrpcStatus.fromCode(Code.UNIMPLEMENTED.code).withDescription("Expected path but is missing")); + responsePlainTextError(ctx, HttpResponseStatus.NOT_FOUND.code(), + GrpcStatus.fromCode(Code.UNIMPLEMENTED.code).withDescription("Expected path but is missing")); return; } final String path = headers.path().toString(); if (path.charAt(0) != '/') { - responsePlainTextError(ctx, HttpResponseStatus.NOT_FOUND.code(), GrpcStatus.fromCode(Code.UNIMPLEMENTED.code) - .withDescription(String.format("Expected path to start with /: %s", path))); + responsePlainTextError(ctx, HttpResponseStatus.NOT_FOUND.code(), + GrpcStatus.fromCode(Code.UNIMPLEMENTED.code) + .withDescription(String.format("Expected path to start with /: %s", path))); return; } final CharSequence contentType = HttpUtil.getMimeType(headers.get(HttpHeaderNames.CONTENT_TYPE)); if (contentType == null) { - responsePlainTextError(ctx, HttpResponseStatus.UNSUPPORTED_MEDIA_TYPE.code(), GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL.code) - .withDescription("Content-Type is missing from the request")); + responsePlainTextError(ctx, HttpResponseStatus.UNSUPPORTED_MEDIA_TYPE.code(), + GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL.code) + .withDescription("Content-Type is missing from the request")); return; } final String contentString = contentType.toString(); if (!TripleUtil.supportContentType(contentString)) { - responsePlainTextError(ctx, HttpResponseStatus.UNSUPPORTED_MEDIA_TYPE.code(), GrpcStatus.fromCode(Code.INTERNAL.code) - .withDescription(String.format("Content-Type '%s' is not supported", contentString))); + responsePlainTextError(ctx, HttpResponseStatus.UNSUPPORTED_MEDIA_TYPE.code(), + GrpcStatus.fromCode(Code.INTERNAL.code) + .withDescription(String.format("Content-Type '%s' is not supported", contentString))); return; } @@ -138,24 +153,59 @@ public void onHeadersRead(ChannelHandlerContext ctx, Http2HeadersFrame msg) thro String originalMethodName = parts[2]; String methodName = Character.toLowerCase(originalMethodName.charAt(0)) + originalMethodName.substring(1); - final Invoker delegateInvoker = getInvoker(headers, serviceName); - if (delegateInvoker == null) { - responseErr(ctx, GrpcStatus.fromCode(Code.UNIMPLEMENTED).withDescription("Service not found:" + serviceName)); + final Invoker invoker = getInvoker(headers, serviceName); + if (invoker == null) { + responseErr(ctx, + GrpcStatus.fromCode(Code.UNIMPLEMENTED).withDescription("Service not found:" + serviceName)); return; } ServiceRepository repo = ApplicationModel.getServiceRepository(); - final ServiceDescriptor descriptor = repo.lookupService(delegateInvoker.getUrl().getServiceKey()); - if (descriptor == null) { - responseErr(ctx, GrpcStatus.fromCode(Code.UNIMPLEMENTED).withDescription("Service not found:" + serviceName)); + final ServiceDescriptor serviceDescriptor = repo.lookupService(invoker.getUrl().getServiceKey()); + if (serviceDescriptor == null) { + responseErr(ctx, + GrpcStatus.fromCode(Code.UNIMPLEMENTED).withDescription("Service not found:" + serviceName)); return; } + MethodDescriptor methodDescriptor = null; + List methodDescriptors = null; - final ServerStream serverStream = new ServerStream(delegateInvoker, descriptor, methodName, ctx); - serverStream.onHeaders(headers); - ctx.channel().attr(TripleUtil.SERVER_STREAM_KEY).set(serverStream); + if (CommonConstants.$INVOKE.equals(methodName) || CommonConstants.$INVOKE_ASYNC.equals(methodName)) { + methodDescriptor = repo.lookupMethod(GenericService.class.getName(), methodName); + } else if (CommonConstants.$ECHO.equals(methodName)) { + methodDescriptor = repo.lookupMethod(EchoService.class.getName(), methodName); + } else { + methodDescriptors = serviceDescriptor.getMethods(methodName); + if (methodDescriptors == null || methodDescriptors.isEmpty()) { + responseErr(ctx, GrpcStatus.fromCode(Code.UNIMPLEMENTED) + .withDescription("Method :" + methodName + " not found of service:" + serviceName)); + return; + } + if (methodDescriptors.size() == 1) { + methodDescriptor = methodDescriptors.get(0); + } + } + final AbstractServerStream stream; + if (methodDescriptor != null && methodDescriptor.isStream()) { + stream = AbstractServerStream.stream(invoker.getUrl()); + } else { + stream = AbstractServerStream.unary(invoker.getUrl()); + } + stream.service(serviceDescriptor) + .invoker(invoker) + .methodName(methodName) + .subscribe(new ServerTransportObserver(ctx)); + if (methodDescriptor != null) { + stream.method(methodDescriptor); + } else { + stream.methods(methodDescriptors); + } + final TransportObserver observer = stream.asTransportObserver(); + observer.tryOnMetadata(new Http2HeaderMeta(headers), false); if (msg.isEndStream()) { - serverStream.halfClose(); + observer.tryOnComplete(); } + + ctx.channel().attr(TripleUtil.SERVER_STREAM_KEY).set(stream); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java index 79bd43acae1..208e351d118 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleHttp2Protocol.java @@ -16,7 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri; - import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.remoting.api.Http2WireProtocol; @@ -32,7 +31,6 @@ @Activate public class TripleHttp2Protocol extends Http2WireProtocol { - @Override public void close() { super.close(); @@ -50,12 +48,13 @@ public void configServerPipeline(ChannelPipeline pipeline, SslContext sslContext .frameLogger(SERVER_LOGGER) .build(); final Http2MultiplexHandler handler = new Http2MultiplexHandler(new TripleServerInitializer()); - pipeline.addLast(codec, new TripleServerConnectionHandler(), handler, new SimpleChannelInboundHandler() { - @Override - protected void channelRead0(ChannelHandlerContext ctx, Object msg) { - // empty - } - }); + pipeline.addLast(codec, new TripleServerConnectionHandler(), handler, + new SimpleChannelInboundHandler() { + @Override + protected void channelRead0(ChannelHandlerContext ctx, Object msg) { + // empty + } + }); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java index 6b5631a74e0..180d889dc6d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleInvoker.java @@ -64,13 +64,13 @@ */ public class TripleInvoker extends AbstractInvoker { - private static final ConnectionManager CONNECTION_MANAGER = ExtensionLoader.getExtensionLoader(ConnectionManager.class).getExtension("multiple"); + private static final ConnectionManager CONNECTION_MANAGER = ExtensionLoader.getExtensionLoader( + ConnectionManager.class).getExtension("multiple"); private final Connection connection; private final ReentrantLock destroyLock = new ReentrantLock(); private final Set> invokers; - public TripleInvoker(Class serviceType, URL url, Set> invokers) throws RemotingException { super(serviceType, url, new String[]{INTERFACE_KEY, GROUP_KEY, TOKEN_KEY}); this.invokers = invokers; @@ -82,7 +82,8 @@ protected Result doInvoke(final Invocation invocation) throws Throwable { RpcInvocation inv = (RpcInvocation) invocation; final String methodName = RpcUtils.getMethodName(invocation); inv.setAttachment(PATH_KEY, getUrl().getPath()); - inv.setAttachment(Constants.SERIALIZATION_KEY, getUrl().getParameter(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION)); + inv.setAttachment(Constants.SERIALIZATION_KEY, + getUrl().getParameter(Constants.SERIALIZATION_KEY, Constants.DEFAULT_REMOTING_SERIALIZATION)); try { int timeout = calculateTimeout(invocation, methodName); invocation.put(TIMEOUT_KEY, timeout); @@ -101,8 +102,7 @@ protected Result doInvoke(final Invocation invocation) throws Throwable { FutureContext.getContext().setCompatibleFuture(respFuture); AsyncRpcResult result = new AsyncRpcResult(respFuture, inv); result.setExecutor(executor); - inv.put("callback.executor",executor ); - + inv.put("callback.executor", executor); if (!connection.isAvailable()) { Response response = new Response(req.getId(), req.getVersion()); @@ -125,9 +125,13 @@ protected Result doInvoke(final Invocation invocation) throws Throwable { return result; } catch (TimeoutException e) { - throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e); + throw new RpcException(RpcException.TIMEOUT_EXCEPTION, + "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + + ", cause: " + e.getMessage(), e); } catch (RemotingException e) { - throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e); + throw new RpcException(RpcException.NETWORK_EXCEPTION, + "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + + ", cause: " + e.getMessage(), e); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TriplePathResolver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TriplePathResolver.java index ac6716155f3..1de6bc3243d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TriplePathResolver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TriplePathResolver.java @@ -16,7 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri; - import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; import org.apache.dubbo.rpc.Invoker; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java index 5b8eee44604..878756d4514 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocol.java @@ -42,9 +42,10 @@ public class TripleProtocol extends AbstractProtocol implements Protocol { private static final Logger logger = LoggerFactory.getLogger(TripleProtocol.class); - private final PathResolver pathResolver = ExtensionLoader.getExtensionLoader(PathResolver.class).getDefaultExtension(); - private final ExecutorRepository executorRepository = ExtensionLoader.getExtensionLoader(ExecutorRepository.class).getDefaultExtension(); - + private final PathResolver pathResolver = ExtensionLoader.getExtensionLoader(PathResolver.class) + .getDefaultExtension(); + private final ExecutorRepository executorRepository = ExtensionLoader.getExtensionLoader(ExecutorRepository.class) + .getDefaultExtension(); @Override public int getDefaultPort() { @@ -79,7 +80,7 @@ public void unexport() { public Invoker refer(Class type, URL url) throws RpcException { TripleInvoker invoker; try { - url = ExecutorUtil.setThreadName(url,"DubboClientHandler"); + url = ExecutorUtil.setThreadName(url, "DubboClientHandler"); url = url.addParameterIfAbsent(THREADPOOL_KEY, DEFAULT_CLIENT_THREADPOOL); executorRepository.createExecutorIfAbsent(url); invoker = new TripleInvoker<>(type, url, invokers); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleServerConnectionHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleServerConnectionHandler.java index 503b46b9537..f73695e761f 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleServerConnectionHandler.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleServerConnectionHandler.java @@ -16,7 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri; - import org.apache.dubbo.common.logger.Logger; import org.apache.dubbo.common.logger.LoggerFactory; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleServerInboundHandler.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleServerInboundHandler.java index 9a477c5e861..ba66c86d766 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleServerInboundHandler.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleServerInboundHandler.java @@ -16,18 +16,17 @@ */ package org.apache.dubbo.rpc.protocol.tri; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufInputStream; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; public class TripleServerInboundHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { - final ServerStream serverStream = TripleUtil.getServerStream(ctx); - final ByteBuf buffer = (ByteBuf) msg; + final AbstractServerStream serverStream = TripleUtil.getServerStream(ctx); + final byte[] data = (byte[]) msg; if (serverStream != null) { - serverStream.onData(new ByteBufInputStream(buffer,buffer.readableBytes(),true)); + serverStream.asTransportObserver() + .tryOnData(data, false); } } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleUtil.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleUtil.java index f35487064bb..10169547ea7 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleUtil.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TripleUtil.java @@ -18,7 +18,6 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.serialize.MultipleSerialization; -import org.apache.dubbo.common.utils.StringUtils; import org.apache.dubbo.remoting.Constants; import org.apache.dubbo.rpc.RpcInvocation; import org.apache.dubbo.rpc.model.MethodDescriptor; @@ -26,15 +25,10 @@ import com.google.protobuf.ByteString; import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; import io.netty.buffer.ByteBuf; -import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.ByteBufUtil; -import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.HttpHeaderNames; -import io.netty.handler.codec.http.QueryStringDecoder; -import io.netty.handler.codec.http.QueryStringEncoder; import io.netty.handler.codec.http2.DefaultHttp2DataFrame; import io.netty.handler.codec.http2.DefaultHttp2Headers; import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame; @@ -48,29 +42,24 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.List; -import java.util.Map; import static io.netty.handler.codec.http.HttpResponseStatus.OK; public class TripleUtil { - - public static final AttributeKey SERVER_STREAM_KEY = AttributeKey.newInstance("tri_server_stream"); - public static final AttributeKey CLIENT_STREAM_KEY = AttributeKey.newInstance("tri_client_stream"); + public static final AttributeKey SERVER_STREAM_KEY = AttributeKey.newInstance( + "tri_server_stream"); + public static final AttributeKey CLIENT_STREAM_KEY = AttributeKey.newInstance( + "tri_client_stream"); private static final SingleProtobufSerialization pbSerialization = new SingleProtobufSerialization(); private static final Base64.Decoder BASE64_DECODER = Base64.getDecoder(); private static final Base64.Encoder BASE64_ENCODER = Base64.getEncoder().withoutPadding(); - public static ServerStream getServerStream(ChannelHandlerContext ctx) { + public static AbstractServerStream getServerStream(ChannelHandlerContext ctx) { return ctx.channel().attr(TripleUtil.SERVER_STREAM_KEY).get(); } - public static void setClientStream(Channel channel, ClientStream clientStream) { - channel.attr(TripleUtil.CLIENT_STREAM_KEY).set(clientStream); - } - - public static ClientStream getClientStream(ChannelHandlerContext ctx) { + public static AbstractClientStream getClientStream(ChannelHandlerContext ctx) { return ctx.channel().attr(TripleUtil.CLIENT_STREAM_KEY).get(); } @@ -89,51 +78,10 @@ public static void responseErr(ChannelHandlerContext ctx, GrpcStatus status) { .status(OK.codeAsText()) .set(HttpHeaderNames.CONTENT_TYPE, TripleConstant.CONTENT_PROTO) .setInt(TripleConstant.STATUS_KEY, status.code.code) - .set(TripleConstant.MESSAGE_KEY, getErrorMsg(status)); + .set(TripleConstant.MESSAGE_KEY, status.toMessage()); ctx.writeAndFlush(new DefaultHttp2HeadersFrame(trailers, true)); } - public static String getErrorMsg(GrpcStatus status) { - final String msg; - if (status.cause == null) { - msg = status.description; - } else { - String placeHolder = status.description == null ? "" : status.description; - msg = StringUtils.toString(placeHolder, status.cause); - } - return percentEncode(msg); - } - - public static String limitSizeTo4KB(String desc) { - if (desc.length() < 4096) { - return desc; - } else { - return desc.substring(0, 4086); - } - } - - public static String percentDecode(CharSequence corpus) { - if (corpus == null) { - return ""; - } - QueryStringDecoder decoder = new QueryStringDecoder("?=" + corpus); - for (Map.Entry> e : decoder.parameters().entrySet()) { - return e.getKey(); - } - return ""; - } - - public static String percentEncode(String corpus) { - if (corpus == null) { - return ""; - } - corpus = limitSizeTo4KB(corpus); - QueryStringEncoder encoder = new QueryStringEncoder(""); - encoder.addParam("", corpus); - // ?= - return encoder.toString().substring(2); - } - public static void responsePlainTextError(ChannelHandlerContext ctx, int code, GrpcStatus status) { Http2Headers headers = new DefaultHttp2Headers(true) .status("" + code) @@ -145,14 +93,8 @@ public static void responsePlainTextError(ChannelHandlerContext ctx, int code, G ctx.write(new DefaultHttp2DataFrame(buf, true)); } - public static boolean needWrapper(Class[] parameterTypes) { - if (parameterTypes.length != 1) { - return true; - } - return !Message.class.isAssignableFrom(parameterTypes[0]); - } - - public static Object unwrapResp(URL url, TripleWrapper.TripleResponseWrapper wrap, MultipleSerialization serialization) { + public static Object unwrapResp(URL url, TripleWrapper.TripleResponseWrapper wrap, + MultipleSerialization serialization) { String serializeType = convertHessianFromWrapper(wrap.getSerializeType()); try { final ByteArrayInputStream bais = new ByteArrayInputStream(wrap.getData().toByteArray()); @@ -164,7 +106,8 @@ public static Object unwrapResp(URL url, TripleWrapper.TripleResponseWrapper wra } } - public static Object[] unwrapReq(URL url, TripleWrapper.TripleRequestWrapper wrap, MultipleSerialization multipleSerialization) { + public static Object[] unwrapReq(URL url, TripleWrapper.TripleRequestWrapper wrap, + MultipleSerialization multipleSerialization) { String serializeType = convertHessianFromWrapper(wrap.getSerializeType()); try { Object[] arguments = new Object[wrap.getArgsCount()]; @@ -180,7 +123,8 @@ public static Object[] unwrapReq(URL url, TripleWrapper.TripleRequestWrapper wra } } - public static TripleWrapper.TripleResponseWrapper wrapResp(URL url, String serializeType, Object resp, MethodDescriptor desc, + public static TripleWrapper.TripleResponseWrapper wrapResp(URL url, String serializeType, Object resp, + MethodDescriptor desc, MultipleSerialization multipleSerialization) { try { final TripleWrapper.TripleResponseWrapper.Builder builder = TripleWrapper.TripleResponseWrapper.newBuilder() @@ -196,6 +140,46 @@ public static TripleWrapper.TripleResponseWrapper wrapResp(URL url, String seria } } + public static TripleWrapper.TripleRequestWrapper wrapReq(URL url, String serializeType, Object req, + String type, + MultipleSerialization multipleSerialization) { + try { + final TripleWrapper.TripleRequestWrapper.Builder builder = TripleWrapper.TripleRequestWrapper.newBuilder() + .addArgTypes(type) + .setSerializeType(convertHessianToWrapper(serializeType)); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + multipleSerialization.serialize(url, serializeType, type, req, bos); + builder.addArgs(ByteString.copyFrom(bos.toByteArray())); + bos.close(); + return builder.build(); + } catch (IOException e) { + throw new RuntimeException("Failed to pack wrapper req", e); + } + } + + public static TripleWrapper.TripleRequestWrapper wrapReq(URL url, RpcInvocation invocation, + MultipleSerialization serialization) { + try { + String serializationName = (String) invocation.getObjectAttachment(Constants.SERIALIZATION_KEY); + final TripleWrapper.TripleRequestWrapper.Builder builder = TripleWrapper.TripleRequestWrapper.newBuilder() + .setSerializeType(convertHessianToWrapper(serializationName)); + for (int i = 0; i < invocation.getArguments().length; i++) { + final String clz = invocation.getParameterTypes()[i].getName(); + builder.addArgTypes(clz); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + serialization.serialize(url, serializationName, clz, invocation.getArguments()[i], bos); + builder.addArgs(ByteString.copyFrom(bos.toByteArray())); + } + return builder.build(); + } catch (IOException e) { + throw new RuntimeException("Failed to pack wrapper req", e); + } + } + + public static T unpack(byte[] data, Class clz) { + return unpack(new ByteArrayInputStream(data), clz); + } + public static T unpack(InputStream is, Class clz) { try { final T req = (T) pbSerialization.deserialize(is, clz); @@ -218,40 +202,18 @@ private static void closeQuietly(Closeable c) { } } - - public static ByteBuf pack(ChannelHandlerContext ctx, Object obj) { - try { - final ByteBuf buf = ctx.alloc().buffer(); - buf.writeByte(0); - buf.writeInt(0); - final ByteBufOutputStream bos = new ByteBufOutputStream(buf); - final int size = pbSerialization.serialize(obj, bos); - buf.setInt(1, size); - return buf; - } catch (IOException e) { - throw new RuntimeException("Failed to pack req", e); - } - } - - public static TripleWrapper.TripleRequestWrapper wrapReq(URL url, RpcInvocation invocation, MultipleSerialization serialization) { + public static byte[] pack(Object obj) { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { - String serializationName = (String) invocation.getObjectAttachment(Constants.SERIALIZATION_KEY); - final TripleWrapper.TripleRequestWrapper.Builder builder = TripleWrapper.TripleRequestWrapper.newBuilder() - .setSerializeType(convertHessianToWrapper(serializationName)); - for (int i = 0; i < invocation.getArguments().length; i++) { - final String clz = invocation.getParameterTypes()[i].getName(); - builder.addArgTypes(clz); - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - serialization.serialize(url, serializationName, clz, invocation.getArguments()[i], bos); - builder.addArgs(ByteString.copyFrom(bos.toByteArray())); - } - return builder.build(); + pbSerialization.serialize(obj, baos); } catch (IOException e) { - throw new RuntimeException("Failed to pack wrapper req", e); + throw new RuntimeException("Failed to pack protobuf object", e); } + return baos.toByteArray(); } - public static String encodeWrapper(URL url, Object obj, String serializeType, MultipleSerialization serialization) throws IOException { + public static String encodeWrapper(URL url, Object obj, String serializeType, MultipleSerialization serialization) + throws IOException { ByteArrayOutputStream bos = new ByteArrayOutputStream(); serialization.serialize(url, serializeType, obj.getClass().getName(), obj, bos); final TripleWrapper.TripleRequestWrapper wrap = TripleWrapper.TripleRequestWrapper.newBuilder() @@ -271,7 +233,8 @@ public static byte[] encodeBase64(byte[] in) { return BASE64_ENCODER.encode(in); } - public static Object decodeObjFromHeader(URL url, CharSequence value, MultipleSerialization serialization) throws InvalidProtocolBufferException { + public static Object decodeObjFromHeader(URL url, CharSequence value, MultipleSerialization serialization) + throws InvalidProtocolBufferException { final byte[] decode = decodeASCIIByte(value); final TripleWrapper.TripleRequestWrapper wrapper = TripleWrapper.TripleRequestWrapper.parseFrom(decode); final Object[] objects = TripleUtil.unwrapReq(url, wrapper, serialization); @@ -296,5 +259,4 @@ public static String convertHessianFromWrapper(String serializeType) { return serializeType; } - } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStream.java new file mode 100644 index 00000000000..6de44eb5204 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStream.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.remoting.exchange.Response; +import org.apache.dubbo.remoting.exchange.support.DefaultFuture2; +import org.apache.dubbo.rpc.AppResponse; + +import java.util.concurrent.Executor; + +public class UnaryClientStream extends AbstractClientStream implements Stream { + + + protected UnaryClientStream(URL url, Executor executor) { + super(url, executor); + } + + @Override + protected StreamObserver createStreamObserver() { + return new ClientStreamObserver(); + } + + @Override + protected TransportObserver createTransportObserver() { + return new UnaryClientTransportObserver(); + } + + private class UnaryClientTransportObserver extends UnaryTransportObserver implements TransportObserver { + + @Override + public void doOnComplete(OperationHandler handler) { + execute(() -> { + try { + final Object resp = deserializeResponse(getData()); + Response response = new Response(getRequest().getId(), TripleConstant.TRI_VERSION); + final AppResponse result = new AppResponse(resp); + result.setObjectAttachments(parseMetadataToMap(getTrailers())); + response.setResult(result); + DefaultFuture2.received(getConnection(), response); + } catch (Exception e) { + final GrpcStatus status = GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withCause(e) + .withDescription("Failed to deserialize response"); + onError(status); + } + }); + } + + protected void onError(GrpcStatus status) { + Response response = new Response(getRequest().getId(), TripleConstant.TRI_VERSION); + if (status.description != null) { + response.setErrorMessage(status.description); + } else { + response.setErrorMessage(status.cause.getMessage()); + } + final byte code = GrpcStatus.toDubboStatus(status.code); + response.setStatus(code); + DefaultFuture2.received(getConnection(), response); + } + } +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryServerStream.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryServerStream.java new file mode 100644 index 00000000000..4ddba40845f --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/UnaryServerStream.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.remoting.TimeoutException; +import org.apache.dubbo.rpc.AppResponse; +import org.apache.dubbo.rpc.Result; +import org.apache.dubbo.rpc.RpcContext; +import org.apache.dubbo.rpc.RpcInvocation; + +import io.netty.handler.codec.http.HttpHeaderNames; + +import java.util.Map; +import java.util.concurrent.CompletionStage; +import java.util.function.BiConsumer; +import java.util.function.Function; + +public class UnaryServerStream extends AbstractServerStream implements Stream { + + protected UnaryServerStream(URL url) { + super(url); + } + + @Override + protected StreamObserver createStreamObserver() { + return null; + } + + @Override + protected TransportObserver createTransportObserver() { + return new UnaryServerTransportObserver(); + } + + private class UnaryServerTransportObserver extends UnaryTransportObserver implements TransportObserver { + @Override + protected void onError(GrpcStatus status) { + transportError(status); + } + + @Override + public void doOnComplete(OperationHandler handler) { + if (getData() == null) { + onError(GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withDescription("Missing request data")); + return; + } + execute(this::invoke); + } + + public void invoke() { + + final RpcInvocation invocation; + try { + final Object[] arguments = deserializeRequest(getData()); + if (arguments != null) { + invocation = buildInvocation(getHeaders()); + invocation.setArguments(arguments); + } else { + return; + } + } catch (Throwable t) { + LOGGER.warn("Exception processing triple message", t); + transportError(GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withDescription("Decode request failed:" + t.getMessage())); + return; + } + + final Result result = getInvoker().invoke(invocation); + CompletionStage future = result.thenApply(Function.identity()); + + BiConsumer onComplete = (appResult, t) -> { + try { + if (t != null) { + if (t instanceof TimeoutException) { + transportError(GrpcStatus.fromCode(GrpcStatus.Code.DEADLINE_EXCEEDED).withCause(t)); + } else { + transportError(GrpcStatus.fromCode(GrpcStatus.Code.UNKNOWN).withCause(t)); + } + return; + } + AppResponse response = (AppResponse) appResult; + if (response.hasException()) { + final Throwable exception = response.getException(); + if (exception instanceof TripleRpcException) { + transportError(((TripleRpcException) exception).getStatus()); + } else { + transportError(GrpcStatus.fromCode(GrpcStatus.Code.UNKNOWN) + .withCause(exception)); + } + return; + } + Metadata metadata = new DefaultMetadata(); + metadata.put(HttpHeaderNames.CONTENT_TYPE, TripleConstant.CONTENT_PROTO); + getTransportSubscriber().tryOnMetadata(metadata, false); + + final ClassLoader tccl = Thread.currentThread().getContextClassLoader(); + final byte[] data; + try { + ClassLoadUtil.switchContextLoader( + getProviderModel().getServiceInterfaceClass().getClassLoader()); + data = encodeResponse(response.getValue()); + } finally { + ClassLoadUtil.switchContextLoader(tccl); + } + getTransportSubscriber().tryOnData(data, false); + + Metadata trailers = new DefaultMetadata() + .put(TripleConstant.STATUS_KEY, Integer.toString(GrpcStatus.Code.OK.code)); + final Map attachments = response.getObjectAttachments(); + if (attachments != null) { + convertAttachment(trailers, attachments); + } + getTransportSubscriber().tryOnMetadata(trailers, true); + } catch (Throwable e) { + LOGGER.warn("Exception processing triple message", e); + if (e instanceof TripleRpcException) { + transportError(((TripleRpcException) e).getStatus()); + } else { + transportError(GrpcStatus.fromCode(GrpcStatus.Code.UNKNOWN) + .withDescription("Exception occurred in provider's execution:" + e.getMessage()) + .withCause(e)); + } + } + }; + + future.whenComplete(onComplete); + RpcContext.removeContext(); + } + } + +} diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatusTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatusTest.java new file mode 100644 index 00000000000..7abed532c15 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/GrpcStatusTest.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +class GrpcStatusTest { + + @Test + public void fromMessage() { + String origin = "haha test 😊"; + final GrpcStatus status = GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withDescription(origin); + Assertions.assertNotEquals(origin, status.toMessage()); + final String decoded = GrpcStatus.fromMessage(status.toMessage()); + Assertions.assertEquals(origin, decoded); + } + + @Test + public void toMessage() { + String content = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"; + final GrpcStatus status = GrpcStatus.fromCode(GrpcStatus.Code.INTERNAL) + .withDescription(content); + Assertions.assertNotEquals(content, status.toMessage()); + } +} \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStreamTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStreamTest.java new file mode 100644 index 00000000000..54eedace2c0 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/UnaryClientStreamTest.java @@ -0,0 +1,79 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.dubbo.rpc.protocol.tri; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.stream.StreamObserver; +import org.apache.dubbo.rpc.Invoker; +import org.apache.dubbo.rpc.RpcInvocation; +import org.apache.dubbo.rpc.model.MethodDescriptor; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import java.util.concurrent.Executor; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class UnaryClientStreamTest { + @Test + @SuppressWarnings("all") + public void testInit() { + URL url = new URL("test", "1.2.3.4", 8080); + final Executor executor = Mockito.mock(Executor.class); + final UnaryClientStream stream = UnaryClientStream.unary(url, executor); + final StreamObserver observer = stream.asStreamObserver(); + RpcInvocation inv = Mockito.mock(RpcInvocation.class); + // no invoker + Assertions.assertThrows(NullPointerException.class, () -> observer.onNext(inv)); + final Invoker mockInvoker = Mockito.mock(Invoker.class); + when(mockInvoker.getUrl()).thenReturn(url); + when(inv.getInvoker()).thenReturn(mockInvoker); + // no subscriber + Assertions.assertThrows(NullPointerException.class, () -> observer.onNext(inv)); + verify(mockInvoker, times(2)).getUrl(); + + TransportObserver transportObserver = Mockito.mock(TransportObserver.class); + stream.subscribe(transportObserver); + // no method descriptor + Assertions.assertThrows(NullPointerException.class, () -> observer.onNext(inv)); + Mockito.verify(transportObserver).tryOnMetadata(any(), anyBoolean()); + + MethodDescriptor md = Mockito.mock(MethodDescriptor.class); + when(md.isNeedWrap()).thenReturn(true); + when(md.isStream()).thenReturn(false); + stream.method(md); + // bad request + Assertions.assertThrows(NullPointerException.class, () -> observer.onNext(inv)); + + String[] params = new String[]{"rainbow ponies!"}; + when(inv.getArguments()).thenReturn(params); + // no serialization + Assertions.assertThrows(NullPointerException.class, () -> observer.onNext(inv)); +// Map attachemnts=new HashMap<>(); +// when(inv.getObjectAttachments()).thenReturn(attachemnts); +// attachemnts.put(Constants.SERIALIZATION_KEY, "hessian2"); +// observer.onNext(inv); + } + +} \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/TripleUtilTest.java b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/UnaryServerStreamTest.java similarity index 70% rename from dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/TripleUtilTest.java rename to dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/UnaryServerStreamTest.java index 944136a791d..ba32a36f497 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/TripleUtilTest.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/UnaryServerStreamTest.java @@ -14,18 +14,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + package org.apache.dubbo.rpc.protocol.tri; -import org.junit.jupiter.api.Assertions; +import org.apache.dubbo.common.URL; + import org.junit.jupiter.api.Test; -class TripleUtilTest { +class UnaryServerStreamTest { @Test - void percentEncoding() { - String content="\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"; - final String encoded = TripleUtil.percentEncode(content); - final String decoded = TripleUtil.percentDecode(encoded); - Assertions.assertEquals(content,decoded); + @SuppressWarnings("all") + public void testInit() { + URL url = new URL("test", "1.2.3.4", 8080); } } \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-triple/src/test/resources/log4j.xml b/dubbo-rpc/dubbo-rpc-triple/src/test/resources/log4j.xml index 09dba05e6cc..ad745c5a726 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/test/resources/log4j.xml +++ b/dubbo-rpc/dubbo-rpc-triple/src/test/resources/log4j.xml @@ -1,19 +1,19 @@