【老哥我最近接到个任务研究一下Zookeeper,对于我这个Linux运维领域的小菜鸟来说也是刚刚听到这个名字,为了养成良好的文档整理和学习能力,我人生第一次开通了博客并把这次的研究经历记录了下来,以后我会不定期的记录下来我对技术领域的探索,希望热爱Linux运维志同道合的兄弟们多指教,一同进步成长。
(ps:我本人平时比较沉默,善于观察思考,对历史人物颇有见解,但是一旦说起话来就会滔滔不绝,谁让我曾经的梦想是当一名教师呢!哈哈!)
同时,送给大家一句话,人生是一场马拉松比赛,只有坚持到最后的人,才会让人肃然起敬,并不是因为他获得多大的成就,而是那知其不可为而为之的精神。】

言归正传,学习一种新的技术首先当然是要拜访Zookeeper的官方网站:http://zookeeper.apache.org/软件也是一种产品,而它的官方网站就是它的说明书,在这里你能找到你所想要的东西。

分布式系统
分布式系统是由独立的计算机通过网络连接在一起,并且通过一些组件来相互交流和协作来完成一个共同的目标。

Zookeeper简介
ZooKeeper是一个集中的服务,用于维护配置信息、命名、提供分布式同步和提供组服务。所有这些服务都以某种形式由分布式应用程序使用。每次实现它们时,都需要做大量工作来修复不可避免的bug和竞争条件。由于难以实现这些服务,应用程序最初通常会节省这些服务,这使得它们在出现更改时变得脆弱,并且难以管理。即使处理正确,这些服务的不同实现在部署应用程序时也会导致管理复杂性。为了解决这一问题,开发人员就研发出了Zookeeper。

一、Zookeeeper单机安装及简单操作
从https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.14/ 下载软件

[root@8c649f93f3bc soft]# wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.14/[root@8c649f93f3bc soft]# ls
Django-2.1.11 jdk-8u162-linux-x64.rpm redis-4.0.9
Django-2.1.11.tar.gz nginx-1.13.12 redis-4.0.9.tar.gz
install.sh nginx-1.13.12.tar.gz zookeeper-3.4.14.tar.gz

解压软件包,解压后会生成一个zookeeper-3.4.14的目录,进入目录就会看到如下内容:

[root@8c649f93f3bc zookeeper-3.4.14]# ls
bin README.md zookeeper-client
build.xml README_packaging.txt zookeeper-contrib
conf src zookeeper-docs
dist-maven zoo1.cfg zookeeper-it
ivysettings.xml zoo2.cfg zookeeper-jute
ivy.xml zoo3.cfg zookeeper.out
lib zookeeper-3.4.14.jar zookeeper-recipes
LICENSE.txt zookeeper-3.4.14.jar.asc zookeeper-server
NOTICE.txt zookeeper-3.4.14.jar.md5
pom.xml zookeeper-3.4.14.jar.sha1

要启动Zookeeper需要一个配置文件,创建配置文件conf/zoo.cfg

[root@8c649f93f3bc zookeeper-3.4.14]# cat conf/zoo.cfg 
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/var/lib/zookeeper
clientPort=2181


下面是每个字段的含义:
tickTime 单位为微秒,用于session注册和客户端和ZooKeeper服务的心跳周期。session超时时长最小为 tickTime的两倍
dataDir :存储内存中数据库快照的位置,以及更新到数据库的事务日志(除非另有指定)。
clientPort: 监听客户机连接的端口,默认是2181

