从Paxos到Zookeeper:分布性一致性原理与实践(实践Zookeeper)

本文着重介绍如何部署一个Zookeeper以及如何顺利运行起来。以及可能遇到的问题和解决方法、客户端脚本的使用、Java客户端API使用。最后,介绍了ZkClient和Curator这两种开源客户端。

  • Zookeeper支持各种系统,需要配置Java环境。
  • Zookeeper具有两种运行模式:集群模式和单机模式。

目录

一. 部署与运行

1. 集群模式

2. 单机模式

3. 伪集群模式

4. 启动与停止

5. 常见异常

二. 客户端脚本

三. Java客户端API使用

1. 创建会话

2. 创建节点

3. 删除节点

4. 读取数据

5. 更新数据

6. 检测节点是否存在

7. 权限控制

四. 开源客户端

1. ZkClient

2. Curator

五. 小结


一. 部署与运行

1. 集群模式

如何使用三台机器搭建一个Zookeeper集群?

首先需要假设已经准备好三台互相联网的Linux机器,IP地址分别为{IP}_1{IP}_2{IP}_3

1. 准备配置Java环境,确保已经安装Java1.6或更高版本的JDK

2. 下载Zookeeper安装包,下载成功后,解压到一个目录里面,例如/opt/zookeeper-3.4.4/目录下,同时在下文中,我们使用%ZK_HOME%代表该目录。

3. 配置文件zoo.cfg:

初次使用Zookeeper,需要将%ZK_HOME%/conf目录下的zoo_sample.cfg文件重命名为zoo.cfg,并且按照以下代码进行简单配置。

关于Zookeeper的参数配置,在集群中的每台机器都需要感知到整个集群是由哪几台机器组成的,在配置文件中,可以进行这样的配置。(例如最后3行)其中,id用来标记该机器在集群中的序号。同时在每台机器上,我们都需要在数据目录下创建一个myid文件,该文件只有一行内容,并且是一个数字,对应于每台机器的Server ID数字。

在Zookeeper的设计中,集群中所有机器上 zoo.cfg文件的内容都应该是一致的。因此最好使用SVN或GIT把此文件管理起来,确保每个机器都能共享到一份相同的配置。

myid文件中只有一个Server ID代表自己的编号。注意每个机器的myid都不同,并且和自己所在机器的server.id的id值对应上。

4. 创建myid文件

在dataDir所配置的目录下,创建一个名为myid的文件,在该文件的第一行写上自身编号。

5. 按照相同步骤,为所有机器都配置好zoo.cfg和myid文件。

6. 启动服务器

至此,所有选项已经配置完毕,使用%ZK_HOME%/bin目录下的zkServer.sh脚本进行服务器的启动,如下:

7. 验证服务器

启动完成后,可以使用如下命令来检查服务器启动是否正常:

 上面就是使用Telnet方式,使用stat命令进行服务器启动的验证,如果出现和上面类似的输出信息,就说明服务器已经正常启动了。

2. 单机模式

上文集群模式中,已经完成了一个Zookeeper集群的搭建了。幸运的是,Zookeeper支持单机部署,只要启动一台Zookepper机器,就可以提供正常服务了。

其实,单机模式只是一种特殊的集群模式--只有一台机器的集群。单机模式的部署步骤和集群模式的部署步骤基本一致,只是在zoo.cfg文件的配置上有些差异。(唯一的区别就在列表上)在单机模式的文件中,只有server.1这一项。修改完这个文件后,就可以启动服务器了。

3. 伪集群模式

上文中,集群和单机两种模式下,基本完成了分别针对生产环境和开发环境Zookeeper服务的搭建,已经可以满足绝大多数场景了。

但是还是有一种情况就是:手上有且只有一个比较好的机器,如果作为单机模式部署,资源明显有点浪费;如果想按照集群模式来部署的话,需要借助硬件上的虚拟化技术,把一台物理机器转换成几台虚拟机,不过这样操作成本太高。所幸,和其他分布式系统(例如Hadoop)一样,Zookeeper也允许你在一台机器上完成一个伪集群的搭建。

伪集群,用一句话说就是,集群所有的机器都在同一台机器上,但是还是以集群的特性对外提供服务。这种模式和集群模式非常类似,只是把zoo.cfg做了如下修改

在zoo.cfg配置中,每一行的机器列表配置都是同一个IP地址,但是后面的端口配置已经不一样了。这其实不难理解,在同一台机器上启动多个进程,就必须绑定不同的接口。

4. 启动与停止

本部分重点说明如何启动与停止Zookeeper服务,如何解决在Zookeeper服务启动阶段出现的常见异常问题。

