目录

zk入门

zk官网

ZooKeeper介绍

简介

ZooKeeper is a centralized service for maintaining configuration information, naming, providing distributed synchronization, and providing group services. All of these kinds of services are used in some form or another by distributed applications. Each time they are implemented there is a lot of work that goes into fixing the bugs and race conditions that are inevitable. Because of the difficulty of implementing these kinds of services, applications initially usually skimp on them, which make them brittle in the presence of change and difficult to manage. Even when done correctly, different implementations of these services lead to management complexity when the applications are deployed.

ZooKeeper是用于维护配置信息,命名,提供分布式同步以及提供组服务的集中式服务。所有这些类型的服务都以某种形式被应用在其他分布式应用程序中。每次这些类型的服务被实施起来时都要进行大量的工作来修复不可避免的bug以及竞争条件。由于实现这类服务的困难程度,应用程序通常最初会跳过它们,这也导致了应用程序在变更时表现地很脆弱以及难以管理。即使一切都正确,这些服务的不同实现也会导致在部署应用程序时管理非常复杂。

Learn more about ZooKeeper on the ZooKeeper Wiki.

它是拿来管理大象(Hadoop)、蜜蜂(Hive)、小猪(Pig)的管理员,Apache Hbase和Apache Solr以及阿里的Dubbo等项目都用到了Zookeeper。

从设计模式的角度来理解,zk是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应,从而实现集群中类似Master/Slave管理模式。

一句话:Zookeeper是一个分布式服务框架,是Apache Hadoop的一个子项目,它主要是用来解决分布式应用中经常遇到的一些数据管理问题,如统一命名服务、集群管理、分布式应用配置项的管理等。

zookeeper = 文件系统 + 通知机制

更直观地解释:

  • zk是一个数据库
  • zk是一个拥有文件系统特点的数据库
  • zk是一个解决了数据一致性问题的分布式数据库
  • zk是一个具有发布和订阅功能的分布式数据库

Google的三篇论文一直是分布式领域传阅的经典

  • MapReduce:Hadoop
  • GFS:HDFS
  • BigTable:HBase

这三篇论文里都提及了Google的一个lock service——Chubby,于是有了Zookeeper

ZooKeeper最为主要的使用场景,是作为分布式系统的分布式协同服务。在学习zookeeper之前,先要对分布式系统的概念有所了解,否则你将完全不知道zookeeper在分布式系统中起到了什么作用,解决了什么问题。

分布式系统面临的问题

我们将分布式系统定义为:分布式系统是同时跨越多个物理主机,独立运行的多个软件所组成系统。类比一下,分布式系统就是一群人一起干活。人多力量大,每个服务器的算力是有限的,但是通过分布式系统,由n个服务器组成起来的集群,算力是可以无限扩张的。

优点显而易见,人多干活快,并且互为备份。但是缺点也很明显。我们可以想象一下,以一个小研发团队开发软件为例,假设我们有一个5人的项目组,要开始一个系统的开发,项目组将面临如下问题:

http://img.cana.space/picStore/20201107135848.png

你一定在想,以上这些问题很简单啊,在我的日常工作中天天都在发生,并没感觉有什么复杂。是的,这是因为我们人类的大脑是个超级计算机,能够灵活应对这些问题,而且现实中信息的交换不依赖网络,不会因网络延迟或者中断,出现信息不对等。而且现实中对以上问题的处理其实并不严谨,从而也引发了很多问题。想一想,项目中是不是出现过沟通不畅造成任务分配有歧义?是否由于人员离职造成任务进行不下去,甚至要联系离职人员协助?是不是出现过任务分配不合理?类似这样的各种问题,肯定会发生于你的项目组中。在现实世界,我们可以人为去协调,即使出错了,人工去补错,加加班搞定就好。但在计算机的世界,这样做是行不通的,一切都要保证严谨,以上问题要做到尽可能不要发生。因此,分布式系统必须采用合理的方式解决掉以上的问题。

