Dubbo学习系列(四--2)服务的暴露--远程暴露-Invoker到Exporter的转化

在看远程暴露之前,我们先来浏览下项目跑动的日志:

[com.alibaba.dubbo.config.AbstractConfig] -  [DUBBO] The service ready on spring started. service: com.api.IDubboTestService,
[com.alibaba.dubbo.config.AbstractConfig] -  [DUBBO] Export dubbo service com.api.IDubboTestService to local registry
[com.alibaba.dubbo.config.AbstractConfig] -  [DUBBO] Export dubbo service com.api.IDubboTestService to url dubbo://192.168.1.12:20880/com.api.IDubboTestService
[com.alibaba.dubbo.config.AbstractConfig] -  [DUBBO] Register dubbo service com.api.IDubboTestService url dubbo://192.168.1.12:20880/com.api.IDubboTestService
[com.alibaba.dubbo.remoting.transport.AbstractServer] -  [DUBBO] Start NettyServer bind /0.0.0.0:20880, export /192.168.1.12:20880
[org.apache.zookeeper.ClientCnxn] - Session establishment complete on server /192.168.1.1:2181, sessionid = 0x16311ba0b5b40a9, negotiated timeout = 5000
[com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry] -  [DUBBO] Register: dubbo://192.168.1.12:20880/com.api.IDubboTestService
[com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry] -  [DUBBO] Subscribe: provider://192.168.1.12:20880/com.api.IDubboTestService
[com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistry] -  [DUBBO] Notify urls for subscribe url provider://192.168.1.12:20880/com.api.IDubboTestService
2018-05-18 16:36:26,914 INFO [org.I0Itec.zkclient.ZkClient] - zookeeper state changed (Disconnected)

从日志里我们可以大概知道一些粗的流程:

1.将接口所需的配置,属性等等全部准备好,ready

2.暴露本地服务,to local标志

3.暴露远程服务

4.启动Netty

5.链接Zk

6.注册接口,ZookeeperRegistry Register

7.订阅接口,ZookeeperRegistry Subscribe

关于远程暴露,首先这一篇我不想深入多少,因为远程暴露很复杂,此阶段学习需要总体理解里面的流程,对于内部的处理细节不想过多追究,一旦深入追究会陷入于此,出不来,对于调用到的代码会去大致了解它的目标。

1.远程暴露入口处(ServiceConfig):

1.1 将ref封装成Invoke(本地暴露时讲过,此篇不在关注,具体调用不难)

1.2 Invoke封装成exporter

