Dubbo principle of service consumption

Foreword

The article "Dubbo of service exposure" analysis of how services are exposed Dubbo, Dubbo paper then analyzes consumer service processes. Mainly from the following aspects analysis: exposure registries ; servicing consumer notification by registered center ; Direct Connect service consumption .
When the consumer side, start the service, will own information registered in the registry directory, the directory also subscribe to the service provider when the service provider's URL changes, real-time access to new data.

Consumer service end process

Below is a flow chart of a consumer service:

You can see the image above, processes and services exposed service consumption process is somewhat similar to reverse. Similarly, Dubbo service is also divided into two major steps: The first step is to remote services by Protocolconverting into Invoker(the concept is explained in the last article). Step by dynamic proxy Invokerconverted into a service interface for consumer needs.

org.apache.dubbo.config.ReferenceConfig Class is the ReferenceBeanparent class, and the production of end services ServiceBeanlike storing the parsed XML and annotation information. Class relationship is as follows:

Conversion service initialization entry

When we call the consumer side will be able to achieve the local interface remote service call, which is how to achieve it? According to the above Scheme, to analyze consumer principles.
When initializing the consumer end ReferenceConfig#init, it will perform ReferenceConfig#createProxyto complete this series of operations. The following is the ReferenceConfig#createProxymain part of the code:

private T createProxy(Map<String, String> map) {
    // 判断是否为 Jvm 本地引用
    if (shouldJvmRefer(map)) {
        // 通过 injvm 协议,获取本地服务
        URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
        invoker = REF_PROTOCOL.refer(interfaceClass, url);
    } else {
        urls.clear();
        // 判断是否有自定义的直连地址,或注册中心地址
        if (url != null && url.length() > 0) { 
            String[] us = SEMICOLON_SPLIT_PATTERN.split(url);
            if (us != null && us.length > 0) {
                for (String u : us) {
                    URL url = URL.valueOf(u);
                    if (StringUtils.isEmpty(url.getPath())) {
                        url = url.setPath(interfaceName);
                    }
                    if (UrlUtils.isRegistry(url)) {
                        // 如果是注册中心Protocol类型,则向地址中添加 refer 服务消费元数据
                        urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                    } else {
                        // 直连服务提供端
                        urls.add(ClusterUtils.mergeUrl(url, map));
                    }
                }
            }
        } else {
            // 组装注册中心的配置
            if (!LOCAL_PROTOCOL.equalsIgnoreCase(getProtocol())) {
                // 检查配置中心
                checkRegistry();
                List<URL> us = ConfigValidationUtils.loadRegistries(this, false);
                if (CollectionUtils.isNotEmpty(us)) {
                    for (URL u : us) {
                        URL monitorUrl = ConfigValidationUtils.loadMonitor(this, u);
                        if (monitorUrl != null) {
                            // 监控上报信息
                            map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                        }
                        // 注册中心地址添加 refer 服务消费元数据
                        urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                    }
                }
            }
        }

        // 只有一条注册中心数据,即单注册中心
        if (urls.size() == 1) {
            // 将远程服务转化成 Invoker
            invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
        } else {
            // 因为多注册中心就会存在多个 Invoker,这里用保存在 List 中
            List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
            URL registryURL = null;
            for (URL url : urls) {
                // 将每个注册中心转换成 Invoker 数据
                invokers.add(REF_PROTOCOL.refer(interfaceClass, url));
                if (UrlUtils.isRegistry(url)) {
                    // 会覆盖前遍历的注册中心,使用最后一条注册中心数据
                    registryURL = url;
                }
            }
            if (registryURL != null) {
                // 默认使用 zone-aware 策略来处理多个订阅
                URL u = registryURL.addParameterIfAbsent(CLUSTER_KEY, ZoneAwareCluster.NAME);
                // 将转换后的多个 Invoker 合并成一个
                invoker = CLUSTER.join(new StaticDirectory(u, invokers));
            } else {
                invoker = CLUSTER.join(new StaticDirectory(invokers));
            }
        }
    }
    // 利用动态代理,将 Invoker 转换成本地接口代理
    return (T) PROXY_FACTORY.getProxy(invoker);
}