实际上要想解决这些问题并没有那么复杂,我们仅需要做一件事就可以万事无忧—让信息在项目组成员中同步。如果能做到信息同步,那么每个人在干什么,大家都是清楚的,干到什么程度也是清晰的,无论谁离职也不会产生问题。分配的工作,能够及时清晰的同步给每个组员,确保每个组员收到的任务分配没有冲突。

分布式系统的协调工作就是通过某种方式,让每个节点的信息能够同步和共享。这依赖于服务进程之间的通信。通信方式有两种:

通过网络进行信息共享

这就像现实世界,开发leader在会上把任务传达下去,组员通过听leader命令或者看leader的邮件知道自己要干什么。当任务分配有变化时,leader会单独告诉组员,或者再次召开会议。信息通过人与人之间的直接沟通,完成传递。

通过共享存储

这就好比开发leader按照约定的时间和路径,把任务分配表放到了svn上,组员每天去svn上拉取最新的任务分配表,然后干活。其中svn就是共享存储。更好一点的做法是,当svn文件版本更新时,触发邮件通知,每个组员再去拉取最新的任务分配表。这样做更好,因为每次更新,组员都能第一时间得到消息,从而让自己手中的任务分配表永远是最新的。此种方式依赖于中央存储。整个过程如下图所示:

http://img.cana.space/picStore/20201107140441.png

ZooKeeper如何解决分布式系统面临的问题

ZooKeeper对分布式系统的协调,使用的是第二种方式,共享存储。其实共享存储,分布式应用也需要和存储进行网络通信。网络通信是分布式系统并发设计的基础。

实际上,通过ZooKeeper实现分布式协同的原理,和项目组通过SVN同步工作任务的例子是一样的。ZooKeeper就像是svn,存储了任务的分配、完成情况等共享信息。每个分布式应用的节点就是组员,订阅这些共享信息。当主节点(组leader),对某个从节点的分工信息作出改变时,相关订阅的从节点得到zookeeper的通知,取得自己最新的任务分配。完成工作后,把完成情况存储到zookeeper。主节点订阅了该任务的完成情况信息,所以将得到zookeeper的完工的通知。参考下图,回味一下,是不是和前面项目组通过svn分配工作的例子一模一样?仅仅是把svn和邮件系统合二为一,以ZooKeeper代替。

http://img.cana.space/picStore/20201107141119.png

注:Slave节点要想获取ZooKeeper的更新通知,需事先在关心的数据节点上设置观察点。

一句话:主节点分配任务并订阅任务完成情况,从节点分配任务并处理任务,从节点完成任务时主节点会收到通知。

大多数分布式系统中出现的问题,都源于信息的共享出了问题。如果各个节点间信息不能及时共享和同步,那么就会在协作过程中产生各种问题。ZooKeeper解决协同问题的关键,在于保证分布式系统信息的一致性。

ZK核心概念

ZooKeeper并不直接暴露分布式服务所需要的原语及原语的调用方法。什么是原语?举个例子,比如说分布式锁机制是一个原语,它会暴露出创建、获取、释放三个调用方法。ZooKeeper以类似文件系统的方式存储数据,暴漏出调用这些数据的API。让应用通过ZooKeeper的机制和API,自己来实现分布式相关原语。

我们若想让应用能够通过ZooKeeper实现分布式协同,那么第一件事就是了解ZooKeeper的特性及相关概念,另外熟悉它给我们提供了哪些API。

znode

znode = path + nodevalue + stat,znode存储的数据大小限制为1M。

第一章讲过Zookeeper会保存任务的分配、完成情况,等共享信息,那么ZooKeeper是如何保存的呢?在ZooKeeper中,这些信息被保存在一个个数据节点上,这些节点被称为znode。它采用了类似文件系统的层级树状结构进行管理。见下图示例:

http://img.cana.space/picStore/20201107142156.png

根节点/包含4个子节点,其中三个拥有下一级节点。有的叶子节点存储了信息。

节点上没有存储数据,也有着重要的含义。比如在主从模式中,当/master节点没有数据时,代表分布式应用的主节点还没有选举出来。

znode节点存储的数据为字节数组。存储数据的格式zookeeper不做限制,也不提供解析,需要应用自己实现。

