Dubbo服务启动过程(二)

在上一章中我只是简单论述了一下服务的启动过程的前置内容,主要是做一些参数的检查和URL的封装,然后在文章末尾引出了本章介绍的重点内容,下面我们真正来看一下服务的暴露过程都做了哪些操作。

其实服务暴露的过程只要是完成了两件事情:

  • 将服务注册到注册中心,供调用方发现
  • 监听指定的端口,等待服务调用方进行调用

将服务注册到注册中心,供调用方发现

这个过程如何产生,还是困扰了我一天的。最初我以为直接通过Protocol获得Extension的时候会将RegistryProtocol作为Wrapper进行包装(原因是从一个阿里云的博客上看到上面这么写到),后来因为先入为主的原因导致我也一直被文章的思路限制,但是我自己看了相关的加载逻辑并且通过单元测试去验证这个,发现不是这样子的。RegistryProtocol虽然作为Protocol的实现类,但是却不是他的包装类。所以并不是在加载包装类的时候加载到的。这里的说法其实是有问题的,最后通过相关的学习发现其实是这么回事:

其实在ServiceConfig中有这么一段代码:

private static final Protocol protocol = ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();

然后在export()最后面是这么调用的:

Invoker invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, registryURL.addParameterAndEncoded(Constants.EXPORT_KEY, url.toFullString()));

//将invoker转化为exporter对象
Exporter exporter = protocol.export(invoker);

exporters.add(exporter); //将exporter添加到需要暴露的列表中取

那么这里的protocol究竟是哪个Protocol的实现呢:

这时候的URL对象大概是这样子:

==registry==://127.0.0.1:2181/com.alibaba.dubbo.registry.RegistryService?application=demo-provider&dubbo=2.0.0&==export=dubbo%3A%2F%2F192.168.153.1%3A20880%2Fcom.alibaba.dubbo.demo.bid.BidService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.bid.BidService%26methods%3DthrowNPE%2Cbid%26optimizer%3Dcom.alibaba.dubbo.demo.SerializationOptimizerImpl%26organization%3Ddubbox%26owner%3Dprogrammer%26pid%3D3872%26serialization%3Dkryo%26side%3Dprovider%26timestamp%3D1422241023451==&organization=dubbox&owner=programmer&pid=3872®istry=zookeeper×tamp=1422240274186

所以说这里的protocol应该是RegistryProtocol
下面我们看那RegistryProtocol的export具体操作:

public  Exporter export(final Invoker originInvoker) throws RpcException {
        //这一步进行真正的暴露操作,下面都是注册服务的代码
        final ExporterChangeableWrapper exporter = doLocalExport(originInvoker);
        //这里就是根据invoker持有的URL获得Register对象,鉴于使用的都ZookeeperRegister,所以这里就以ZookeeperRegister作为研究对象
        final Registry registry = getRegistry(originInvoker);
        //获得URL中黄色的部分,然后去除部分不需要的参数
        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);
                }
            }
        };
    }
    
    

先看一下暴露服务的时候都做了什么:

//这个操作本质上就是将上述的黄色部分对应的值与exporter作为一个映射存储下来
private  ExporterChangeableWrapper  doLocalExport(final Invoker originInvoker){
        //获得上文中黄色部分的URL
        String key = getCacheKey(originInvoker);
        //exporter代理,建立返回的exporter与protocol export出的exporter的对应关系,在override时可以进行关系修改.本质上就是维护exporter和invoker映射关系的类
        ExporterChangeableWrapper exporter = (ExporterChangeableWrapper) bounds.get(key);
        if (exporter == null) {
            synchronized (bounds) {
                exporter = (ExporterChangeableWrapper) bounds.get(key);
                if (exporter == null) {
                    //InvokerDelegete简单的Invoker代理
                    final Invoker invokerDelegete = new InvokerDelegete(originInvoker, getProviderUrl(originInvoker));
                    //将原先的url替换为上面黄色部分,重新暴露
                    exporter = new ExporterChangeableWrapper((Exporter)protocol.export(invokerDelegete), originInvoker);
                    bounds.put(key, exporter);
                }
            }
        }
        return (ExporterChangeableWrapper) exporter;
    }
    
    private String getCacheKey(final Invoker originInvoker){
        URL providerUrl = getProviderUrl(originInvoker);
        String key = providerUrl.removeParameters("dynamic", "enabled").toFullString();
        return key;
    }
    
    /**
     * 通过invoker的url 获取 providerUrl的地址
     * @param origininvoker
     * @return
     
     首先说明一点:仔细看上面的URL的话会发现为什么会有%26和%3D这种编码符号,看起来也不利于阅读。
     %26 = &,%3D = ‘=’,经过URL编码的含义就在于将很多key,value的件值对作为一个整体封装成一个大的value,含义就相当于URL里面可以嵌套URL。下面这段代码就是取出export对应的URL,这个URL嵌套在原始的invoker持有的URL中,是用来进一步暴露服务需要的URL
     */
    private URL getProviderUrl(final Invoker origininvoker){
        String export = origininvoker.getUrl().getParameterAndDecoded(Constants.EXPORT_KEY);
        if (export == null || export.length() == 0) {
            throw new IllegalArgumentException("The registry export url is null! registry: " + origininvoker.getUrl());
        }
        
        URL providerUrl = URL.valueOf(export);
        return providerUrl;
    }