启动服务

  • Java命令行启动Zookeeper服务
  • 使用Zookeeper自带的启动脚本来启动Zookeeper

停止服务

  • 使用zkServer脚本的stop命令来完成

5. 常见异常

在启动的时候,通常会碰到一些异常。

1. Address already in use:端口被占用

2. No space left on device:磁盘没有剩余空间(通常做法是清理磁盘,加上用量监控及日志自动清理)

3. Invalid config,exiting abnormally:无法找到myid文件,创建一个即可

4. Cannot open channel to 2 at election address /122.228.242.241:3888:集群中其它机器Leader选举端口未开(由于虽然当前机器启动了,但是其他机器还未启动完成,因此无法和其他机器在相应端口上进行连接),需要快速启动其他机器。 

  现在就已经搭建起了一个能够正常运行的Zookeeper集群了

二. 客户端脚本

现在已经搭建起了一个能够正常运行的Zookeeper集群了,接下来学习如何使用客户端对Zookeeper进行操作。

首先客户端需要连接上本地的Zookeeper服务器:进入到Zookeeper的bin目录后,直接执行:sh zkCli.sh 就成功连接上本地的Zookeeper服务器。

1. 使用create命令创建一个Zookeeper节点

2. 使用ls或get命令进行读取

3. 使用set命令,更新指定节点的内容

4. 使用delete命令删除Zookeeper上的指定节点

三. Java客户端API使用

Zookeeper作为一个分布式系统框架,主要用来解决分布式数据一致性问题,提供了简单的分布式原语,并且对多种编程语言提供了API。

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)

1. 创建会话

客户端可以通过创建一个ZooKeeper(org.apache.zookeeper.Zookeeper)实例来连接ZooKeeper服务器。Zookeeper的四种构造方法如下:

使用任意一个构造方法都可以顺利完成与Zookeeper服务器的会话(Session)创建,每个参数的说明:

  • connectString
    • 类型:String
    • 描述:指定Zookeeper集群的连接字符串,包括一个或多个Zookeeper服务器的地址
    • 格式:host1:port1,host2:port2,...
    • 作用:
      • 用于指定Zookeeper集群的服务器列表,客户端会根据这些地址尝试连接Zookeeper服务器。
      • 典型的用法是提供多个服务器地址,以确保高可用性,即使某些服务器不可用,客户端仍能连接到集群中的其他服务器。
  • sessionTimeout
    • 类型:int
    • 描述:会话超时时间,以毫秒为单位
    • 作用:
      • 用于设置客户端与Zookeeper服务器之间的会话超时时间
      • 如果客户端在这个时间内没有与Zookeeper服务器进行任何通信,Zookeeper将认为客户端已经断开连接,并清除该客户端的会话信息(包括任何临时节点)
  • watcher
    • 类型:Watcher
    • 描述:一个实现了Watcher接口的对象,用于处理来自Zookeeper服务器的通知
    • 作用:
      • 当Zookeeper服务器上被监视的节点发生变化时,会触发Watcher回调
      • Watcher机制是Zookeeper的一个重要特性,用于实现配置变更通知、节点变动监控等功能

扩展构造方法

Zookeeper还提供了扩展的构造方法,以支持更多高级配置,如连接超时设置、重试策略等。以下是一个扩展构造方法的例子:

public ZooKeeper(String connectString, int sessionTimeout, Watcher watcher, boolean canBeReadOnly)
  • canBeReadOnly
    • 类型:boolean
    • 描述:指定客户端是否可以进入只读模式
    • 作用:
      • 在集群不可用的情况下,客户端可以选择进入只读模式,从而仍然能够读取数据,但不能进行写操作
  • sessionId
    • 类型:long
    • 描述:用于恢复以前的会话
    • 作用
      • 当客户端断开连接后重新连接到Zookeeper服务器时,可以使用以前的会话ID重新加入会话
  • sessionPasswd
    • 类型:byte[]
    • 描述:会话密码,用于会话恢复
    • 作用:
      • 与sessionId一起使用,确保会话的安全恢复

小结

Zookeeper的构造方法参数用于指定的连接信息,会话超时时间和时间处理机制等,通过这些参数,客户端可以灵活的配置和连接到Zookeeper集群,使用Watcher机制处理节点变更事件,并支持高级功能如会话恢复和只读模式

2. 创建节点

客户端可以通过Zookeeper的API来创建一个数据节点,有两个接口。这两个接口分别以同步和异步方式创建节点,以满足不同的应用场景。同步方式适用于需要立即获取结果的简单场景,代码简单但可能会阻塞线程。异步方式适用于需要高并发处理的复杂场景,性能更优但是代码复杂度较高。选择哪种方式取决于具体的应用需求和系统设计。

