`
manzhizhen
  • 浏览: 292681 次
  • 性别: 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源代码(2.8.4)

    总的来说,Dubbo 2.8.4的源代码是一个深度学习分布式服务治理框架的好材料,它揭示了服务治理的核心机制和实现细节,对于理解微服务架构和提升Java开发能力大有裨益。通过深入研究,开发者可以更好地利用Dubbo来构建...

    alibaba-dubbo-dubbo-2.5.7-0-ge2d63ad.tar.gz

    通过研究这些源代码,开发者可以了解到Dubbo如何处理服务的注册与发现、如何实现RPC通信、如何进行服务路由和负载均衡等核心功能。此外,还可以学习到Dubbo如何与其他组件如Zookeeper、Spring等进行集成,以及如何...

    阿里巴巴dubbo-2.5.4源代码

    Dubbo提供了多种负载均衡策略,如随机、轮询、权重等,源代码中这些策略的实现可以帮助我们理解如何在多台服务提供者间进行智能调度。 5. **监控与调用链跟踪** `Monitor`接口是监控服务的核心,用于收集服务调用...

    dubbo2.53 注册中心

    - 这个包很可能包含了简单的注册中心的源代码,开发者可以通过阅读源码来理解注册中心的工作原理,例如服务注册、服务发现的逻辑,以及与其他组件的交互方式。 - 可能包含相关的配置文件,如XML配置、properties...

    Dubbo服务框架 v2.7.22.zip

    1. **服务注册与发现**:Dubbo支持多种注册中心,如Zookeeper、Eureka等,服务提供者可以将服务注册到注册中心,服务消费者通过查询注册中心获取服务提供者的地址,实现服务的动态发现。 2. **远程调用**:Dubbo...

    dubbo实例代码

    而“src”目录则是源代码存放的地方,可能包含服务提供者和服务消费者的实现代码,以及相应的配置文件,如dubbo配置、Zookeeper连接配置等。通过这些代码,我们可以进一步了解如何在实际项目中集成和使用Dubbo和...

    Dubbo源码(注释版)

    此压缩包提供的" Dubbo源码(注释版)"是针对Dubbo 2.7.3版本的源代码,包含了详细的注释,方便开发者深入理解其内部机制。 一、服务注册与发现 Dubbo的核心功能之一是服务注册与发现。服务提供者在启动时会将自身...

    dubbo-3.1.4源码包

    在【压缩包子文件的文件名称列表】"apache-dubbo-3.1.4-src"中,你可以找到dubbo的主要模块和源代码,包括: 1. `dubbo-common`:包含了dubbo的核心通用模块,如配置管理、日志处理、序列化等。 2. `dubbo-remoting...

    dubbo源码实例

    Dubbo的核心组件包括服务提供者(Provider)、服务消费者(Consumer)、注册中心(Registry)和服务监控(Monitor)。Provider暴露服务,Consumer调用服务,两者通过Registry进行连接,Monitor则用于统计服务调用的...

    dubbo多个版本-jar包

    - **dubbo-2.5.2-sources**:这个版本包含了源代码,对于开发者来说,可以深入理解内部实现,方便自定义扩展或调试。 2. **Dubbo核心组件**: - **Service**: 服务提供者,定义了对外提供的服务接口和实现。 - *...

    dubbo2.0源码解读

    5. **源文件概述**:Dubbo的源代码包括多个模块,如dubbo-common、dubbo-remoting、dubbo-rpc、dubbo-cluster等,分别处理基础工具、网络通信、远程调用、集群逻辑等。 6. **核心机制分析**: - **服务注册与发现*...

    Dubbo与Zookeeper、spring框架的整合。

    1. **Dubbo核心概念**:Dubbo提供了服务提供者(Provider)、服务消费者(Consumer)、注册中心(Registry)和服务监控(Monitor)等核心组件。服务提供者发布服务,服务消费者调用服务,注册中心负责服务的注册与...

    ares-remoting:mini版dubbo源代码,分布式服务注册与调用原理解释

    《ares-remoting:迷你版Dubbo源代码解析——分布式服务注册与调用揭秘》 在IT行业中,分布式服务已经成为大型系统架构的核心组成部分,而Dubbo作为阿里巴巴开源的一款高性能、轻量级的服务治理框架,备受业界关注...

    dubbo源码解析2

    - **7.2.2 DubboRegistryFactory创建注册中心过程**:具体实现注册中心创建过程。 - **7.2.3 注册中心启动**:启动注册中心的相关步骤。 - **7.2.4 生产者发布服务** - **7.2.4.1 Export发布服务流程**:服务提供者...

    Dubbo入门案例和项目源码

    【Dubbo入门案例和项目源码】是一个针对初学者的教程资源,包含了使用Dubbo构建Web服务的基础示例和完整的项目源代码。Dubbo是阿里巴巴开源的一款高性能、轻量级的Java服务框架,广泛应用于分布式系统开发,尤其在...

    dubbo源码解析2.0.7z

    《Dubbo源码解析2.0》是一份深入剖析阿里巴巴开源框架Dubbo核心机制的资料,专注于2.0版本的源代码分析。Dubbo作为Java领域最知名的分布式服务框架之一,其设计理念、实现原理以及在实际应用中的优化策略都是开发者...

    dubbo-admin 项目

    7. **扩展性**:dubbo-admin的源代码是开放的,可以根据实际需求进行定制和扩展,例如添加新的监控指标,或者对接其他类型的服务注册中心。 总结来说,dubbo-admin是Dubbo生态中的重要组件,它为服务治理提供了直观...

    dubbo-demo项目源码

    通过这个dubbo-demo项目源码,你可以学习到如何定义服务接口、实现服务提供者、创建服务消费者,以及如何配置注册中心和监控中心。此外,还可以了解Dubbo的负载均衡、容错机制、调用链路跟踪等高级特性。动手实践...

    dubbo-master

    【标题】"dubbo-master" 是Dubbo框架的核心源代码仓库,它包含了构建Dubbo管理员管理平台所需的所有源代码和资源。Dubbo是一款由阿里巴巴开源的高性能、轻量级的服务治理框架,它主要应用于分布式系统中的服务调用,...

    Dubbo分布式服务管理

    1. 创建Maven项目结构:包括src/main/java(源代码)、src/main/resources(资源配置)和pom.xml(项目配置文件)。 2. 配置pom.xml:引入Dubbo和Spring的相关依赖。 3. 配置服务提供者:编写服务接口和服务实现,...

Global site tag (gtag.js) - Google Analytics