if (! Constants.SCOPE_LOCAL.toString().equalsIgnoreCase(scope) ){
                if (logger.isInfoEnabled()) {
                    logger.info("Export dubbo service " + interfaceClass.getName() + " to url " + url);
                }
                if (registryURLs != null && registryURLs.size() > 0
                        && url.getParameter("register", true)) {
                    for (URL registryURL : registryURLs) {
                        url = url.addParameterIfAbsent("dynamic", registryURL.getParameter("dynamic"));
                        URL monitorUrl = loadMonitor(registryURL);
                        if (monitorUrl != null) {
                            url = url.addParameterAndEncoded(Constants.MONITOR_KEY, monitorUrl.toFullString());
                        }
                        if (logger.isInfoEnabled()) {
                            logger.info("Register dubbo service " + interfaceClass.getName() + " url " + url + " to registry " + registryURL);
                        }
                        Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

                        Exporter exporter = protocol.export(invoker);
                        exporters.add(exporter);
                    }
                } else {
                    Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

                    Exporter exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }
2. Invoke 转化 Exporter
Exporter exporter = protocol.export(invoker);

此处通过SPI机制实现自动调用,具体调用哪一个不在说明,此处调用的是RegistryProtocol。它主要实现了远程暴露的全部流程

2-1 Invoke 到 Exporter的转化

2-2 Netty的启动

2-3 注册服务到zk。

2-4 订阅当前注册服务。

2-5 返回新的exporter实例

    public  Exporter export(final Invoker originInvoker) throws RpcException {
        //export invoker
        final ExporterChangeableWrapper exporter = doLocalExport(originInvoker);
        //registry provider
        final Registry registry = getRegistry(originInvoker);
        final URL registedProviderUrl = getRegistedProviderUrl(originInvoker);
        registry.register(registedProviderUrl);
        // 订阅override数据
        // FIXME 提供者订阅时,会影响同一JVM即暴露服务,又引用同一服务的的场景,因为subscribed以服务名为缓存的key,导致订阅信息覆盖。
        final URL overrideSubscribeUrl = getSubscribedOverrideUrl(registedProviderUrl);
        final OverrideListener overrideSubscribeListener = new OverrideListener(overrideSubscribeUrl);
        overrideListeners.put(overrideSubscribeUrl, overrideSubscribeListener);
        registry.subscribe(overrideSubscribeUrl, overrideSubscribeListener);
        //保证每次export都返回一个新的exporter实例
        return new Exporter() {
            public Invoker getInvoker() {
                return exporter.getInvoker();
            }
            public void unexport() {
            	try {
            		exporter.unexport();
            	} catch (Throwable t) {
                	logger.warn(t.getMessage(), t);
                }
                try {
                	registry.unregister(registedProviderUrl);
                } catch (Throwable t) {
                	logger.warn(t.getMessage(), t);
                }
                try {
                	overrideListeners.remove(overrideSubscribeUrl);
                	registry.unsubscribe(overrideSubscribeUrl, overrideSubscribeListener);
                } catch (Throwable t) {
                	logger.warn(t.getMessage(), t);
                }
            }
        };
    }

这一篇我们主要去学习Invoke到Exporter的转化,也就是上面代码的第一行......毕竟暴露的过程是很复杂的。

2.1doLocalExport()

过多的细节不去深究,主要它获取了一个invokerDelegete。

    private  ExporterChangeableWrapper  doLocalExport(final Invoker originInvoker){
        String key = getCacheKey(originInvoker);
        ExporterChangeableWrapper exporter = (ExporterChangeableWrapper) bounds.get(key);
        if (exporter == null) {
            synchronized (bounds) {
                exporter = (ExporterChangeableWrapper) bounds.get(key);
                if (exporter == null) {
                    final Invoker invokerDelegete = new InvokerDelegete(originInvoker, getProviderUrl(originInvoker));
                    exporter = new ExporterChangeableWrapper((Exporter)protocol.export(invokerDelegete), originInvoker);
                    bounds.put(key, exporter);
                }
            }
        }
        return (ExporterChangeableWrapper) exporter;
    }

2.2 invokerDelegete

invokerDelegete是Invoker的委托类,存储了发布过程最原始的originInvoker,另外还存储了providerUrl,这个url就是我们要在注册中心注册的url,通过debug可以看见。

final Invoker invokerDelegete = new InvokerDelegete(originInvoker, getProviderUrl(originInvoker));
    public static class InvokerDelegete extends InvokerWrapper{
        private final Invoker invoker;
        /**
         * @param invoker 
         * @param url invoker.getUrl返回此值
         */
        public InvokerDelegete(Invoker invoker, URL url){
            super(invoker, url);
            this.invoker = invoker;
        }
        public Invoker getInvoker(){
            if (invoker instanceof InvokerDelegete){
                return ((InvokerDelegete)invoker).getInvoker();
            } else {
                return invoker;
            }
        }
    }
2.3 ExporterChangeableWrapper

这边又是SPI机制的调用,也同样不在叙述,最终调用的是DubboProtocol,需要注意的是这边是存在装饰器类的作用,也就是dubbo的IOC和AOP。包装的过程我们不去关注,只能说明分支太杂了,哎,累人啊。

exporter = new ExporterChangeableWrapper((Exporter) protocol.export(invokerDelegete), originInvoker)

我们直接关注经过层层保证之后的最终调用。

首先从url中获取需要暴露的key,本篇博客中的获取到的key是dev/com.api.IDubboTestService:2.0.0:20880。因为笔者项目中是存在version的概念的,所以多了一个dev,表示开发环境。然后去创建exporter。

    public  Exporter export(Invoker invoker) throws RpcException {
        URL url = invoker.getUrl();
        
        // export service.
        String key = serviceKey(url);
        DubboExporter exporter = new DubboExporter(invoker, key, exporterMap);
        exporterMap.put(key, exporter);
        
        //export an stub service for dispaching event
        Boolean isStubSupportEvent = url.getParameter(Constants.STUB_EVENT_KEY,Constants.DEFAULT_STUB_EVENT);
        Boolean isCallbackservice = url.getParameter(Constants.IS_CALLBACK_SERVICE, false);
        if (isStubSupportEvent && !isCallbackservice){
            String stubServiceMethods = url.getParameter(Constants.STUB_EVENT_METHODS_KEY);
            if (stubServiceMethods == null || stubServiceMethods.length() == 0 ){
                if (logger.isWarnEnabled()){
                    logger.warn(new IllegalStateException("consumer [" +url.getParameter(Constants.INTERFACE_KEY) +
                            "], has set stubproxy support event ,but no stub methods founded."));
                }
            } else {
                stubServiceMethodsMap.put(url.getServiceKey(), stubServiceMethods);
            }
        }

        openServer(url);
        
        return exporter;
    }

2.4 DubboExporter的构造。

    public DubboExporter(Invoker invoker, String key, Map> exporterMap){
        super(invoker);
        this.key = key;
        this.exporterMap = exporterMap;
    }


2.5 ExchangeServer的开启

如果serverMap中没有获取到对应的(host:port)对应的Server,则创建。

    private void openServer(URL url) {
        // find server.
        String key = url.getAddress();
        //client 也可以暴露一个只有server可以调用的服务。
        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支持reset,配合override功能使用
        		server.reset(url);
        	}
        }
    }
