Dubbo服务启动过程(一)

之前都有读过一些Dubbo的源码,但是发现一个问题就是过一段时间后再去看的话非常陌生,而且常常不知道自己上次读到了哪个位置,而且自己以前积累的内容也没有一个记录的地方,所以从这次开始做一个记录,就当是自己学习的笔记。

指引

Dubbo的启动主要是发布服务的过程,起到核心作用的就是ServiceConfig(ServiceConfig就是我们在Dubbo的配置文件中配置的dubbo:service这些配置项对应的实体类)。服务的启动初始位置也基本是在这里,下面我们来看看具体的实现内容。
再讲基本内容前首先理清楚几个名词概念:

Invoker:Invoker的概念我们在动态代理的时候就接触过,中文的意思大概是执行者,这里其实可以理解为具体方法的执行者。其核心内容大致如下:

  1. Class<T> getInterface();
  2. Result invoke(Invocation invocation) throws RpcException;
  3. URL getUrl();

    通过以上的三个方法们就可以执行到具体的方法并且获得方法的执行结果。通过getUrl获得需要执行的方法具体实现细节,主要是获得具体的ref;其次就是组装方法的参数信息等等,这些信息在invocation里面都有封装;最后通过执行invoke方法触发具体的方法并返回结果。
    从这里可以看出Invoker是具体方法执行的最后一个守关者,获得了Invoker,就获得了具体接口的代码,然后执行代理就可以。

    Invoker仅仅是作为一个代理的门面,其不仅可以代表本地执行Duubo调用的代理,还可以充当RPC时候的代理,更是可以将自己包装成一个多个Invoker聚合而成的代理(主要是处理集群的一些策略,包括负载均衡和路由等)。 </br>

Exporter:服务暴露的过程中会将Invoker转换成Exporter(暴露者),及Exporter其实包含了Invoker,主要是用于不同层面的服务发布。
其实Dubbo 还有一些比较重要的对象,像Protocol,Exchanger等等。我认为在这里直接说明不太合适,所以等到我们用到之后再开始说明。

1. 核心的属性信息

一些基本的属性:group,version,interfaceName,interfaceClass,timeout等等。我们凡是可以在dubbo:service上配置的属性都在ServiceConfig中可以找得到对应的属性;

//dubbo对应的服务发布协议,这里可以清楚地看到Dubbo在这里使用的自己的spi机制,来保证灵活性。(至于SPI机制的具体实现,之后有机会的话会讲到,简单理解就是通过getExtensionLoader获得对应类的扩展类实现类)

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

//ProxyFactory(生成代理的工厂类,将具体的实现类包装成Invoker)的扩展类

private static final ProxyFactory proxyFactory = ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();

//urls主要存储了使用到的url(url是dubbo内部通信的信息载体,Exporter是服务暴露的主要出口对象)

private final List<URL> urls = new ArrayList<URL>();

private final List<Exporter<?>> exporters = new ArrayList<Exporter<?>>();

2.服务暴露过程

对于服务暴露来说,在ServiceConfig里面的初始方法就是export()方法了,下面我们从export方法开始来看看:

