Dubbo源码深入学习-Dubbo服务引入原理

Dubbo源码-Dubbo服务引入原理

前言

我们在平常开发中使用 dubbo 是需要从注册中心拉取服务信息的,然后进行数据组装路由过滤负载均衡发起网络请求一系列步骤进行远程调用,这一系列操作对于我们来说如同黑盒,所以我们在平时开发之余,也需要对dubbo消费者如何进行服务导入有所了解,本篇我们就一块对 dubbo 服务导入的原理进行探索

前置知识

依据之前springDubbo的整合原理,我们知道,当通过@Reference 注解引入一个Dubbo服务的时候,会生成一个ReferenceBean代理对象,然后对属性进行赋值代理对象,ReferenceBean是一个FactoryBean,重写FactoryBeangetObject方法会返回一个代理对象并交给Spring管理,那么ReferenceBean是如何创建这个代理对象的,咱们就来看看这个ReferenceBean的getObject方法。

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

服务引入

根据前置知识,我们知道服务消费端创建代理对象会调用ReferenceBean的父类ReferenceConfig的get方法

    public synchronized T get() {
        checkAndUpdateSubConfigs();

        if (destroyed) {
            throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");
        }
        if (ref == null) {
            // 入口
            init();
        }
        return ref;  // Invoke代理
    }

1.1 更新ServiceConfig配置

checkAndUpdateSubConfigsServiceBean那里一样,检查和更新参数,把ReferenceBean里的属性的值更新为优先级最高的参数值,接着会调用init方法创建动态代理对象,并付给ref属性,然后返回ref。

1.2 创建代理对象

init()是消费端创建动态代理对象的入口

    private void init() {
        if (initialized) {
            return;
        }

        //把配置项放在map中

        // 得到一个代理对象
        ref = createProxy(map);

        String serviceKey = URL.buildKey(interfaceName, group, version);
        ApplicationModel.initConsumerModel(serviceKey, buildConsumerModel(serviceKey, attributes));
        initialized = true;
    }

init方法中,主要做了

1.把读取到的配置都放在一个map中,后续根据这个map中的参数去从注册中心查找服务

2.调用createProxy方法生成代理对象

createProxy方法中,会获取注册中心配置URL,代码如下

 					// 加载注册中心地址
                    List<URL> us = loadRegistries(false);
                    if (CollectionUtils.isNotEmpty(us)) {
                        for (URL u : us) {
                            URL monitorUrl = loadMonitor(u);
                            if (monitorUrl != null) {
                                map.put(MONITOR_KEY, URL.encode(monitorUrl.toFullString()));
                            }
                            // 对于注册中心地址都添加REFER_KEY
                            urls.add(u.addParameterAndEncoded(REFER_KEY, StringUtils.toQueryString(map)));
                        }
                    }

这里获取所有的注册中心的URL,并把配置信息的map也加到注册中心URL中,接下来就是一个分支逻辑:

1.如果消费者配了一个注册中心:

if (urls.size() == 1) {
    // RegistryProtocol.refer() 
    invoker = REF_PROTOCOL.refer(interfaceClass, urls.get(0));
} 

那么直接调用Protocol的refer(interfaceClass, urls.get(0));得到一个Invoker对象。

2.如果消费者配了多个注册中心:

                // 1. 根据每个url,refer得到对应的invoker
                // 2. 如果这多个urls中存在注册中心url,则把所有invoker整合为RegistryAwareClusterInvoker,该Invoker在调用时,会查看所有Invoker中是否有默认的,如果有则使用默认的Invoker,如果没有,则使用第一个Invoker
                // 2. 如果这多个urls中不存在注册中心url,则把所有invoker整合为FailoverCluster

                List<Invoker<?>> invokers = new ArrayList<Invoker<?>>();
                URL registryURL = null; // 用来记录urls中最后一个注册中心url
                for (URL url : urls) {
                    invokers.add(REF_PROTOCOL.refer(interfaceClass, url));

                    if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
                        registryURL = url; // use last registry url
                    }
                }

                // 如果存在注册中心地址
                if (registryURL != null) { // registry url is available
                    // use RegistryAwareCluster only when register's CLUSTER is available
                    URL u = registryURL.addParameter(CLUSTER_KEY, RegistryAwareCluster.NAME);
                    // StaticDirectory表示静态服务目录,里面的invokers是不会变的, 生成一个RegistryAwareCluster
                    invoker = CLUSTER.join(new StaticDirectory(u, invokers));
                } else { // not a registry url, must be direct invoke.
                    // 如果不存在注册中心地址, 生成一个FailoverClusterInvoker
                    invoker = CLUSTER.join(new StaticDirectory(invokers));
                }