启动Zookeeper,由于我的配置文件的相对路径比较长,所以我对其做了个符号连接,以便操作
[root@8c649f93f3bc zookeeper-3.4.14]# ln -s conf/zoo.cfg zoo.cfg
此时就可以指定配置文件启动Zookeeper
[root@8c649f93f3bc zookeeper-3.4.14]# bin/zkServer.sh start zoo.cfg 
ZooKeeper JMX enabled by default
Using config: /home/data/zookeeper/zookeeper-3.4.14/bin/../conf/zoo.cfg
Starting zookeeper ... already running as process 11614.
看到上述描述,就表示Zookeeper启动成功。查看Zookeeper的状态
[root@8c649f93f3bc zookeeper-3.4.14]# bin/zkServer.sh status zoo.cfg 
ZooKeeper JMX enabled by default
Using config: /home/data/zookeeper/zookeeper-3.4.14/bin/../conf/zoo.cfg
Mode: standalone
这里可以看到ZK的运行模式为独立的。对ZK进行管理,连接到zookeeper
[root@8c649f93f3bc zookeeper-3.4.14]# bin/zkCli.sh -server 127.0.0.1:2181
Connecting to 127.0.0.1:2181
敲一下help查看一下支持的命令
[zk: 127.0.0.1:2181(CONNECTED) 0] help
ZooKeeper -server host:port cmd args
stat path [watch]
set path data [version]
ls path [watch]
delquota [-n|-b] path
ls2 path [watch]
setAcl path acl
setquota -n|-b val path
history 
redo cmdno
printwatches on|off
delete path [version]
sync path
listquota path
rmr path
get path [watch]
create [-s] [-e] path data acl
addauth scheme auth
quit 
getAcl path
close 
connect host:port通过运行create /zk_test my_data创建一个新的znode,这将创建一个新的znode并将字符串“my_data”与该节点关联。
[zk: 127.0.0.1:2181(CONNECTED) 1] ls /
[zookeeper][zk: 127.0.0.1:2181(CONNECTED) 2] create /zk_test my_data
Created /zk_test[zk: 127.0.0.1:2181(CONNECTED) 4] ls /
[zookeeper, zk_test]接下来,运行get命令验证数据是否与znode关联,如下所示:
[zk: 127.0.0.1:2181(CONNECTED) 5] get /zk_test
my_data
cZxid = 0x6
ctime = Tue Aug 20 05:29:52 UTC 2019
mZxid = 0x6
mtime = Tue Aug 20 05:29:52 UTC 2019
pZxid = 0x6
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 0也可以通过发出set命令来更改与zk_test关联的数据
[zk: 127.0.0.1:2181(CONNECTED) 6] set /zk_test junk 
cZxid = 0x6
ctime = Tue Aug 20 05:29:52 UTC 2019
mZxid = 0x7
mtime = Tue Aug 20 05:37:26 UTC 2019
pZxid = 0x6
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0[zk: 127.0.0.1:2181(CONNECTED) 7] get /zk_test
junk
cZxid = 0x6
ctime = Tue Aug 20 05:29:52 UTC 2019
mZxid = 0x7
mtime = Tue Aug 20 05:37:26 UTC 2019
pZxid = 0x6
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0最后,我们可以用如下命令删除node
[zk: 127.0.0.1:2181(CONNECTED) 2] ls /
[zookeeper, zk_test][zk: 127.0.0.1:2181(CONNECTED) 3] delete /zk_test
[zk: 127.0.0.1:2181(CONNECTED) 4] ls /
[zookeeper]

二、了解了Zookeeper的单机安装,下面我们来了解一下Zookeeper的基本概念:

Zookeeper的数据模型
ZooKeeper有一个分层的名称空间,很像一个分布式文件系统。惟一的区别是名称空间中的每个节点都可以拥有与其关联的数据和子节点。这就像一个文件系统允许一个文件同时也是一个目录。节点的路径总是表示为规范的、绝对的、斜杠分隔的路径;任何unicode字符均可在符合下列限制的路径中使用:
1.null字符(\u0000)不能作为路径名的一部分。(这会导致C绑定出现问题。)
2.以下字符不能使用,因为它们不能很好地显示,或者以令人困惑的方式呈现:\u0001 - \u001F和\u007F
3.\ u009F。
4.不允许使用以下字符:\ud800 - uF8FF, \uFFF0 - uFFFF。
5.“.”字符可以用作另一个名称的一部分,但是“.”和“..”不能单独用于指示路径上的节点,因为ZooKeeper不使用相对路径。下列内容无效:“/a/b/”。/ c”或“c / a / b / . . /”。
6.象征性的“zookeeper”被保留。

