Dubbo服务引入源码流程

1,054 阅读5分钟

1. 整体过程

在看源码之前,我们不妨想一下,服务引入的流程。是不是就是以下几步:

  • 消费者(consumer)从注册中心获取服务地址
  • 消费者根据从注册中心提供的信息,获得接口与具体实现类实例
  • 消费者通过代理方式,调用接口提供的方法(实际上是调用实现类的方法)

1.1 服务目录RegistryDirectory

消费端每个服务对应⼀个服务⽬录RegistryDirectory。

⼀个服务⽬录中包含了:

  • serviceType:表示服务接⼝
  • serviceKey:表示引⼊的服务key,serviceclass+version+group
  • queryMap:表示引⼊的服务的参数配置
  • onfigurators:动态配置
  • routerChain:路由链
  • invokers:表示服务⽬录当前缓存的服务提供者Invoker
  • ConsumerConfigurationListener:监听本应⽤的动态配置
  • ReferenceConfigurationListener:监听所引⼊的服务的动态配置

2. 服务引入的入口

服务引入对应的Bean是ReferenceBean。下面是该类的类图: ReferenceBean类图

这里看到该类实现了FactoryBean接口,这里复习一下Spring的知识点。

2.1 BeanFactory与Factory区别

BeanFactory是Spring里的顶级工厂类,就是生产Bean的工厂。 FactoryBean是Spring提供的一个扩展功能,可以按照用户自定义的定义Bean,在真正要获取这个Bean的时候容器会调用FactoryBean.getObject()方法。

举个例子,BeanFactory就像一个制定好各个标准的造车厂,提供的汽车不支持定制化,而FactoryBean就像是一辆可以自己定制的汽车,按照用户的喜好,自己指定车型、性能、颜色等等。

2.2 服务引入的三种方式

  • 本地引入:先去本地缓存找找看有没有本地服务
  • 直接远程引入:由 Consumer 直接配置写死 Provider 的地址,然后直连即可(测试的时候用)
  • 通过注册中心引入:Consumer 通过注册中心得知 Provider 的相关信息,然后进行服务的引入,这里还包括多注册中心,同一个服务多个提供者的情况,如何抉择如何封装,如何进行负载均衡、容错并且让使用者无感知

3. 服务引入源码流程

3.1 ReferenceBean.getObject()

    public Object getObject() {
        return get();
    }

3.2 get()方法

    public synchronized T get() {
        if (destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
        }
        if (ref == null) {
            //初始化方法
            init();
        }
        return ref;
    }

3.3 init()方法

这个方法很长,具体分以下三步:

  • 根据服务信息,生成一个map
  • 向本地的缓存中注册消费者服务
  • 生成代理

3.3.1 生成配置信息map

这个过程太长了,只贴一个map的信息。 map信息

3.3.2 向本地缓存注册服务

向ConcurrentMap<String, ConsumerModel> consumers里注册。

3.3.3 生成代理createProxy()

这方法比较长,我们来一点一点分析。

3.3.3.1 本地引入

这里就是从map里的信息判断一下是不是本地引入,就是去之前服务暴露的exporterMap拿到服务。

if (shouldJvmRefer(map)) {
            URL url = new URL(LOCAL_PROTOCOL, LOCALHOST_VALUE, 0, interfaceClass.getName()).addParameters(map);
            invoker = REF_PROTOCOL.refer(interfaceClass, url);
            if (logger.isInfoEnabled()) {
                logger.info("Using injvm service " + interfaceClass.getName());
            }
}

3.3.3.2 点对点引入(不从配置中心获取)

这里如果配置了url,就证明是点对点引入,就是把url拼接一下。

if (url != null && url.length() > 0) { // user specified URL, could be peer-to-peer address, or register center's address.
                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)) {
                            urls.add(url.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                        } else {
                            urls.add(ClusterUtils.mergeUrl(url, map));
                        }
                    }
                }
            }

3.3.3.3 从注册中心引入

这里urls里最终是这样

registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-zyz-consumer-demo&dubbo=2.0.2&pid=2924&qos.enable=false&refer=application%3Ddubbo-zyz-consumer-demo%26dubbo%3D2.0.2%26init%3Dfalse%26interface%3Dcom.zyz.DemoService%26methods%3DsayHello%2CsayHelloAsync%26pid%3D2924%26qos.enable%3Dfalse%26register.ip%3D192.168.31.158%26release%3D2.7.5%26revision%3Ddefault%26side%3Dconsumer%26sticky%3Dfalse%26timestamp%3D1617280229760%26version%3Ddefault&registry=zookeeper&release=2.7.5&timestamp=1617282302206
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()));
            }
            urls.add(u.addParameterAndEncoded(REFER_KEY,StringUtils.toQueryString(map)));
        }
    }
    if (urls.isEmpty()) {
        throw new IllegalStateException("No such any registry to reference " + interfaceName + " on the consumer " + NetUtils.getLocalHost() + " use dubbo version " + Version.getVersion() + ", please config <dubbo:registry address=\"...\" /> to your spring config.");
    }
}