遍历每个注册中心,每次调用Protocolrefer(interfaceClass, url);得到一个Invoker对象添加到invokers中,然后把invokers调用CLUSTER.join(new StaticDirectory(u, invokers))把所有invokers进行封装得到一个invoker,这里是通过SPI机制,使用RegistryAwareCluster的join方法实现的,会把invoker对象封装成RegistryAwareClusterInvoker,在服务调用的时候,会遍历所有的注册中心的invoker,只要有一个是可用的就直接返回。

return (T) PROXY_FACTORY.getProxy(invoker);

最后在createProxy方法的最后,根据最终得到的invoker对象调用PROXY_FACTORY.getProxy(invoker);得到一个代理对象,并返回,这个代理对象就是ref

1.2.1 Invoker对象的生成

每个注册中心都会调用REF_PROTOCOL.refer(interfaceClass, url)生成一个Invoker对象,我们就看看是如何生成Invoker对象的。

refer方法有两个参数:

type:表示引入的服务

url:注册中心URL(包含我们配置信息生成的url)

REF_PROTOCOL有两个包装类,在调用最终的protocol类之前,会经过两次包装类的refer方法,进行AOP增强

1.ProtocolFilterWrapper

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {  // dubbo://
            return protocol.refer(type, url);
        }
        return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
    }

这里用于添加dubbo protocol对应的invoker的过滤器链,自己实现的Filter扩展类就是在这里加载的,服务调用的时候会详讲,这里是registry protocol,所以只会走进第一个if中。

2.ProtocolListenerWrapper

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {  // dubbo://
            return protocol.refer(type, url);
        }
        return new ListenerInvokerWrapper<T>(protocol.refer(type, url),
                Collections.unmodifiableList(
                        ExtensionLoader.getExtensionLoader(InvokerListener.class)
                                .getActivateExtension(url, INVOKER_LISTENER_KEY)));
    }

这里用于给dubbo protocol对应的invoker添加监听器,用于处理结果,这里可以进行扩展,这里是registry protocol,所以只会走进第一个if中。

最终会调用RegistryProtocol#refer方法

    @Override
    @SuppressWarnings("unchecked")
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {

        // 从registry://的url中获取对应的注册中心,比如zookeeper, 默认为dubbo,dubbo提供了自带的注册中心实现
        // url由 registry:// 改变为---> zookeeper://
        url = URLBuilder.from(url)
                .setProtocol(url.getParameter(REGISTRY_KEY, DEFAULT_REGISTRY))
                .removeParameter(REGISTRY_KEY)
                .build();

        // 拿到注册中心实现,ZookeeperRegistry
        Registry registry = registryFactory.getRegistry(url);

       //...无关代码

        // qs表示 queryString, 表示url中的参数,表示消费者引入服务时所配置的参数,和之前注册中心URL添加配置参数一样
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(REFER_KEY));

        // group="a,b" or group="*"
        String group = qs.get(GROUP_KEY);
        if (group != null && group.length() > 0) {
            if ((COMMA_SPLIT_PATTERN.split(group)).length > 1 || "*".equals(group)) {
                // group有多个值,这里的cluster为MergeableCluster
                return doRefer(getMergeableCluster(), registry, type, url);
            }
        }

        // 这里的cluster是cluster的Adaptive对象
        return doRefer(cluster, registry, type, url);
    }

refer方法主题逻辑就是

1.把注册URL的协议换为zookeeper,方便拿到zk的具体实现类

2.调用doRefer方法

1.2.3 创建动态目录

继续调用==RegistryProtocol#doRefer==:

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        // RegistryDirectory表示动态服务目录,会和注册中心的数据保持同步
        // type表示一个服务对应一个RegistryDirectory,url表示注册中心地址
        // 在消费端,最核心的就是RegistryDirectory
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);


        // all attributes of REFER_KEY
        // 引入服务所配置的参数
        Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());

        // 消费者url
        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));

            // 注册简化后的消费url
            registry.register(directory.getRegisteredConsumerUrl());
        }

        // 构造路由链,路由链会在引入服务时按路由条件进行过滤
        // 路由链是动态服务目录中的一个属性,通过路由链可以过滤某些服务提供者
        directory.buildRouterChain(subscribeUrl);


        directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
                PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));

        // 利用传进来的cluster,join得到invoker,
        Invoker invoker = cluster.join(directory);
        ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
        return invoker;
    }