Zookeeper中创建会话和创建节点的主要区别是,创建会话是初始化客户端与服务器连接的过程,而创建节点是实际在Zookeeper中存储数据和实现分布式协调的操作。会话是所有操作的基础,而节点创建是具体的应用场景。

3. 删除节点

客户端可以用Zookeeper的API来删除一个节点,有两个接口,这两个API分别是同步和异步的删除接口。Zookeeper中,只允许删除叶子节点。也就是说,如果一个节点存在至少一个子节点的话,那么该节点无法被直接删除,必须先删除点其所有子节点。

4. 读取数据

读取数据,包括子节点列表的获取和节点数据的获取。Zookeeper分别提供了不同的API来获取数据。客户端可以通过Zookeeper的API来获取一个节点的所有子节点,即getChildren,有如下8个接口可以供使用:

其中包含了同步和异步的接口。

5. 更新数据

客户端可以通过Zookeeper的API来更新一个节点的数据内容,有如下两个接口,也是分别为同步和异步方式。setData

6. 检测节点是否存在

客户端可以通过Zookeeper的API来检测一个接口是否存在,这四个接口也是分别用同步和异步的方式来检测的:

该接口主要用于检测节点是否存在,返回值是一个stat对象。另外,如果在调用接口时注册Watcher的话,还可以对节点是否存在进行监听,一旦节点被创建、被删除或是数据被更新,都会通知客户端。

7. 权限控制

在Zookeeper的实际使用中,我们的做法往往是搭建一个共用的Zookeeper集群,统一为若干个应用提供服务。在这种情况下,不同的应用之间往往是不会存在共享数据的使用场景的,因此需要解决不同应用之间的权限问题。

Zookeeper提供了ACL的权限控制机制,简单的讲,就是通过设置Zookeeper服务器上的数据节点的ACL,来控制客户端对该数据节点的访问权限:如果一个客户端符合该ACL控制,那么就可以对其进行访问,否则无法操作。

针对这样的控制机制,Zookeeper提供了多种权限控制模式(Scheme),分别为world、auth、digest、ip和super

开发人员如果要使用Zookeeper的权限控制功能,需要在完成Zookeeper会话创建后,给该会话添加上相关的权限信息(AuthInfo)。Zookeeper客户端提供了相应的API接口来进行权限信息的设置,即addAuthInfo(String sheme, byte auth) -> 为当前的Zookeeper会话添加权限信息。

四. 开源客户端

上一节中,讲解了如何使用Zookeeper的Java客户端来进行一些基本操作,如创建会话、创建节点、读取数据、更新数据、删除节点和检测节点是否存在等。本节中,我们将围绕ZkClient和Curator这两个开源的Zookeeper客户端产品,来进一步看看如何更好地使用Zookeeper

1. ZkClient

ZkClient是一个更易用的Zookeeper客户端。同时,ZkClient在内部实现了诸如Session超时重连、Watcher反复注册等功能,使得Zookeeper客户端的这些繁琐细节对开发者透明。

本节中将从创建会话、创建节点、读取数据、更新数据、删除节点和检测节点是否存在等方面来介绍如何使用ZkClient这个Zookeeper客户端。

(1) 首先添加ZkClient的Maven依赖

(2) 创建会话:使用ZkClient接口创建会话,很多参数与Zookeeper的原生参数一致(在使用Zookeeper原生API创建会话的时候,是一个异步的过程,开发人员需要自己等待处理。而ZkClient通过内部包装,将这个异步的会话创建过程同步化了,对于开发者更为方便)

(3) Zookeeper的节点内容只支持字符数组(byte[]),不负责为节点内的内容序列化,需要开发人员自己使用序列化工具进行序列化和反序列化。而ZkClient实现了一个接口,允许用户传入一个序列化实现,例如Hessian或Kryo,默认情况下,ZkClient使用Java自带的默认序列化方式进行对象的序列化)

(4) ZkClient与原生Zookeeper的最大区别,就是在ZkClient的构造方法中,不再提供传入Watcher对象的参数了。那么,ZkClient如何去监听服务端的相关事宜呢?ZkClient引入了大多数Java程序都使用过的Listener来实现Watcher注册,这样的用法更贴近Java工程师的开发习惯。

