浅谈Dubbo服务导出到注册中心源码

Posted 默辨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了浅谈Dubbo服务导出到注册中心源码相关的知识,希望对你有一定的参考价值。

Dubbo服务导出到注册中心源码




由于ServiceBean实现了ApplicationListener接口。所以当Spring容器启动完成之后,会触发对应的事件,继而调用到onApplicationEvent方法,最终调用到当前类的export方法。

export方法核心分成两大部分(在ServiceBean的父类ServiceConfig中实现):第一部分为处理所有配置的参数,第二部分为将服务真正进行导出



一、检查更新配置(checkAndUpdateSubConfigs)

1、completeCompoundConfigs

如果ServiceConfig中的某些属性如果是空的,那么就从ProviderConfig、ModuleConfig、ApplicationConfig中获取。即补全ServiceConfig中部分的属性



2、startConfigCenter

获取dubbo-admin远程客户端上配置项,然后将对应的配置项覆盖到前面已经设置过值得对象上(完善ConfigManager对象),这里可以看出远程配置平台得配置优先级是高于本地得dubbo的配置文优先级

1、获取远程配置中心的参数值:


2、覆盖对应的参数值

从配置中心取到配置数据后,刷新所有的XxConfig中的属性,除开ServiceConfig。即将配置中心配置的数据对除了ServiceConfig对象以外的对象进行赋值



3、refresh

前面覆盖了很多参数,但是没有设置ServiceConfig的配置,该方法用来配置对ServiceConfig对象的参数设置

1)调用getConfiguration方法,获取配置数据


2)设置远程配置中心优先级和@Service注解上的优先级顺序

CompositeConfiguration对象内部维护了一个有序的LinkList对象,用来顺序存放所有的配置信息,后期执行set方法的时候就按照这个顺序进行依次set值。初始顺序如上图。

configCenterFirst是一个配置参数,默认为true。即默认情况下, 当前ServiceConfig配置项(ServiceBean的父类,这里可以直接理解为@Service上的配置)放在第四个位置。由于未来是顺序执行,即远程配置中心的配置优先级高于@Service注解上的配置信息。

同理,如果配置为false,则表示@Service注解上的配置项优先级高于远程配置中心的配置。


3)依次调用set方法完成参数设置

这里会对遍历ServiceBean对象的set方法,如果对应的参数没有进行赋值(value=null),就获取配置项(上一步构建的linkList顺序里的配置项)里面的value,进行赋值操作






二、执行服务导出(doExport)

服务导出里面有一个重要的概念:

URL:我们的协议(http、dubbo)可以是一个URL,我们的注册中心(zookeeper、redis)也会是一个URL。在源码中对应着registryUrl(注册中心构建的url)和providerUrl(服务提供者和通信协议构建的url)

1、doExportUrls

在每一个选择暴露的ServiceBean服务实体类中,都会进行服务导出操作。在前面提到的更新配置基础上,紧接着开始获取到所有注册中心的列表,然后将服务按照不同的协议都注册到对应的注册中心。比如,我们的服务配置了redis和zookeeper两个不同的注册中心,那么这里的registyURLs的size就等于2,如果我们的服务配置了dubbo和http两种通信协议,这里的protocols的size也会等于2。然后将对应的注册中心list和单个协议传入指定的方法,完成后续操作(为每个协议导出一次)



2、loadRegistries

该方法为获取注册中心列表的逻辑

protected List<URL> loadRegistries(boolean provider) 
    // check && override if necessary
    List<URL> registryList = new ArrayList<URL>();
    if (CollectionUtils.isNotEmpty(registries)) 
        for (RegistryConfig config : registries) 
            String address = config.getAddress();
            // 如果注册中心没有配地址,则地址为0.0.0.0
            if (StringUtils.isEmpty(address)) 
                address = ANYHOST_VALUE;
            
            // 如果注册中心的地址不是"N/A"
            if (!RegistryConfig.NO_AVAILABLE.equalsIgnoreCase(address)) 
                Map<String, String> map = new HashMap<String, String>();
                // 把application中的参数放入map中,注意,map中的key是没有prefix的
                appendParameters(map, application);
                // 把config中的参数放入map中,注意,map中的key是没有prefix的
                // config是RegistryConfig,表示注册中心
                appendParameters(map, config);
                // 此处path值固定为RegistryService.class.getName(),因为现在是在加载注册中心
                map.put(PATH_KEY, RegistryService.class.getName());
                // 把dubbo的版本信息和pid放入map中
                appendRuntimeParameters(map);

                // 如果map中如果没有protocol,那么默认为dubbo
                if (!map.containsKey(PROTOCOL_KEY)) 
                    map.put(PROTOCOL_KEY, DUBBO_PROTOCOL);
                

                // 构造注册中心url,地址+参数
                List<URL> urls = UrlUtils.parseURLs(address, map);

                for (URL url : urls) 
                    url = URLBuilder.from(url)
                            .addParameter(REGISTRY_KEY, url.getProtocol())
                            .setProtocol(REGISTRY_PROTOCOL)
                            .build();
                    // 到此为止,url的内容大概为:
                    // registry://127.0.0.1:2181/org.apache.dubbo.registry.RegistryService?application=dubbo-demo-annotation-provider&dubbo=2.0.2&pid=269936&registry=zookeeper&timestamp=1584886077813
                    // 该url表示:使用registry协议调用org.apache.dubbo.registry.RegistryService服务
                    // 参数为application=dubbo-demo-annotation-provider&dubbo=2.0.2&pid=269936&registry=zookeeper&timestamp=1584886077813

                    // 这里是服务提供者和服务消费者区别的逻辑
                    // 如果是服务提供者,获取register的值,如果为false,表示该服务不注册到注册中心
                    // 如果是服务消费者,获取subscribe的值,如果为false,表示该引入的服务不订阅注册中心中的数据
                    if ((provider && url.getParameter(REGISTER_KEY, true))
                            || (!provider && url.getParameter(SUBSCRIBE_KEY, true))) 
                        registryList.add(url);
                    
                
            
        
    
    return registryList;



