文档章节

dubbo源码分析(5)

李白吃白菜
 李白吃白菜
发布于 2016/04/21 10:30
字数 1332
阅读 131
收藏 3

前几篇已经分析了服务提供者和服务消费者加载以及启动,本篇将大致分析服务提供者和服务消费者是如何向注册中心注册的。

服务注册

对于服务提供方,它需要发布服务,而且由于应用系统的复杂性,服务的数量、类型也不断膨胀;对于服务消费方,它最关心如何获取到它所需要的服务,而面对复杂的应用系统,需要管理大量的服务调用。而且,对于服务提供方和服务消费方来说,他们还有可能兼具这两种角色,即既需要提供服务,有需要消费服务。通过将服务统一管理起来,可以有效地优化内部应用对服务发布/使用的流程和管理。服务注册中心可以通过特定协议来完成服务对外的统一。

dubbo提供的注册中心,查看源码有以下几种类型

服务首先暴露在服务端,然后调用Registry的register方法在注册中心注册服务,然后用户通过配置文件中配置的service的url去subscribe(订阅服务),Registry接收到订阅消息后会往url对应的的List<NotifyListener>中塞入当前NotifyListener,反之从这个list中移除listener就是取消订阅。registry会调用据consumer的订阅情况调用notify方法推送服务列表给Consumer。

以下实例我们以zookeeper注册中心为实例来分析,其他几种也比较类型,没有用到过。暂时忽略。

根据dubbo扩展机制,前篇文章中提到过,这里不再说明

文件中内容:

zookeeper=com.alibaba.dubbo.registry.zookeeper.ZookeeperRegistryFactory

首先会执行ZookeeperRegistryFactory类中的createRegistry(URL url),然后调用ZookeeperRegistry中的构造函数

代码如下:

  public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
        super(url);
        //如果provider的url是“0.0.0.0”或者在参数中带anyHost=true则抛出异常注册地址不存在
        if (url.isAnyHost()) {
    		throw new IllegalStateException("registry address == null");
    	}
    	//服务分组(默认“dubbo”) 
        String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT);
        if (! group.startsWith(Constants.PATH_SEPARATOR)) {
            group = Constants.PATH_SEPARATOR + group;
        }
        this.root = group;
        zkClient = zookeeperTransporter.connect(url);
        zkClient.addStateListener(new StateListener() {
            public void stateChanged(int state) {
            	if (state == RECONNECTED) {
	            	try {
			     recover();
			} catch (Exception e) {
			      logger.error(e.getMessage(), e);
			}
            	}
            }
        });
    }

服务提供者(provider)初始化时会调用doRegister方法向注册中心发起注册

 protected void doRegister(URL url) {
        try {
           //连接注册中心
           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);
        }
    }

服务端注册之后,那么客户端又是怎么调用的呢?

服务消费者在初始化ConsumerConfig时会调用RegistryProtocol的refer方法进一步调用RegistryDirectory的subscribe方法最终调用ZookeeperRegistry的subscribe方法向注册中心订阅服务。

RegistryProtocol中refer方法

 @SuppressWarnings("unchecked")
public <T> Invoker<T> refer(Class<T> type, URL url) throws RpcException {
      url = url.setProtocol(url.getParameter(Constants.REGISTRY_KEY, Constants.DEFAULT_REGISTRY)).removeParameter(Constants.REGISTRY_KEY);
        Registry registry = registryFactory.getRegistry(url);
        if (RegistryService.class.equals(type)) {
        	return proxyFactory.getInvoker((T) registry, type, url);
        }

        // group="a,b" or group="*"
        Map<String, String> qs = StringUtils.parseQueryString(url.getParameterAndDecoded(Constants.REFER_KEY));
        String group = qs.get(Constants.GROUP_KEY);
        if (group != null && group.length() > 0 ) {
            if ( ( Constants.COMMA_SPLIT_PATTERN.split( group ) ).length > 1
                    || "*".equals( group ) ) {
                return doRefer( getMergeableCluster(), registry, type, url );
            }
        }
        return doRefer(cluster, registry, type, url);
    }