在一个服务目录中包含了:

  1. serviceType:表示服务接口
  2. serviceKey:表示引入的服务key,serviceclass+version+group
  3. queryMap:表示引入的服务的参数配置
  4. configurators:动态配置信息
  5. routerChain:路由链
  6. invokers:表示服务目录当前缓存的服务提供者Invoker
  7. ConsumerConfigurationListener:监听本应用的动态配置
  8. ReferenceConfigurationListener:监听所引入的服务的动态配置

在这里的主逻辑为

1.创建一个动态服务目录

2.生成消费者URL并注册到注册中心

3.构建路由链,为后续消费端进行服务路由做准备

4.注册监听并从注册中心拉取配置信息以及服务提供者URL

5.生成invoker对象

image-20231213101540710

1.2.3 构建路由链

RegistryProtocol.doRefer方法的directory.buildRouterChain(subscribeUrl),会根据消费者URL构建路由链

image-20231213235235260

    public void buildRouterChain(URL url) {
        this.setRouterChain(RouterChain.buildChain(url));
    }

接着看RouterChain.buildChain(url),会调用RouterChain的构造方法

    private RouterChain(URL url) {
        
        List<RouterFactory> extensionFactories = ExtensionLoader.getExtensionLoader(RouterFactory.class)
                .getActivateExtension(url, (String[]) null);

        // 然后利用RouterFactory根据url生成各个类型的Router
        // 这里生产的routers已经是真实可用的了,但是有个比较特殊的:
        // 对于应用条件路由和服务条件路由对于的Router对象,对象内部已经有真实可用的数据了(数据已经从配置中心得到了)
        // 但是对于标签路由则没有,它暂时还相当于一个没有内容的对象(还没有从配置中心获取标签路由的数据)
        List<Router> routers = extensionFactories.stream()
                .map(factory -> factory.getRouter(url))
                .collect(Collectors.toList());

        // 把routers按priority进行排序
        initWithRouters(routers);
    }

1.获取RouterFactory接口的所有实现类
// 0 = {MockRouterFactory@2880}
// 1 = {TagRouterFactory@2881} // 标签路由
// 2 = {AppRouterFactory@2882} // 应用条件路由
// 3 = {ServiceRouterFactory@2883} // 服务条件路由

2.调用每个Factory的getRouter方法,应用条件路径和服务条件路由相似,以AppRouterFactory为例

创建路由会来到AppRouterFactory.createRouter方法

    private Router createRouter(URL url) {
        // 内部会进行初始化
        return new AppRouter(DynamicConfiguration.getDynamicConfiguration(), url);
    }

DynamicConfiguration.getDynamicConfiguration()代表拿到配置中心实例,url代表消费者URL

AppRouter继承了ListenableRouter,这里会来到ListenableRouter的构造方法

    public ListenableRouter(DynamicConfiguration configuration, URL url, String ruleKey) {
        super(configuration, url);
        this.force = false;
        // ruleKey为服务名或应用名
        // 初始化,会绑定一个监听器,负责监听配置中心条件路由的修改,并且会主动从配置中心获取一下当前条件路由的数据并做解析
        this.init(ruleKey);
    }

调用ListenableRouter.init方法

    private synchronized void init(String ruleKey) {
        if (StringUtils.isEmpty(ruleKey)) {
            return;
        }
        // 服务名+".condition-router",或 应用名+".condition-router"
        String routerKey = ruleKey + RULE_SUFFIX;
        // 绑定一个监听器去监听routerKey对应的路径,当前类ListenableRouter就自带了一个监听器
        configuration.addListener(routerKey, this);

        // 绑定完监听器后,主动的从配置中心获取一下当前服务或消费者应用的对应的路由配置
        String rule = configuration.getRule(routerKey, DynamicConfiguration.DEFAULT_GROUP);

        if (StringUtils.isNotEmpty(rule)) {
            // 手动调用监听器处理事件的方法process()
            this.process(new ConfigChangeEvent(routerKey, rule));
        }
    }

这里会绑定监听并主动读取一次路由信息,读取完路由信息,生成的规则会赋值给其父类的成员变量。

image-20231213235859360

