草庐IT

微服务架构 | 3.3 Apache Zookeeper 注册中心

多氯环己烷 2023-03-28 原文

@


前言

参考资料
《Spring Microservices in Action》
《Spring Cloud Alibaba 微服务原理与实战》
《B站 尚硅谷 SpringCloud 框架开发教程 周阳》
《Zookeeper 教程》
《Zookeeper 官网》

ZooKeeper 是一个分布式的,开放源码的分布式应用程序协调服务,是 Google 的 Chubby 一个开源的实现,是 Hadoop 和 Hbase 的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等;


1. Zookeeper 基础知识

1.1 Zookeeper 是什么

  • Zookeeper 是一个分布式协调工具,可以实现注册中心功能;
  • 是 Apache Hadoop 的一个子项目,用来解决分布式应用中经常遇到的一些数据管理问题。包括:统一命名服务、状态同步服务、集群管理、分布式应用配置项的管理等;
  • 简单来说:zookeeper=文件系统+监听通知机制;
  • ZooKeeper 的架构通过冗余服务实现高可用性;
  • 【注意】ZooKeeper 本身并不是注册中心,只是基于 ZooKeeper 本身的特性可以实现注册中心这个场景;

1.2 Zookeeper 的数据结构

  • 系统概述

    • ZooKeeper 的数据模型和分布式文件系统类似,是一种层次化的属性结构;
    • 和文件系统不同的是,ZooKeeper 的数据是结构化存储的,并没有在物理上体现出文件和目录;
    • ZooKeeper 上的每个节点的数据都是允许读和写,且必须要按照层级创建;
  • Znode 节点

    • Zookeeper 中的所有存储的数据是由 Znode 组成,并以 key/value 形式存储数据;
    • ZooKeeper 只是管理和协调有关的数据,所以 value 的数据大小不建议设置得非常大,较大的数据会带来更大的网络开销;
    • Znode 维护一个 stat 状态信息,其中包含数据变化的时间和版本等;
  • stat 状态信息

    属性 说明
    cZxid 创建节点时的事务 ID
    ctime 创建节点时的时间
    mZxid 最后修改节点时的事务 ID
    mtime 最后修改节点时的时间
    pZxid 表示该节点的子节点列表最后一次修改的事务 ID(包括增删子节点,不包括改子节点内容)
    cversion 子节点版本号,子节点每次修改版本号加 1
    dataversion 数据版本号,数据每次修改该版本号加 1
    aclversion 权限版本号,权限每次修改该版本号加 1
    ephemeralOwner 创建该临时节点的会话的 sessionID(持久节点,该属性值为 0)
    dataLength 该节点的数据长度
    numChildren 该节点拥有直接子节点的数量
  • Znode 的节点类型

    • 持久化节点:节点的数据会持久化到磁盘;
    • 临时节点:节点的生命周期和创建该节点的客户端的生命周期保持一致。客户端会话结束,则该临时节点删除;
    • 有序节点:在创建的节点后增加一个递增的序列,该序列在同一级父节点之下是唯一的;
    • 容器节点:容器节点下最后一个子节点被删除时,容器节点自动删除(3.5.3版本后);
    • TTL 节点:设置一个存活时间,如果在存活时间之内该节点没有任何修改并且没有子节点,则自动删除(3.5.3版本后);
  • 一个示例图

1.3 Watcher 机制

  • 一种针对 Znode 的订阅/通知机制;
  • 当 Znode 节点状态发生变化时或者 ZooKeeper 客户端连接状态发生变化时,会触发事件通知;
  • 该机制在服务注册与发现中,用于服务调用者及时感知到服务提供者的变化;
  • ZooKeeper 提供了三种 JavaAPI 机制进行对 Znode 进行注册监听:
    • getData():用于获取指定节点的 value 信息,并且可以注册监听,当监听的节点进行创建、修改、删除操作时,会触发相应的事件通知;
    • getChildren():用于获取指定节点的所有子节点,并且允许注册监听,当监听节点的子节点进行创建、修改、删除操作时,触发相应的事件通知;
    • exists():用于判断指定节点是否存在,同样可以注册针对指定节点的监听,监听的时间类型和 getData() 相同
  • Watcher 事件的触发都是一次性的,客户端在发现节点修改后需要在事件回调中再次注册事件;