FailBackRegistry的subscribe方法

 @Override
    public void subscribe(URL url, NotifyListener listener) {
        super.subscribe(url, listener);
        removeFailedSubscribed(url, listener);
        try {
            // 向服务器端发送订阅请求
            doSubscribe(url, listener);
        } catch (Exception e) {
            Throwable t = e;

            List<URL> urls = getCacheUrls(url);
            if (urls != null && urls.size() > 0) {
                notify(url, listener, urls);
                logger.error("Failed to subscribe " + url + ", Using cached list: " + urls + " from cache file: " + getUrl().getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/dubbo-registry-" + url.getHost() + ".cache") + ", cause: " + t.getMessage(), t);
            } else {
                // 如果开启了启动时检测,则直接抛出异常
                boolean check = getUrl().getParameter(Constants.CHECK_KEY, true)
                        && url.getParameter(Constants.CHECK_KEY, true);
                boolean skipFailback = t instanceof SkipFailbackWrapperException;
                if (check || skipFailback) {
                    if(skipFailback) {
                        t = t.getCause();
                    }
                    throw new IllegalStateException("Failed to subscribe " + url + ", cause: " + t.getMessage(), t);
                } else {
                    logger.error("Failed to subscribe " + url + ", waiting for retry, cause: " + t.getMessage(), t);
                }
            }

            // 将失败的订阅请求记录到失败列表,定时重试
            addFailedSubscribed(url, listener);
        }
    }

然后,又调用Zookeeper中的doSubscribe方法

   protected void doSubscribe(final URL url, final NotifyListener listener) {
        try {
            //如果provider的service接口配置是“*”
            if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
                //获取服务分组根路径
                String root = toRootPath();
                //获取服务的listener
                ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                if (listeners == null) {
                    //如果没有则创建一个
                    zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                    listeners = zkListeners.get(url);
                }
                ChildListener zkListener = listeners.get(listener);
                //如果没有子监听器则创建
                if (zkListener == null) {
                    listeners.putIfAbsent(listener, new ChildListener() {
                        public void childChanged(String parentPath, List<String> currentChilds) {
                            for (String child : currentChilds) {
								child = URL.decode(child);
                                if (! anyServices.contains(child)) {
                                    anyServices.add(child);
                                    subscribe(url.setPath(child).addParameters(Constants.INTERFACE_KEY, child, 
                                            Constants.CHECK_KEY, String.valueOf(false)), listener);
                                }
                            }
                        }
                    });
                    zkListener = listeners.get(listener);
                }
                //向服务器订阅服务,注册中心会调用NotifyListener的notify函数返回服务列表
                zkClient.create(root, false);
                //获取服务地址列表
                List<String> services = zkClient.addChildListener(root, zkListener);
                //如果存在服务 
                if (services != null && services.size() > 0) {
                    for (String service : services) {
			service = URL.decode(service);
			anyServices.add(service);
			//如果serviceInterface是“*”则从分组根路径遍历service并订阅所有服务  
                        subscribe(url.setPath(service).addParameters(Constants.INTERFACE_KEY, service, 
                                Constants.CHECK_KEY, String.valueOf(false)), listener);
                    }
                }
            } else {
            //如果serviceInterface不是“*”则创建Zookeeper客户端索取服务列表,并通知(notify)消费者(consumer)这些服务可以用了 
                List<URL> urls = new ArrayList<URL>();
                //获取类似于http://xxx.xxx.xxx.xxx/context/com.service.xxxService/consumer的地址
                for (String path : toCategoriesPath(url)) {
                    ConcurrentMap<NotifyListener, ChildListener> listeners = zkListeners.get(url);
                    if (listeners == null) {
                        zkListeners.putIfAbsent(url, new ConcurrentHashMap<NotifyListener, ChildListener>());
                        listeners = zkListeners.get(url);
                    }
                    //获取ChildListener
                    ChildListener zkListener = listeners.get(listener);
                    if (zkListener == null) {
                        listeners.putIfAbsent(listener, new ChildListener() {
                            public void childChanged(String parentPath, List<String> currentChilds) {
                            	ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                            }
                        });
                        zkListener = listeners.get(listener);
                    }
                    //创建Zookeeper客户端
                    zkClient.create(path, false);
                    List<String> children = zkClient.addChildListener(path, zkListener);
                    if (children != null) {
                    	urls.addAll(toUrlsWithEmpty(url, path, children));
                    }
                }
                //提醒消费者  
                notify(url, listener, urls);
            }
        } catch (Throwable e) {
            throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
        }
    }

 至此,Dubbo的源码分析结束。如有不对的地方,敬请指教!

