精华内容
参与话题
问答
  • Nacos做服务注册中心使用案例

    万次阅读 2020-09-21 04:27:51
    关于环境搭建部分请借鉴:nacos做配置中心和服务注册中心 的完整使用案例 <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-...

    关于环境搭建部分请借鉴:nacos做配置中心和服务注册中心 的完整使用案例

    <dependency>
        <groupId>com.alibaba.cloud</groupId>
        <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
    </dependency>
    
    server.port=8070
    spring.application.name=server-provider
    spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848
    

    主启动类示例代码

    @SpringBootApplication
    @EnableDiscoveryClient
    public class NacosProviderApplication {
    
    	public static void main(String[] args) {
    		SpringApplication.run(NacosProviderApplication.class, args);
    	}
    
    	@RestController
    	class EchoController {
    		@RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)
    		public String echo(@PathVariable String string) {
    			return "Hello Nacos Discovery " + string;
    		}
    	}
    }
    

    然后启动该主启动类,注意:主启动类至少有一个restful风格的请求资源,不然容易失败,因为是一个空服务

    启动项目后,如下在服务列表中能找到说明成功!
    在这里插入图片描述

    Springboot的示例代码

    在这里插入图片描述

    展开全文
  • 微服务:注册中心ZooKeeper、Eureka、Consul 、Nacos对比

    万次阅读 多人点赞 2019-08-22 21:11:09
    服务注册中心本质上是为了解耦服务提供者和服务消费者。对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的。更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和...

    前言

    服务注册中心本质上是为了解耦服务提供者和服务消费者。对于任何一个微服务,原则上都应存在或者支持多个提供者,这是由微服务的分布式属性决定的。更进一步,为了支持弹性扩缩容特性,一个微服务的提供者的数量和分布往往是动态变化的,也是无法预先确定的。因此,原本在单体应用阶段常用的静态LB机制就不再适用了,需要引入额外的组件来管理微服务提供者的注册与发现,而这个组件就是服务注册中心。

    CAP理论

    CAP理论是分布式架构中重要理论

    • 一致性(Consistency) (所有节点在同一时间具有相同的数据)
    • 可用性(Availability) (保证每个请求不管成功或者失败都有响应)
    • 分隔容忍(Partition tolerance) (系统中任意信息的丢失或失败不会影响系统的继续运作)

    关于

    P的理解,我觉得是在整个系统中某个部分,挂掉了,或者宕机了,并不影响整个系统的运作或者说使用,

    而可用性是,某个系统的某个节点挂了,但是并不影响系统的接受或者发出请求,CAP 不可能都取,只能取其中2个

    原因是

    如果C是第一需求的话,那么会影响A的性能,因为要数据同步,不然请求结果会有差异,但是数据同步会消耗时间,期间可用性就会降低。

    如果A是第一需求,那么只要有一个服务在,就能正常接受请求,但是对与返回结果变不能保证,原因是,在分布式部署的时候,数据一致的过程不可能想切线路那么快。

    再如果,同事满足一致性和可用性,那么分区容错就很难保证了,也就是单点,也是分布式的基本核心,好了,明白这些理论,就可以在相应的场景选取服务注册与发现了

     

    服务注册中心解决方案

    设计或者选型一个服务注册中心,首先要考虑的就是服务注册与发现机制。纵观当下各种主流的服务注册中心解决方案,大致可归为三类:

    • 应用内:直接集成到应用中,依赖于应用自身完成服务的注册与发现,最典型的是Netflix提供的Eureka

    • 应用外:把应用当成黑盒,通过应用外的某种机制将服务注册到注册中心,最小化对应用的侵入性,比如Airbnb的SmartStack,HashiCorp的Consul

    • DNS:将服务注册为DNS的SRV记录,严格来说,是一种特殊的应用外注册方式,SkyDNS是其中的代表

    注1:对于第一类注册方式,除了Eureka这种一站式解决方案,还可以基于ZooKeeper或者Etcd自行实现一套服务注册机制,这在大公司比较常见,但对于小公司而言显然性价比太低。

    注2:由于DNS固有的缓存缺陷,本文不对第三类注册方式作深入探讨。

    除了基本的服务注册与发现机制,从开发和运维角度,至少还要考虑如下五个方面:

    • 测活:服务注册之后,如何对服务进行测活以保证服务的可用性?

    • 负载均衡:当存在多个服务提供者时,如何均衡各个提供者的负载?

    • 集成:在服务提供端或者调用端,如何集成注册中心?

    • 运行时依赖:引入注册中心之后,对应用的运行时环境有何影响?

    • 可用性:如何保证注册中心本身的可用性,特别是消除单点故障?

     

    主流注册中心产品

    软件产品特性并非一成不变,如果发现功能特性有变更,欢迎评论指正

      Nacos Eureka Consul CoreDNS Zookeeper
    一致性协议 CP+AP AP CP CP
    健康检查 TCP/HTTP/MYSQL/Client Beat Client Beat TCP/HTTP/gRPC/Cmd Keep Alive
    负载均衡策略 权重/
    metadata/Selector
    Ribbon Fabio RoundRobin
    雪崩保护
    自动注销实例 支持 支持 支持 不支持 支持
    访问协议 HTTP/DNS HTTP HTTP/DNS DNS TCP
    监听支持 支持 支持 支持 不支持 支持
    多数据中心 支持 支持 支持 不支持 不支持
    跨注册中心同步 支持 不支持 支持 不支持 不支持
    SpringCloud集成 支持 支持 支持 不支持 支持
    Dubbo集成 支持 不支持 支持 不支持 支持
    K8S集成 支持 不支持 支持 支持 不支持
    • Consul是支持自动注销服务实例, 请见文档: https://www.consul.io/api-docs/agent/service,在check的 DeregisterCriticalServiceAfter 这个参数-- 感谢@超帅的菜鸟博主提供最新信息
    • 新版本的Dubbo也扩展了对 Consul 的支持。 参考: https://github.com/apache/dubbo/tree/master/dubbo-registry

    Apache Zookeeper -> CP


    与 Eureka 有所不同,Apache Zookeeper 在设计时就紧遵CP原则,即任何时候对 Zookeeper 的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性,但是 Zookeeper 不能保证每次服务请求都是可达的。

    从 Zookeeper 的实际应用情况来看,在使用 Zookeeper 获取服务列表时,如果此时的 Zookeeper 集群中的 Leader 宕机了,该集群就要进行 Leader 的选举,又或者 Zookeeper 集群中半数以上服务器节点不可用(例如有三个节点,如果节点一检测到节点三挂了 ,节点二也检测到节点三挂了,那这个节点才算是真的挂了),那么将无法处理该请求。所以说,Zookeeper 不能保证服务可用性。


    当然,在大多数分布式环境中,尤其是涉及到数据存储的场景,数据一致性应该是首先被保证的,这也是 Zookeeper 设计紧遵CP原则的另一个原因。

    但是对于服务发现来说,情况就不太一样了,针对同一个服务,即使注册中心的不同节点保存的服务提供者信息不尽相同,也并不会造成灾难性的后果。

    因为对于服务消费者来说,能消费才是最重要的,消费者虽然拿到可能不正确的服务实例信息后尝试消费一下,也要胜过因为无法获取实例信息而不去消费,导致系统异常要好(淘宝的双十一,京东的618就是紧遵AP的最好参照)。

    当master节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长,30~120s,而且选举期间整个zk集群都是不可用的,这就导致在选举期间注册服务瘫痪。

    在云部署环境下, 因为网络问题使得zk集群失去master节点是大概率事件,虽然服务能最终恢复,但是漫长的选举事件导致注册长期不可用是不能容忍的。

     

    Spring Cloud Eureka  -> AP

     


    Spring Cloud Netflix 在设计 Eureka 时就紧遵AP原则(尽管现在2.0发布了,但是由于其闭源的原因 ,但是目前 Ereka 1.x 任然是比较活跃的)。

    Eureka Server 也可以运行多个实例来构建集群,解决单点问题,但不同于 ZooKeeper 的选举 leader 的过程,Eureka Server 采用的是Peer to Peer 对等通信。这是一种去中心化的架构,无 master/slave 之分,每一个 Peer 都是对等的。在这种架构风格中,节点通过彼此互相注册来提高可用性,每个节点需要添加一个或多个有效的 serviceUrl 指向其他节点。每个节点都可被视为其他节点的副本。

    在集群环境中如果某台 Eureka Server 宕机,Eureka Client 的请求会自动切换到新的 Eureka Server 节点上,当宕机的服务器重新恢复后,Eureka 会再次将其纳入到服务器集群管理之中。当节点开始接受客户端请求时,所有的操作都会在节点间进行复制(replicate To Peer)操作,将请求复制到该 Eureka Server 当前所知的其它所有节点中。

    当一个新的 Eureka Server 节点启动后,会首先尝试从邻近节点获取所有注册列表信息,并完成初始化。Eureka Server 通过 getEurekaServiceUrls() 方法获取所有的节点,并且会通过心跳契约的方式定期更新。

    默认情况下,如果 Eureka Server 在一定时间内没有接收到某个服务实例的心跳(默认周期为30秒),Eureka Server 将会注销该实例(默认为90秒, eureka.instance.lease-expiration-duration-in-seconds 进行自定义配置)。

    当 Eureka Server 节点在短时间内丢失过多的心跳时,那么这个节点就会进入自我保护模式。

    Eureka的集群中,只要有一台Eureka还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)。除此之外,Eureka还有一种自我保护机制,如果在15分钟内超过85%的节点都没有正常的心跳,那么Eureka就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:

    1. Eureka不再从注册表中移除因为长时间没有收到心跳而过期的服务;
    2. Eureka仍然能够接受新服务注册和查询请求,但是不会被同步到其它节点上(即保证当前节点依然可用);
    3. 当网络稳定时,当前实例新注册的信息会被同步到其它节点中;

    因此,Eureka可以很好的应对因网络故障导致部分节点失去联系的情况,而不会像zookeeper那样使得整个注册服务瘫痪。

    Consul:

    Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置。Consul 使用 Go 语言编写,因此具有天然可移植性(支持Linux、windows和Mac OS X)。

    Consul 内置了服务注册与发现框架、分布一致性协议实现、健康检查、Key/Value 存储、多数据中心方案,不再需要依赖其他工具(比如 ZooKeeper 等),使用起来也较为简单。

    Consul 遵循CAP原理中的CP原则,保证了强一致性和分区容错性,且使用的是Raft算法,比zookeeper使用的Paxos算法更加简单。虽然保证了强一致性,但是可用性就相应下降了,例如服务注册的时间会稍长一些,因为 Consul 的 raft 协议要求必须过半数的节点都写入成功才认为注册成功 ;在leader挂掉了之后,重新选举出leader之前会导致Consul 服务不可用。

    默认依赖于SDK

     

     Consul本质上属于应用外的注册方式,但可以通过SDK简化注册流程。而服务发现恰好相反,默认依赖于SDK,但可以通过Consul Template(下文会提到)去除SDK依赖。

    Consul Template

    Consul Template

    Consul,默认服务调用者需要依赖Consul SDK来发现服务,这就无法保证对应用的零侵入性。

    所幸通过Consul Template,可以定时从Consul集群获取最新的服务提供者列表并刷新LB配置(比如nginx的upstream),这样对于服务调用者而言,只需要配置一个统一的服务调用地址即可。

     

    Consul强一致性(C)带来的是:

    1. 服务注册相比Eureka会稍慢一些。因为Consul的raft协议要求必须过半数的节点都写入成功才认为注册成功
    2. Leader挂掉时,重新选举期间整个consul不可用。保证了强一致性但牺牲了可用性。

    Eureka保证高可用(A)和最终一致性:

    1. 服务注册相对要快,因为不需要等注册信息replicate到其他节点,也不保证注册信息是否replicate成功
    2. 当数据出现不一致时,虽然A, B上的注册信息不完全相同,但每个Eureka节点依然能够正常对外提供服务,这会出现查询服务信息时如果请求A查不到,但请求B就能查到。如此保证了可用性但牺牲了一致性。

    其他方面,eureka就是个servlet程序,跑在servlet容器中; Consul则是go编写而成。

    Nacos:

    Nacos是阿里开源的,Nacos 支持基于 DNS 和基于 RPC 的服务发现。在Spring Cloud中使用Nacos,只需要先下载 Nacos 并启动 Nacos server,Nacos只需要简单的配置就可以完成服务的注册发现。

    Nacos除了服务的注册发现之外,还支持动态配置服务。动态配置服务可以让您以中心化、外部化和动态化的方式管理所有环境的应用配置和服务配置。动态配置消除了配置变更时重新部署应用和服务的需要,让配置管理变得更加高效和敏捷。配置中心化管理让实现无状态服务变得更简单,让服务按需弹性扩展变得更容易。

    一句话概括就是Nacos = Spring Cloud注册中心 + Spring Cloud配置中心。
     

    参考链接:

    https://yq.aliyun.com/articles/698930

    https://nacos.io

    展开全文
  • nacos和eureka注册中心对比 and CAP定律理解

    万次阅读 多人点赞 2019-05-14 11:16:06
    1. CP 和 AP不可能同时满足 2.P代表分区容错, 在整个分布式系统中某个节点服务挂掉了,并不影响整个系统的运作和使用, ... 注册中心集群中: leader的作用, 所有的写操作都依赖于leader来完成,为了保证数...

    1. CP 和 AP不可能同时满足

    2.P代表分区容错, 在整个分布式系统中某个节点服务挂掉了,并不影响整个系统的运作和使用,

                               因为他可以在稍后或者通过切换可用节点立即恢复使用
     

    3.C:  写操作之后的读操作,必须返回该值。

             注册中心集群中: leader的作用, 所有的写操作都依赖于leader来完成,为了保证数据的一致性,  leader只有一个

             假如: 没有leader,首先加入我们新加入一台数据处理服务,就会向注册中心1进行注册,注册中心1写入数据处理服务的ip

                      等等基本信息,并且准备同步给其他注册中心节点, 结果这个在还没发生同步的过程中,注册中心1挂掉了,

                      然后客户端准备调用数据中心写入,这个时候就因为注册中心1挂掉了,就直接切到了注册中心2,但是注册中心2没有

                      收到数据处理服务的添加请求,所以没有这个服务,这个时候就对客户端显示不可用了.

      4. A:   没有leader,可以很容易的切换到可用的注册中心,对于客户端的调用总是及时反应, 在上述C操作的例子中,

                 对于向服务注册,获取服务注册的基本信息,比如ip来说,基本不会存在,因为像Eureka来说,我们的服务可以

                 向所有的注册中心节点发起注册请求,  这样就不会存在注册中心节点服务列表不一致的情况

     

       阿里的nacos : 性能最好

         他同时支持AP和CP模式,他根据服务注册选择临时和永久来决定走AP模式还是CP模式,

        他这里支持CP模式对于我的理解来说,应该是为了配置中心集群,因为nacos可以同时作为注册中心和配置中心,

        因为他的配置中心信息是保存在nacos里面的,假如因为nacos其中一台挂掉后,还没有同步配置信息,

        就可能发生配置不一致的情况., 配置中心的配置变更是服务端有监听器,配置中心发生配置变化,

        然后服务端会监听到配置发生变化,从而做出改变

        

     eureka+spring cloud config: 

       性能也不差,对于服务数量小于上千台来说,性能没有问题

       eureka: 可以做注册中心,完全AP,支持注册中心之间的节点复制,同时支持服务端同时注册多个注册中心节点,

                      所以不存节点信息不一致的情况

      config: 单独服务,是从git仓库拉取配置信息,然后服务端从config服务里面拉取配置信息缓存到本地仓库

                  这里配置的变更比较麻烦,他需要结合bus组件,同时约束了只能用rabbitmq和kafka来进行通知服务端进行配置变更

                  但是保证了数据的一致性,因为他的配置信息在git仓库上,git仓库只有一个,就会数据一致          

     

    阿里nacos异常情况 leader挂了

       1.不影响服务之间互相调用

        2.不影响服务注册

        3.不影响服务正常启动拉取配置文件

        4.选举新leader差不多4,5秒钟

    展开全文
  • Dubbo与注册中心Zookeeper的交互过程

    万次阅读 多人点赞 2018-06-09 19:15:13
    Zookeeper作为注册中心在Dubbo框架中的作用过程。

    Dubbo的Provider,Consumer在启动时都会创建一个注册中心,注册中心可以选择Zookeeper,Redis。常用的是Zookeeper,我们这篇博客主要讲的就是Dubbo与Zookeeper的注册交互过程。

    Dubbo里默认使用zkclient来操作zookeeper服务器,其对zookeeper原始客户单做了一定的封装,操作zookeeper时能便捷一些,比如不需要手动处理session超时,不需要重复注册watcher等等。

    Dubbo在Zookeeper上注册的节点目录:假设接口名称是:com.bob.dubbo.service.CityDubboService

    这里写图片描述

    Dubbo启动时,Consumer和Provider都会把自身的URL格式化为字符串,然后注册到zookeeper相应节点下,作为一个临时节点,当连断开时,节点被删除。

    Consumer在启动时,不仅仅会注册自身到 …/consumers/目录下,同时还会订阅…/providers目录,实时获取其上Provider的URL字符串信息。

    下面我们就看相关的代码实现:

    public class ZookeeperRegistry extends FailbackRegistry {
    
    	......
    
    	/**
         * 默认端口
         */
        private final static int DEFAULT_ZOOKEEPER_PORT = 2181;
        /**
         * 默认 Zookeeper 根节点
         */
        private final static String DEFAULT_ROOT = "dubbo";
    
        /**
         * Zookeeper 根节点
         */
        private final String root;
        /**
         * Service 接口全名集合
         */
        private final Set<String> anyServices = new ConcurrentHashSet<String>();
        /**
         * 监听器集合
         */
        private final ConcurrentMap<URL, ConcurrentMap<NotifyListener, ChildListener>> zkListeners
            = new ConcurrentHashMap<URL, ConcurrentMap<NotifyListener, ChildListener>>();
        /**
         * Zookeeper 客户端
         */
        private final ZookeeperClient zkClient;
    
        public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
            super(url);   // 调用父类FailbackRegistry的构造函数
            if (url.isAnyHost()) {
                throw new IllegalStateException("registry address == null");
            }
            // 获得 Zookeeper 根节点, 未指定 "group" 参数时为 dubbo
            String group = url.getParameter(Constants.GROUP_KEY, DEFAULT_ROOT); // `url.parameters.group` 参数值
            if (!group.startsWith(Constants.PATH_SEPARATOR)) {
                group = Constants.PATH_SEPARATOR + group;
            }
            this.root = group;   // root = "/dubbo"
            // 创建 Zookeeper Client
            zkClient = zookeeperTransporter.connect(url);
            // 添加 StateListener 对象。该监听器,在重连时,调用恢复方法。
            zkClient.addStateListener(new StateListener() {
                @Override
                public void stateChanged(int state) {
                    if (state == RECONNECTED) {
                        try {
                            recover();
                        } catch (Exception e) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
            });
        }
    }
    
    public abstract class FailbackRegistry extends AbstractRegistry {
    	
    	......
    
    	/**
         * 发起注册失败的 URL 集合
         */
        private final Set<URL> failedRegistered = new ConcurrentHashSet<URL>();
        /**
         * 取消注册失败的 URL 集合
         */
        private final Set<URL> failedUnregistered = new ConcurrentHashSet<URL>();
        /**
         * 发起订阅失败的监听器集合
         */
        private final ConcurrentMap<URL, Set<NotifyListener>> failedSubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
        /**
         * 取消订阅失败的监听器集合
         */
        private final ConcurrentMap<URL, Set<NotifyListener>> failedUnsubscribed = new ConcurrentHashMap<URL, Set<NotifyListener>>();
        /**
         * 通知通知的 URL 集合
         */
        private final ConcurrentMap<URL, Map<NotifyListener, List<URL>>> failedNotified = new ConcurrentHashMap<URL, Map<NotifyListener, List<URL>>>();
    
    	public FailbackRegistry(URL url) {
            super(url);
            // 重试频率,单位:毫秒 ,默认 5*1000
            int retryPeriod = url.getParameter(Constants.REGISTRY_RETRY_PERIOD_KEY, Constants.DEFAULT_REGISTRY_RETRY_PERIOD);
            // 创建失败重试定时器
            this.retryFuture = retryExecutor.scheduleWithFixedDelay(new Runnable() {
                public void run() {
                    // Check and connect to the registry
                    try {
                        retry();
                    } catch (Throwable t) { // Defensive fault tolerance
                        logger.error("Unexpected error occur at failed retry, cause: " + t.getMessage(), t);
                    }
                }
            }, retryPeriod, retryPeriod, TimeUnit.MILLISECONDS);
        }
    
    	/**
         * 重试
         */
        // Retry the failed actions
        protected void retry() {
    		// 重试执行注册
    		if (!failedRegistered.isEmpty()) {
    			......
    			for (URL url : failed) {
                    try {
                        // 执行注册
                        doRegister(url);
                        // 移除出 `failedRegistered`
                        failedRegistered.remove(url);
                    } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                        logger.warn("Failed to retry register " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                    }
                }
    		}
    		// 重试执行取消注册
            if (!failedUnregistered.isEmpty()) {
    			......
    			for (URL url : failed) {
                    try {
                        // 执行取消注册
                        doUnregister(url);
                        // 移除出 `failedUnregistered`
                        failedUnregistered.remove(url);
                    } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                        logger.warn("Failed to retry unregister  " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                    }
                }
    		}
    		// 重试执行订阅
            if (!failedSubscribed.isEmpty()) {
    			......
    			for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
                    URL url = entry.getKey();
                    Set<NotifyListener> listeners = entry.getValue();
                    for (NotifyListener listener : listeners) {
                        try {
                            // 执行订阅
                            doSubscribe(url, listener);
                            // 移除监听器
                            listeners.remove(listener);
                        } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                            logger.warn("Failed to retry subscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                        }
                    }
                }
    		}
    		// 重试执行取消订阅
            if (!failedUnsubscribed.isEmpty()) {
    			......
    			for (Map.Entry<URL, Set<NotifyListener>> entry : failed.entrySet()) {
                    URL url = entry.getKey();
                    Set<NotifyListener> listeners = entry.getValue();
                    for (NotifyListener listener : listeners) {
                        try {
                            // 执行取消订阅
                            doUnsubscribe(url, listener);
                            // 移除监听器
                            listeners.remove(listener);
                        } catch (Throwable t) { // Ignore all the exceptions and wait for the next retry
                            logger.warn("Failed to retry unsubscribe " + failed + ", waiting for again, cause: " + t.getMessage(), t);
                        }
                    }
                }
    		}
    	}
    
    }
    

    ZookeeperRegistry 在实例化时,调用父类构造函数。在父类构造函数中,会创建一个定时任务,每隔5S执行retry( ) 方法。

    在retry( ) 方法中,重试那些失败的动作。重试的动作包括:

    1. Provider向zookeeper注册自身的url,生成一个临时的znode
    2. Provider从Dubbo容器中退出,停止提供RPC调用。也就是移除zookeeper内自身url对应的znode
    3. Consumer订阅 " /dubbo/…Service/providers" 目录的子节点,生成ChildListener
    4. Consumer从Dubbo容器中退出,移除之前创建的ChildListener

    为什么如此设置? 主要是和zookeeper的通信机制有关的。当zookeeper的Client和Server连接断开,或者心跳超时,那么Server会将相应Client注册的临时节点删除,当然注册的Listener也相应删除。

    而Provider和Consumer注册的URL就属于临时节点,当连接断开时,Dubbo注册了zookeeper的StateListener,也就是状态监听器,当Dubbo里的zookeeper Client和Server重新连接上时,将之前注册的的URL添加入这几个失败集合中,然后重新注册和订阅。

    看ZookeeperRegistry 的构造函数,其添加了一个StateListener:

    public class ZookeeperRegistry extends FailbackRegistry {
    
    	public ZookeeperRegistry(URL url, ZookeeperTransporter zookeeperTransporter) {
    		......
    		// 添加 StateListener 对象。该监听器,在重连时,调用恢复方法。
            zkClient.addStateListener(new StateListener() {
                @Override
                public void stateChanged(int state) {
                    if (state == RECONNECTED) {
                        try {
                            recover();
                        } catch (Exception e) {
                            logger.error(e.getMessage(), e);
                        }
                    }
                }
            });
    	}
    
    }
    
    public abstract class FailbackRegistry extends AbstractRegistry {
    
    	protected void recover() throws Exception {
            // register 恢复注册,添加到 `failedRegistered` ,定时重试
            Set<URL> recoverRegistered = new HashSet<URL>(getRegistered());
            if (!recoverRegistered.isEmpty()) {
                if (logger.isInfoEnabled()) {
                    logger.info("Recover register url " + recoverRegistered);
                }
                for (URL url : recoverRegistered) {
                    failedRegistered.add(url);
                }
            }
            // subscribe 恢复订阅,添加到 `failedSubscribed` ,定时重试
            Map<URL, Set<NotifyListener>> recoverSubscribed = new HashMap<URL, Set<NotifyListener>>(getSubscribed());
            if (!recoverSubscribed.isEmpty()) {
                if (logger.isInfoEnabled()) {
                    logger.info("Recover subscribe url " + recoverSubscribed.keySet());
                }
                for (Map.Entry<URL, Set<NotifyListener>> entry : recoverSubscribed.entrySet()) {
                    URL url = entry.getKey();
                    for (NotifyListener listener : entry.getValue()) {
                        addFailedSubscribed(url, listener);
                    }
                }
            }
        }
    
    }
    

    ZookeeperRegistry 构造函数中为zookeeper的操作客户端添加了一个状态监听器 StateListener,当重新连接时( 重新连接意味着之前连接断开了 ),将已经注册和订阅的URL添加到失败集合中,定时重试,也就是重新注册和订阅。

    zookeeper Client与Server断开连接后,会定时的不断尝试重新连接,当连接成功后就会触发一个Event,Dubbo注册了CONNECTED状态的监听器,当连接成功后重新注册和订阅。

    zookeeper Server宕机了,Dubbo里的Client并没有对此事件做什么响应,当然其内部的zkClient会不停地尝试连接Server。当Zookeeper Server宕机了不影响Dubbo里已注册的组件的RPC调用,因为已经通过URL生成了Invoker对象,这些对象还在Dubbo容器内。当然因为注册中心宕机了,肯定不能感知到新的Provider。同时因为在之前订阅获得的Provider信息已经持久化到本地文件,当Dubbo应用重启时,如果zookeeper注册中心不可用,会加载缓存在文件内的Provider信息,还是能保证服务的高可用。

    Consumer会一直维持着对Provider的ChildListener,监听Provider的实时数据信息。当Providers节点的子节点发生变化时,实时通知Dubbo,更新URL,同时更新Dubbo容器内的Consumer Invoker对象,只要是订阅成功均会实时同步Provider,更新Invoker对象,无论是第一次订阅还是断线重连后的订阅:

    public class ZookeeperRegistry extends FailbackRegistry {
    
    	protected void doSubscribe(final URL url, final NotifyListener listener) {
            try {
                // 处理所有 Service 层的发起订阅,例如监控中心的订阅
                if (Constants.ANY_VALUE.equals(url.getServiceInterface())) {
                    ......
                    // 处理指定 Service 层的发起订阅,例如服务消费者的订阅
                } else {
                    // 子节点数据数组
                    List<URL> urls = new ArrayList<URL>();
                    // 循环分类数组 , router, configurator, provider
                    for (String path : toCategoriesPath(url)) {
                        // 获得 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) { //  不存在子目录的监听器,进行创建 ChildListener 对象
                            // 订阅父级目录, 当有子节点发生变化时,触发此回调函数
                            listeners.putIfAbsent(listener, new ChildListener() {
                                @Override
                                public void childChanged(String parentPath, List<String> currentChilds) {
                                    // 变更时,调用 `#notify(...)` 方法,回调 NotifyListener
                                    ZookeeperRegistry.this.notify(url, listener, toUrlsWithEmpty(url, parentPath, currentChilds));
                                }
                            });
                            zkListener = listeners.get(listener);
                        }
                        // 创建 Type 节点。该节点为持久节点。
                        zkClient.create(path, false);
                        // 向 Zookeeper ,PATH 节点,发起订阅,返回此节点下的所有子元素 path : /根节点/接口全名/providers, 比如 : /dubbo/com.bob.service.CityService/providers
                        List<String> children = zkClient.addChildListener(path, zkListener);
                        // 添加到 `urls` 中
                        if (children != null) {
                            urls.addAll(toUrlsWithEmpty(url, path, children));
                        }
                    }
                    // 首次全量数据获取完成时,调用 `#notify(...)` 方法,回调 NotifyListener, 在这一步从连接Provider,实例化Invoker
                    notify(url, listener, urls);
                }
            } catch (Throwable e) {
                throw new RpcException("Failed to subscribe " + url + " to zookeeper " + getUrl() + ", cause: " + e.getMessage(), e);
            }
        }
    
    }
    

    订阅获取Providers的最新URL字符串,调用notify(…)方法,通知监听器,最终会执行如下代码:

    public class RegistryDirectory<T> extends AbstractDirectory<T> implements NotifyListener {
    
    	private volatile List<Configurator> configurators;
        
        private volatile Map<String, Invoker<T>> urlInvokerMap;
     
        private volatile Map<String, List<Invoker<T>>> methodInvokerMap;  
    
        private volatile Set<URL> cachedInvokerUrls; 
    
    
    	private void refreshInvoker(List<URL> invokerUrls) {
            // 从zookeeper获取到的url已经没有合适的了,在订阅返回为空时,会手动生成一个 EMPTY_PROTOCOL 的 url
            if (invokerUrls != null && invokerUrls.size() == 1 && invokerUrls.get(0) != null
                && Constants.EMPTY_PROTOCOL.equals(invokerUrls.get(0).getProtocol())) {
                this.forbidden = true; // Forbid to access
                this.methodInvokerMap = null; // Set the method invoker map to null
                destroyAllInvokers(); // Close all invokers
            } else {
                this.forbidden = false; // Allow to access
                Map<String, Invoker<T>> oldUrlInvokerMap = this.urlInvokerMap; // local reference
                if (invokerUrls.isEmpty() && this.cachedInvokerUrls != null) {
                    invokerUrls.addAll(this.cachedInvokerUrls);
                } else {
                    this.cachedInvokerUrls = new HashSet<URL>();
                    this.cachedInvokerUrls.addAll(invokerUrls);//Cached invoker urls, convenient for comparison
                }
                if (invokerUrls.isEmpty()) {
                    return;
                }
                Map<String, Invoker<T>> newUrlInvokerMap = toInvokers(invokerUrls);// Translate url list to Invoker map
                Map<String, List<Invoker<T>>> newMethodInvokerMap = toMethodInvokers(newUrlInvokerMap); // Change method name to map Invoker Map
                // state change
                // If the calculation is wrong, it is not processed.
                if (newUrlInvokerMap == null || newUrlInvokerMap.size() == 0) {
                    logger.error(new IllegalStateException(
                        "urls to invokers error .invokerUrls.size :" + invokerUrls.size() + ", invoker.size :0. urls :" + invokerUrls.toString()));
                    return;
                }
                this.methodInvokerMap = multiGroup ? toMergeMethodInvokerMap(newMethodInvokerMap) : newMethodInvokerMap;
                this.urlInvokerMap = newUrlInvokerMap;
                try {
                    destroyUnusedInvokers(oldUrlInvokerMap, newUrlInvokerMap); // Close the unused Invoker
                } catch (Exception e) {
                    logger.warn("destroyUnusedInvokers error. ", e);
                }
            }
        }
    
    }
    

    更新Dubbo内的Invoker相关数据,保证Consumer能实时感知到Provider的信息,保证PRC调用不会出错。

    以上就是Dubbo内Zookeeper注册中心的实现过程。

    总结:

    1. Provider和Consumer向Zookeeper注册临时节点,当连接断开时删除相应的注册节点。
    2. Consumer订阅Providers节点的子节点,实时感知Provider的变化情况,实时同步自身的Invoker对象,保证RPC的可用性。
    展开全文
  • 上一篇: idea创建springboot项目图文教程(配置文件)(五) ...idea创建springcloud项目图文教程(EurekaServer注册中心)(六) 1,new -project 选择spring initializr 、 2,创建自己的包名...
  • Eureka服务注册中心搭建

    万次阅读 2019-01-18 14:10:58
    单机模式Eureka注册中心搭建 引入Eureka-Server依赖 创建启动类 添加配置 高可用Eureka注册中心搭建 双节点注册中心 修改配置文件 修改hosts文件 启动测试 多节点注册中心 修改配置文件 启动测试 常见...
  • springboot 整合 nacos 作为注册中心和配置中心,三分钟学会的超简单的教程。
  • dubbo注册中心

    千次阅读 2019-06-13 14:17:12
    Dubbo目前支持4种注册中心,(multicast zookeeper redis simple) 推荐使用Zookeeper注册中心, Multicast注册中心 不需要启动任何中心节点,只要广播地址一样,就可以互相发现 组播受网络结构限制,只适合小规模...
  • Consul 注册中心介绍

    万次阅读 2019-07-08 20:46:32
    在 Spring Cloud 体系中,几乎每个角色都会有两个以上的产品提供选择,比如在注册中心有:Eureka、Consul、zookeeper、etcd 等;网关的产品有 Zuul、Spring Cloud Gateway 等。在注册中心产品中,最常使用的是 ...
  • 注册中心zookeeper

    千次阅读 2018-08-04 21:04:09
    注册中心zookeeper 注册中心是干什么用的? 注册中心的主要作用就是为消费者解决服务地址发现的问题,因为在集群环境下,传统的服务消费者直接记录服务提供者信息的服务地址很难管理,只要提供者地址信息发生变化消费...
  • Nacos(二):SpringCloud项目中接入Nacos作为注册中心

    万次阅读 多人点赞 2019-07-09 17:18:01
    通过上一篇文章:Nacos介绍 简单了解了Nacos的发展历程和现状,本文我们开始Nacos试水的第一步: 使用Nacos做注册中心 上周末(7.6)Nacos发布了V1.1.0版本,这次更新支持灰度配置、地址服务器模式、配置文件导入...
  • Dubbo注册中心

    万次阅读 多人点赞 2016-05-24 16:54:46
    Dubbo目前支持4中注册中心,(multicast zookeeper redis simple)   推荐使用Zookeeper注册中心, Multicast注册中心 不需要启动任何中心节点,只要广播地址一样,就可以互相发现 ...
  • 微服务注册中心 Eureka

    千次阅读 2019-09-13 14:34:20
    微服务注册中心 Eureka
  • 注册中心以及演进

    2020-08-01 21:42:46
    注册中心现在是服务框架实现服务治理的核心,随着服务的增多,注册中心也是大势所趋,但是对于大多数的公司来说,小型注册中心是够用的,但是对于大厂来说,显起来不那么够用。 1:dubbo注册中心 服务提供方...
  • seata 的注册中心和配置中心

    千次阅读 2020-05-12 16:18:42
    注册中心: 服务端注册中心(位于seata-server的registry.conf配置文件中的registry.type参数),为了实现seata-server集群高可用不会使用file类型,例如下边代码表示:使用zookeeper作为seata服务们的注册管理中心,...
  • 在学习Eureka中配置高可用注册中心,启动两个注册中心c1和c2,但是在c1注册中心的available-replicas项中没有c2存在,反而是unavailable-replicas中有; 在网上搜索了之后,说是如下原因: eureka.client.service...
  • 微服务架构注册中心

    千次阅读 2019-06-05 11:47:47
    1什么是注册中心 注册中心相当于手机的通讯录,服务注册就是将服务的地址添加到通讯录里面,服务发现就是当需要找这个服务的时候通过这个通讯录找到服务的地址进行拨号。 2为什么要注册中心 服务中心的作用不仅是...
  • dubbo多注册中心

    千次阅读 2018-10-19 16:01:04
    Dubbo 支持同一服务向多注册中心同时注册,或者不同服务分别注册到不同的注册中心上去,甚至可以同时引用注册在不同注册中心上的同名服务。另外,注册中心是支持自定义扩展的。 多注册中心注册 比如:中文站有些服务...
  • SpringBoot使用Nacos作为配置中心服务和服务注册中心

    万次阅读 多人点赞 2019-03-07 18:15:41
    简介 ...目前已有的配置中心 携程开源的Apollo:数据保存在mysql中,支持命名空间和分发更新配置 springcloud 中的springcloud config:必须使用git保存配置信息 阿里的开源Nacos 其他项目未关注 最次...
  • 微服务注册中心理解

    千次阅读 2019-07-30 22:20:20
    当下最火热微服务框架之一SpringCloud, 使用它可以轻松地构建分布式服务,它核心组件少不了注册中心,它提供了服务注册于发现功能。常见的注册中心框架有zookeeper、Eureka、consul、etcd 等。zk保证一致性,eureka...
  • 注册中心nacos完整部署及与eureka区别

    万次阅读 2019-10-15 12:12:00
    1. 场景描述 nacos最近用的比较多,介绍...(1)springcloud eureka是注册中心,负责微服务的注册与发现,起到承上启下的作用,在微服务架构中相当于人体的 大脑,很重要,nacos是阿里巴巴出的,功能类似eureka,区...
  • Springboot集成nacos实现注册中心+配置中心开始pom文件服务注册中心服务配置中心nacos热更新暂时就到这里了,如有问题欢迎大家指出,谢谢。 开始 请提前搭建好nacos服务,这里不做搭建过程说明。 我们首先需要先创建...
  • Spring Cloud 注册中心整合配置中心

    千次阅读 2018-12-01 16:07:38
    Spring Cloud 注册中心整合配置中心背景实现 背景 实现
  • 前言 springboot作为当下最流行... 注册中心的互相发现可以有效的解决当一个注册中心挂掉以后整个服务就丢失服务的状态,当一个注册中心挂掉后,还会有一个相同的服务继续工作。 这篇博客主要说一下如何自己搭建...
  • 文章 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka) 介绍了服务注册与发现,其中服务注册中心Eureka Server,是一个实例,当成千上万个服务向它注册的时候,它的负载是非常高的,这...
  • Eureka高可用注册中心registered-replicas没有分布式注册中心 【写在前面】如果看完这篇博客,对你有帮助的话,欢迎加入全栈技术交流群,群内不定时发布热门学习资料,也欢迎进行技术交流,对我的博客有疑问也可以...
  • Dubbo注册中心原理

    千次阅读 2019-08-01 16:23:05
    Dubbo的注册中心有好多种,包括Multicast(广播协议)、Zookeeper、Redis、Simple等。Dubbo官方推荐使用Zookeeper注册中心。如需了解Dubbo使用,请移步:Dubbo实例快速上手 1.基于ZooKeeper解析Dubbo注册中心实现原理...
  • 配置中心和 服务注册中心顺序如何

    千次阅读 2019-08-20 08:58:54
    配置中心和 服务注册中心顺序如何?? 如果配置中心也是通过 服务注册中心查的话,在Application.xml里配置的 服务注册中心就 不会起作用了。 然后查了一下spring cloud文档。 默认是config server优先 如果想改成 ...

空空如也

1 2 3 4 5 ... 20
收藏数 40,350
精华内容 16,140
关键字:

注册中心