服务路由和应用路由类似,这里就不解读了,至于标签路由的监听是在读取服务提供者URL信息的时候进行监听的,可以继续往下看。

1.2.4 监听节点拉取配置

image-20231213220510585

RegistryProtocol.doRefer方法的上边这块代码,会进行消费端的监听器的注册以及主动拉取注册中心服务提供者的信息。

    public void subscribe(URL url) {
        setConsumerUrl(url);
        CONSUMER_CONFIGURATION_LISTENER.addNotifyListener(this); // 监听consumer应用
        serviceConfigurationListener = new ReferenceConfigurationListener(this, url); // 监听所引入的服务的动态配置
        registry.subscribe(url, this);
    }

CONSUMER_CONFIGURATION_LISTENER和ReferenceConfigurationListener和生产者那一块的逻辑是一样的,都是新版本的监听器,在构造方法中调用initWith方法进行监听器的绑定以及首次读取服务的应用级别和服务级别的动态配置信息

// 当前所引入的服务的消费应用目录:/dubbo/config/dubbo/dubbo-demo-consumer-application.configurators
// 当前所引入的服务动态配置目录:/dubbo/config/dubbo/org.apache.dubbo.demo.DemoService:1.1.1:g1.configurators

而 registry.subscribe(url, this)负责的是监听老版本动态配置目录的信息以及主动从注册中心拉取服务提供者URL信息。

这里的registry是ZookeeperRegistry,它会调用父类的subscribe方法,最终会来到自己的doSubscribe方法,下边是进行注册监听的代码


                List<URL> urls = new ArrayList<>();
                // 得到真正要监听的zk上的路径,
                for (String path : toCategoriesPath(url)) {
                    // 根据监听地址去拿listeners,如果没有则生成
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                    if (listeners == null) {
                        zkListeners.putIfAbsent(url, new ConcurrentHashMap<>());
                        listeners = zkListeners.get(url);
                    }

                    // 一个NotifyListener对应一个ChildListener
                    ChildListener zkListener = listeners.get(listener);
                    if (zkListener == null) {
                        // lambda表达式就是监听逻辑, parentPath表示父path,currentChilds表示当前拥有的child, 会调用notify方法进行实际的处理
                        listeners.putIfAbsent(listener, (parentPath, currentChilds) -> ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds)));
                        zkListener = listeners.get(listener);
                    }
                    // 创建zk上路径
                    zkClient.create(path, false);

                    // 添加真正跟zk相关的ChildListener,ChildListener中的逻辑就是监听到zk上数据发生了变化后会触发的逻辑
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    if (children != null) {
                        urls.addAll(toUrlsWithEmpty(url, path, children));
                    }
                }
                // 这里的urls就是从现在所引入的服务的目录下查到的url,比如下面这个三个目录下的路径
//                "/dubbo/org.apache.dubbo.demo.DemoService/providers"
//                "/dubbo/org.apache.dubbo.demo.DemoService/configurators"
//                "/dubbo/org.apache.dubbo.demo.DemoService/routers"
                notify(url, listener, urls);

可以看到在注册的时候,手工的调用notify方法拉取配置,这里会来到RegistryDirectory.notify(List urls)

    @Override
    public synchronized void notify(List<URL> urls) {
        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 "";
                }));

        // 获取动态配置URL,生成configurators
        List<URL> configuratorURLs = categoryUrls.getOrDefault(CONFIGURATORS_CATEGORY, Collections.emptyList());
        this.configurators = Configurator.toConfigurators(configuratorURLs).orElse(this.configurators);

        // 获取老版本路由URL,生成Router,并添加到路由链中
        List<URL> routerURLs = categoryUrls.getOrDefault(ROUTERS_CATEGORY, Collections.emptyList());
        toRouters(routerURLs).ifPresent(this::addRouters);

        // 获取服务提供者URL
        List<URL> providerURLs = categoryUrls.getOrDefault(PROVIDERS_CATEGORY, Collections.emptyList());
        refreshOverrideAndInvoker(providerURLs);
    }

在这里会读取一下三个目录的信息,这里主要是读取服务提供者配置信息,会调用RegistryDirectory.refreshOverrideAndInvoker方法

“/dubbo/org.apache.dubbo.demo.DemoService/providers”
“/dubbo/org.apache.dubbo.demo.DemoService/configurators”
“/dubbo/org.apache.dubbo.demo.DemoService/routers”

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