The conversion process above can mainly be summarized as follows: the first is divided into two types of local reference and remote reference. Local is for local service inJvm agreements, which do not do much to explain; remote reference into direct services and through the registry. Registration Registration Center is divided into the case of single and multi-center registry, registry single good solution, can be used directly, when the multi-registry, the Invoker converted into a merger Invoker. Finally Invoker into a local dynamic interface agent by proxy.

Get Invoker examples

As the local service directly from the cache, here on the consumption registry analysis, the above code snippet is used REF_PROTOCOL.referto convert the method code:

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
    // 获取服务的注册中心url,里面会设置注册中心的协议和移除 registry 的参数
    url = getRegistryUrl(url);
    // 获取注册中心实例
    Registry registry = registryFactory.getRegistry(url);
    if (RegistryService.class.equals(type)) {
        return proxyFactory.getInvoker((T) registry, type, url);
    }

    // 获取服务消费元数据
    Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));
    // 从服务消费元数据中获取分组信息
    String group = qs.get(GROUP_KEY);
    if (group != null && group.length() > 0) {
        if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
            // 执行 Invoker 转换工作
            return doRefer(getMergeableCluster(), registry, type, url);
        }
    }
    // 执行 Invoker 转换工作
    return doRefer(cluster, registry, type, url);
}

The above examples are mainly registry consumer access to services and service groups were finally call the doRefermethod of conversion work, the following doRefercode:

private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
    // 创建 RegistryDirectory 对象
    RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
    // 设置注册中心
    directory.setRegistry(registry);
    // 设置协议
    directory.setProtocol(protocol);
    // directory.getUrl().getParameters() 是服务消费元数据
    Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());
    URL subscribeUrl = new URL(CONSUMER_PROTOCOL, parameters.remove(REGISTER_IP_KEY), 0, type.getName(), parameters);
    if (!ANY_VALUE.equals(url.getServiceInterface()) && url.getParameter(REGISTER_KEY, true)) {
        directory.setRegisteredConsumerUrl(getRegisteredConsumerUrl(subscribeUrl, url));
        // 消费消息注册到注册中心
        registry.register(directory.getRegisteredConsumerUrl());
    }

    directory.buildRouterChain(subscribeUrl);
    // 服务消费者订阅:服务提供端,动态配置,路由的通知
    directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
            PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));

    // 多个Invoker合并为一个
    Invoker invoker = cluster.join(directory);
    return invoker;
}

Mainly done to achieve the above objects created RegistryDirectory, consumption will register service metadata into the registry, by RegistryDirectory objects inside information, to the service provider, dynamic configuration and routing of subscription-related functions.

RegistryDirectory This class implements NotifyListener this notification listener interfaces, when the subscription service, configuration or routing changes, a notification is received, make the appropriate changes:

public synchronized void notify(List<URL> urls) {
    // 将服务提供方配置,路由配置,服务提供方的服务分别以不同的 key 保存在 Map 中
    Map<String, List<URL>> categoryUrls = urls.stream()
            .filter(Objects::nonNull)
            .filter(this::isValidCategory)
            .filter(this::isNotCompatibleFor26x)
            .collect(Collectors.groupingBy(url -> {
                if (UrlUtils.isConfigurator(url)) {
                    return CONFIGURATORS_CATEGORY;
                } else if (UrlUtils.isRoute(url)) {
                    return ROUTERS_CATEGORY;
                } else if (UrlUtils.isProvider(url)) {
                    return PROVIDERS_CATEGORY;
                }
                return "";
            }));

    // 更新服务提供方配置
    List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
    this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);

    // 更新路由配置
    List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
    toRouters(routerURLs).ifPresent(this::addRouters);

    // 加载服务提供方的服务信息
    List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
    /**
     * 3.x added for extend URL address
     */
    ExtensionLoader<AddressListener> addressListenerExtensionLoader = ExtensionLoader.getExtensionLoader(AddressListener.class);
    List<AddressListener> supportedListeners = addressListenerExtensionLoader.getActivateExtension(getUrl(), (String[]) null);
    if (supportedListeners != null && !supportedListeners.isEmpty()) {
        for (AddressListener addressListener : supportedListeners) {
            providerURLs = addressListener.notify(providerURLs, getUrl(),this);
        }
    }
    // 重新加载 Invoker 实例
    refreshOverrideAndInvoker(providerURLs);
}