实际上图就是主从模式存储数据的示例,这里先简单讲解:

  • /master,存储了当前主节点的信息
  • /workers,下面的每个子znode代表一个从节点,子znode上存储的数据,如“foo.com:2181”,代表从节点的信息。
  • /tasks,下面的每个子znode代表一个任务,子znode上存储的信息如“run cmd”,代表该任务内容
  • /assign,下面每个子znode代表一个从节点的任务集合。如/assign/worker-1,代表worker-1这个从节点的任务集合。/assign/worker-1下的每个子znode代表分配给worker-1的一个任务。

znode节点类型

  • persistent 持久化目录节点

  • persistent_sequential 持久化顺序编号目录节点

    演示:

    1
    2
    3
    4
    5
    6
    7
    8
    
    [zk: localhost:2181(CONNECTED) 20] create -s /seq
    Created /seq0000000002
    [zk: localhost:2181(CONNECTED) 21] create -s /seq
    Created /seq0000000003
    [zk: localhost:2181(CONNECTED) 22] create -s /seq
    Created /seq0000000004
    [zk: localhost:2181(CONNECTED) 23] ls /
    [lock, seq0000000002, seq0000000003, seq0000000004, test, zookeeper]
    
  • ephemeral 临时目录节点

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # 创建临时节点
    [zk: localhost:2181(CONNECTED) 33] create -e /eph 1
    Created /eph
    # 关闭会话
    quit
    # 再次连接,可以发现已经没有临时节点了
    $ ./zkCli.sh
    [zk: localhost:2181(CONNECTED) 0] ls /
    [zookeeper]
    
  • ephemeral_sequential 临时顺序编号目录节点

    编号:当客户端创建这个节点时,会根据parent-znode的zxid状态,为这个节点编写一个全目录唯一的编号(这个编号只会一直增长)

    1
    2
    3
    4
    5
    6
    
    [zk: localhost:2181(CONNECTED) 1] create -es /es 1
    Created /es0000000006
    [zk: localhost:2181(CONNECTED) 2] create -es /es 1
    Created /es0000000007
    [zk: localhost:2181(CONNECTED) 3] ls /
    [es0000000006, es0000000007, zookeeper]
    
  • 容器节点(Container Nodes)

    容器节点会被定时扫描,当没有子节点的时候,容器节点会被删除;

  • TTL节点(TTL Nodes)

    过了TTL指定的时间,会被删除;

持久节点(persistent)和临时节点(ephemeral)

持久节点只能通过delete删除。临时节点在创建该节点的客户端崩溃或关闭时,自动被删除。

前面例子中的/master应该使用临时节点,这样当主节点失效或者退出时,该znode被删除,其他节点知道主节点崩溃了,开始进行选举的逻辑。另外/works/worker-1也应该是临时节点,在此从节点失效的时候,该临时节点自动删除。

在目前的版本,由于临时znode会因为创建者会话过期被删除,所以不允许临时节点拥有子节点。

有序节点

znode可以被设置为有序(sequential)节点。有序znode节点被分配唯一一个单调递增的证书。如果创建了个一有序节点为/workers/worker-,zookeeper会自动分配一个序号1,追加在名字后面,znode名称为/workers/worker-1。通过这种方式,可以创建唯一名称znode,并且可以直观的看到创建的顺序。

znode支持的操作及暴露的API

  • create /path data

    创建一个名为/path的znode,数据为data。

  • delete /path

    删除名为/path的znode。

  • exists /path

    检查是否存在名为/path的znode

  • setData /path data

    设置名为/path的znode的数据为data

  • getData /path

    返回名为/path的znode的数据

  • getChildren /path

    返回所有/path节点的所有子节点列表

数据结构

Znode维护了一个stat结构,这个stat结构包含数据变化的版本号、访问控制列表变化、时间戳。版本号和时间戳一起,可以让Zookeeper验证缓存和协调更新。每次znode的数据发生了变化,版本号就增加。