ZNodes
ZooKeeper树中的每个节点都被称为znode。Znodes维护一个stat结构,其中包含数据更改、acl更改的版本号。stat结构还具有时间戳。版本号和时间戳允许ZooKeeper验证缓存并协调更新。每次znode的数据发生变化,版本号就会增加。例如,每当客户机检索数据时,它也接收数据的版本。当客户机执行更新或删除操作时,它必须提供正在更改的znode数据的版本。如果它提供的版本与数据的实际版本不匹配,更新将失败。
(在分布式应用工程中,节点可以指一般的主机、服务器、集成的成员、客户机进程等。在ZooKeeper文档中,znodes指的是数据节点。服务器是指构成Zookeeper服务的机器;仲裁对等点是指组成合集的服务器;客户端是指任何使用ZooKeeper服务的主机或进程。)

znode有如下几个特征:

  1. Watches
    客户端可以在znode上设置watches。对该znode的更改将触发watches,然后清除watches。当一个watches触发时,ZooKeeper会向客户端发送一个通知。
    2.数据访问控制(Data Access)
    存储在名称空间中的每个znode中的数据是原子式读写的。读取获取与znode关联的所有数据字节,而写入则替换所有数据。每个节点都有一个访问控制列表(ACL),用于限制谁可以做什么。(ZooKeeper并不是一个通用数据库或大型对象存储库。相反,它管理协调数据。这些数据可以以配置、状态信息、集合等形式出现。各种形式的协调数据的一个共同特性是它们相对较小:以千字节为单位测量。ZooKeeper客户机和服务器实现都进行了完整性检查,以确保znode的数据少于1M,但平均数据应该比这少得多。在相对较大的数据大小上操作将导致某些操作比其他操作花费更多的时间,如果需要大数据存储,通常处理此类数据的模式是将其存储在一个大存储系统上,比如NFS或HDFS,并将指向ZooKeeper中存储位置的指针存储。)
    3.Ephemeral Nodes(临时节点)
    ZooKeeper也有临时节点的概念。只要创建znode的会话处于活动状态,这些znode就会存在。当会话结束时,删除znode。由于这种行为,临时znode不允许有子节点。
    4.Sequence Nodes -- Unique Naming(顺序节点)
    5.Container Nodes(容器节点) Added in 3.5.3
    ZooKeeper有容器znode的概念。容器znode是一种特殊用途的znode,可用于leader、lock等。当删除容器的最后一个子元素时,容器将成为将来某个时候服务器要删除的候选对象。

Time in ZooKeeper
1.zxid 对ZooKeeper状态的每次更改都会收到一个zxid (ZooKeeper事务Id)形式的戳记。这将向ZooKeeper公开所有更改的总顺序。每个更改都有一个惟一的zxid,如果zxid1小于zxid2,则zxid1发生在zxid2之前。
2.Version numbers 对节点的每一次更改都会导致该节点的版本号之一增加。这三个版本号是version(对znode数据的更改数量)、cversion(对znode子节点的更改数量)和hate(对znode ACL的更改数量)。
3.Ticks 当使用多服务器ZooKeeper时,服务器使用刻度来定义事件的时间,例如状态上传、会话超时、对等点之间的连接超时等。滴答时间仅通过最小会话超时(滴答时间的2倍)间接公开;如果客户机请求的会话超时小于最小会话超时,服务器将告诉客户机会话超时实际上是最小会话超时。
4.Real time ZooKeeper根本不使用实时或时钟时间,除了在znode创建和修改时将时间戳放入stat结构之外。

