原文链接:《ZooKeeper 安装部署与配置》http://www.ytbean.com/posts/zookeeper-installation/

单实例安装

下载

zookeeper使用Java编写,依赖的JDK版本为1.6+,因此zookeeper的运行需要Java环境的支持。
可以在以下网站下载到zookeeper的安装包
apache镜像
下载到的安装包为:zookeeper-3.4.8.tar.gz

在linux下用tar命令解压

tar zxvf zookeeper-3.4.8.tar.gz

image-20220310181423639

其中bin目录包含了一些脚本,用来运行zookeeper的主程序、客户端,以及环境变量配置的脚本。
conf目录,见名思义,放置了配置文件。
lib目录,包含了zookeeper运行时所需要依赖的jar包。

运行一下

conf目录下包含了一个文件zoo_sample.cfg,然而它并不是真正的配置文件,只是一个配置文件的范例。
启动zk server的时候,如果不显式指定启动时的配置文件,默认为使用conf目录下的zoo.cfg文件作为配置文件。
可是conf目录下并没有啊,因此首先要把文件改下名:

mv conf/zoo_sample.cfg conf/zoo.cfg

接下来切换到bin目录下,启动zookeeper

./zkServer.sh start

image-20220310181442950

有时候我觉得每次都要切换到bin目录,然后敲命令,挺麻烦的。所以会做一些alias,爱护手指,减少打字。编辑自己home目录下的.bash_profile文件,加上

image-20220310181514740

然后source一下,让它立即生效

image-20220310181556802

注意到这次启动和上一次动的输出不一样,明显输出的信息多了很多,原因是在启动zk的时候用的是zkServer.sh start-foreground
这种启动方式让zk以前台进程的方式运行,这种运行方式也会占用屏幕。不过在调试的时候,我们还是挺需要这些输出信息的,有时候能帮我们做出一些基本的判断。

zkServer.sh这个脚本支持多种参数

image-20220310181624407

可以看到Usage这一行,包括了所有支持的参数。

客户端连接

bin目录下包含有一个名为zkCli.sh的脚本,zookeeper也支持其它客户端。例如Java的有curator等。
但这个脚本作为我们尝试zookeeper的客户端,也是挺便利的。
再启动zk后,另开一个会话窗口,启动客户端去连接zk。

image-20220310181641013

从输出的信息中可以看到客户端运行的环境,比如jdk的版本,classpath
这里值得注意的是最后有一个watcher相关的信息,每当客户端与zk服务器建立连接时,服务端都会发送一个状态为SyncConnected的watch event给客户端。客户端可以基于此,做出一些相应的动作。由于我们用的是这样的脚本形式的客户段,还没能对这个event进行处理。
zookeeper自带的客户端以及一些其他的Java客户端例如curator都能对这种事件进行处理。

伪集群安装

只有一台linux主机,但却想要搭建一套zookeeper集群的环境。
可以使用伪集群模式来搭建。
伪集群模式本质上就是在一个linux操作系统里面启动多个zookeeper实例。
这些不同的实例使用不同的端口,配置文件以及数据目录。

创建独立的目录

创建三个目录,隔离开3个zookeeper实例的数据文件,配置文件:

[beanlam@localhost ~]$ mkdir zk1
[beanlam@localhost ~]$ mkdir zk2
[beanlam@localhost ~]$ mkdir zk3

然后,再分别为每个目录创建一个数据目录,用来存放数据以及id文件

[beanlam@localhost ~]$ mkdir zk1/data
[beanlam@localhost ~]$ mkdir zk2/data
[beanlam@localhost ~]$ mkdir zk3/data

指定id

zookeeper启动的时候,会在它的数据目录下寻找id文件,以便知道它自己在集群中的编号。

[beanlam@localhost ~]$ echo 1 > zk1/data/myid
[beanlam@localhost ~]$ echo 2 > zk2/data/myid
[beanlam@localhost ~]$ echo 3 > zk3/data/myid

修改配置文件

这3个实例,每个实例都会使用不同的配置文件启动。
配置示例如下:

# The number of milliseconds of each tick
tickTime=2000

# The number of ticks that the initial
# synchronization phase can take
initLimit=10

# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5

# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/home/beanlam/zk1/data

# the port at which the clients will connect
clientPort=2181

server.1=127.0.0.1:2222:2223
server.2=127.0.0.1:3333:3334
server.3=127.0.0.1:4444:4445

这是第一个实例的配置,z1.cfg。把这份配置文件放置在zk1/目录下。
同理,第二个和第三个实例的配置分别为z2.cfg和z3.cfg。和第一个实例一样,放在相同的位置。
唯一不同的是,clientPort必须修改一下,z1.cfg为2181,z2.cfg和z3.cfg不能也是2181,必须彼此不同,比如2182或者2183。