其次的话就是将服务注册到注册中心上去,这里的核心是:
registry.register(registedProviderUrl);
这里以ZookeeperRegistry为准来介绍:

    因为ZookeeperRegistry还是继承了FailBackRegistry的,所以入口在这里面:
    @Override
    public void register(URL url) {
        //在内部的已经注册的服务列表加上要注册的url
        super.register(url);
        //首先尝试从两个失败的列表里面移除这个url,如果该次重试的时候依然失败的话就再次从这里面移出来
        failedRegistered.remove(url);
        failedUnregistered.remove(url);
        try {
            // 向服务器端发送注册请求
            doRegister(url);
        } catch (Exception e) {
            Throwable t = e;

            // 如果开启了启动时检测,则直接抛出异常
            boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                    && url.getParameter(Constants.CHECK_KEY, true)
                    && ! Constants.CONSUMER_PROTOCOL.equals(url.getProtocol());
            boolean skipFailback = t instanceof SkipFailbackWrapperException;
            if (check || skipFailback) {
                if(skipFailback) {
                    t = t.getCause();
                }
                throw new IllegalStateException("Failed to register " + url + " to registry " + getUrl().getAddress() + ", cause: " + t.getMessage(), t);
            } else {
                logger.error("Failed to register " + url + ", waiting for retry, cause: " + t.getMessage(), t);
            }

            // 将失败的注册请求记录到失败列表,定时重试
            failedRegistered.add(url);
        }
    }
    
    
    //失败后会将失败的url记录到失败列表中,然后定时重试,下面是重试的逻辑,是无限次的重试噢。
    protected void retry() {
        //看看有没有注册失败的URL
        if (! failedRegistered.isEmpty()) {
            Set failed = new HashSet(failedRegistered);
            if (failed.size() > 0) {
                if (logger.isInfoEnabled()) {
                    logger.info("Retry register " + failed);
                }
                try {
                    for (URL url : failed) {
                        try {
                            doRegister(url);
                            //不报错就代表注册成功,然后从失败的列表中移除改url即可 failedRegistered.remove(url);//在这里就移除那些已经尝试注册成功的url
                        } catch (Throwable t) { // 忽略所有异常,等待下次重试
                            logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                        }
                    }
                } catch (Throwable t) { // 忽略所有异常,等待下次重试
                    logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                }
            }
        }
    }
    
    然后我们看一下ZooKeeperRegistry中的注册方法:
    protected void doRegister(URL url) {
        try {
            //这里就要理解一下这个toUrlPath的返回结果了,我本地测试了一下这个方法的返回结果是这样的:
            // /dubbo/com.alibaba.dubbo.demo.bid.BidService/providers/dubbo%3A%2F%2F192.168.153.1%3A20880%2Fcom.alibaba.dubbo.demo.bid.BidService%3Fanyhost%3Dtrue%26application%3Ddemo-provider%26dubbo%3D2.0.0%26generic%3Dfalse%26interface%3Dcom.alibaba.dubbo.demo.bid.BidService%26methods%3DthrowNPE%2Cbid%26optimizer%3Dcom.alibaba.dubbo.demo.SerializationOptimizerImpl%26organization%3Ddubbox%26owner%3Dprogrammer%26pid%3D3872%26serialization%3Dkryo%26side%3Dprovider%26timestamp%3D1422241023451
            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);
        }
    }
    
Dubbo服务启动过程(二)_第1张图片
image

从这个图中对比一下就能清楚的看到具体注册到zookeeper中的结构,图中的URL层面就是实际上provider端的参数汇总URL。
因为服务端就把整个URL都注册到注册中心中去,所以服务调用者在调用的时候就能方便的拿到服务端配置的参数,后面的话再次进行覆盖或者变更也十分方便。所以注册中心不仅仅是发现服务的一个途径,更是参数沟通的一个渠道。

其实在服务的注册过程中有更多的细节值得探究,比如服务的变更通知,本地存储的缓存文件等等。但这些我认为这些都不影响对于主流程的分析,在第一遍读源码的时候还是以主流程为主,以后如果需要了在进行二次分析。

服务启动过程的第一步服务注册,基本在这里就是这个内容了。之后会讲解如何监听端口进行下一步的。

你可能感兴趣的:(Dubbo服务启动过程(二))