`
manzhizhen
  • 浏览: 288641 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Dubbo源代码实现三:注册中心Registry

阅读更多

 

<!--StartFragment-->         我们知道,对于服务治理框架来说,服务通信(RPC)和服务管理两部分必不可少,而服务管理又分为服务注册、服务发现和服务人工介入,我们来看看Dubbo框架的结构图(来源网络):<!--EndFragment-->


图中可以看出,服务提供者Provider往服务注册中心Registry注册服务,而的消费者Consumer从服务注册中心订阅它需要的服务,而不是全部服务,当有新的Provider出现,或者现有Provider宕机,注册中心Registry都应该能尽早发现,并将新的Provider列表推送给对应的Consumer,有了这样的机制,Dubbo才能做到Failover,而Failover的时效性,由注册中心Registry的实现决定。

         Dubbo线上支持三种注册中心:自带的Simple Registry、Redis和Zookeeper,当然,最常用的还是Zookeeper作为注册中心,因为太多分布式的中间件需要依赖Zookeeper作为协作者。那么怎么才能让Dubbo知道我们使用哪个实现作为注册中心呢?我们只需要在dubbo的xml配置文件中配置dubbo:registry节点即可

<dubbo:registry id="dubboRegistry" protocol="zookeeper" address="${dubbo.registry.address}" />

没错,protocol就指明了注册中心的实现。

        要想做到服务的可靠,避免分布式系统的单点问题,除了Provider可以集群部署外,注册中心的弱依赖也是必须的,注册中心的宕机,不会影响现有服务的运行,只是不能注册新的服务和进行服务发现,Failover还是可以做的,比如Consumer可以通过服务调用来简单判断当前的Provier是否可用。如果某个Consumer宕机了,当它重启后,发现注册中心也挂了,那咋办?为了防止这种问题出现,Dubbo的Consumer会将自己需要的Provider列表在本地保存一份,当然,里面也包括自己暴露的服务信息(即自己也作为Provider),我们可以看看AbstractRegistry中的实现:

 

public AbstractRegistry(URL url) {

    setUrl(url);

    // 启动文件保存定时器

    syncSaveFile = url.getParameter(Constants.REGISTRY_FILESAVE_SYNC_KEY, false);

    String filename = url.getParameter(Constants.FILE_KEY, System.getProperty("user.home") + "/.dubbo/dubbo-registry-" + url.getHost() + ".cache");

    File file = null;

    if (ConfigUtils.isNotEmpty(filename)) {

        file = new File(filename);

        if(! file.exists() && file.getParentFile() != null && ! file.getParentFile().exists()){

            if(! file.getParentFile().mkdirs()){

                throw new IllegalArgumentException("Invalid registry store file " + file + ", cause: Failed to create directory " + file.getParentFile() + "!");

            }

        }

    }

    this.file = file;

    loadProperties();

    notify(url.getBackupUrls());

}

 

注意看黄底代码部分,如果没有在属性文件中配置fileConstants.FILE_KEY,就将在用户的当前用户目录/.dubbo/目录下新建一个dubbo-registry开头的保存所有URL信息的Cache文件,通常来说一个应用可以在多个注册中心暴露自己的服务,也可以从多个注册中心订阅不同的服务,所以这里的Cache文件名加入了注册中心的主机名。还有一个lock文件,用来防止不同的JVM进程同时修改Cache文件,注意,这里只是防止,所以意味着同一目录的Cache文件可以由多个JVM进程共享,当多个JVM进程恰巧同时修改Cache文件时,将会有一个进程获取lock文件的锁失败,见保存Cache的过程的AbstractRegistry#doSaveProperties方法的片段:

FileChannel channel = raf.getChannel();

try {

    FileLock lock = channel.tryLock();

   if (lock == null) {

        throw new IOException("Can not lock the registry cache file " + file.getAbsolutePath() + ", ignore and retry later, maybe multi java process use the file, please config: dubbo.registry.file=xxx.properties");

    }

这将导致某个URL更新到Cache文件失败,但Dubbo提供了重试机制,以保证Cache文件中信息能和内存中的信息最终一致。但不要认为Cache文件中的ProviderConsumer列表是和当前运行的服务一致,因为当一个服务部署多个应用时,Cache文件被多个JVM同时写的概率还是很大的,所以这时总有JVM进程度lock文件获取锁失败(即FileChannel#tryLock()失败),这时它只能乖乖稍后重试了。写Cache的方式也很简单粗暴,即先读取整个Cache文件,然后再往其写入当前处理的URL,然后再全量写入,可见,如果某个服务(URL)已经不再使用,它有可能一直存在于Cache文件中。

         保存Cache还分为同步保存和异步保存,我们知道内存中服务列表的更新相对于服务调用来说肯定是异步的,但为啥保存Cache文件还要分同步和异步呢?因为在Dubbo中,服务(或者叫URL)是一个个来更新的,也就是说,当服务比较多时,使用异步保存Cache文件能使应用启动和服务更新速度更快,而整个更新过程是由AbstractRegistry#notify来触发的。

      我们再来看看如果选择使用Zookeeper用来做Dubbo的注册中心,那么Provider和Consumer的数据在上是怎么存储的。Dubbo在ZK的所有数据都在/dubbo节点下,如下图:

/dubbo

        /com.manzhizhen.user.Service1

                 /consumers

                 /routers

                 /providers

                 /configurators

        /com.manzhizhen.user.Service2

                 /consumers

                 /routers

                 /providers

                 /configurators

        /com.manzhizhen.user.Service3

                 /consumers

                 /routers

                 /providers

                 /configurators

我们可以看到,每个服务(URL)在dubbo节点下都会有一个对应的ZK持久化节点,而每个服务节点下面都会有四个持久化子节点,代表消费者(consumer)、路由(routers)、提供者(providers)和配置(configurators),consumer和providers节点好理解,放的就是该URL下消费者和提供者的URL全部信息,而routers和configurators主要用于控制路由规则,这在正常情况下是用的比较少的,所以这两个节点数据通常为空。

      现在我们说说和服务注册相关的两个异常信息, 先给出Dubbo的集群容错图:


一个常见的异常信息是"Forbid consumer XXXXX access service XXXXX from registry XXXXX use dubbo version 2.5.3, Please check registry access list (whitelist/blacklist).",当我们需要调用服务时,会先从本地的注册目录也就是RegistryDirectory来拿取调用(Invoker)列表,见上图Directory节点,RegistryDirectory#doList代码片段如下:

 

public List<Invoker<T>> doList(Invocation invocation) {

    if (forbidden) {

        throw new RpcException(RpcException.FORBIDDEN_EXCEPTION, "Forbid consumer " +  NetUtils.getLocalHost() + " access service " + getInterface().getName() + " from registry " + getUrl().getAddress() + " use dubbo version " + Version.getVersion() + ", Please check registry access list (whitelist/blacklist).");

    }

    List<Invoker<T>> invokers = null;

    Map<String, List<Invoker<T>>> localMethodInvokerMap = this.methodInvokerMap; // local reference

 

可见,当forbidden为false时,会抛出该异常信息,当注册中心给它推送最新的Provider列表时,上面的forbidden的值已经变成了false,见RegistryDirectory#refreshInvoker代码片段:

 

private void refreshInvoker(List<URL> invokerUrls){

    if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null

            && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {

        this.forbidden = true; // 禁止访问

        this.methodInvokerMap = null; // 置空列表

        destroyAllInvokers(); // 关闭所有Invoker

    } else {

        this.forbidden = false; // 允许访问

        Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference

        if (invokerUrls.size() == 0 && this.cachedInvokerUrls != null){

            invokerUrls.addAll(this.cachedInvokerUrls);

        } else {

            this.cachedInvokerUrls = new HashSet<URL>();

            this.cachedInvokerUrls.addAll(invokerUrls);//缓存invokerUrls列表,便于交叉对比

        }

 

从上面代码可以看出当该URL协议为empty说明该URL已经被禁止(forbidden)了,那什么时候URL的协议会被设置成empty呢?我们看看ZookeeperRegistry#toUrlsWithEmpty方法:

 

private List<URL> toUrlsWithEmpty(URL consumer, String path, List<String> providers) {

    List<URL> urls = toUrlsWithoutEmpty(consumer, providers);

    if (urls == null || urls.isEmpty()) {

       int i = path.lastIndexOf('/');

       String category = i < 0 ? path : path.substring(i + 1);

       URL empty = consumer.setProtocol(Constants.EMPTY_PROTOCOL).addParameter(Constants.CATEGORY_KEY, category);

        urls.add(empty);

    }

    return urls;

}

 

可见,当providers列表为空时,也就是某个URL下没有活着的Provider时,Consumer会将本地的invokerUrl的协议设置成empty,而toUrlsWithEmpty是在ZookeeperRegistry订阅方法doSubscribe中被调用的,这里不再给出代码。

 

另一个是"Failed to invoke the method XXXXX in the service XXXXX. No provider available for the service XXXXX from registry XXXXX on the consumer XXXXX using the dubbo version 2.5.3. Please check if the providers have been started and registered.",因为每次调用时都会去检查调用列表,如果列表有多个可用服务(即多个Provider),将会使用配置的负载均衡方式来选择一个服务来调用,但如果服务列表为空,则会抛异常,也就是在上图的Invoker节点抛出异常,这种情况一般是说明当前没有可用的Provider,见AbstractClusterInvoker#checkInvokers代码:

 

protected void checkInvokers(List<Invoker<T>> invokers, Invocation invocation) {

    if (invokers == null || invokers.size() == 0) {

        throw new RpcException("Failed to invoke the method "

                + invocation.getMethodName() + " in the service " + getInterface().getName()

                + ". No provider available for the service " + directory.getUrl().getServiceKey()

                + " from registry " + directory.getUrl().getAddress()

                + " on the consumer " + NetUtils.getLocalHost()

                + " using the dubbo version " + Version.getVersion()

                + ". Please check if the providers have been started and registered.");

    }

}

 

对于这两个异常的直接结论是,如果某个URL去注册中心注册过,但后来该URL下没有Provider了,那么此时Consumer调用Provider将报第一种异常;如果Consumer调用了一个从未去注册中心注册过的URL,则会报第二种异常。

 

需要明确一点的是,注册中心的两个重要目的是服务发现和服务人工介入,线上的Provider和Consumer都不能强依赖注册中心,哪怕注册中心是双机部署,但要做到对注册中心的弱依赖,Consumer端需要有简单的负载均衡和Failover机制。 


 

<!--StartFragment--> <!--EndFragment--> 

  • 大小: 109.6 KB
  • 大小: 123.4 KB
分享到:
评论

相关推荐

    dubbo示例代码dubbo-sample

    dubbo示例代码dubbo-sample

    Dubbo源代码(2.5.4)

    Dubbo的源代码打包,2.5.4开发发布版,及与Maven构建,本人亲测,可以编译通过。

    Dubbo源代码(2.8.4)

    Dubbo的源代码打包,2.8.4开发发布版,及与Maven构建,本人亲测,可以编译通过。

    dubbo源代码

    dubbo分布式服务框架,方便大家对分布式服务的学习,方便对dubbo的扩展

    dubbo2.8.4.jar

    如果使用dubbo遇到错误:com.alibaba.dubbo.remoting.RemotingException: Fail to decode request due to: RpcInvocation 请下载这个jar,替换掉你项目中的那个jar,应该可以解决。

    dubbo-admin-2.5.4及dubbo-monitor-2.5.3 安装及配置

    dubbo.registry.address dubbo接口服务注册地址: 单机zookeeper情况zookeeper://ip:port; 集群zookeeper情况zookeeper://ip:port?backup=ip:port,ip:port; dubbo.admin.root.password dubbo-admin root账号的...

    DubboDemo.rar

    Dubbo是 [1] 阿里巴巴公司开源的一个高性能优秀的服务框架,使得应用可通过高性能的 RPC 实现服务的输出和输入功能,可以和 [2] Spring框架无缝集成。...这里放的是dubbo的入门示例(不使用注册中心)

    Java-dubbo实践代码(注册中心)

    学习时个人编写的样例代码,以dubbo、zookeeper为基础实现的注册中心服务样例

    Dubbo源代码分析之远程调用过程(2.5.4开发版)

    该文档分析了 Dubbo 框架中 RPC 调用的整个流程,并基于源代码按照执行 时序进行说明,源码版本为2.5.4开发版。 涉及的关键点包括:Invocation、Invoker、Directory、路由、负载均衡、集群容错、过滤器以及监控模块...

    dubbo registry

    dubbo registry

    Dubbo工程demo

    传统工程改造成Dubbo工程...dubbodemo-service : 只做逻辑实现,也就是dubbo中的生产者 dubbodemo-web : 负责页面跳转及渲染,也就是dubbo中的消费者 讲解 : http://blog.csdn.net/shangmingtao/article/details/78017510

    dubbo registry protocol

    dubbo registry protocol

    dubbo入门源代码

    apache dubbo官网最简单的小例子,只是按照说明弄了下。

    Spring+mybatis+dubbo整合源代码及jar包

    此框架为Spring4.1.6+mybatis3.2.8+dubbo2.5.3的框架源码以及依赖的包

    dubbo-registry-etcd:etcd3注册表的dubbo

    dubbo-registry-etcd3 达博框架的etcd3注册表易于使用的etcd3 在pom.xml添加依赖项: &lt; dependency&gt; &lt; groupId&gt;org.apache.dubbo&lt;/ groupId&gt; &lt; artifactId&gt;dubbo-registry-etcd3&lt;/ artifactId&gt; &lt; version&gt;2.7.0-...

    dubbo技术介绍

    &lt;dubbo:registry/&gt; &lt;dubbo:monitor/&gt; &lt;dubbo:application/&gt; &lt;dubbo:module/&gt; &lt;dubbo:provider/&gt; &lt;dubbo:consumer/&gt; &lt;dubbo:method/&gt; &lt;dubbo:argument/&gt; &lt;dubbo:parameter/&gt; 协议参考手册 dubbo:// rmi:// hessian://...

    dubbo+zookeeper实现服务注册发现 dubbo+nacos实现服务注册发现

    dubbo+zookeeper实现服务注册发现 dubbo+nacos实现服务注册发现 源码

    dubbo-admin:dubbo服务监控

    dubbo服务监控 目录包含: dubbo-admin dubbo-monitor-simple dubbo-registry-simple pom.xml README.md

    SSM+dubbo项目源代码

    SSM+dubbo项目源代码

    dubbo.xsd文件

    targetNamespace="http://code.alibabatech.com/schema/dubbo"&gt; &lt;xsd:import namespace="http://www.w3.org/XML/1998/namespace"/&gt; &lt;xsd:import namespace="http://www.springframework.org/schema/beans"/&gt; ...

Global site tag (gtag.js) - Google Analytics