dubbo容器-注册中心

1,Dubbo服务分为注册中心,服务提供者,消费者三个模块,而三个模块的启动都是分开的。
2,注册中心的 注册过程。
通常,注册中心的配置基本如下,具体源码在dubbo-registrt-simple包下面。

<beans xmlns = "http://www.springframework.org/schema/beans"
    xmlns :xsi = "http://www.w3.org/2001/XMLSchema-instance"
    xmlns :dubbo = "http://code.alibabatech.com/schema/dubbo"
    xsi :schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd"
>
     <bean  class = "org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" >
         <property name = "systemPropertiesModeName" value = "SYSTEM_PROPERTIES_MODE_OVERRIDE"  />
         <property name = "location" value = "classpath:dubbo.properties"  />
     bean >
    
     <dubbo :application name = "${dubbo.application.name}" owner = "${dubbo.application.owner}"  />
    
     <dubbo :protocol name = "dubbo" port = "${dubbo.protocol.port}" heartbeat = "180000"  />
    
     <dubbo :service id = "registryServiceConfig"  interface = "com.alibaba.dubbo.registry.RegistryService" ref = "registryService" registry = "N/A" ondisconnect = "disconnect" callbacks = "1000" >
         <dubbo :method name = "subscribe" ><dubbo :argument index = "1" callback = "true"  />dubbo :method >
         <dubbo :method name = "unsubscribe" ><dubbo :argument index = "1" callback = "false"  />dubbo :method >
     dubbo :service >
    
     <bean id = "registryService"  class = "com.alibaba.dubbo.registry.simple.SimpleRegistryService"  />
beans >

我们看到,通过此配置文件,dubbo制定了服务名,协议,以及Service,这点配置可以看出,registryService也是作为一个dubbo服务对外暴漏的。
我们查看对应SimpleRegistryService的源码,该类主要提供如下几个方法:

1,  public boolean isAvailable ( )
2public List <URL > lookup (URL url ) 
3,  public void register (URL url )
4,  public void unregister (URL url )
5,  public void subscribe (URL url , NotifyListener listener )
6public void unsubscribe (URL url , NotifyListener listener )

具体解释,我们查看RegistryService接口。

public  interface RegistryService  {
     /**
     * 注册数据,比如:提供者地址,消费者地址,路由规则,覆盖规则,等数据。
     * 
     * 注册需处理契约:

     * 1. 当URL设置了check=false时,注册失败后不报错,在后台定时重试,否则抛出异常。

     * 2. 当URL设置了dynamic=false参数,则需持久存储,否则,当注册者出现断电等情况异常退出时,需自动删除。

     * 3. 当URL设置了category=routers时,表示分类存储,缺省类别为providers,可按分类部分通知数据。

     * 4. 当注册中心重启,网络抖动,不能丢失数据,包括断线自动删除数据。

     * 5. 允许URI相同但参数不同的URL并存,不能覆盖。

     * 
     * @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     */

    void register (URL url ) ;
     /**
     * 取消注册.
     * 
     * 取消注册需处理契约:

     * 1. 如果是dynamic=false的持久存储数据,找不到注册数据,则抛IllegalStateException,否则忽略。

     * 2. 按全URL匹配取消注册。

     * 
     * @param url 注册信息,不允许为空,如:dubbo://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     */

    void unregister (URL url ) ;
     /**
     * 订阅符合条件的已注册数据,当有注册数据变更时自动推送.
     * 
     * 订阅需处理契约:

     * 1. 当URL设置了check=false时,订阅失败后不报错,在后台定时重试。

     * 2. 当URL设置了category=routers,只通知指定分类的数据,多个分类用逗号分隔,并允许星号通配,表示订阅所有分类数据。

     * 3. 允许以interface,group,version,classifier作为条件查询,如:interface=com.alibaba.foo.BarService&version=1.0.0

     * 4. 并且查询条件允许星号通配,订阅所有接口的所有分组的所有版本,或:interface=*&group=*&version=*&classifier=*

     * 5. 当注册中心重启,网络抖动,需自动恢复订阅请求。

     * 6. 允许URI相同但参数不同的URL并存,不能覆盖。

     * 7. 必须阻塞订阅过程,等第一次通知完后再返回。

     * 
     * @param url 订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     * @param listener 变更事件监听器,不允许为空
     */