© 著作权归作者所有

共有 人打赏支持
李白吃白菜
粉丝 12
博文 33
码字总数 18863
作品 0
朝阳
Dubbo 实现原理与源码解析系列 —— 精品合集

摘要: 原创出处 http://www.iocoder.cn/Dubbo/good-collection/ 「芋道源码」欢迎转载,保留摘要,谢谢! 1.【芋艿】精尽 Dubbo 原理与源码专栏 2.【老徐】RPC 专栏 3.【肥朝】Dubbo 源码解析...

芋道源码掘金Java群217878901
06/23
0
0
Dubbo源码分析-Dubbo是如何整合spring-framework的

这篇文章是Dubbo源码分析的开端,与其说这篇文章是Dubbo源码分析,不如是spring源码分析,因为大部分都是在分析spring如何解析xml配置文件的,为了与后面的Dubbo源码分析保持一致,姑且这样命...

农码人生
07/23
0
0
从线程池理论浅析为什么要看源码

前言 很多时候,我都想向大家传输一个思想,那就是只有懂了原理,才能随心随心所欲写代码.而看源码,又是了解原理的一个非常重要的途径. 然而,肥朝之前的文章,大致分为三类 源码解析,穿插怎么看源...

肥朝
10/13
0
0
dubbo中的那些“坑"(3)-netty4-rpc网络接口中的高并发的bug

在几个月前改造dubbo时,netty4已经稳定很久了,一时手痒,按照netty3-rpc的源码克隆了一套netty4,在修正了大量的包、类型不同之后,基本保持了netty3的风格,并发量小或者数据包很小时,一...

阿阮
2014/12/02
0
10
dubbo源码解析-router

前言 估算了一下,里面涉及的东西还是比较多的.比如谈到框架的时候,设计模式都是一个老生常谈的话题,再比如我们开发中我们不常用的一些概念,、,以及和zookeeper相关的一些知识,比如的使用,这些...

肥朝
2017/09/30
0
0

没有更多内容

加载失败,请刷新页面

加载更多

中国移动蔡谦:5G传输准备就绪

目前5G已成业界热议话题,在即将到来的万物互联时代,5G是非常关键的技术。且5G相比4G,业务场景多种多样,对5G承载网带来巨大挑战。5G传输,承载先行并不仅仅是一个口号。当前5G承载网的部署...

linux-tao
21分钟前
4
0
维护“修理权”,苹果使用专有软件工具来修复MacBook Pro和iMac Pro

根据上月发给苹果授权服务提供商的一份文件,苹果公司正在使用新的专有软件诊断工具来修复MacBook Pros和iMac Pros,如果不用专有软件工具来修复关键部件,将会导致“系统失效和修复不完整”...

linuxCool
50分钟前
2
0
cacti监控安装

cacti是用PHP实现的一个软件,它用snmp服务获取数据,然后用rrdtool存储和更新数据,并生成图表展示。比较适合用于交换机、路由器的网络监控,插件众多,可图示化显示网络状况。 cacti官方推...

hiwill
今天
4
0
shell特殊符号、cut、sort、uniq、wc、tee、tr、split命令

10月15日任务 8.10 shell特殊符号cut命令 8.11 sort_wc_uniq命令 8.12 tee_tr_split命令 8.13 shell特殊符号下 cut 命令 cut作用:截取字符串 用法如下:cat /etc/passwd |head -2 |cut -d ...

hhpuppy
今天
4
0
Springboot实现filter拦截token验证和跨域

背景 web验证授权合法的一般分为下面几种 1使用session作为验证合法用户访问的验证方式 使用自己实现的token 使用OCA标准 在使用API接口授权验证时,token是自定义的方式实现起来不需要引入其...

funnymin
今天
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部