overrideDirectoryUrl主要是根据动态配置信息重新动态目录URL信息,我们重点看refreshInvoker方法,这里会把服务提供者URL传到方法中去。

image-20231213230619353

refreshInvoker方法中,会调用toInvokers方法,生成inwoker对象,这个方法是重点,主要逻辑有:

image-20231213234334042

1.服务提供者可能有多个,可能是dubbo协议也有可能是rest协议,如果消费者配置了具体的协议,先根据协议过滤掉一些服务提供者URL

2.调用mergeUrl对服务提供者进行重写,主要包括消费者本身的配置信息,动态配置信息对服务提供者URL进行重写覆盖

3.判断当前服务提供者是否已经生成invoker对象,如果没有,调用==invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl)==生成一个Dubbo Invoker对象,这里根据url会调用DubboProtocol类的refer方法,再次之前会先调用wrapper类,进行AOP增强。

image-20231213234420626

4.toInvokers调用完毕,生成一个map,key是我们服务提供者的url,value就是生成的invoker

然后把生成的invoker集合设置到路由链中,并赋值给动态目录的属性invokers,注意这里的invoker都是dubbo invoker 代表的是一个服务的多个提供者。

image-20231213234522739

5.监听标签路由信息

在routerChain.setInvokers(newInvokers)放中,除了把新生成的invoker赋值给路由链,在这一过程中还有进行标签路由配置信息的监听,为什么要在这里监听呢?因为标签路由是针对服务提供者打标签,而我们消费者引入服务的时候是不知道服务的应用信息的,所以只能从服务提供者URL中获取到服务应用信息,然后再进行监听。

    public void setInvokers(List<Invoker<T>> invokers) {
        this.invokers = (invokers == null ? Collections.emptyList() : invokers);
        routers.forEach(router -> router.notify(this.invokers));
    }

在这里会遍历所有的路由规则,调用notify方法,其实只有标签路由实现了这个方法,我们再看下TagRouter.notify方法

@Override
public <T> void notify(List<Invoker<T>> invokers) {
    if (CollectionUtils.isEmpty(invokers)) {
        return;
    }


    // invoker表示一个服务执行者
    Invoker<T> invoker = invokers.get(0);
    URL url = invoker.getUrl();

    // 要执行的服务在哪个应用上,服务提供者应用
    String providerApplication = url.getParameter(CommonConstants.REMOTE_APPLICATION_KEY);


    synchronized (this) {
        // 服务提供者所属的应用
        // application是TagRouter中的一个属性,表示当前TagRouter是在哪个应用上
        if (!providerApplication.equals(application)) {
            if (!StringUtils.isEmpty(application)) {
                configuration.removeListener(application + RULE_SUFFIX, this);
            }

            // dubbo-demo-provider-application.tag-router
            String key = providerApplication + RULE_SUFFIX;
            configuration.addListener(key, this);

            application = providerApplication;

            String rawRule = configuration.getRule(key, DynamicConfiguration.DEFAULT_GROUP);
            if (StringUtils.isNotEmpty(rawRule)) {
                this.process(new ConfigChangeEvent(key, rawRule));
            }
        }
    }

1.2.5 生成DubboInvoker对象

在监听拉取配置的时候,在RegistryDirectory.toInvokers方法中会拿到最终更新的服务提供者URL生成一个Invoker对象

invoker = new InvokerDelegate<>(protocol.refer(serviceType, url), url, providerUrl);

url这里是根据不同的服务提供者协议,调用不同的protocol类,比如我们服务提供者使用的是dubbo协议,那么就会调用==DubboProtocol.refer方法,但是我们之前分析过Protocol接口是有包装的类的,ProtocolListenerWrapperProtocolFilterWrapper,所以这里会依次调用ProtocolListenerWrapper.refer=>ProtocolFilterWrapper.refer=>DubboProtol.refer,

我们重点说下ProtocolListenerWrapper.refer

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        if (REGISTRY_PROTOCOL.equals(url.getProtocol())) {
            return protocol.refer(type, url);
        }
        return buildInvokerChain(protocol.refer(type, url), REFERENCE_FILTER_KEY, CommonConstants.CONSUMER);
    }
    private static <T> Invoker<T> buildInvokerChain(final Invoker<T> invoker, String key, String group) {
        Invoker<T> last = invoker;
        // 根据url获取filter,根据url中的parameters取key为key的value所对应的filter,但是还会匹配group
        List<Filter> filters = ExtensionLoader.getExtensionLoader(Filter.class).getActivateExtension(invoker.getUrl(), key, group);

        // ConsumerContextFilter--->FutureFilter--->MonitorFilter
        // ConsumerContextFilter用来设置RpcContext
        //

       ///

        return new CallbackRegistrationInvoker<>(last, filters);
    }