例如,无论何时客户端检索数据,它也一起检索数据的版本号。并且当客户端执行更新和删除时,客户端必须提供它正在改变的znode版本号。如果它提供的版本号和真实的数据版本号不一致,更新将会失败。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
[zk: localhost:2181(CONNECTED) 19] get -s /lock
1
cZxid = 0xd
ctime = Sat Nov 07 19:10:13 CST 2020
mZxid = 0xd
mtime = Sat Nov 07 19:10:13 CST 2020
pZxid = 0xd
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 1
numChildren = 0
  • cZxid

    引起这个znode创建的zxid,创建节点的事务的zxid(ZooKeeper Transaction id)

    每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID。事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生。

  • ctime

    znode被创建的毫秒数(从1970年开始)

  • mZixd

    znode最后更新的zxid

  • mtime

    znode最后修改的毫秒数(从1970年开始)

  • pZxid

    znode最后更新的子节点zxid

  • cversion

    znode子节点变化号,znode子节点修改次数

  • dataVersion

    znode数据变化号

  • aclVersion

    znode访问控制列表变化号,acl access control list

    acl一般不设置,和redis不设置密码一回事,既然用户已经登录操作系统就被认为是可信用户。

  • ephemeralOwner

    临时Owner,如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0.

  • dataLength

    znode的数据长度

  • numChildren

    znode子节点数量

观察与通知

分布式应用需要及时知道zookeeper中znode的变化,从而了解到分布式应用整体的状况,如果采用轮询方式,代价太大,绝大多数查询都是无效的。因此,zookeeper采用了通知的机制。客户端向zookeeper请求,在特定的znode设置观察点(watch)。当该znode发生变化时,会触发zookeeper的通知,客户端收到通知后进行业务处理。观察点触发后立即失效。所以一旦观察点触发,需要再次设置新的观察点。

我们使用Zookeeper不能期望能够监控到节点每次的变化。思考如下场景:

  1. 客户端C1设置观察点在/tasks
  2. 观察点触发,C1处理自己的逻辑
  3. C1设置新的观察点前,C2更新了/tasks
  4. C1处理完逻辑,再次设置了观察点。

此时C1不会得到第三步的通知,因此错过了C2更新/tasks这次操作。要想不错过这次更新,C1需要在设置监视点前读取/tasks的数据,进行对比,发现更新。

再如下面的场景:

  1. 客户端C1设置观察点在/tasks
  2. /tasks上发生了连续两次更新
  3. C1在得到第一次更新的通知后就读取/tasks的数据
  4. 此时第二次更新也已经发生,C1用第一次的通知,读取到两次更新后的数据

此时C1虽然错过了第二次通知,但是C1最终还是读取到了最新的数据。

因此Zookeeper只能保证最终的一致性,而无法保证强一致性。

zookeeper可以定义不同的观察类型。例如观察znode数据变化,观察znode子节点变化,观察znode创建或者删除。

版本

每个znode都有版本号,随着每次数据变化自增。setData和delete,以版本号作为参数,当传入的版本号和服务器上不一致时,调用失败。当多个zookeeper客户端同时对一个znode操作时,版本将会起到作用,假设c1,c2同时往一个znode写数据,c1先写完后版本从1升为2,但是c2写的时候携带版本号1,c2会写入失败。

法定人数

zookeeper服务器运行于两种模式:独立模式和仲裁模式(集群)。仲裁模式下,会复制所有服务器的数据树。但如果让客户端等待所有复制完成,延迟太高。这里引入法定人数概念,指为了使zookeeper集群正常工作,必须有效运行的服务器数量。同时也是服务器通知客户端保存成功前,必须保存数据的服务器最小数。例如我们有一个5台服务器的zookeeper集群,法定人数为3,只要任何3个服务器保存了数据,客户端就会收到确认。只要有3台服务器存活,整个zookeeper集群就是可用的。

下图展示了客户端提交请求到收到回复的过程:

http://img.cana.space/picStore/20201107143929.png

法定人数需要大于服务器数量的一半。也称为多数原则。举个例子说明,假如集群有5台服务器,法定人数为2,那么有2台服务器参与复制即可,若这2台server刚刚复制完/z这个znode,就挂掉了。此时剩下了3台server,大于法定人数2,所以zookeeper认为集群正常,但这三台服务器是无法发现/z这个znode的。如果法定人数大于服务器数量一半,那么法定人数复制完成,就可以确保集群存活时,至少有一台服务器有最新的znode,否则集群认为自己已经崩溃。