ZooKeeper Stat Structure
ZooKeeper中每个znode的统计结构由以下字段组成:
1.czxid zxid的变化会导致这个节点被创建
2.mzxid 最后修改的zxid
3.pzxid 最后修改zxid的子节点
4.ctime 从创建znode开始的时间(以毫秒为单位)。
5.mtime 此znode最后一次修改的时间(以毫秒为单位)。
6.version 此znode的数据的更改数。
7.cversion 此znode的子节点的更改数。
8.aversion 更改此znode的ACL的次数。
9.ephemeralOwner 如果znode是临时节点,则此znode所有者的会话id。如果不是临时节点,则为零。
10.dataLength 此znode的数据字段的长度。
11.numChildren 这个znode的子节点的个数。

ZooKeeper Sessions
ZooKeeper客户端通过使用语言绑定创建服务句柄来与ZooKeeper服务建立会话。创建好句柄后,句柄将以连接状态启动,客户端库将尝试连接到构成ZooKeeper服务的服务器之一,此时它将切换到连接状态。在正常操作期间,客户端句柄将处于这两种状态之一。如果出现不可恢复的错误,比如会话过期或身份验证失败,或者应用程序显式关闭句柄,句柄将移动到关闭状态。要创建一个客户端会话,应用程序代码必须提供一个连接字符串,其中包含一个逗号分隔的host:port对列表,每个host:port对对应于ZooKeeper服务器(例如“127.0.0.1:4545”或“127.0.0.1:3000、127.0.0.1:3001 127.0.0.1:3002”)。ZooKeeper客户端库将选择一个任意的服务器并尝试连接到它。如果这个连接失败,或者客户机由于任何原因与服务器断开连接,客户机将自动尝试列表中的下一个服务器,直到(重新)建立连接。在3.2.0版本后,还可以在连接字符串后面附加一个可选的“chroot”后缀。这将运行客户机命令,同时解释与此根相关的所有路径(类似于unix chroot命令)。如果使用示例的样子:“127.0.0.1:4545 / app /”或“127.0.0.1:3000、127.0.0.1:3001 127.0.0.1:3002 / app /“客户端将扎根在/ app /和所有路径是相对于这个根——即root - ie getting/setting/etc... "/foo/bar"将导致在“/app/a/foo/bar”上运行操作(从服务器的角度来看)。这个特性在多租户环境中特别有用,在多租户环境中,特定ZooKeeper服务的每个用户都可以有不同的根。这使得重用更加简单,因为每个用户都可以编写他/她的应用程序,就像它植根于“/”一样,而实际位置(比如/app/a)可以在部署时确定。
当客户端获得ZooKeeper服务的句柄时,ZooKeeper创建一个ZooKeeper会话(表示为64位数字),并将其分配给客户端。如果客户机连接到另一个ZooKeeper服务器,它将发送会话id作为连接握手的一部分。作为安全措施,服务器为任何ZooKeeper服务器都可以验证的会话id创建一个密码。当客户端建立会话时,密码将与会话id一起发送给客户端。每当客户机使用新服务器重新建立会话时,它就用会话id发送这个密码。
创建ZooKeeper会话的ZooKeeper客户端库调用的参数之一是会话超时(以毫秒为单位)。客户机发送一个请求的超时,服务器用它可以给客户机的超时进行响应。当前实现要求超时时间最少为tickTime的2倍(在服务器配置中设置),最多为tickTime的20倍。ZooKeeper客户端API允许访问协商超时。
当客户机(会话)从ZK服务集群中分区时,它将开始搜索会话创建期间指定的服务器列表。最终,当客户机和至少一个服务器之间的连接被重新建立时,会话将再次过渡到“connected”状态(如果在会话超时值内重新连接),或者过渡到“expired”状态(如果在会话超时之后重新连接)。不建议创建一个用于断开连接的新会话对象(一个新的zookeeper .class或c绑定中的zookeeper句柄)。ZK客户端库将为您处理重新连接。特别是,我们在客户端库中构建了启发式来处理诸如“羊群效应”等问题。只有当您被通知会话过期(强制)时才创建一个新会话。
会话过期由ZooKeeper集群本身管理,而不是由客户机管理。当ZK客户机与集群建立一个会话时,它提供了上面详细描述的“超时”值。集群使用此值来确定客户机的会话何时过期。当集群在指定的会话超时期间(即没有心跳)内没有收到客户机的消息时,就会发生终止。在会话过期时,集群将删除该会话拥有的任何/所有临时节点,并立即通知任何/所有连接的客户机更改(任何监视这些znode的人)。此时,过期会话的客户机仍然与集群断开连接,直到/除非它能够重新建立到集群的连接,否则不会通知它会话过期。客户机将保持断开连接状态,直到使用集群重新建立TCP连接,此时过期会话的监视程序将收到“会话过期”通知。
ZooKeeper会话建立调用的另一个参数是默认的监视程序。当客户机中发生任何状态更改时,会通知监视者。例如,如果客户端失去与服务器的连接,客户端将得到通知,或者如果客户端会话过期,等等。此监视程序应该考虑断开初始状态(即在任何状态更改事件由客户端库发送到监视程序之前)。在新连接的情况下,发送给观察者的第一个事件通常是会话连接事件。
会话通过客户机发送的请求保持活动状态。如果会话空闲一段时间会超时会话,客户端将发送一个PING请求来保持会话活动。这个PING请求不仅允许ZooKeeper服务器知道客户机仍然处于活动状态,而且还允许客户机验证它到ZooKeeper服务器的连接是否仍然处于活动状态。PING的时间足够保守,可以确保有合理的时间检测死连接并重新连接到新服务器。
一旦成功建立到服务器的连接(连接)基本上有两个情况下,客户端自由生成connectionloss(结果用c代码绑定,用Java异常,请查看API文档绑定具体细节)当执行一个同步或异步操作,下列是适用的:
1.应用程序调用不再有效的会话上的操作
2.当服务器上有挂起的操作时,ZooKeeper客户端断开与服务器的连接,有一个挂起的异步调用。