(5) 创建节点:原生接口只允许传入字符数组作为对象,而ZkClient由于支持自定义序列化器,可以传入复杂对象作为参数。ZkClient将各种不同节点的创建使用不同的方法名字进行区分,在创建节点的时候,不需要再去判定父节点是否存在并进行一系列的相关操作,而是可哟通过createParents参数,在内部递归建立父节点。此外,还支持异步创建节点的方法。

(6) 删除节点:原生Zookeeper只允许删除叶子结点。但是真正的产品使用中,我们的节点层级往往比较复杂,通常在4层左右。如果每层都逐层遍历来删除节点,会非常繁琐。在ZkClient中,deleteRecursive这个接口将自动帮我们完成逐层遍历删除节点的工作,为开发人员带来便利。

(7) 读取数据:在ZkClient中,可以通过getChildren获取指定节点的子节点列表,ZkClient只提供了一个对外API,用于获取指定节点的子节点列表。这个API的返回值是子节点的相对路径。在ZkClient中,可以使用readData来获取指定节点的数据内容。

  • 和Zookeeper原生API相比,ZkClient提供的API没有了Watcher注册的功能,在“创建会话”部分中我们已经提到,ZkClient中引入了Listener的概念,客户端可以通过注册相关的时间监听来实现对Zookeeper服务端事件的订阅。在获取子节点接口列表这个接口上,可以通过subscribeChildChanges接口来进行注册监听,传入的IZkChildListener接口,只有一个接口方法handleChildChange,用来处理服务端发送过来的事件通知。
  • 另外,和Zookeeper原生提供的Watcher不同的是,ZkClient的Listener不是一次性的,客户端只需要注册一次就会一直生效
  • 客户端可以对一个不存在的节点进行子节点变更的监听
  • 一旦客户端对一个节点注册了子节点列表变更监听之后,当该节点的子节点列表发生变更的时候,服务端都会通知客户端,并将最新的子节点列表发送给客户端
  • 该节点本身的创建或删除也会通知到客户端

(8) 更新数据:在ZkClient中,可以通过writeData这个API来更新指定节点的数据。可以通过exists这个API来检测指定节点是否存在。

2. Curator

和ZkClient一样,Curator解决了很多Zookeeper客户端非常底层的细节开发共工作,包括连接重连、反复注册Watcher和NodeExistsException异常等,是全世界使用最广泛的Zookeeper客户端之一。

除了封装一些开发人员不需要关注的底层细节之外,Curator还在Zookeeper的原生API的基础上进行包装,提供了一套易用性和可读性更强的Fluent风格的客户端API框架。

除此之外,Curator中还提供了Zookeeper各种应用场景(Recipe,如共享锁服务、Master选举机制和分布式计数器)的抽象封装。

(1) 首先要添加Curator的Maven依赖

(2) 创建会话:

Curator提供的API接口在设计上最大的亮点在于其遵循了Fluent的设计风格

  • 使用CuratorFrameworkFactory这个工厂类的两个静态方法来创建一个客户端
  • 通过调用CuratorFramework中的start方法来启动会话(在重试策略上,Curator通过一个接口RetryPolicy来让用户实现自定义的重试策略)
  • 使用Curator创建会话

(3) 创建节点

  • 初始内容为空的节点:client.create().forPath(path);
  • 附带初始内容的节点:client.create().forPath(path,"init",getBytes());

(4) 删除节点

  • 只能删除叶子结点:client.delete().forPath(path);
  • 还具有递归删除所有叶子结点、保障措施保证只要客户端会话有效会在后台持续进行删除操作,直到节点删除成功(这样可以保证只要客户端会话有效,断网等各种情况下还会继续重试,直到节点删除成功)。

(5) 读取数据

  • 读取一个节点的数据内容:client.getData().forPath(path);
  • 还有读取一个节点的数据内容,同时获取到该节点的stat等操作

(6) 更新数据

  • 更新一个节点的数据内容:client.setData().forPath(path);

Curator引入了BackgroundCallback接口,用来处理异步接口调用之后服务端返回的结果信息。

五. 小结

本章主要围绕Zookeeper服务的使用展开,对Zookeeper的基本使用方式进行了全面的讲解。首先,分别从集群、单机和伪集群三种模式介绍如何部署与运行一个可用的Zookeeper服务,同时介绍了在Zookeeper服务部署与运行过程中的系统环境配置以及对于常见异常问题的解决。接下来,主要围绕Zookeeper自带的客户端脚本,就Zookeeper服务的基本使用方式向读者进行了介绍。最后,通过对Zookeeper提供的Java API接口以及开源客户端ZkClient和Curator的分别讲解以及源代码示例的展示,帮助读者更好地在Java应用程序中使用Zookeeper服务。

  • 30
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值