ProtocolListenerWrapper.refer会给DubboProtocol的url对应的invoker添加Filter过滤器,这些过滤器在服务调用的时候会执行对应的方法,这里我们先简单了解一下即可。

接着就是调用DubboProtol.refer方法,但是DubboProtol没有实现refer方法,会调用其父类AbstractProtocol.refer

    @Override
    public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
        // 异步转同步Invoker , type是接口,url是服务地址
        // DubboInvoker是异步的,而AsyncToSyncInvoker会封装为同步的
        return new AsyncToSyncInvoker<>(protocolBindingRefer(type, url));
    }

protocolBindingRefer是由DubboProtol实现的

    @Override
    public <T> Invoker<T> protocolBindingRefer(Class<T> serviceType, URL url) throws RpcException {
        // 优化序列化?在dubbo的protocol中可以通过serialization配置序列化方式,但是这里的optimizer属性是干嘛的?
        optimizeSerialization(url);

        // create rpc invoker.
        // clients很重要,为什么一个DubboInvoker会有多个clients,为了提高效率,因为每个client和server之间都会有一个socket
        // 在DubboInvoker发送请求时会轮询clients去发送数据
        DubboInvoker<T> invoker = new DubboInvoker<T>(serviceType, url, getClients(url), invokers);
        invokers.add(invoker);

        return invoker;
    }

invoker在层层包装下会形成一下结构

image-20231214212859899

而且在生成DubboInvoker的时候,就会去创建一个NettyClient,和服务端建立Socket连接,主要方法在getClients(url)方法上。

1.2.6 构建传输链路建立Netty连接

在Dubbo协议中, 是基于Netty进行数据传输的,生产者和消费者是可以互相传输数据的,Dubbo在此之上抽象了一个数据交换层,用于区分请求和响应

image-20231214214534870

我们继续在创建DubboInvoker,追踪DubboProtocol.getClients

    private ExchangeClient initClient(URL url) {

        // client type setting.
        // 拿设置的client,默认为netty
        String str = url.getParameter(CLIENT_KEY, url.getParameter(SERVER_KEY, DEFAULT_REMOTING_CLIENT));

        // 编码方式
        url = url.addParameter(CODEC_KEY, DubboCodec.NAME);

        // enable heartbeat by default
        // 心跳, 默认60 * 1000,60秒一个心跳
        url = url.addParameterIfAbsent(HEARTBEAT_KEY, String.valueOf(DEFAULT_HEARTBEAT));

        // BIO is not allowed since it has severe performance issue.
        // 如果没有指定的client扩展,则抛异常
        if (str != null && str.length() > 0 && !ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str)) {
            throw new RpcException("Unsupported client type: " + str + "," +
                    " supported client type is " + StringUtils.join(ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions(), " "));
        }

        ExchangeClient client;
        try {
            // connection should be lazy
            if (url.getParameter(LAZY_CONNECT_KEY, false)) {
                client = new LazyConnectExchangeClient(url, requestHandler);

            } else {
                // client是在refer的时候生成的,这个时候就已经建立好连接了?
                // 答案是就是会去建立连接,也是能够理解了,只有连接建立好了才有client和server之分
                // 先建立连接,在调用方法时再基于这个连接去发送数据
                client = Exchangers.connect(url, requestHandler);  // connect
            }

        } catch (RemotingException e) {
            throw new RpcException("Fail to create remoting client for service(" + url + "): " + e.getMessage(), e);
        }

        return client;
    }

真正创建我们交换层client的代码在client = Exchangers.connect(url, requestHandler); // connect

    public static ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        if (handler == null) {
            throw new IllegalArgumentException("handler == null");
        }
        url = url.addParameterIfAbsent(Constants.CODEC_KEY, "exchange");

        // 得到一个HeaderExchanger去connect
        return getExchanger(url).connect(url, handler);
    }

这里是调用getExchanger方法根据SPI机制获取Exchanger接口的扩展点拿到具体实现,默认是HeaderExchanger,我们继续追踪HeaderExchanger的connect方法

    @Override
    public ExchangeClient connect(URL url, ExchangeHandler handler) throws RemotingException {
        // 利用NettyTransporter去connect
        // 为什么在connect和bind时都是DecodeHandler,解码,解的是把InputStream解析成AppResponse对象
        return new HeaderExchangeClient(Transporters.connect(url, new DecodeHandler(new HeaderExchangeHandler(handler))), true);
    }