举个例子,如果有5个server ,其中三个复制了/z Znode,其中两个复制过的挂掉了。导致剩下的3个server只有一个有/z Znode,那么他会将/z Znode复制给其他2个完成同步,实现最终一致性。

下面两个例子阐明了,为何要遵循多数原则。

下图展示了5台server,法定人数为3,在确保zookeeper集群存活的前提下,最坏的情况挂了2台server(剩余及器数量3>=法定人数3),zookeeper是如何能确保数据完备,集群继续工作的。

http://img.cana.space/picStore/20201107144435.png

接下来两张图展示了5台server,未遵循多数原则,法定人数设为2。同样挂了两台server时,为什么zookeeper集群会出问题。

首先,客户端发起请求,2个server复制数据后即返回客户端接收成功。

http://img.cana.space/picStore/20201107144723.png

就在此刻,很不幸,在继续同步更新给其他节点前,刚刚两个复制了数据的节点挂了。此时会怎样呢?如下图:

http://img.cana.space/picStore/20201107144740.png

可以看到创建/z的操作在zookeeper集群中丢失了。

相信通过以上讲解,你已经能够理解为什么法定人数一定要多于一半服务器的数量。

此外,我们要尽量选用奇数个服务器,这样集群能容忍崩溃服务器占比更大,性价比更高。例如4台服务器的集群,法定人数最少为3,那么只能允许1台服务器崩溃,也就是仅允许25%的机器崩溃。而5台服务器的集群,法定人数最少也是3,但是此时允许2台服务器崩溃。换句话讲,40%的机器崩溃后还能工作。

仲裁模式下,负载均衡通过客户端随机选择连接串中的某个服务器来实现。

会话

客户端对zookeeper集群发送任何请求前,需要和zookeeper集群建立会话。客户端提交给zookeeper的所有操作均关联在一个会话上。当一个会话因某种原因终止时,会话期间创建的临时节点将会消失。而当当前服务器的问题,无法继续通信时,会话将被透明的转移到另外一台zookeeper集群的服务器上。

会话提供了顺序保障。同一个会话中的请求以FIFO顺序执行。并发会话的FIFO顺序无法保证。

会话状态和生命周期

会话状态有:connecting、connected、closed、not_connected

创建会话时,需要设置会话超时这个重要的参数。如果经过时间t后服务接受不到这个会话的任何消息,服务就会声明会话过期。客户端侧,t/3时间未收到任何消息,客户端向服务器发送心跳消息,2t/3时间后,客户端开始寻找其他服务器。此时他有t/3的时间去寻找,找不到的话,会话失效。

重连服务器时,只有更新大于客户端的服务器才能被连接,以免连接到落后的服务器。zookeeper中通过更新建立的顺序,分配事务标识符。只有服务器的事物标识符大于客户端携带的标识符时,才可连接。

zookeeper特点

  • 使用简单

    ZooKeeper允许分布式程序通过一个类似于标准文件系统的共享的层次化名称空间来相互协调。名称空间由数据寄存器(称为znode)组成,在ZooKeeper中,它们类似于文件和目录。

  • 高吞吐低延迟

    与为存储而设计的典型文件系统不同,ZooKeeper数据保存在内存中,这意味着ZooKeeper可以达到高吞吐量和低延迟数

  • 同步与复制

    组成ZooKeeper服务的服务器必须互相有感知。客户端连接到一个ZooKeeper服务器。客户端维护一个TCP连接,通过它发送请求、获取响应、获取观察事件和发送心跳。如果连接到服务器的TCP连接中断,客户端将连接到另一个服务器。

  • 有序

  • 在进行大量读操作时,运行速度奇快

zookeeper与eureka浅谈

一个分布式系统不可能同时满足C(一致性)、A(可用性)和P(分区容错性)

zookeeper优先保证CP,当服务发生故障会进行leader的选举,整个期间服务处在不可用状态,如果选举时间过长势必会大幅度降低性能,另外就用途来说zookeeper偏向于服务的协调,当然含有注册中心的作用

