• 微服务架构中注册中心Zookeeper和Eureka我们应该怎么选?


    背景

    因为注册中心是微服务架构中最核心的基础服务之一,在选择注册中心方案之前,我们先了解以下什么是微服务架构。

    在Monolithic架构中我们经常会面临以下问题:

    1. 系统间以api的形式互相访问,导致系统之间紧密的耦合在一起难以维护

    2. 不同的业务需要相同的技术栈,无法快速应用新技术

    3. 系统任何修改都必须整个系统一起部署,维护成本高

    4. 系统负载增加时候,难以水平拓展

    5. 一个问题影响全局

    为了解决这些问题,微服务架构就产生了,微服务是一种架构风格,也就说将复杂的应用拆分成多个独立的服务,服务之间通过松耦合的方式调用。在松耦合方式中注册中心扮演了重要的角色。注册中心可以说是微服务架构中的”通讯录“,它记录了服务和服务地址的映射关系。在分布式架构中,服务会注册到这里,当服务需要调用其它服务时,就这里找到服务的地址,进行调用。

    目前经常被用到作为注册中心的有Zookeeper和Eureka,那么此时我们就会纠结如何去选择这两个注册中心,下面我们来系统的学习这两种注册中心的原理和优劣势,这样才能在架构搭建中合理的选择注册中心。

    注册中心概念

    注册中心涉及三大角色

    ● 服务提供者

    ● 服务消费者

    ● 注册中心

    他们之间的关系主要是

    ● 各个服务启动的时候,将自己网络地址等信息记录到注册中心

    ● 服务消费者从注册中心查询服务提供者提供的服务,并通过对应的地址调用服务提供者的api

    ● 微服务与注册中心使用心跳机制通信,如果注册中心与微服务长时间无法通信,就会注销该服务实例

    注册中心架构图:

    注册中心的主要作用

    在微服务架构中,注册中心主要起到了协调者的作用,主要有以下功能

    1. 服务发现

    服务注册/反注册:保存服务提供者和服务调用者的信息

    服务订阅/取消订阅:服务调用者订阅服务提供者的信息,最好有实时推送的功能

    服务路由(可选):具有筛选整合服务提供者的能力。

    2. 服务配置

    配置订阅:服务提供者和服务调用者订阅微服务相关的配置

    配置下发:主动将配置推送给服务提供者和服务调用者

    3. 服务健康检测

    检测服务提供者的健康情况

    Zookeeper和Eureka如何选择

    在分布式架构中往往伴随CAP的理论。因为分布式的架构,不再使用传统的单机架构,多机为了提供可靠服务所以需要冗余数据因而会存在分区容忍性P。

    冗余数据的同时会在复制数据的同时伴随着可用性A 和强一致性C的问题。是选择停止可用性达到强一致性还是保留可用性选择最终一致性。通常选择后者。其中 Zookeeper 和 Eureka分别是注册中心CP AP 的两种的实践。

    Zookeeper概述

    ZooKeeper主要为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。曾经是Hadoop项目中的一个子项目,用来控制集群中的数据,目前已升级为独立的顶级项目。很多场景下也用它作为Service发现服务解决方案。

    ZooKeeper是基于CP来设计的,即任何时刻对ZooKeeper的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性,但是它不能保证每次服务请求的可用性。从实际情况来分析,在使用ZooKeeper获取服务列表时,如果Zookeeper正在选主,或者ZooKeeper集群中半数以上机器不可用,那么将无法获得数据。所以说,ZooKeeper不能保证服务可用性。

    Zookeeper(数据一致性)数据同步分析

    我们知道 Zookeeper 集群在选举结束之后,leader 节点将进入 LEADING 状态,follower 节点将进入 FOLLOWING 状态;此时集群中节点将进行数据同步操作,以保证数据一致。只有数据同步完成之后 Zookeeper 集群才具备对外提供服务的能力。

    public void run() {
        try {
            /*
             * Main loop
             */
            while (running) {
                switch (getPeerState()) {
                case LEADING:
                    LOG.info("LEADING");
                    try {
                        setLeader(makeLeader(logFactory));
                        leader.lead();
                        setLeader(null);
                    } catch (Exception e) {
                        LOG.warn("Unexpected exception",e);
                    } finally {
                        if (leader != null) {
                            leader.shutdown("Forcing shutdown");
                            setLeader(null);
                        }
                        setPeerState(ServerState.LOOKING);
                    }
                    break;
                }
            }
        } finally {
    
        }
    } 
    
    
    void lead() throws IOException, InterruptedException {
        try {
            // 省略
    
            /**
             * 开启线程用于接收 follower 的连接请求
             */
            cnxAcceptor = new LearnerCnxAcceptor();
            cnxAcceptor.start();
    
            readyToStart = true;
    
            /**
             * 阻塞等待计算新的 epoch 值,并设置 zxid
             */
            long epoch = getEpochToPropose(self.getId(), self.getAcceptedEpoch());          
            zk.setZxid(ZxidUtils.makeZxid(epoch, 0));
    
    
            /**
             * 阻塞等待接收过半的 follower 节点发送的 ACKEPOCH 信息; 此时说明已经确定了本轮选举后 epoch 值
             */
            waitForEpochAck(self.getId(), leaderStateSummary);
            self.setCurrentEpoch(epoch);
    
            try {
                /**
                 * 阻塞等待 超过半数的节点 follower 发送了 NEWLEADER ACK 信息;此时说明过半的 follower 节点已经完成数据同步
                 */
                waitForNewLeaderAck(self.getId(), zk.getZxid(), LearnerType.PARTICIPANT);
            } catch (InterruptedException e) {
                // 省略
            }
    
            /**
             * 启动 zk server,此时集群可以对外正式提供服务
             */
            startZkServer();
    
            // 省略
    }
    
    

    从 lead 方法的实现可得知,leader 与 follower 在数据同步过程中会执行如下过程:

    ● 接收 follower 连接

    ● 计算新的 epoch 值

    ● 通知统一 epoch 值

    ● 数据同步

    ● 启动 zk server 对外提供服务

    public void run() {
        try {
            /*
             * Main loop
             */
            while (running) {
                switch (getPeerState()) {
                case LOOKING:
                    // 省略
                case OBSERVING:
                    // 省略
                case FOLLOWING:
                    try {
                        LOG.info("FOLLOWING");
                        setFollower(makeFollower(logFactory));
                        follower.followLeader();
                    } catch (Exception e) {
                        LOG.warn("Unexpected exception",e);
                    } finally {
                        follower.shutdown();
                        setFollower(null);
                        setPeerState(ServerState.LOOKING);
                    }
                    break;
                }
            }
        } finally {
    
        }
    }
    
    

    ● 请求连接 leader

    ● 提交节点信息计算新的 epoch 值

    ● 数据同步

    ● 以上就是zookeeper的同步逻辑

    Eureka概述

    1

    Eureka本身是Netflix开源的一款提供服务注册和发现的产品,并且提供了相应的Java封装。在它的实现中,节点之间相互平等,部分注册中心的节点挂掉也不会对集群造成影响,即使集群只剩一个节点存活,也可以正常提供发现服务。哪怕是所有的服务注册节点都挂了,Eureka Clients(客户端)上也会缓存服务调用的信息。这就保证了我们微服务之间的互相调用足够健壮。

    2

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

    3

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

    4

    一个新的Eureka Server节点启动后,会首先尝试从邻近节点获取所有实例注册表信息,完成初始化。Eureka Server通过getEurekaServiceUrls()方法获取所有的节点,并且会通过心跳续约的方式定期更新。默认配置下,如果Eureka Server在一定时间内没有接收到某个服务实例的心跳,Eureka Server将会注销该实例(默认为90秒,通过eureka.instance.lease-expiration-duration-in-seconds配置)。当Eureka Server节点在短时间内丢失过多的心跳时(比如发生了网络分区故障),那么这个节点就会进入自我保护模式

    Eureka高可用原理

    架构图

    Eureka Client向Eureka Serve注册,并将自己的一些客户端信息发送Eureka Serve。然后,Eureka Client通过向Eureka Serve发送心跳(每30秒)来续约服务的。如果客户端持续不能续约,那么,它将在大约90秒内从服务器注册表中删除。注册信息和续订被复制到集群中的Eureka Serve所有节点。来自任何区域的Eureka Client都可以查找注册表信息(每30秒发生一次)。根据这些注册表信息,Application Client可以远程调用Applicaton Service来消费服务。

    Eureka高可用实际上将自己作为服务向其他服务注册中心注册自己,这样就可以形成一组相互注册的服务注册中心,从而实现服务清单的互相同步,达到高可用效果。

    Eureka集群环境搭建

    ###服务端口号
    server:
      port: 8000
    ###serviceId
    spring:
      application:
        name: eureka-server
    ###eureka 基本信息配置
    eureka:
      instance:
        ###注册到eurekaip地址
        hostname: 127.0.0.1
      client:
        serviceUrl:
          defaultZone:  http://127.0.0.1:8100/eureka/
    ### 3台就需要让3台服务相互注册,用逗号分隔。defaultZone:  http://127.0.0.1:8100/eureka/,http://127.0.0.1:xxxx/eureka/
        ###因为自己是为注册中心,不需要自己注册自己
        register-with-eureka: true
        ###因为自己是为注册中心,不需要检索服务
        fetch-registry: true
      server:
        # 测试时关闭自我保护机制,保证不可用服务及时踢出
        enable-self-preservation: false
        eviction-interval-timer-in-ms: 2000
    
    

    Eureka02配置

    ###服务端口号
    server:
      port: 8100
    ###serviceId
    spring:
      application:
        name: eureka-server
    ###eureka 基本信息配置
    eureka:
      instance:
        ###注册到eurekaip地址
        hostname: 127.0.0.1
      client:
        serviceUrl:
          defaultZone:  http://127.0.0.1:8000/eureka/
        ###因为自己是为注册中心,不需要自己注册自己
        register-with-eureka: true
        ###因为自己是为注册中心,不需要检索服务
        fetch-registry: true
      server:
        # 测试时关闭自我保护机制,保证不可用服务及时踢出
        enable-self-preservation: false
        eviction-interval-timer-in-ms: 2000
    
    
    ###服务启动端口号
    server:
      port: 8002
    ###服务名称(服务注册到eureka名称)
    spring:
      application:
        name: app-itmayiedu-order
    ###服务注册到eureka地址
    eureka:
      client:
        service-url:
          defaultZone: http://localhost:8000/eureka,http://localhost:8100/eureka
    
        ###因为该应用为注册中心,不会注册自己
        register-with-eureka: true
        ###是否需要从eureka上获取注册信息
        fetch-registry: true
    
    

    总结

    ZooKeeper基于CP,不保证高可用,如果zookeeper正在选主,或者ZooKeeper集群中半数以上机器不可用,那么将无法获得数据。Eureka基于AP,能保证高可用,即使所有机器都挂了,也能拿到本地缓存的数据。作为注册中心,其实配置是不经常变动的,只有发版和机器出故障时会变。对于不经常变动的配置来说,CP是不合适的,而AP在遇到问题时可以用牺牲一致性来保证可用性,既返回旧数据,缓存数据。

    所以理论上Eureka是更适合做注册中心。而现实环境中大部分项目可能会使用ZooKeeper,那是因为集群不够大,并且基本不会遇到用做注册中心的机器一半以上都挂了的情况。所以实际上也没什么大问题。

    更多技术文章

  • 相关阅读:
    LeetCode二叉树系列——701.二叉搜索树中的插入操作
    分享一个连接远端计算机与传输文件的脚本
    论文精读 —— Gradient Surgery for Multi-Task Learning
    Django模型层之如何开启事务、常见的字段类型和参数、ORM字段参数、关系字段
    Python数据分析与机器学习29-支持向量机(SVM)
    LeetCode994. 腐烂的橘子(C++中等题)
    Unity6 URP17使用初探
    go 并发编程之-工作池
    大数据高级开发工程师——Spark学习笔记(7)
    【已解决】EOFError: Ran out of input
  • 原文地址:https://blog.csdn.net/Tester_muller/article/details/126475859