1.4 常见应用场景分析

  • 分布式锁
    • 排他性:避免在同一时刻多个进程同时访问某一个共享资源;
    • 基于节点的性质:临时节点、同级节点的唯一性;
    • 获得锁的过程:在获得排他锁时,所有客户端可以去 ZooKeeper 服务器上 /Exclusive_Locks 节点下创建一个临时节点 /lock。只有一个客户端能创建成功;
    • 释放锁的过程:有两种情况:
      • 获得锁的客户端因为异常断开了和服务端的连接,基于临时节点的特性,/lock 节点会被自动删除;
      • 获得锁的客户端执行完业务逻辑之后,主动删除了创建的 /lock 节点;
  • Master 选举
    • 当集群机器中有一个机器宕机后,其他机器会接替故障机器继续工作;
    • 基于节点的性质有两种实现方式:
      • 唯一性:类似分布式锁。在 ZooKeeper 服务器上创建一个临时节点 /master-election,只有成功创建的机器工作。其他机器针对该节点注册 Watcher 事件;
      • 临时有序节点:所有参与选举的客户端在 ZooKeeper 服务器的 /master 节点下创建一个临时有序节点,编号最小的节点表示 Master,后续的节点可以监听前一个节点的删除事件,用于触发重新选举。如下图所示:

1.5 Zookeeper 的版本冲突问题

  • Zookeeper 的依赖如下:
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
</dependency>
  • 如果我们直接添加这个依赖可能会出现以下问题,出现以下日志信息是说明出现 Zookeeper 版本冲突问题:

  • 原因是:上述依赖自带 3.5.3beta 的版本,与本地的下载的 zookeeper 版本对应不上;
  • 可以通过修改 pom.xml 文件解决;
  • 修改后的示例请见本篇《2.1 引入 pom.xml 依赖》;

1.6 Zookeeper 注册中心的实现原理

本篇或《12.1 使用 Apache Dubbo 实现远程通信》里的《5. Dubbo 使用 Zookeeper 作为注册中心》构建了如下示例;

  • 当 Dubbo 服务启动时:
    • 在 Zookeeper 服务器上的 /dubbo/com.dlhjw.dubbo.service.impl.TestServiceImpl/providers 目录下创建当前服务的 URL;
    • com.dlhjw.dubbo.service.impl.TestServiceImpl 表示发布服务的接口全路径名称;
    • providers 表示服务提供者的类型;
    • dubbo://ip:port 表示该服务发布的协议类型及访问地址;
    • URL 是临时节点,其他皆为持久化节点;
  • 当 Dubbo 服务消费者启动时:
    • /dubbo/com.dlhjw.dubbo.service.impl.TestServiceImpl/providers 目录下子节点注册 Watcher 监听;
    • 服务消费者会在 /dubbo/com.dlhjw.dubbo.service.impl.TestServiceImpl/consumers 目录下写入自己的 URL;
    • 服务消费者如果需要调用 TestServiceImpl 服务,它会先去 /dubbo/com.dlhjw.dubbo.service.impl.TestServiceImpl/providers 路径下获得所有该服务的提供方 URL 列表,然后通过负载均衡算法计算出一个地址进远程访问;

1.7 下面示例的相关说明

  • 客户端(在这里体现不出来)调用服务消费者http://consumer/zk 接口,请求消费者的资源;
  • 接着服务消费者通过负载均衡算法调用 http://zkcloud-provider/provider/zk 接口,请求服务提供者的资源;

2. 安装并运行 Zookeeper 服务器

基于 Win10 下的 Zookeeper 服务器安装;

2.1 下载 Zookeeper

2.2 修改配置

  • apache-zookeeper-3.7.0-bin\apache-zookeeper-3.7.0-bin\conf 目录下,备份一份 zoo_sample.cfg 配置文件;

  • tickTime:Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每隔 tickTime 时间就会发送一个心跳;
  • dataDir:Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里;
  • dataLogDir:Zookeeper 保存日志文件的目录;
  • clientPort:客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求;
  • 注意:查看 bin 目录下的 zkEvn.cmd 里的 JAVA_HOME 名字对不对;

2.3 运行 Zookeeper 服务器

  • 到 bin 目录下:
    • 双击 zkServer.cmd,会启动一个 java 进程,即服务端;
    • 双击 zkCli.cmd,会启动一个客户端 ;


3. 使用 Zookeeper 管理服务提供者

使用 Zookeeper 构建服务提供者大致与 Nacos 和 Consul 相同; Nacos 与 Consul 的构建方式详情请见《3.2 Alibaba Nacos 注册中心》与《3.4 HashiCorp Consul 注册中心》;

3.1 引入 pom.xml 依赖

<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
  <!--先排除自带的zookeeper-->
  <exclusions>
    <exclusion>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<!--添加zookeeper3.4.9版本,这里可根据自己本地的下载的 zookeeper 版本进行配置-->
<dependency>
  <groupId>org.apache.zookeeper</groupId>
  <artifactId>zookeeper</artifactId>
  <version>3.4.9</version>
</dependency>
  • Spring 管理的依赖自带 3.5.3beta 版本,与本地的下载的 zookeeper 版本对应不上,可以通过修改 pom 文件解决;