public synchronized void export() {
        if (provider != null) {
            //默认取provider的配置
            if (export == null) {
                export = provider.getExport();
            }
            if (delay == null) {
                delay = provider.getDelay();
            }
        }
        //如果export设置为false的话就直接返回
        if (export != null && ! export.booleanValue()) {
            return;
        }
        //如果设置延迟时间的话就延迟指定时间然后进行暴露
        if (delay != null && delay > 0) {
            Thread thread = new Thread(new Runnable() {
                public void run() {
                    try {
                        Thread.sleep(delay);
                    } catch (Throwable e) {
                    }
                    doExport();
                }
            });
            thread.setDaemon(true);//将暴露接口的线程设置为守护线程
            thread.setName("DelayExportServiceThread");
            thread.start();
        } else {
            doExport(); //一切暴露的核心还都是要看doExport方法。
        }
    }
    
    protected synchronized void doExport() {
        // 防止服务多次暴露
        
        // 设置默认的基本属性
        
        // 针对泛化接口做单独处理
        if (ref instanceof GenericService) {
            interfaceClass = GenericService.class;
            if (StringUtils.isEmpty(generic)) {
                generic = Boolean.TRUE.toString();
            }
        } else {
            try {//通过反射初始化接口(interfaceName是实现类的全称)
                interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
                        .getContextClassLoader());
            } catch (ClassNotFoundException e) {
                throw new IllegalStateException(e.getMessage(), e);
            }
            //检查定义的方法是否为接口中的方法
            checkInterfaceAndMethods(interfaceClass, methods);
            //检查引用不为空,并且引用必需实现接口
            checkRef();
            //如果到这一步的话说明类实现是自己定义的,所以设置generic为false
            generic = Boolean.FALSE.toString();
        }
        
        // 处理Local和Stub代理处理
        
        // 检查Application,Registry,Protocol的配置情况
        
        //将配置的属性绑定到当前对象
        appendProperties(this); 
        
        //针对Local,Stub和Mock进行校验
        
        //上面的操作主要是做一些检验和初始化的操作,没有涉及到具体的暴露服务逻辑
        
        doExportUrls();
    }
    
    private void doExportUrls() {
        //取到注册中心的URL
        List<URL> registryURLs = loadRegistries(true);
        for (ProtocolConfig protocolConfig : protocols) {
            //根据配置的通信协议将服务暴露到注册中心
            doExportUrlsFor1Protocol(protocolConfig, registryURLs);
        }
    }
    
    private void doExportUrlsFor1Protocol(ProtocolConfig protocolConfig, List<URL> registryURLs) {
        
        //  默认采用Dubbo协议
        
        //之后的部分逻辑就是想尽一切办法取到host
        
        //取到端口号(之后的部分关于端口的逻辑就是想尽一切办法取端口号)
        
        // 这个map十分重要,它的意义在于所有存储所有最终使用到的属性,我们知道一个属性例如timeout,可能在Application,provider,service中都有配置,具体以哪个为准,都是这个map处理的事情。
        Map<String, String> map = new HashMap<String, String>();
        if (anyhost) { //如果此时anyhost为true的话
            map.put(Constants.ANYHOST_KEY, "true");
        }
        // 存储简单的服务信息
        
        //将application,module,provider,protocol和service的信息设置到map里面
        //将应用配置的树勇按照层级存入map中。注意这里的层级关系,是一层层覆盖的 即关系为:ServiceConfig->PrtocolConfig->ProviderConfig->ModuleConfig->ApplicaionConfig
        
        //单独处理好method层级的参数关系
        
        //判断有没有配置通配协议
        if (ProtocolUtils.isGeneric(generic)) {
            map.put("generic", generic);
            map.put("methods", Constants.ANY_VALUE);
        } else {
            String revision = Version.getVersion(interfaceClass, version);
            if (revision != null && revision.length() > 0) {
                map.put("revision", revision);
            }
            //通过包装类将interfaceClass进行包装然后取得方法名字,对于wapper包装器就是将不同的类统一化
            //参考http://blog.csdn.net/quhongwei_zhanqiu/article/details/41597261理解Wapper
            String[] methods = Wrapper.getWrapper(interfaceClass).getMethodNames();
            if(methods.length == 0) {
                logger.warn("NO method found in service interface " + interfaceClass.getName());
                map.put("methods", Constants.ANY_VALUE);
            }
            else {
                //将所有的方法拼接成以逗号为分隔符的字符串
                map.put("methods", StringUtils.join(new HashSet<String>(Arrays.asList(methods)), ","));
            }
        }
        //处理token属性
        
        //如果配置的injvm的话就代表本地调用(本地调用还用Dubbo的话实在有点蛋疼)
        
        //所有的核心属性最后都成了URL的拼接属性,如果我们还记得map里面拼装了多少属性的话就知道这个URL内容有多丰富
        URL url = new URL(name, host, port, (contextPath == null || contextPath.length() == 0 ? "" : contextPath + "/") + path, map);
        
        
        // 下面是核心暴露过程,将不会省略源码
        String scope = url.getParameter(Constants.SCOPE_KEY);
        //配置为none不暴露
        if (! Constants.SCOPE_NONE.toString().equalsIgnoreCase(scope)) {

            //配置不是remote的情况下做本地暴露 (配置为remote,则表示只暴露远程服务)
            if (!Constants.SCOPE_REMOTE.toString().equalsIgnoreCase(scope)) {
                exportLocal(url);
            }
            //如果配置不是local则暴露为远程服务.(配置为local,则表示只暴露远程服务)
            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) {
                        //dynamic表示是否需要人工管理服务的上线下线(动态管理模式)
                        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对象(ref为接口实现类的引用)
                        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添加到需要暴露的列表中取
                    }
                    ================================================================
                } else {
                    //如果找不到注册中心的话就自己充当自己的注册中心吧
                    Invoker<?> invoker = proxyFactory.getInvoker(ref, (Class) interfaceClass, url);

                    Exporter<?> exporter = protocol.export(invoker);
                    exporters.add(exporter);
                }
            }
        }
        //多个协议就有多个url与其对应,所以要一一存储。
        this.urls.add(url);
    }

通过ServicConfig中的内容分解,我们看出来里面主要做的内容如下:

  • 检验所需参数的合法性
  • 将多层的参数(可能重复配置)最终整理出最终的结果(map),然后根据参数拼接成暴露服务需用到的url。
  • 处理generic,Stub,injvm等其他需要支持的内容,补充dubbo的功能多样性,但是都不涉及核心流程。
  • 根据对应的协议将服务进行暴露(将提供的服务推送到注册中心供服务调用者发现),默认使用Dubbo协议。

其中最核心的暴露流程在此篇并没有涉及,会在下一章揭晓具体的暴露过程(===内圈出的内容即为具体的暴露过程

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容