Updating the list of servers 更新服务器列表:允许客户端通过提供一个新的逗号分隔的host:port对列表来更新连接字符串,每个host:port对对应于ZooKeeper服务器。该函数调用一个概率负载平衡算法,该算法可能导致客户端与其当前主机断开连接,目标是在新列表中实现每个服务器预期的统一连接数。如果客户端连接到的当前主机不在新列表中,此调用将始终导致连接被删除。否则,则根据serv的数量来决定。例如,如果以前的连接字符串包含3台主机,而现在列表包含这3台主机和另外2台主机,那么连接到这3台主机的40%的客户端将移动到其中一台新主机,以平衡负载。该算法将导致客户端断开与当前主机的连接,该连接的概率为0.4,在本例中,将导致客户端连接到随机选择的两个新主机之一。另一个例子,假设我们有5现在主机和主机的更新列表,删除2,剩下的客户端连接到3主机将保持联系,而所有客户机连接到2删除主机需要搬到一个3主机,随机选取的。如果连接被删除,客户端将切换到一种特殊模式,在这种模式中,他将使用概率算法选择新服务器连接,而不仅仅是循环。在第一个示例中,每个客户机决定断开连接的概率为0.4,但是一旦做出了这个决定,它将尝试连接到一个随机的新服务器,只有当它不能连接到任何一个新服务器时,它才尝试连接到旧服务器。在找到一个服务器之后,或者尝试新列表中的所有服务器,但是连接失败之后,客户机返回到正常的操作模式,从connectString中选择一个任意的服务器,并尝试连接到它。如果失败,它将继续在轮询中尝试不同的随机服务器。