3、doExportUrlsFor1Protocol

这里的appendParameters方法,大致逻辑为找到第二个对象里面的所有getXxx方法,然后得到对应的value。然后以xxx为key,对应的get方法获取到的值为value,放进第一个参数的map中。

由于这是一个map,所以如果key相同则对应的value是会覆盖的


最终将map中的数据,和已有的信息拼接为一个url,然后存放到注册中心里面



4、PROXY_FACTORY.getInvoker

该方法主要作为可以简单理解为将ref代码的对象(真正被代理的对象)封装为一个Invoker对象,未来想要调用被代理对象的业业务逻辑,直接使用Invoker对象进行调用即可

这个方法同时还完成将注册中心的url(registryURL为传入该方法的registryURLs的其中一个值)与前面构建出来的new URL()创建出来的url(ServiceBean属性构建的url字符串)对象进行一个拼接。即将单个注册中心和指定协议下的服务拼接在了一起

得到Invoker对象后,再封装为一个DelegateProviderMetaDataInvoker对象,然后调用export方法,完成正真的服务到导出功能



5、export

使用特定的协议来对服务进行导出,这里的协议为RegistryProtocol,导出成功后得到一个Exporter

  1. 先使用RegistryProtocol进行服务注册
  2. 注册完了之后,使用DubboProtocol进行导出
@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException 
    // 导出服务
    // registry://   ---> RegistryProtocol
    // zookeeper://  ---> ZookeeperRegistry
    // dubbo://      ---> DubboProtocol

    // 得到注册中心url,即registry://xxx?xx=xx&registry=zookeeper ---> zookeeper://xxx?xx=xx     
    URL registryUrl = getRegistryUrl(originInvoker); 
    // 得到服务提供者url,表示服务提供者
    URL providerUrl = getProviderUrl(originInvoker); 
    
    
    // 在服务提供者url的基础上,生成一个overrideSubscribeUrl,协议为provider://,增加参数category=configurators&check=false
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(providerUrl);

    // 一个overrideSubscribeUrl对应一个OverrideListener,用来监听变化事件,监听到overrideSubscribeUrl的变化后,
    // OverrideListener就会根据变化进行相应处理,具体处理逻辑看OverrideListener的实现
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);


    // 在这个方法里会利用providerConfigurationListener和serviceConfigurationListener去重写providerUrl
    // providerConfigurationListener表示应用级别的动态配置监听器,providerConfigurationListener是RegistyProtocol的一个属性
    // serviceConfigurationListener表示服务级别的动态配置监听器,serviceConfigurationListener是在每暴露一个服务时就会生成一个
    providerUrl = overrideUrlWithConfig(providerUrl, overrideSubscribeListener);

    // export invoker
    // 根据动态配置重写了providerUrl之后,就会调用DubboProtocol或HttpProtocol去进行导出服务了
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker, providerUrl);

    // url to registry
    // 得到注册中心-ZookeeperRegistry
    final Registry registry = getRegistry(originInvoker);

    // 对存入到注册中心的providerUrl的参数进行简化
    final URL registeredProviderUrl = getRegisteredProviderUrl(providerUrl, registryUrl);

    // 将当前服务提供者Invoker,以及该服务对应的注册中心地址,以及简化后的服务url存入ProviderConsumerRegTable
    ProviderInvokerWrapper<T> providerInvokerWrapper = ProviderConsumerRegTable.registerProvider(originInvoker,
            registryUrl, registeredProviderUrl);


    //to judge if we need to delay publish
    //是否需要注册到注册中心
    boolean register = providerUrl.getParameter(REGISTER_KEY, true);
    if (register) 
        // 注册服务,把简化后的服务提供者url注册到registryUrl中去
        register(registryUrl, registeredProviderUrl);
        providerInvokerWrapper.setReg(true);
    

    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);

    exporter.setRegisterUrl(registeredProviderUrl);
    exporter.setSubscribeUrl(overrideSubscribeUrl);
    //Ensure that a new exporter instance is returned every time export
    return new DestroyableExporter<>(exporter);