配置文件最底下有一个server.n的配置项,这里配置了两个端口,却一种第一个用于集群间实例的通信,第二个用于leader选举。
至于2181,用于监听客户端的连接。

启动和连接

按照以下方式,依次启动3个实例:

[beanlam@localhost ~]$ cd zk1
[beanlam@localhost zk1]$ ~/zookeeper-3.4.8/bin/zkServer.sh start-foreground ./z1.cfg

启动第一个和第二个实例的时候会有报错信息,因为其它实例还没启动完全,连接无法建立的原因,可以直接忽略。
启动完3个实例后,会发现其中有一个是leader,另外两个是follower。可观察输出信息。

接下来启动一个客户端去进行连接:

[beanlam@localhost ~]$ ~/zookeeper-3.4.8/bin/zkCli.sh -server 127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183

可以看到,客户端连接上了刚才启动的三个实例中的其中一个。

集群中实例的数量

应用程序通过zookeeper客户端连接zookeeper。
客户端可以是zookeeper自身携带的客户端(zookeeper把client代码跟server放在一起,这点很多人有非议)
也可以是一些其它的开源客户端例如apache curator和zkClient。

image-20220310181907886

zookeeper可以有两种部署模式,一种是单机版,一种是集群版。
所谓单机版,亦即只有一个zookeeper实例。
集群版会有多个zookeeper实例,多个实例之间会有一个master,他们之间的状态信息也会进行复制。

集群的数目:奇数

从各种书或网上的资料上经常能看到一个建议:

zookeeper的集群里,实例的个数最好是奇数

对于客户端来说,它并不需要关心zookeeper集群中有多少个实例,实例之间是怎么协商的。
如果客户端新建了一个znode,并得到了集群的响应,那么客户端就可以认为集群已经替它保存好了这个znode。
而实际上,在zookeeper集群内部,它们并不会把znode的创建都通知到每个zookeeper实例后才返回响应消息给客户端。

可以看出来,在zookeeper集群中,有一件非常重要的事经常会发生,那就是如何让集群中的每个实例对某个公共状态的变化达成共识。
公共状态的变化包括什么呢?

  • leader选举
  • 来自客户端的各种updates

zookeeper采用的方式是,如果集群中有过半数的实例同意某个公共状态的变化,那么便认为集群最终会对这个公共状态达成一致意见。

为什么是过半数?

我们先看看如果不是“过半数”,会发生什么情况:
假设现在集群中有5个实例,当客户端新建一个znode “/test”后,只有2个实例同步了这个新建的znode,并通知客户端znode创建成功了。
在这2个实例通知其它3个实例之前,与其他3个实例发生了网络隔离(俗称“脑裂”,split-brain),变成了两个小集群了。
有2个实例拥有/test这个znode,而另外3个没有/test这个znode,并且在网络隔离还没恢复之前,这3个没有znode的实例永远不会得到关于/test这个znode被创建的通知。
当有一个客户端碰巧连接到的是那3个实例中的其中一个时,客户端永远也看不到/test这个znode。
这就出现了数据不一致的情况了。
如果是“过半数”,那情况就不一样了:
还是假设集群中有5个实例,这个时候,必须得有至少3个实例同步了/test这个znode后,客户端才会得到响应。
再次考虑网络隔离发生的场景,在这个3个实例还没通知到另外2个实例之前,又被隔离成两个小集群。
假设客户端连接到的是2个实例的那个集群,由于zookeeper认为要至少3个实例(过半数)存活才能提供服务,所以客户端获取不成功,数据是一致的。

只要是采取“过半数”的策略,无论网络怎么隔离,无论脑怎么裂,能够提供服务(意味着有过半数的实例)的那个小集群里,至少有一个实例是同步到最新的状态信息的。

包括zookeeper本身的leader选举,以及对znode的更新操作,都需要“过半数“这个作为基本方针。

为什么是奇数

偶数也可以有过半数,例如,4个实例的集群,3就是一个过半数
但为什么还是奇数最好?
如果集群有5个实例,那么只能容忍2个实例的崩溃。
如果集群中有6个实例,同样也只能容忍2个实例的崩溃。
在相同的容忍度下,6个和5个有什么区别:

  • 由于集群间需要互相通信,实例越多,网络开销越大
  • 实例越多,5个实例的时候,发生3个实例崩溃的概率要小于6个实例的时候。

那我们为什么不比较6个和7个?
没有可比性,容忍度不同。

如果有3,5,7供选择,该怎么选?
集群中的实例数目越多,就越稳定。
但实例数目越多,网络开销以及实例之间协调的耗费也会比较大。

这只是一个权衡利弊取其轻的原则

联系我


ytbean
3.1k 声望714 粉丝

十年学会编程