RegistryDirectory#notifyWhich will refresh the Invoker last reload, here is the core code:

private void refreshOverrideAndInvoker(List<URL> urls) {
    // mock zookeeper://xxx?mock=return null
    overrideDirectoryUrl();
    // 刷新 invoker 
    refreshInvoker(urls);
}

private void refreshInvoker(List<URL> invokerUrls) {
    Assert.notNull(invokerUrls, "invokerUrls should not be null");

    if (invokerUrls.size() == 1
            && invokerUrls.get(0) != null
            && EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {

        ......

    } else {
        // 刷新之前的 Invoker
        Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
        // 加载新的 Invoker Map
        Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
        // 获取新的 Invokers
        List<Invoker<T>> newInvokers = Collections.unmodifiableList(new ArrayList<>(newUrlInvokerMap.values()));
        // 缓存新的 Invokers
        routerChain.setInvokers(newInvokers);
        this.invokers = multiGroup ? toMergeInvokerList(newInvokers) : newInvokers;
        this.urlInvokerMap = newUrlInvokerMap;

        try {
            // 通过新旧 Invokers 对比,销毁无用的 Invokers
            destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
        } catch (Exception e) {
            logger.warn("destroyUnusedInvokers error. ", e);
        }
    }
}

Get Invokers before and after the refresh, the new Invokers re-cached, by contrast, destruction of unwanted Invoker.

The above URL will be converted Invoker is in RegistryDirectory#toInvokersprogress.

private Map<String, Invoker<T>> toInvokers(List<URL> urls) {
    Map<String, Invoker<T>> newUrlInvokerMap = new HashMap<>();
   
    Set<String> keys = new HashSet<>();
    String queryProtocols = this.queryMap.get(PROTOCOL_KEY);
    for (URL providerUrl : urls) {

        // 过滤消费端不匹配的协议,及非法协议
        ......

        // 合并服务提供端配置数据
        URL url = mergeUrl(providerUrl);
        // 过滤重复的服务提供端配置数据
        String key = url.toFullString();
        if (keys.contains(key)) {
            continue;
        }
        keys.add(key);

        // 缓存键是不与使用者端参数合并的url,无论使用者如何合并参数,如果服务器url更改,则再次引用
        Map<String, Invoker<T>> localUrlInvokerMap = this.urlInvokerMap; // local reference
        Invoker<T> invoker = localUrlInvokerMap == null ? null : localUrlInvokerMap.get(key);
        
        // 缓存无对应 invoker,再次调用 protocol#refer 是否有数据
        if (invoker == null) {
            try {
                boolean enabled = true;
                if (url.hasParameter(DISABLED_KEY)) {
                    enabled = !url.getParameter(DISABLED_KEY, false);
                } else {
                    enabled = url.getParameter(ENABLED_KEY, true);
                }
                if (enabled) {
                    invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);
                }
            } catch (Throwable t) {
                logger.error("Failed to refer invoker for interface:" + serviceType + ",url:(" + url + ")" + t.getMessage(), t);
            }
            // 将新的 Invoker 缓存起来
            if (invoker != null) { // Put new invoker in cache
                newUrlInvokerMap.put(key, invoker);
            }
        } else {
            // 缓存里有数据,则进行重新覆盖
            newUrlInvokerMap.put(key, invoker);
        }
    }
    keys.clear();
    return newUrlInvokerMap;
}

to sum up

By "Dubbo of service exposure" and two articles on this article Dubbo service exposure and understanding of the principles of consumer services. We can see, whether it is exposed or consumption, Dubbo are based on Invoker as the main body of data exchange, initiated by Invoker call to implement a remote or local implementation.

Personal blog: https://ytao.top
public attention [No.] ytao, more original good text
My public number

Guess you like

Origin www.cnblogs.com/ytao-blog/p/12551251.html