6、overrideSubscribeListener、overrideUrlWithConfig、ProviderConfigurationListener(初始化监听器)

这里涉及两种类型的监听器:

都可以用来监听dubbo-admin客户端的配置修改。未来发生了修改,overrideSubscribeListener这个监听器会调用到notify方法,而overrideUrlWithConfig和ProviderConfigurationListener会调用到notifyOverrides方法

1)overrideUrlWithConfig监听器调用的方法


2)overrideUrlWithConfig监听器调用的方法



7、doLocalExport(初始化服务注册的连通方式)

如果使用dubbo通讯协议,底层会开启netty

1)调用到DubboProtocol的export方法

与前面调用export方式类似。这里传进来的URL是providerUrl,即服务提供者对应的export方法。我们这里使用dubbo协议进行传输,所以这里的export方法会调用到DubboProtocol的export方法

private <T> ExporterChangeableWrapper<T> doLocalExport(final Invoker<T> originInvoker, URL providerUrl) 
    String key = getCacheKey(originInvoker);

    return (ExporterChangeableWrapper<T>) bounds.computeIfAbsent(key, s -> 
        Invoker<?> invokerDelegate = new InvokerDelegate<>(originInvoker, providerUrl);

        return new ExporterChangeableWrapper<>((Exporter<T>) protocol.export(invokerDelegate), originInvoker);
    );


2)在该方法种会开启对应的服务连接

openServer底层再调用createServer方法。由于该调用链路比较长,后面直接放一张流程图


3)new ServerBootstrap

真正初始化netty服务端的代码(doOpen方法中)。初始化netty服务端用来处理底层的通讯



8、register(将服务注册到注册中心)

在执行上一步的export方法的过程中,会调用register方法,即register(registryUrl, registeredProviderUrl);方法。传入的参数为注册中心url,以及简化版的服务提供者url,这两个url就可以理解为一个是注册中心的地址,一个是暴露服务的参数信息

public void register(URL registryUrl, URL registeredProviderUrl) 
    Registry registry = registryFactory.getRegistry(registryUrl);
    // 调用ZookeeperRegistry的register方法
    registry.register(registeredProviderUrl);

1)addFailedRegistered

如果调用doRegister方法发生异常,则会有对应的重试机制


2)使用具体注册中心,完成注册功能






三、总结

用自己的话,总结一下Dubbo服务导出代码的主体流程,主要分为三部分。

1-3:完成基本的参数配置;

4-6:构建注册时需要的参数;

7-9:完成正真的注册逻辑。

  1. 首先会去检查ServiceBean部分属性是否有值(ServiceConfig是ServiceBean的父类,源码检查的是其父类),每值就进行赋值
  2. 获取全程配置中心dubbo-admin上的配置参数,然后对相关对象进行赋值(除开对ServiceConfig对象进行赋值)
  3. 配置ServiceConfig对象。设置该对象涉及到很多配置,如JVM环境变量、操作系统环境变量、配置中心APP配置、配置中心Global配置还有dubbo.properties配置文件还有@Service注解上的配置。这几个配置按照优先级顺序完成对ServiceConfig对象参数的设置
  4. 获取部分对象的属性值,以key-value的形式放进一个map。部分对象包括运行时参数、监控中心参数、应用相关参数、模块相关参数、提供者相关参数、协议相关参数、服务本身相关参数、服务中某些方法参数
  5. 将registryURL(注册中心URL对象)和protocolConfig(协议和服务都成的URL对象)封装为一个invoker对象。未来在使用这连个URL对象的时候,直接使用invoker对象就都能获取到
  6. invoker对象已经能够完成服务和注册中心一对一的关系。接下来就是完成具体的注册逻辑
  7. 初始化监听器,用来监听未来dubbo-admin客户端发生的动态配置命令
  8. 初始化对应的通讯通道(dubbo就是初始化NettyServer对象,http就是初始化Tomcat对象,对应TomcatHttpServer类)
  9. 创建注册中心对象,完成将服务注册到注册中心上。如果注册失败,还有对应的重试机制

以上是关于浅谈Dubbo服务导出到注册中心源码的主要内容,如果未能解决你的问题,请参考以下文章

Dubbo 源码分析 - 服务导出全过程解析

Dubbo源码分析系列---服务的发布

[源码阅读] Dubbo注册中心模块实现原理

Dubbo--服务注册与发布原理分析

Dubbo学习-源码学习

源码分析Dubbo系列之寻找注册中心服务提供者服务消费者功能入口