dubbo源码 -- 服务导出

结论

所谓的服务导出,就是服务提供者将本地服务注册到zk集群,并且开启netty,用来接收消费者的请求,我们也可以认为:dubbo服务提供者就是一个netty服务端

对于服务导出,会进行以下几个步骤
1.进行一些校验、参数取值、赋值等(同一个参数配置的覆盖)
2.获取所有的注册中心、获取所有配置的协议
3.组装url对象,由于dubbo是基于url来完成注册的,所以,会先拼接、组装url
4.根据url和registry(注册中心)生成Invoker对象
5.将Invoker对象进行包装,然后进行真正的导出
5.1 首先会根据服务的ip和端口,开启一个netty服务
5.2 然后将url转换成zk的节点,进行注册,其实就是注册到注册中心,比如:zookeeper
5.3 监听路径信息(这里具体监听的路径需要再次确认学习)

所以,我们可以认为服务导出分为两大步
1.参数校验、组装参数
2.服务暴露到远程
在暴露到远程的时候,会分为以下几个步骤
2.1 开启一个netty服务
2.2 注册到zk

导出源码

我学习用的版本还是2.6,所以和2.7的代码会有些差别
dubbo服务导出的源码,可以理解为是从

com.alibaba.dubbo.config.spring.ServiceBean#onApplicationEvent

这行代码开始的,spring容器在启动之后,会发生一个ContextRefreshedEvent事件,dubbo在监听到该事件之后,会开始进行服务导出
具体,dubbo如何利用spring扩展点完成初始化,可以参考这篇博客 --> dubbo如何利用spring扩展点完成初始化

com.alibaba.dubbo.config.ServiceConfig#export
public synchronized void export() {
    
    
        if (provider != null) {
    
    
            if (export == null) {
    
    
                export = provider.getExport();
            }
            if (delay == null) {
    
    
                delay = provider.getDelay();
            }
        }
        /**
         * 1.判断是否已经导出,如果已经导出,return
         * 2.判断是否是延迟导出
         * 3.如果是非延迟导出,就进行服务导出
         */
        if (export != null && !export) {
    
    
            return;
        }

        if (delay != null && delay > 0) {
    
    
            delayExportExecutor.schedule(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    doExport();
                }
            }, delay, TimeUnit.MILLISECONDS);
        } else {
    
    
            doExport();
        }
    }

在doExport()方法中,会有一些列的参数赋值以及校验,这里我给一张截图

在这里插入图片描述
这里的doExportUrls,是去进行服务导出的

private void doExportUrls() {
    
    
    /**
         * 如果是多注册中心,这里会获取到所有配置的注册中心
         */
    List<URL> registryURLs = loadRegistries(true);
    /**
         * 如果是多个协议,这里会循环多个协议
         */
    for (ProtocolConfig protocolConfig : protocols) {
    
    
        doExportUrlsFor1Protocol(protocolConfig, registryURLs);
    }
}

doExportUrlsFor1Protocol()在这个方法中,总结而言,完成了两件事情:
1.组装、重拼接url
2.进行真正的服务导出
在这个方法里面,会根据服务提供者配置的scope属性,来判断是导出到本地,还是导出到远程服务
在进行服务导出的时候,会根据服务提供者,生成一个Invoker对象,然后对Invoker对象进行服务导出

Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));
DelegateProviderMetaDataInvoker wrapperInvoker = new DelegateProviderMetaDataInvoker(invoker, this);
Exporter<?> exporter = protocol.export(wrapperInvoker);

在调用protocol.export()方法的时候,会依次经过以下调用链路

org.apache.dubbo.rpc.protocol.ProtocolListenerWrapper#export
org.apache.dubbo.rpc.protocol.ProtocolFilterWrapper#export
org.apache.dubbo.qos.protocol.QosProtocolWrapper#export
org.apache.dubbo.registry.integration.RegistryProtocol#export

这里的调用顺序和dubbo的spi机制有关系,每个export干的事情,后面再讨论
核心的工作,就是在org.apache.dubbo.registry.integration.RegistryProtocol#export方法中

