ZooKeeper 是 Apache 的一个顶级项目,为分布式应用提供高效、高可用的分布式协调服务,提供了诸如数据发布/订阅、负载均衡、命名服务、分布式协调/通知和分布式锁等分布式基础服务。因为 ZooKeeper 便捷的使用方式、卓越的性能和良好的稳定性,被普遍地应用于诸如 Hadoop、HBase、Kafka 和 Dubbo 等大型分布式系统中。javascript
本文的目标读者是对 ZooKeeper 有必定了解的技术人员,将从 ZooKeeper 运行模式、集群组成、容灾和水平扩容四方面逐步深刻,最终构建出高可用的 ZooKeeper 集群。php
Zookeeper 有三种运行模式:单机模式、伪集群模式和集群模式。html
这种模式通常适用于开发测试环境,一方面咱们没有那么多机器资源,另外就是平时的开发调试并不须要极好的稳定性。java
在 Linux 环境下运行单机模式须要执行如下步骤:算法
一、准备 Java 运行环境apache
安装 Java 1.6 或更高版本的 JDK,并配置好 Java 相关的环境变量 $JAVA_HOME 。服务器
二、下载 ZooKeeper 安装包微信
下载地址:http://zookeeper.apache.org/releases.html。选择最新的 stable 版本并解压到指定目录,咱们用 $ZK_HOME 表示该目录。网络
三、配置 zoo.cfg负载均衡
首次使用 ZooKeeper,须要将 $ZK_HOME 下的 zoo_sample.cfg 文件重命名为 zoo.cfg,并进行如下配置:
tickTime=2000 ##Zookeeper最小时间单元,单位毫秒(ms),默认值为3000 dataDir=/var/lib/zookeeper ##Zookeeper服务器存储快照文件的目录,必须配置 dataLogDir=/var/lib/log ##Zookeeper服务器存储事务日志的目录,默认为dataDir clientPort=2181 ##服务器对外服务端口,通常设置为2181 initLimit=5 ##Leader服务器等待Follower启动并完成数据同步的时间,默认值10,表示tickTime的10倍 syncLimit=2 ##Leader服务器和Follower之间进行心跳检测的最大延时时间,默认值5,表示tickTime的5倍
四、启动服务
使用 $ZK_HOME/bin 目录下的 zkServer.sh 脚本进行服务的启动。
一个 ZooKeeper 集群一般由一组机器组成,通常 3 台以上就能够组成一个可用的 ZooKeeper 集群了。
组成 ZooKeeper 集群的每台机器都会在内存中维护当前的服务器状态,而且每台机器之间都会互相保持通讯。
重要的一点是,只要集群中存在超过一半的机器可以正常工做,那么整个集群就可以正常对外服务。
ZooKeeper 的客户端程序会选择和集群中的任意一台服务器建立一个 TCP 链接,并且一旦客户端和服务器断开链接,客户端就会自动链接到集群中的其余服务器。
那么如何运行 ZooKeeper 集群模式呢?首先假如咱们有三台服务器,IP 分别为 IP一、IP2 和 IP3,则须要执行如下步骤:
一、准备 Java 运行环境(同上)
二、下载 ZooKeeper 安装包(同上)
三、配置 zoo.cfg
tickTime=2000 dataDir=/var/lib/zookeeper dataLogDir=/var/lib/log clientPort=2181 initLimit=5 syncLimit=2 server.1=IP1:2888:3888 server.2=IP2:2888:3888 server.3=IP3:2888:3888
能够看到,相比于单机模式,集群模式多了 server.id=host:port1:port2 的配置。
其中,ID 被称为 Server ID,用来标识该机器在集群中的机器序号(在每台机器的 dataDir 目录下建立 myid 文件,文件内容即为该机器对应的 Server ID 数字)。host 为机器 IP,port1 用于指定 Follower 服务器与 Leader 服务器进行通讯和数据同步的端口,port2用于进行 Leader 选举过程当中的投票通讯。
四、建立 myid 文件,在 dataDir 目录下建立名为 myid 的文件,在文件第一行写上对应的 Server ID。
五、按照相同步骤,为其余机器配置 zoo.cfg 和 myid文件。
六、启动服务
这是一种特殊的集群模式,即集群的全部服务器都部署在一台机器上。当你手头上有一台比较好的机器,若是做为单机模式进行部署,就会浪费资源,这种状况下,ZooKeeper容许你在一台机器上经过启动不一样的端口来启动多个 ZooKeeper 服务实例,以此来以集群的特性来对外服务。
这种模式下,只须要把 zoo.cfg 作以下修改:
tickTime=2000 dataDir=/var/lib/zookeeper dataLogDir=/var/lib/log clientPort=2181 initLimit=5 syncLimit=2 server.1=IP1:2888:3888 server.2=IP1:2889:3889 server.3=IP1:2890:3890
要搭建一个高可用的 ZooKeeper 集群,咱们首先须要肯定好集群的规模。
关于 ZooKeeper 集群的服务器组成,相信不少对 ZooKeeper 了解可是理解不够深刻的读者,都存在或曾经存在过这样一个错误的认识:为了使得 ZooKeeper 集群可以顺利地选举出 Leader,必须将 ZooKeeper 集群的服务器数部署成奇数。这里咱们须要澄清的一点是:任意台 ZooKeeper 服务器都能部署且能正常运行。
那么存在于这么多读者中的这个错误认识是怎么回事呢?其实关于 ZooKeeper 集群服务器数,ZooKeeper 官方确实给出了关于奇数的建议,但绝大部分 ZooKeeper 用户对于这个建议认识有误差。在本书前面提到的“过半存活便可用”特性中,咱们已经了解了,一个 ZooKeeper 集群若是要对外提供可用的服务,那么集群中必需要有过半的机器正常工做而且彼此之间可以正常通讯。基于这个特性,若是想搭建一个可以容许 N 台机器 down 掉的集群,那么就要部署一个由 2*N+1 台服务器构成的 ZooKeeper 集群。所以,一个由 3 台机器构成的 ZooKeeper 集群,可以在挂掉 1 台机器后依然正常工做,而对于一个由 5 台服务器构成的 ZooKeeper 集群,可以对 2 台机器挂掉的状况进行容灾。注意,若是是一个由6台服务器构成的 ZooKeeper 集群,一样只可以挂掉 2 台机器,由于若是挂掉 3 台,剩下的机器就没法实现过半了。
所以,从上面的讲解中,咱们其实能够看出,对于一个由 6 台机器构成的 ZooKeeper 集群来讲,和一个由 5 台机器构成的 ZooKeeper 集群,其在容灾能力上并无任何显著的优点,反而多占用了一个服务器资源。基于这个缘由,ZooKeeper 集群一般设计部署成奇数台服务器便可。
所谓容灾,在 IT 行业一般是指咱们的计算机信息系统具备的一种在遭受诸如火灾、地震、断电和其余基础网络设备故障等毁灭性灾难的时候,依然可以对外提供可用服务的能力。
对于一些普通的应用,为了达到容灾标准,一般咱们会选择在多台机器上进行部署来组成一个集群,这样即便在集群的一台或是若干台机器出现故障的状况下,整个集群依然可以对外提供可用的服务。
而对于一些核心应用,不只要经过使用多台机器构建集群的方式来提供服务,并且还要将集群中的机器部署在两个机房,这样的话,即便其中一个机房遭遇灾难,依然可以对外提供可用的服务。
上面讲到的都是应用层面的容灾模式,那么对于 ZooKeeper 这种底层组件来讲,如何进行容灾呢?讲到这里,可能多少读者会有疑问,ZooKeeper 既然已经解决了单点问题,那为何还要进行容灾呢?
单点问题是分布式环境中最多见也是最经典的问题之一,在不少分布式系统中都会存在这样的单点问题。
具体地说,单点问题是指在一个分布式系统中,若是某一个组件出现故障就会引发整个系统的可用性大大降低甚至是处于瘫痪状态,那么咱们就认为该组件存在单点问题。
ZooKeeper 确实已经很好地解决了单点问题。咱们已经了解到,基于“过半”设计原则,ZooKeeper 在运行期间,集群中至少有过半的机器保存了最新的数据。所以,只要集群中超过半数的机器还可以正常工做,整个集群就可以对外提供服务。
解决了单点问题,是否是该考虑容灾了呢?答案是否认的,在搭建一个高可用的集群的时候依然须要考虑容灾问题。正如上面讲到的,若是集群中超过半数的机器还在正常工做,集群就可以对外提供正常的服务。
那么,若是整个机房出现灾难性的事故,这时显然已经不是单点问题的范畴了。
在进行 ZooKeeper 的容灾方案设计过程当中,咱们要充分考虑到“过半原则”。也就是说,不管发生什么状况,咱们必须保证 ZooKeeper 集群中有超过半数的机器可以正常工做。所以,一般有如下两种部署方案。
双机房部署
在进行容灾方案的设计时,咱们一般是以机房为单位来考虑问题。在现实中,不少公司的机房规模并不大,所以双机房部署是个比较常见的方案。可是遗憾的是,在目前版本的 ZooKeeper 中,尚未办法可以在双机房条件下实现比较好的容灾效果——由于不管哪一个机房发生异常状况,都有可能使得 ZooKeeper 集群中可用的机器没法超过半数。固然,在拥有两个机房的场景下,一般有一个机房是主要机房(通常而言,公司会花费更多的钱去租用一个稳定性更好、设备更可靠的机房,这个机房就是主要机房,而另一个机房则更加廉价一些)。咱们惟一能作的,就是尽可能在主要机房部署更多的机器。例如,对于一个由 7 台机器组成的 ZooKeeper 集群,一般在主要机房中部署 4 台机器,剩下的 3 台机器部署到另一个机房中。
三机房部署
既然在双机房部署模式下并不能实现好的容灾效果,那么对于有条件的公司,选择三机房部署无疑是个更好的选择,不管哪一个机房发生了故障,剩下两个机房的机器数量都超过半数。假如咱们有三个机房能够部署服务,而且这三个机房间的网络情况良好,那么就能够在三个机房中都部署若干个机器来组成一个 ZooKeeper 集群。
咱们假定构成 ZooKeeper 集群的机器总数为 N,在三个机房中部署的 ZooKeeper 服务器数分别为 N一、N2 和 N3,若是要使该 ZooKeeper 集群具备较好的容灾能力,咱们能够根据以下算法来计算 ZooKeeper 集群的机器部署方案。
若是 ZooKeeper 集群的服务器总数是 N,那么:
N1 = (N-1)/2
在 Java 中,“/” 运算符会自动对计算结果向下取整操做。举个例子,若是 N=8,那么 N1=3;若是 N=7,那么 N1 也等于 3。
N2 的计算规则和 N1 很是相似,只是 N2 的取值是在一个取值范围内:
N2 的取值范围是 1~(N-N1)/2
即若是 N=8,那么 N1=3,则 N2 的取值范围就是 1~2,分别是 1 和 2。注意,1 和 2 仅仅是 N2 的可选值,并不是最终值——若是 N2为某个可选值的时候,没法计算出 N3 的值,那么该可选值也无效。
很显然,如今只剩下 N3 了,能够简单的认为 N3 的取值就是剩下的机器数,即:
N3 = N - N1 - N2
只是 N3 的取值必须知足 N3 < N1+N2。在知足这个条件的基础下,咱们遍历步骤 2 中计算获得的 N2 的可选值,便可获得三机房部署时每一个机房的服务器数量了。
如今咱们以 7 台机器为例,来看看如何分配三机房的机器分布。根据算法的步骤 1,咱们首先肯定 N1 的取值为 3。根据算法的步骤 2,咱们肯定了 N2 的可选值为 1 和 2。最后根据步骤 3,咱们遍历 N2 的可选值,便可获得两种部署方案,分别是(三、一、3)和(三、二、2)。如下是 Java 程序代码对以上算法的一种简单实现:
public class Allocation { static final int n = 7; public static void main(String[] args){ int n1,n2,n3; n1 = (n-1) / 2; int n2_max = (n-n1) / 2; for(int i=1; i<=n2_max; i++){ n2 = i; n3 = n - n1 -n2; if(n3 >= (n1+n2)){ continue; } System.out.println("("+n1+","+n2+","+n3+")"); } } }
水平可扩容能够说是对一个分布式系统在高可用性方面提出的基本的,也是很是重要的一个要求,经过水平扩容可以帮助系统在不进行或进行极少改进工做的前提下,快速提升系统对外的服务支撑能力。简单地讲,水平扩容就是向集群中添加更多的机器,以提升系统的服务质量。
很遗憾的是,ZooKeeper 在水平扩容扩容方面作得并不十分完美,须要进行整个集群的重启。一般有两种重启方式,一种是集群总体重启,另一种是逐台进行服务器的重启。
所谓集群总体重启,就是先将整个集群中止,而后更新 ZooKeeper 的配置,而后再次启动。若是在你的系统中,ZooKeeper 并非个很是核心的组件,而且可以容许短暂的服务中止(一般是几秒钟的时间间隔),那么不妨选择这种方式。在总体重启的过程当中,全部该集群的客户端都没法链接上集群。等到集群再次启动,这些客户端就可以自动链接上——注意,总体启动前创建起的客户端会话,并不会由于这次总体重启而失效。也就是说,在总体重启期间花费的时间将不计入会话超时时间的计算中。
这种方式更适合绝大多数的实际场景。在这种方式中,每次仅仅重启集群中的一台机器,而后逐台对整个集群中的机器进行重启操做。这种方式能够在重启期间依然保证集群对外的正常服务。