3.2 修改 boostrap.yml 配置文件

#8004表示注册到zookeeper服务器的支付服务提供者端口号
server:
  port: 8004
#服务别名----注册zookeeper到注册中心名称
spring:
  application:
    name: zkcloud-provider
  cloud:
    zookeeper:
      connect-string: 192.168.111.144:2181

3.3 在主启动类上添加注解

  • @EnableDiscoveryClient:使用其他组件(Nacos、zookeeper、Consul)作为注册中心;

3.4 编写业务类,并在 controller 层开放接口

这里编写一个简单接口仅作为示例;

@RestController
public class providerController{
    @Value("${server.port}")
    private String serverPort;

    @RequestMapping(value = "/provider/zk")
    public String providerzk(){
        return "springcloud with zookeeper: "+serverPort+"\t"+ UUID.randomUUID().toString();
    }
}

4. 使用 Zookeeper 管理服务消费者

使用 Zookeeper 构建服务消费者大致与 Nacos 和 Consul 相同; Nacos 与 Consul 的构建方式详情请见《3.2 Alibaba Nacos 注册中心》与《3.4 HashiCorp Consul 注册中心》;

4.1 引入 pom.xml 依赖

同服务提供者端的依赖;

<!-- SpringBoot整合zookeeper客户端 -->
<dependency>
  <groupId>org.springframework.cloud</groupId>
  <artifactId>spring-cloud-starter-zookeeper-discovery</artifactId>
  <!--先排除自带的zookeeper-->
  <exclusions>
    <exclusion>
      <groupId>org.apache.zookeeper</groupId>
      <artifactId>zookeeper</artifactId>
    </exclusion>
  </exclusions>
</dependency>
<!--添加zookeeper3.4.9版本-->
<dependency>
  <groupId>org.apache.zookeeper</groupId>
  <artifactId>zookeeper</artifactId>
  <version>3.4.9</version>
</dependency>

4.2 修改 boostrap.yml 配置文件

server:
  port: 80
spring:
  application:
    name: zkcloud-consumer
  cloud:
  #注册到zookeeper地址
    zookeeper:
      connect-string: 192.168.111.144:2181

4.3 主启动类上不需要添加额外注解

4.4 编写业务类,调用提供者接口

  • 由于我们使用 Ribbon + RestTemplate 的负载均衡策略,因此需要在 IoC 容器中添加一个 RestTemplate JavaBean;
  • 详情请见《4.1 基于 Ribbon 的负载均衡详解》;
  • 该 Bean 可以在主启动类中添加;也可以在主启动类所在包或子包的 config 包中添加,如下:
@Configuration
public class ApplicationContextBean{
    @Bean
    @LoadBalanced
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
}
  • 我们在 controller 层开放接口给客户端,并在该接口里调用提供者的 API;
@RestController
public class ComsumerZKController{
    public static final String INVOKE_URL = "http://zkcloud-provider";

    @Autowired
    private RestTemplate restTemplate;

    @RequestMapping(value = "/consumer/zk")
    public String paymentInfo(){
        String result = restTemplate.getForObject(INVOKE_URL+"/provider/zk", String.class);
        System.out.println("消费者调用提供者获取服务--->result:" + result);
        return result;
    }
}

5. Dubbo 使用 Zookeeper 作为注册中心

  • 详情请见:本系列另外一篇文章《12.1 使用 Apache Dubbo 实现远程通信》里的《5. Dubbo 使用 Zookeeper 作为注册中心》;


最后

新人制作,如有错误,欢迎指出,感激不尽!
欢迎关注公众号,会分享一些更日常的东西!
如需转载,请标注出处!