eureka优先保证AP, 即服务的节点各个都是平等的,没有leader不leader一说, 当服务发生故障时,其余的节点仍然可以提供服务,因此在出现故障时,性能表现优于zookeeper,但是可能会造成数据不一致的情况。

Zookeeper使用场景

命名服务

NameService 如 Dubbo服务注册中心

  1. 在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源和服务的地址,提供者等信息;
  2. 被命名的实体通常可以是集群中的机器,提供的服务器地址,远程对象等等–这些我们都可以统称他们为名字(Name),其中较为常见的就是一些分布式服务框架中的服务地址列表,通过调用zk提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为一个名称。阿里巴巴集团开源的分布式服务框架Dubbo就是使用zookeeper来作为其命名服务,维护全局的服务器地址列表;

http://img.cana.space/picStore/20201107181404.png

配置中心

如淘宝开源配置管理框架Diamond

场景需求

集群上有很多个节点运行同一个任务,这个任务会有一些可能经常改变的配置参数,要求是当配置参数改变之后能够很快地同步到每个节点上,如果将这些配置参数放在本地文件中则每次都要修改本地文件费时费力还可能会有遗漏,所以这个时候一个比较自然的想法就是将配置单独提取出来作为一个服务,比如自己开发一个http服务器提供一个接口来获取服务,这有两个问题

  • 配置下发这其实是一个推模型,当配置发生改变时需要服务器去主动推给客户端而不是客户端不断地去轮询,
  • 配置中心不能是单点故障,对配置中心的可用性有一定要求

这时候如果有zookeeper集群的话直接拿来作为配置中心使用不失为一种简单的方案。

分析

一个配置中心的核心是什么:

  1. 低延迟:配置改变后能够尽快的将最新配置同步给每一个节点。
  2. 高可用:配置中心需要能够稳定不间断的提供服务。

第一点可以通过zookeeper的watcher机制实现,约定一个节点用来存放配置信息,每个客户端都监听这个节点的NodeDataChanged事件,当配置发生改变时将最新的配置更新到这个节点上(谁更新无所谓,任意一个节点都可以更新,或者做一个另外的配置管理后台用来更新都没有问题),这个节点触发NodeDataChanged事件,通知所有监听此节点NodeDataChanged事件的客户端获取此节点的最新值,因为watcher是一次性的,所以在获取最新值的时候需要重新设置监听事件,因为getData是原子性操作,所以能够保证获取到的一定是最新的值。这里需要注意的是存放在节点上的配置文件不宜过大,如果配置文件部分很大而每次变更的只是一部分的话或许可以考虑对其进行拆分,存放在多个节点上。

第二点的高可用性就是交由zookeeper集群来保证,在应用层面不需要做额外的工作。

下面是分布式配置管理中心简单的示意图,程序运行过程中不断的重复123步骤。

http://img.cana.space/picStore/20201107182009.png

使用zk作为配置分发的优点是低延迟、高可靠性,当然也有缺点,因为watcher是跟会话绑定的,而要维护每个会话需要一个tcp一直连接到服务器,这对集群来说也是一种负载,不过考虑到2c4g单台机器支持几百连接并发很轻松,再加上整个zookeeper集群中会有多台机器平均一下,这点负载基本忽略了。

集群管理

如Hadoop分布式集群管理

随着分布式系统规模的日益扩大,集群中的机器规模也随之变大,因此,如何更好的进行集群管理也显得越来越重要了。

所谓集群管理,包括集群监控与集群控制两大块,前者侧重对集群运行时状态的收集,后者则是对集群进行操作与控制。在日常开发和运维过程中,我们经常会有类似于如下的需求。

  • 希望知道当前集群中究竟有多少机器在工作。
  • 对集群中每台机器的运行时状态进行数据收集。
  • 对集群中机器进行上下线操作。

分布式消息同步和协调机制

负载均衡

分布式锁

1
2
3
4
[zk: localhost:2181(CONNECTED) 3] create /lock 1
Created /lock
[zk: localhost:2181(CONNECTED) 4] create /lock 2
Node already exists: /lock