@Override
public <T> Exporter<T> export(final Invoker<T> originInvoker) throws RpcException {
    
    
    //export invoker
    /**
    * 1、导出服务,创建nettyServer
    * 这里是调用的是DubboProtocol的export方法来开启netty服务的
    */
    final ExporterChangeableWrapper<T> exporter = doLocalExport(originInvoker);

    //获取注册中心的URL, zookeeper://ip:port/xxxxx
    URL registryUrl = getRegistryUrl(originInvoker);

    //registry provider
    final Registry registry = getRegistry(originInvoker);
    final URL registeredProviderUrl = getRegisteredProviderUrl(originInvoker);

    //to judge to delay publish whether or not
    boolean register = registeredProviderUrl.getParameter("register", true);

    ProviderConsumerRegTable.registerProvider(originInvoker, registryUrl, registeredProviderUrl);

    if (register) {
    
    
        /**
         * 2、这里是真正的服务注册的逻辑
         * registryUrl:是以zookeeper://ip:port
         * registeredProviderUrl:是以协议开头的,dubbo://ip:port
         *
         * 调用FailBackRegistry(这里要看配置的是哪种服务容错策略)
         */
        register(registryUrl, registeredProviderUrl);
        ProviderConsumerRegTable.getProviderWrapper(originInvoker).setReg(true);
    }

    // Subscribe the override data
    // FIXME When the provider subscribes, it will affect the scene : a certain JVM exposes the service and call the same service. Because the subscribed is cached key with the name of the service, it causes the subscription information to cover.
    //3、事件监听 待学习补充
    final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registeredProviderUrl);
    final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl, originInvoker);
    overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
    registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
    //Ensure that a new exporter instance is returned every time export
    return new DestroyableExporter<T>(exporter, originInvoker, overrideSubscribeUrl, registeredProviderUrl);
}

开启netty服务

doLocalExport(originInvoker)
在这个方法中,会通过spi的扩展机制,调用dubboProtocol的export方法,在export这个方法中,会开启netty服务端

下面这个方法的调用链是这样的:

com.alibaba.dubbo.registry.integration.RegistryProtocol#export
	com.alibaba.dubbo.registry.integration.RegistryProtocol#doLocalExport
		com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#export
			com.alibaba.dubbo.rpc.protocol.dubbo.DubboProtocol#openServer
private void openServer(URL url) {
    
    
        // find server.
        String key = url.getAddress();
        //client can export a service which's only for server to invoke
        boolean isServer = url.getParameter(Constants.IS_SERVER_KEY, true);
        if (isServer) {
    
    
            ExchangeServer server = serverMap.get(key);
            if (server == null) {
    
    
                serverMap.put(key, createServer(url));
            } else {
    
    
                // server supports reset, use together with override
                server.reset(url);
            }
        }
    }

createServer()方法后面的逻辑在本篇笔记中,不展开叙述,我们可以认为,createServer就是开启了netty服务端

注册中心注册

com.alibaba.dubbo.registry.integration.RegistryProtocol#export 方法中,会先调用上面的doLocalExport()方法,去启动netty服务端,接着会去注册中心注册服务

@SPI("dubbo")
public interface RegistryFactory {
    
    

    @Adaptive({
    
    "protocol"})
    Registry getRegistry(URL url);
}

获取注册中心的时候,也是通过SPI机制实现的,默认是dubbo,我们以zk为例,如果我们配置的注册中心是zk,那就会创建ZookeeperRegistry

然后在

com.alibaba.dubbo.registry.integration.RegistryProtocol#export
	com.alibaba.dubbo.registry.integration.RegistryProtocol#register

根据registryUrl获取到当前的注册中心工厂,如果是zk,那接着会调用

com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry#doRegister

@Override
    protected void doRegister(URL url) {
    
    
        try {
    
    
            /**
             * 这里的toUrlPath,最终会把路径转换为 dubbo/接口全类名/服务暴露的地址
             * 这里true,表示zk创建的是临时节点
             */
            zkClient.create(toUrlPath(url), url.getParameter(Constants.DYNAMIC_KEY, true));
        } catch (Throwable e) {
    
    
            throw new RpcException("Failed to register " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

这里可以看到,直接调用的zkClient.create()方法

dubbo导出的原理,基本就是这样的,在dubbo官网文档改版之后,提供了服务导出和服务引入的时序图 dubbo官网
在这里插入图片描述

所以以上就是dubbo服务导出的核心原理

猜你喜欢

转载自blog.csdn.net/CPLASF_/article/details/113794622