2.5.1 创建ExchangeServer:createServer(URL providerUrl)

关于Netty是怎么启动的,怎么绑定的,不在本文反问内,只关注一些包装,

1. 默认开启了readonly事件

2.默认加上了心跳参数

channel.readonly.sent=true&heartbeat=60000&codec=dubbo

    private ExchangeServer createServer(URL url) {
        //默认开启server关闭时发送readonly事件
        url = url.addParameterIfAbsent(Constants.CHANNEL_READONLYEVENT_SENT_KEY, Boolean.TRUE.toString());
        //默认开启heartbeat
        url = url.addParameterIfAbsent(Constants.HEARTBEAT_KEY, String.valueOf(Constants.DEFAULT_HEARTBEAT));
        String str = url.getParameter(Constants.SERVER_KEY, Constants.DEFAULT_REMOTING_SERVER);

        if (str != null && str.length() > 0 && ! ExtensionLoader.getExtensionLoader(Transporter.class).hasExtension(str))
            throw new RpcException("Unsupported server type: " + str + ", url: " + url);

        url = url.addParameter(Constants.CODEC_KEY, Version.isCompatibleVersion() ? COMPATIBLE_CODEC_NAME : DubboCodec.NAME);
        ExchangeServer server;
        try {
            server = Exchangers.bind(url, requestHandler);
        } catch (RemotingException e) {
            throw new RpcException("Fail to start server(url: " + url + ") " + e.getMessage(), e);
        }
        str = url.getParameter(Constants.CLIENT_KEY);
        if (str != null && str.length() > 0) {
            Set supportedTypes = ExtensionLoader.getExtensionLoader(Transporter.class).getSupportedExtensions();
            if (!supportedTypes.contains(str)) {
                throw new RpcException("Unsupported client type: " + str);
            }
        }
        return server;
    }

2.6 至此完成Invoker到exporter的转化,里面还夹杂着Netty的启动等等详情,此处没有关注,因为我没有Netty的经验,所以很多源码看的不是很清楚。

你可能感兴趣的:(Dubbo)