    void subscribe (URL url , NotifyListener listener ) ;
     /**
     * 取消订阅.
     * 
     * 取消订阅需处理契约:

     * 1. 如果没有订阅,直接忽略。

     * 2. 按全URL匹配取消订阅。

     * 
     * @param url 订阅条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     * @param listener 变更事件监听器,不允许为空
     */

    void unsubscribe (URL url , NotifyListener listener ) ;
     /**
     * 查询符合条件的已注册数据,与订阅的推模式相对应,这里为拉模式,只返回一次结果。
     * 
     * @see com.alibaba.dubbo.registry.NotifyListener#notify(List)
     * @param url 查询条件,不允许为空,如:consumer://10.20.153.10/com.alibaba.foo.BarService?version=1.0.0&application=kylin
     * @return 已注册信息列表,可能为空,含义同{@link com.alibaba.dubbo.registry.NotifyListener#notify(List)}的参数。
     */

    List <URL > lookup (URL url ) ;
}

通过以上方法实现对客户端对Dubbo服务的注册等操作。我们查看对应的注册操作:

public void register (URL url )  {
        String client  = RpcContext .getContext ( ) .getRemoteAddressString ( ) ;
        Set <URL > urls  = remoteRegistered .get (client ) ;
         if  (urls  ==  null )  {
            remoteRegistered .putIfAbsent (client ,  new ConcurrentHashSet <URL > ( ) ) ;
            urls  = remoteRegistered .get (client ) ;
         }
        urls .add (url ) ;
        super .register (url ) ;
        registered (url ) ;
     }
     protected void registered (URL url )  {
         for  (Map .Entry <URL , Set <NotifyListener >> entry  : getSubscribed ( ) .entrySet ( ) )  {
            URL  key  = entry .getKey ( ) ;
             if  (UrlUtils .isMatch ( key , url ) )  {
                List <URL >  list  = lookup ( key ) ;
                 for  (NotifyListener listener  : entry .getValue ( ) )  {
                    listener .notify ( list ) ;
                 }
             }
         }
     }

具体的注册操作在registered方法完成,同时发布NotifyListener监听事件。

public  interface NotifyListener  {
     /**
     * 当收到服务变更通知时触发。
     * 
     * 通知需处理契约:

     * 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。

     * 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。

     * 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。

     * 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。

     * 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。

     * 
     * @param urls 已注册信息列表,总不为空,含义同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。
     */

    void notify (List <URL > urls ) ;
}

3,Zookeeper注册中心
一般情况下,我们在生产环境中,通过zookeeper来实现对Dubbo的注册,所以我们具体查看下ZookeeperRegistry的实现。

public ZookeeperRegistry (URL url , ZookeeperTransporter zookeeperTransporter )  {
        super (url ) ;
         if  (url .isAnyHost ( ) )  {
             throw  new IllegalStateException ( "registry address == null" ) ;
         }
        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 ) ;
                     }
                 }
             }
         } ) ;
     }

ZookeeperRegistry与SimpleRegistryService类一样,继承了AbstractRegistry类。ZookeeperRegistry的注册方法,在其父类中实现。

@Override
     public void register (URL url )  {
        super .register (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 ) ;
         }
     }

而且具体实现,doRegister是在ZookeeperRegistry中实现的,此处实现了模板方法。

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 ) ;
         }
     }