有关微服务架构 | 3.3 Apache Zookeeper 注册中心的更多相关文章

  1. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  2. 阿里云国际版免费试用:如何注册以及注意事项 - 2

    作为新的阿里云用户,您可以50免费试用多种优惠,价值高达1,700美元(或8,500美元)。这将让您了解和体验阿里云平台上提供的一系列产品和服务。如果您以个人身份注册免费试用,您将获得价值1,700美元的优惠。但是,如果您是注册公司,您可以选择企业免费试用,提交基本信息通过企业实名注册验证,即可开始价值$8,500的免费试用!本教程介绍了如何设置您的帐户并使用您的免费试用版。​关于免费试用在我们开始此试用之前,您还必须遵守以下条款和条件才能访问您的免费试用:只有在一年内创建的账户才有资格获得阿里云免费试用。通过此免费试用优惠,用户可以免费试用免费试用活动页面上列出的每种产品一次。如果您有多个帐

  3. ruby-on-rails - 设计注册确认 - 2

    我在我的项目中有一个用户和一个管理员角色。我使用Devise创建了身份验证。在我的管理员角色中,我没有任何确认。在我的用户模型中,我有以下内容:devise:database_authenticatable,:confirmable,:recoverable,:rememberable,:trackable,:validatable,:timeoutable,:registerable#Setupaccessible(orprotected)attributesforyourmodelattr_accessible:email,:username,:prename,:surname,:

  4. ruby - Ruby 和 Ruby on Rails 中的三层架构 - 2

    我是一名决定学习Ruby和RubyonRails的ASP.NETMVC开发人员。我已经有所了解并在RoR上创建了一个网站。在ASP.NETMVC上开发,我一直使用三层架构:数据层、业务层和UI(或表示)层。尝试在RubyonRails应用程序中使用这种方法,我发现没有关于它的信息(或者也许我只是找不到它?)。也许有人可以建议我如何在RubyonRails上创建或使用三层架构?附言我使用ruby​​1.9.3和RubyonRails3.2.3。 最佳答案 我建议在制作RoR应用程序时遵循RubyonRails(RoR)风格。Rails

  5. ruby-on-rails - 特征未注册 : attribute name - 2

    完成这个有困难。我正在使用seed.rb+factory_girl来使用rakedb:seed填充数据库。(我知道固定装置存在,但我想以这种方式完成,这只是一个示例,数据库将填充复杂的关联对象。)我的种子.rb:require'factory_girl_rails'["QM","CDC","SI","QS"].eachdo|n|FactoryGirl.create(:grau,nome:n)end还有我的/factories/graus.rbFactoryGirl.definedofactory:graudonomeendend但是当我运行时:rakedb:seed我得到:rakeab

  6. ruby-on-rails - Rails 4.1 Devise 3.3 列 users.password 不存在 - 2

    我想通过控制台手动创建一个用户:User.find_or_create_by(email:"user@mail.com",first_name:"Stan",last_name:"Smith",password:"password",password_confirmation:"password",confirmed_at:Time.now)我在过去的项目中多次这样做,但这次没有用。它没有获取Devise密码模型属性,所以这是我得到的错误:PG::UndefinedColumn:ERROR:columnusers.passworddoesnotexist我有Rails4.1.4和Dev

  7. ruby-on-rails - 带有自定义处理器的 CarrierWave 未注册 - 2

    我正在使用carrierwave上传视频然后有一个名为thumb的版本,带有自定义处理器,可以获取视频并使用streamio-ffmpeg创建屏幕截图。视频和文件都已正确上传,但在调用uploader.url(:thumb)时我得到:ArgumentError:Versionthumbdoesn'texist!VideoUploader.rbrequire'carrierwave/processing/mime_types'require'streamio-ffmpeg'classVideoUploader5)File.renamethumb_path,current_pathendd

  8. ruby-on-rails - 具有六边形架构和 DCI 模式的框架和数据库适配器 - 2

    我尝试用Ruby设计一个基于Web的应用程序。我开发了一个简单的核心应用程序,在没有框架和数据库的情况下在六边形架构中实现DCI范例。核心六边形中有小六边形和网络,数据库,日志等适配器。每个六边形都在没有数据库和框架的情况下自行运行。在这种方法中,我如何提供与数据库模型和实体类的关系作为独立于数据库的关系。我想在将来将框架从Rails更改为Sinatra或数据库。事实上,我如何在这个核心Hexagon中实现完全隔离的rails和mongodb的数据库适配器或框架适配器。有什么想法吗? 最佳答案 ROM呢?(Ruby对象映射器)。还有

  9. ruby-on-rails - 如何访问设计 token 授权注册 Controller ? - 2

    我正在使用Deviseauthtokengem用于验证我的Rails应用程序的某些部分。但是,当我尝试使用注册路径创建新用户时,出现以下错误{"errors":["Authorizedusersonly."]}。这是我用于测试的rspec代码,it'createsauserusingemail/passwordcombo'dopostapi_user_registration_path,{email:'xxx',password:'yyy',password_confirmation:'yyy'}putslast_response.bodyexpect(last_response.bo

  10. ruby - 如何将 Thor::Group 注册为带参数的子命令 - 2

    这道题开始于here.但随着我对雷神的了解越来越多,情况发生了很大变化。我正在尝试创建一个带参数的Thor::Group子命令。奇怪的是,如果没有参数,它就可以工作。我可以使用Thor::Group作为子命令吗?这在我输入时有效:foocounterfoo/bin/foomoduleFooclassCLI但是当我输入时这不起作用:foocounter5moduleFooclassCLI','Countupfromtheinput.')endclassCounter:numeric,:desc=>"Thenumbertostartcounting"desc"Prints2numbersb

随机推荐