至此三种引入都分析完毕。

3.3.4 RegistryProtocol.refer()方法

接下来会判断urls的长度是不是等于1,如果等于1就根据SPI机制,根据传入的URL找到对应的实现类,执行对应方法,这里会执行RegistryProtocol.refer(

public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        //获取注册url
        url = getRegistryUrl(url);
        //去掉多余的参数
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
            return proxyFactory.getInvoker((T) registry, type, url);
        }

        // group="a,b" or group="*"
        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)) {
                return doRefer(getMergeableCluster(), registry, type, url);
            }
        }
        return doRefer(cluster, registry, type, url);
    }

3.3.5 doRefer()方法

可以看到生成了 RegistryDirectory这个directory塞了注册中心实例,它自身也实现了NotifyListener 接口,因此注册中心的监听其实是靠这家伙来处理的。

然后向注册中心注册自身的信息,并且向注册中心订阅了 providers 节点、 configurators 节点 和 routers 节点,订阅了之后 RegistryDirectory 会收到这几个节点下的信息,就会触发 DubboInvoker 的生成了,即用于远程调用的 Invoker。

拿到了Provider的信息之后就可以通过监听触发 DubboProtocol.refer了。

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        //创建一个开篇提到的服务目录
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        //放入注册中心实例
        directory.setRegistry(registry);
        //放入动态生成的protocol
        directory.setProtocol(protocol);

        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));
            //向注册中心注册消费者,在consumers目录下创建新节点
            registry.register(directory.getRegisteredConsumerUrl());
        }
        directory.buildRouterChain(subscribeUrl);
        //订阅注册中心的providers目录、configurators目录和routers目录
        directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
                PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));
        //封装多个invoker
        Invoker invoker = cluster.join(directory);
        return invoker;
    }

3.3.6 AbstractProtocol.refer

DubboProtocol中并没有refer⽅法,是在它的⽗类AbstractProtocol中才有的refer⽅法。

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
    }

这里通过protocolBindingRefer()调用DubboProtocol里的这里通过protocolBindingRefer()方法。

3.3.7 DubboProtocol.protocolBindingRefer()方法

这里会创建rpc的invoker

    @Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
        optimizeSerialization(url);

        //创建rpc的invoker
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);

        return invoker;
    }

3.3.8 getClients

由于是网络连接,这里会创建一个客户端。

    private ExchangeClient[] getClients(URL url) {
        // whether to share connection

        boolean useShareConnect = false;

        int connections = url.getParameter(CONNECTIONS_KEY, 0);
        List<ReferenceCountExchangeClient> shareClients = null;
        // if not configured, connection is shared, otherwise, one connection for one service
        if (connections == 0) {
            useShareConnect = true;

            /**
             * The xml configuration should have a higher priority than properties.
             */
            String shareConnectionsStr = url.getParameter(SHARE_CONNECTIONS_KEY, (String) null);
            connections = Integer.parseInt(StringUtils.isBlank(shareConnectionsStr) ? ConfigUtils.getProperty(SHARE_CONNECTIONS_KEY,
                    DEFAULT_SHARE_CONNECTIONS) : shareConnectionsStr);
            shareClients = getSharedClient(url, connections);
        }

        ExchangeClient[] clients = new ExchangeClient[connections];
        for (int i = 0; i < clients.length; i++) {
            if (useShareConnect) {
                clients[i] = shareClients.get(i);

            } else {
                clients[i] = initClient(url);
            }
        }

        return clients;
    }

这里不细分析了,最终会调用Netty的客户端,new NettyClient(url, listener)。

3.3.9 存储服务信息

这里是从2.7版本之后改的,在这里把服务信息存入到本地,这样就不用每次都从注册中心获取了。(除非配置中心的地址有变化)

最终放入这个缓存里ConcurrentNavigableMap<String, String> serviceDefinitions。

        String metadata = map.get(METADATA_KEY);
        WritableMetadataService metadataService = WritableMetadataService.getExtension(metadata == null ? DEFAULT_METADATA_STORAGE_TYPE : metadata);
        if (metadataService != null) {
            URL consumerURL = new URL(CONSUMER_PROTOCOL, map.remove(REGISTER_IP_KEY), 0, map.get(INTERFACE_KEY), map);
            metadataService.publishServiceDefinition(consumerURL);
        }

3.3.10 getProxy()

最终创建代理。

4. 总结

整体流程图 服务引入