4,Dubbo监听器
通过以上代码我们可以看到,Dubbo的实现,离不开其监听器notifyListener,在注册服务等代码的具体实现都离不开对监听器的依赖。我们再次看监听器的注释。

   /**
     * 当收到服务变更通知时触发。
     * 
     * 通知需处理契约:

     * 1. 总是以服务接口和数据类型为维度全量通知,即不会通知一个服务的同类型的部分数据,用户不需要对比上一次通知结果。

     * 2. 订阅时的第一次通知,必须是一个服务的所有类型数据的全量通知。

     * 3. 中途变更时,允许不同类型的数据分开通知,比如:providers, consumers, routers, overrides,允许只通知其中一种类型,但该类型的数据必须是全量的,不是增量的。

     * 4. 如果一种类型的数据为空,需通知一个empty协议并带category参数的标识性URL数据。

     * 5. 通知者(即注册中心实现)需保证通知的顺序,比如:单线程推送,队列串行化,带版本对比。

     * 
     * @param urls 已注册信息列表,总不为空,含义同{@link com.alibaba.dubbo.registry.RegistryService#lookup(URL)}的返回值。
     */

    void notify (List <URL > urls ) ;

我们查看ZookeeperRegistry中的doSubscribe方法,可以看到具体的监听器的实现。

protected void doSubscribe (final URL url , final NotifyListener listener )  {
        try  {
             if  (Constants .ANY_VALUE .equals (url .getServiceInterface ( ) ) )  {
                String root  = toRootPath ( ) ;
                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 ) ;
                 }
                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 ) ;
                        subscribe (url .setPath (service ) .addParameters (Constants .INTERFACE_KEY , service , 
                                Constants .CHECK_KEY , String .valueOf ( false ) ) , listener ) ;
                     }
                 }
             }

该监听器实现了对服务的注册。
而具体的取消注册方法,是移除了对应的监听器。

  protected void doUnsubscribe (URL url , NotifyListener listener )  {
        ConcurrentMap <NotifyListener , ChildListener > listeners  = zkListeners .get (url ) ;
         if  (listeners  !=  null )  {
            ChildListener zkListener  = listeners .get (listener ) ;
             if  (zkListener  !=  null )  {
                zkClient .removeChildListener (toUrlPath (url ) , zkListener ) ;
             }
         }
     }

具体是现在AbstractZookeeperClient:

     public void removeChildListener (String path , ChildListener listener )  {
        ConcurrentMap <ChildListener , TargetChildListener > listeners  = childListeners .get (path ) ;
         if  (listeners  !=  null )  {
            TargetChildListener targetListener  = listeners .remove (listener ) ;
             if  (targetListener  !=  null )  {
                removeTargetChildListener (path , targetListener ) ;
             }
         }
     }

具体的监听器,缓存在AbstrackZookeeperClient:

private final ConcurrentMap <String , ConcurrentMap <ChildListener , TargetChildListener >> childListeners  =  new ConcurrentHashMap <String , ConcurrentMap <ChildListener , TargetChildListener >> ( ) ;

5,注册中心初始化
我们知道Spring的Bean都是通过一系列BeanFactory来完成的初始化的,Dubbo也一样,注册中心通过DubboRegistryFactory来完成Bean的初始化。其基类是RegistryFactory。

public  interface RegistryFactory  {
     /**
     * 连接注册中心.
     * 
     * 连接注册中心需处理契约:

     * 1. 当设置check=false时表示不检查连接,否则在连接不上时抛出异常。

     * 2. 支持URL上的username:password权限认证。

     * 3. 支持backup=10.20.153.10备选注册中心集群地址。

     * 4. 支持file=registry.cache本地磁盘文件缓存。

     * 5. 支持timeout=1000请求超时设置。

     * 6. 支持session=60000会话超时或过期设置。

     * 
     * @param url 注册中心地址,不允许为空
     * @return 注册中心引用,总不返回空
     */

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

AbstractRegistryFactory实现了大概的实现。