对Dubbo的支持

安装

下载

此地址 下载zookeeper-3.5.8-bin.tar.gz

注意
从3.5.5版本开始,带有bin名称的包才是我们想要的下载可以直接使用的里面有编译后的二进制的包,而之前的普通的tar.gz的包里面是只是源码的包无法直接使用。

解压

1
2
3
4
5
6
7
8
# 解压
$ tar -zxvf apache-zookeeper-3.5.8.tar.gz -C ~/soft
# 切换到配置目录下
$ cd ~/soft/apache-zookeeper-3.5.8-bin/conf
# 备份,更改默认配置文件名称
$ cp zoo_sample.cfg zoo.cfg
# 编辑配置文件,自定义dataDir,快照存储路径,不要使用/tmp
vim zoo.cfg

配置文件说明

1
2
3
4
5
6
7
8
tickTime=2000
dataDir=/var/lib/zookeeper
clientPort=2181
initLimit=5
syncLimit=2
server.1=ip1:2888:3888
server.2=ip2:2888:3888
server.3=ip3:2888:3888
  • tickTime

    这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。

  • dataDir

    顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。

  • clientPort

    这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。

  • initLimit

    这个配置项是用来配置 Zookeeper 接受客户端(这里所说的客户端不是用户连接 Zookeeper 服务器的客户端,而是 Zookeeper 服务器集群中连接到 Leader 的 Follower 服务器)初始化连接时最长能忍受多少个心跳时间间隔数。当已经超过 10个心跳的时间(也就是 tickTime)长度后 Zookeeper 服务器还没有收到客户端的返回信息,那么表明这个客户端连接失败。总的时间长度就是 10*2000=20 秒

  • syncLimit

    这个配置项标识 Leader 与 Follower 之间发送消息,请求和应答时间长度,最长不能超过多少个 tickTime 的时间长度,总的时间长度就是 5*2000=10秒

  • server.A=B:C:D

    其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。

单机启动Zookeeper

启(启动服务器)连(连接服务器)退(退出会话)关(关闭服务器)

1
2
3
4
5
# david @ Davids-Macbook-Pro in ~/soft/apache-zookeeper-3.5.8-bin/bin [15:16:06]
$ ./zkServer.sh start
ZooKeeper JMX enabled by default
Using config: /Users/david/soft/apache-zookeeper-3.5.8-bin/bin/../conf/zoo.cfg
Starting zookeeper ... STARTED

通过客户端连接Zookeeper

1
2
3
4
5
6
7
$ ./zkCli.sh
Connecting to localhost:2181
....
WATCHER::

WatchedEvent state:SyncConnected type:None path:null
[zk: localhost:2181(CONNECTED) 0]

从客户端退出输入quit回车即可

停止Server

1
2
3
4
5
# david @ Davids-Macbook-Pro in ~/soft/apache-zookeeper-3.5.8-bin/bin [15:18:01] C:127
$ ./zkServer.sh stop
ZooKeeper JMX enabled by default
Using config: /Users/david/soft/apache-zookeeper-3.5.8-bin/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED

通过客户端执行基本命令

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 创建znode,名为/test,携带数据david
[zk: localhost:2181(CONNECTED) 0] create /test david
Created /test
# 查看znode信息
[zk: localhost:2181(CONNECTED) 1] get /test
david
# 修改znode数据
[zk: localhost:2181(CONNECTED) 2] set /test hello
[zk: localhost:2181(CONNECTED) 3] get /test
hello
# 创建子znode
[zk: localhost:2181(CONNECTED) 4] create /test/t1 world
Created /test/t1
# 列出子znode
[zk: localhost:2181(CONNECTED) 5] ls /test
[t1]
# 删除znode,有子节点的znode不能直接删除,否则会报错
[zk: localhost:2181(CONNECTED) 6] delete /test
Node not empty: /test
# 删除子节点znode,再次查看
[zk: localhost:2181(CONNECTED) 7] delete /test/t1
[zk: localhost:2181(CONNECTED) 8] get /test/t1
org.apache.zookeeper.KeeperException$NoNodeException: KeeperErrorCode = NoNode for /test/t1