ZooKeeper Watches
1.当数据发生变化时,将向客户端发送一次触发One watch事件。例如,如果客户机执行getData("/znode1", true),然后更改或删除/znode1的数据,客户机将获得/znode1的watch事件。如果/znode1再次更改,则不会发送任何watch事件,除非客户端完成了设置新Watches的另一次读取。
2.发送到客户端意味着一个事件正在发送到客户端途中,但是在成功地将更改操作的返回代码发送到发起更改的客户端之前,可能无法到达客户端。手表以异步方式发送给观察者。ZooKeeper提供了一个订购保证:客户端在第一次看到watch事件之前,永远不会看到设置了Watches的更改。网络延迟或其他因素可能导致不同的客户端在不同的时间看到Watches并从更新中返回代码。关键是不同客户看到的所有东西都有一个一致的顺序。

  1. Watches在客户机连接到的ZooKeeper服务器上本地维护。这使得 Watches的设置、维护和分派都很轻量。当客户端连接到新服务器时,将为任何会话事件触发 Watches。当与服务器断开连接时,将不会接收到 Watches。当客户端重新连接时,任何以前注册的 Watches都将重新注册并在需要时触发。一般来说,这一切都是透明的。有一种情况下可能会错过一个 Watches:如果创建了znode,则会错过一个尚未创建znode的 Watches。
    关于Watches的注意事项:
    1.Watches是一次性触发器;如果您获得了一个watch事件,并且希望得到关于未来更改的通知,则必须设置另一个watch。
    2.因为Watches是一次性触发器,并且在获取事件和发送获取Watches的新请求之间存在延迟,所以您不能可靠地查看ZooKeeper中节点发生的每个更改。准备处理znode在获取事件和再次设置Watches之间多次更改的情况。(你可能不在乎,但至少意识到它可能会发生。)
    3.对于给定的通知,监视对象或函数/上下文对只会触发一次。例如,如果为exists注册了相同的watch对象,并且为相同的文件调用了getData,然后删除了该文件,那么使用该文件的删除通知只会调用该watch对象一次。
    4.当断开与服务器的连接时(例如,当服务器失败时),在重新建立连接之前,不会得到任何Watches。因此,会话事件被发送给所有未完成的Watches处理程序。使用会话事件进入安全模式:断开连接时不会接收事件,因此您的流程应该在该模式下谨慎地工作。

ZooKeeper access control using ACLs
ZooKeeper使用acl控制对其znode (ZooKeeper数据树的数据节点)的访问。ACL实现与UNIX文件访问权限非常相似:它使用权限位允许/不允许对节点和应用这些位的范围执行各种操作。与标准UNIX权限不同,ZooKeeper节点不受user(文件所有者)、group和world(其他)三个标准范围的限制。ZooKeeper没有znode的所有者的概念。相反,ACL指定与这些id关联的id和权限集。
还要注意,ACL只属于特定的znode。它尤其不适用于儿童。例如,如果/app只能通过ip:172.16.16.1读取,而/app/status是世界可读的,那么任何人都可以读取/app/status;acl不是递归的。
ZooKeeper支持可插入的身份验证方案。id使用表单scheme:expression指定,其中scheme是id对应的身份验证方案。该方案定义了一组有效表达式。例如,ip:172.16.16.1是使用ip方案的地址为172.16.16.1的主机的id,而digest:bob:password是使用摘要方案的用户的id,其名称为bob。
当客户端连接到ZooKeeper并验证自身时,ZooKeeper将与客户端连接对应的所有id关联起来。当客户机试图访问某个节点时,将根据znodes的acl检查这些id。acl由一对(scheme:expression, perms)组成。表达式的格式是特定于方案的。例如,这对(ip:19.22.0.0/16, READ)将读权限授予ip地址以19.22开头的任何客户机。
ZooKeeper支持以下权限:

CREATE: you can create a child node
READ: you can get data from a node and list its children.
WRITE: you can set data for a node
DELETE: you can delete a child node
ADMIN: you can set permissions