DecodeHandler把handler(ExchangeHandlerAdapter)包装两层,接着Transporters.connect来创建Client连接对象:

    public static Client connect(URL url, ChannelHandler... handlers) throws RemotingException {
        if (url == null) {
            throw new IllegalArgumentException("url == null");
        }
        ChannelHandler handler;
        if (handlers == null || handlers.length == 0) {
            handler = new ChannelHandlerAdapter();
        } else if (handlers.length == 1) {
            handler = handlers[0];
        } else {
            handler = new ChannelHandlerDispatcher(handlers);
        }
        // NettyTransporter
        return getTransporter().connect(url, handler);
    }

调用完getTransporter().connect(url, handler)方法,会把NettyClient作为参数返回,然后调用HeaderExchangeClient的构造方法,同时开启重连任务和心跳检测任务。

    public HeaderExchangeClient(Client client, boolean startTimer) {
        Assert.notNull(client, "Client can't be null");
        this.client = client;
        this.channel = new HeaderExchangeChannel(client);

        if (startTimer) {
            URL url = client.getUrl();
            startReconnectTask(url);
            startHeartBeatTask(url);
        }
    }

至此我们服务导出链路走完,

1.2.7 获取最终invoker对象

我们重新回到RegistryProtocol#doRefer

    private <T> Invoker<T> doRefer(Cluster cluster, Registry registry, Class<T> type, URL url) {
        // RegistryDirectory表示动态服务目录,会和注册中心的数据保持同步
        // type表示一个服务对应一个RegistryDirectory,url表示注册中心地址
        // 在消费端,最核心的就是RegistryDirectory
        RegistryDirectory<T> directory = new RegistryDirectory<T>(type, url);
        directory.setRegistry(registry);
        directory.setProtocol(protocol);


        // all attributes of REFER_KEY
        // 引入服务所配置的参数
        Map<String, String> parameters = new HashMap<String, String>(directory.getUrl().getParameters());

        // 消费者url
        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));

            // 注册简化后的消费url
            registry.register(directory.getRegisteredConsumerUrl());
        }

        // 构造路由链,路由链会在引入服务时按路由条件进行过滤
        // 路由链是动态服务目录中的一个属性,通过路由链可以过滤某些服务提供者
        directory.buildRouterChain(subscribeUrl);


        directory.subscribe(subscribeUrl.addParameter(CATEGORY_KEY,
                PROVIDERS_CATEGORY + "," + CONFIGURATORS_CATEGORY + "," + ROUTERS_CATEGORY));

        // 利用传进来的cluster,join得到invoker,
        Invoker invoker = cluster.join(directory);
        ProviderConsumerRegTable.registerConsumer(invoker, url, subscribeUrl, directory);
        return invoker;
    }

还有重要的一步cluster.join(directory),把我们的服务目录作为参数,调用join方法,cluster是一个接口,默认实现为FailoverCluster,还有一个包装类型的MockClusterWrapper

FailoverCluster.join

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new FailoverClusterInvoker<T>(directory);
    }

MockClusterWrapper.join

    @Override
    public <T> Invoker<T> join(Directory<T> directory) throws RpcException {
        return new MockClusterInvoker<T>(directory,
                this.cluster.join(directory));
    }

所以我们最终返回的invoker对象是这样的结构

MockClusterInvoker

​ invoker->FailoverClusterInvoker

​ directory->

​ List DubboInvoer集合

1.2.8 根据invoker创建动态对象

return (T) PROXY_FACTORY.getProxy(invoker);

小结

现在做个小结,总结一下服务引入到底都做了那些事情?

1.扫描加了@Reference注解的地方,为其进行属性注入

2.根据@Reference注解的信息,生成一个ReferenceBean对象

3.ReferenceBean是一个Factory,会通过getObject创建一个代理对象

4.创建代理对象需要获取一个Invoker对象,其创建过程为:

调用RegistryProtocol.refer()

1.创建动态目录

2.构建路由链

3.注册监听并拉取配置

4.根据服务提供者URL生成DubboInvoker对象

5.构建Netty客户端,并和Server建立连接

6.对Invoker对象进行合并包装

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值