  public Registry getRegistry (URL url )  {
        url  = url .setPath (RegistryService . class .getName ( ) )
                 .addParameter (Constants .INTERFACE_KEY , RegistryService . class .getName ( ) )
                 .removeParameters (Constants .EXPORT_KEY , Constants .REFER_KEY ) ;
        String  key  = url .toServiceString ( ) ;
         // 锁定注册中心获取过程,保证注册中心单一实例
        LOCK .lock ( ) ;
        try  {
            Registry registry  = REGISTRIES .get ( key ) ;
             if  (registry  !=  null )  {
                 return registry ;
             }
            registry  = createRegistry (url ) ;
             if  (registry  ==  null )  {
                 throw  new IllegalStateException ( "Can not create registry "  + url ) ;
             }
            REGISTRIES .put ( key , registry ) ;
             return registry ;
         } finally  {
             // 释放锁
            LOCK .unlock ( ) ;
         }
     }

我们看到注册中心被锁定为单例模式,并且维护在该类的缓存中。
而具体的实现方式,由DubboRegistryFactory来实现。

  public Registry createRegistry (URL url )  {
        url  = getRegistryURL (url ) ;
        List <URL > urls  =  new ArrayList <URL > ( ) ;
        urls .add (url .removeParameter (Constants .BACKUP_KEY ) ) ;
        String backup  = url .getParameter (Constants .BACKUP_KEY ) ;
         if  (backup  !=  null  && backup .length ( )  >  0 )  {
            String [ ] addresses  = Constants .COMMA_SPLIT_PATTERN . split (backup ) ;
             for  (String address  : addresses )  {
                urls .add (url .setAddress (address ) ) ;
             }
         }
        RegistryDirectory <RegistryService > directory  =  new RegistryDirectory <RegistryService > (RegistryService . class , url .addParameter (Constants .INTERFACE_KEY , RegistryService . class .getName ( ) ) .addParameterAndEncoded (Constants .REFER_KEY , url .toParameterString ( ) ) ) ;
        Invoker <RegistryService > registryInvoker  = cluster . join (directory ) ;
        RegistryService registryService  = proxyFactory .getProxy (registryInvoker ) ;
        DubboRegistry registry  =  new DubboRegistry (registryInvoker , registryService ) ;
        directory .setRegistry (registry ) ;
        directory .setProtocol (protocol ) ;
        directory .notify (urls ) ;
        directory .subscribe ( new URL (Constants .CONSUMER_PROTOCOL , NetUtils .getLocalHost ( ) ,  0 , RegistryService . class .getName ( ) , url .getParameters ( ) ) ) ;
         return registry ;
     }

注册过程相对简单,通过URL,获取到对应的Registry数据,以及Invoker,生成Registry对象。Registry实现了RegistryService接口,即我们之前的ZookeeperRegistryService的父类。
而此处的DubboRegistry的构方法中,

  public DubboRegistry (Invoker <RegistryService > registryInvoker , RegistryService registryService )  {
        super (registryInvoker .getUrl ( ) ) ;
        this .registryInvoker  = registryInvoker ;
        this .registryService  = registryService ;
         // 启动重连定时器
        int reconnectPeriod  = registryInvoker .getUrl ( ) .getParameter (Constants .REGISTRY_RECONNECT_PERIOD_KEY , RECONNECT_PERIOD_DEFAULT ) ;
        reconnectFuture  = scheduledExecutorService .scheduleWithFixedDelay ( new Runnable ( )  {
             public void run ( )  {
                 // 检测并连接注册中心
                try  {
                    connect ( ) ;
                 } catch  (Throwable t )  {  // 防御性容错
                    logger .error ( "Unexpected error occur at reconnect, cause: "  + t .getMessage ( ) , t ) ;
                 }
             }
         } , reconnectPeriod , reconnectPeriod , TimeUnit .MILLISECONDS ) ;
     }

实现了对注册中心的注册。

你可能感兴趣的:(Dubbo)