三、Zookeeper集群的搭建
前面我讲解了ZK的单机搭建,现在讲解一下ZK集群的搭建,我们以三个节点的集群为例,由于没有真实环境条件,只能在一台主机上搭建三个节点的伪集群,集群搭建过程中要注意几个点,下面我会逐一进行说明。
用第一节讲解的环境进行下一步操作,首先再创建三个配置文件,由于路径的原因,我对这三个配置文件分别做了符号连接,文件内容如下:

[root@8c649f93f3bc zookeeper-3.4.14]# cat zoo1.cfg 
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/var/lib/zookeeper/zoo1
clientPort=2182
server.1=localhost:2666:3666
server.2=localhost:2667:3667
server.3=localhost:2668:3668
[root@8c649f93f3bc zookeeper-3.4.14]# cat zoo2.cfg 
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/var/lib/zookeeper/zoo2
clientPort=2183
server.1=localhost:2666:3666
server.2=localhost:2667:3667
server.3=localhost:2668:3668
[root@8c649f93f3bc zookeeper-3.4.14]# cat zoo3.cfg 
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/var/lib/zookeeper/zoo3
clientPort=2184
server.1=localhost:2666:3666
server.2=localhost:2667:3667
server.3=localhost:2668:3668


需要说明的是,每个配置文件的端口号和dataDir不要重复,server.no=ip:port1:port2 ,其中no为集群的标识符,ip为主机地址,port1为集群内部通讯的端口,port2为集群内部选举leader的端口。
然后在数据目录/var/lib/zookeeper/zoo3下创建myid文件,文件的内容和你的server id 保持一致,否则启动时会报错,内容如下:

[root@8c649f93f3bc zoo1]# cat myid 
1
[root@8c649f93f3bc zoo2]# cat myid 
2
[roo

t@8c649f93f3bc zoo3]# cat myid
3

编辑好配置文件就可以启动服务了,如下所示:

[root@8c649f93f3bc zookeeper-3.4.14]# bin/zkServer.sh start zoo1.cfg 
ZooKeeper JMX enabled by default
Using config: /home/data/zookeeper/zookeeper-3.4.14/bin/../conf/zoo1.cfg
Starting zookeeper ... STARTED
[root@8c649f93f3bc zookeeper-3.4.14]# bin/zkServer.sh start zoo2.cfg 
ZooKeeper JMX enabled by default
Using config: /home/data/zookeeper/zookeeper-3.4.14/bin/../conf/zoo2.cfg
Starting zookeeper ... STARTED
[root@8c649f93f3bc zookeeper-3.4.14]# bin/zkServer.sh start zoo3.cfg 
ZooKeeper JMX enabled by default
Using config: /home/data/zookeeper/zookeeper-3.4.14/bin/../conf/zoo3.cfg
Starting zookeeper ... STARTED

查看集群各个节点的状态及所处的模式,如下所示

[root@8c649f93f3bc zookeeper-3.4.14]# bin/zkServer.sh status zoo1.cfg 
ZooKeeper JMX enabled by default
Using config: /home/data/zookeeper/zookeeper-3.4.14/bin/../conf/zoo1.cfg
Mode: follower
[root@8c649f93f3bc zookeeper-3.4.14]# bin/zkServer.sh status zoo2.cfg 
ZooKeeper JMX enabled by default
Using config: /home/data/zookeeper/zookeeper-3.4.14/bin/../conf/zoo2.cfg
Mode: leader
[root@8c649f93f3bc zookeeper-3.4.14]# bin/zkServer.sh status zoo3.cfg 
ZooKeeper JMX enabled by default
Using config: /home/data/zookeeper/zookeeper-3.4.14/bin/../conf/zoo3.cfg
Mode: follower


官方文档给的建议是主机的数量是奇数(2n+1),集群中允许坏的最多数量为n个。