集群配置与启动

本例搭建的是伪集群模式,即一台机器上启动三个zookeeper实例组成集群,真正的集群模式无非就是实例IP地址不同,搭建方法没有区别

  1. 复制一份zoo.cfg,命名为zoo-1.cfg

    1
    
    $ cp zoo.cfg zoo-1.cfg
    
  2. 修改配置文件zoo-1.cfg,修改成下面的值,没有的则加上

    1
    2
    3
    4
    5
    6
    7
    8
    
    tickTime=2000
    initLimit=10
    syncLimit=5
    dataDir=/tmp/zookeeper-1
    clientPort=2181
    server.1=127.0.0.1:2881:3881
    server.2=127.0.0.1:2882:3882
    server.3=127.0.0.1:2883:3883
    

    配置说明:server.A=B:C:D

    其中 A 是一个数字,表示这个是第几号服务器;B 是这个服务器的 ip 地址;C 表示的是这个服务器与集群中的 Leader 服务器交换信息的端口;D 表示的是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的 Leader,而这个端口就是用来执行选举时服务器相互通信的端口。如果是伪集群的配置方式,由于 B 都是一样,所以不同的 Zookeeper 实例通信端口号不能一样,所以要给它们分配不同的端口号。

  3. 再从zoo-1.cfg复制两个配置文件zoo-2.cfg和zoo-3.cfg,只需修改dataDir和clientPort不同即可

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    # david @ Davids-Macbook-Pro in ~/soft/apache-zookeeper-3.5.8-bin/conf [15:48:53]
    $ cp zoo-1.cfg zoo-2.cfg
       
    # david @ Davids-Macbook-Pro in ~/soft/apache-zookeeper-3.5.8-bin/conf [15:52:50]
    $ cp zoo-1.cfg zoo-3.cfg
       
    $ vim zoo-2.cfg
    dataDir=/tmp/zookeeper-2
    clientPort=2182
       
    $ vim zoo-3.cfg
    dataDir=/tmp/zookeeper-3
    clientPort=2183
    
  4. 标识Server ID

    创建三个文件夹/tmp/zookeeper-1,/tmp/zookeeper-2,/tmp/zookeeper-2,在每个目录中创建文件myid 文件,写入当前实例的server id,即1.2.3

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
    # cd /tmp/zookeeper-1
    # vim myid
    1
    # cd /tmp/zookeeper-2
    # vim myid
    2
    # cd /tmp/zookeeper-3
    # vim myid
    3
    
  5. 启动三个zk实例

    1
    2
    3
    
    $ ./zkServer.sh start ../conf/zoo-1.cfg
    $ ./zkServer.sh start ../conf/zoo-2.cfg
    $ ./zkServer.sh start ../conf/zoo-3.cfg
    
  6. 检查集群状态

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    
    $ ./zkServer.sh status ../conf/zoo-1.cfg
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-1.cfg
    Client port found: 2181. Client address: localhost.
    Mode: follower
       
    # david @ Davids-Macbook-Pro in ~/soft/apache-zookeeper-3.5.8-bin/bin [15:59:12]
    $ ./zkServer.sh status ../conf/zoo-2.cfg
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-2.cfg
    Client port found: 2182. Client address: localhost.
    Mode: leader
       
    # david @ Davids-Macbook-Pro in ~/soft/apache-zookeeper-3.5.8-bin/bin [15:59:19]
    $ ./zkServer.sh status ../conf/zoo-3.cfg
    ZooKeeper JMX enabled by default
    Using config: ../conf/zoo-3.cfg
    Client port found: 2183. Client address: localhost.
    Mode: follower
    

    可以看到一主二从的集群架构已经搭建好了。

  7. 关闭3个节点服务

    1
    2
    3
    
    $ ./zkServer.sh stop ../conf/zoo-2.cfg
    $ ./zkServer.sh stop ../conf/zoo-2.cfg
    $ ./zkServer.sh stop ../conf/zoo-3.cfg
    

参考

Zookeeper笔记之基于zk的分布式配置中心

ZooKeeper的典型应用场景之集群管理