顾名思义 zookeeper 就是动物园管理员,他是用来管 hadoop(大象)、Hive(蜜蜂)、pig(小 猪)的管理员, Apache Hbase 和 Apache Solr 的分布式集群都用到了 zookeeper;Zookeeper: 是一个分布式的、开源的程序协调服务,是 hadoop 项目下的一个子项目。他提供的主要功 能包括:配置管理、名字服务、分布式锁、集群管理。
在我们的应用中除了代码外,还有一些就是各种配置。比如数据库连接等。一般我们都 是使用配置文件的方式,在代码中引入这些配置文件。当我们只有一种配置,只有一台服务 器,并且不经常修改的时候,使用配置文件是一个很好的做法,但是如果我们配置非常多, 有很多服务器都需要这个配置,这时使用配置文件就不是个好主意了。这个时候往往需要寻 找一种集中管理配置的方法,我们在这个集中的地方修改了配置,所有对这个配置感兴趣的 都可以获得变更。Zookeeper 就是这种服务,它使用 Zab 这种一致性协议来提供一致性。现 在有很多开源项目使用 Zookeeper 来维护配置,比如在 HBase 中,客户端就是连接一个 Zookeeper,获得必要的 HBase 集群的配置信息,然后才可以进一步操作。还有在开源的消 息队列 Kafka 中,也使用 Zookeeper来维护broker的信息。在 Alibaba开源的 SOA 框架Dubbo 中也广泛的使用 Zookeeper 管理一些配置来实现服务治理。
名字服务这个就很好理解了。比如为了通过网络访问一个系统,我们得知道对方的 IP 地址,但是 IP 地址对人非常不友好,这个时候我们就需要使用域名来访问。但是计算机是 不能是域名的。怎么办呢?如果我们每台机器里都备有一份域名到 IP 地址的映射,这个倒 是能解决一部分问题,但是如果域名对应的 IP 发生变化了又该怎么办呢?于是我们有了 DNS 这个东西。我们只需要访问一个大家熟知的(known)的点,它就会告诉你这个域名对应 的 IP 是什么。在我们的应用中也会存在很多这类问题,特别是在我们的服务特别多的时候, 如果我们在本地保存服务的地址的时候将非常不方便,但是如果我们只需要访问一个大家都 熟知的访问点,这里提供统一的入口,那么维护起来将方便得多了。
其实在第一篇文章中已经介绍了 Zookeeper 是一个分布式协调服务。这样我们就可以利 用 Zookeeper 来协调多个分布式进程之间的活动。比如在一个分布式环境中,为了提高可靠 性,我们的集群的每台服务器上都部署着同样的服务。但是,一件事情如果集群中的每个服 务器都进行的话,那相互之间就要协调,编程起来将非常复杂。而如果我们只让一个服务进 行操作,那又存在单点。通常还有一种做法就是使用分布式锁,在某个时刻只让一个服务去干活,当这台服务出问题的时候锁释放,立即 fail over 到另外的服务。这在很多分布式系统 中都是这么做,这种设计有一个更好听的名字叫 Leader Election(leader 选举)。比如 HBase 的 Master 就是采用这种机制。但要注意的是分布式锁跟同一个进程的锁还是有区别的,所 以使用的时候要比同一个进程里的锁更谨慎的使用。
在分布式的集群中,经常会由于各种原因,比如硬件故障,软件故障,网络问题,有些 节点会进进出出。有新的节点加入进来,也有老的节点退出集群。这个时候,集群中其他机 器需要感知到这种变化,然后根据这种变化做出对应的决策。比如我们是一个分布式存储系 统,有一个中央控制节点负责存储的分配,当有新的存储进来的时候我们要根据现在集群目 前的状态来分配存储节点。这个时候我们就需要动态感知到集群目前的状态。还有,比如一 个分布式的 SOA 架构中,服务是一个集群提供的,当消费者访问某个服务时,就需要采用 某种机制发现现在有哪些节点可以提供该服务(这也称之为服务发现,比如 Alibaba 开源的 SOA 框架 Dubbo 就采用了 Zookeeper 作为服务发现的底层机制)。还有开源的 Kafka 队列就 采用了 Zookeeper 作为 Cosnumer 的上下线管理。
在 Zookeeper 中,znode 是一个跟 Unix 文件系统路径相似的节点,可以往这个节点存储 或获取数据。 Zookeeper 底层是一套数据结构。这个存储结构是一个树形结构,其上的每一个节点, 我们称之为“znode” zookeeper 中的数据是按照“树”结构进行存储的。而且 znode 节点还分为 4 中不同的类 型。 每一个 znode 默认能够存储 1MB 的数据(对于记录状态性质的数据来说,够了) 可以使用 zkCli 命令,登录到 zookeeper 上,并通过 ls、create、delete、get、set 等命令 操作这些 znode 节点
(1)PERSISTENT 持久化节点: 所谓持久节点,是指在节点创建后,就一直存在,直到 有删除操作来主动清除这个节点。否则不会因为创建该节点的客户端会话失效而消失。
(2)PERSISTENT_SEQUENTIAL 持久顺序节点:这类节点的基本特性和上面的节点类 型是一致的。额外的特性是,在 ZK 中,每个父节点会为他的第一级子节点维护一份时序, 会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属 性,那么在创建节点过程中,ZK 会自动为给定节点名加上一个数字后缀,作为新的节点名。 这个数字后缀的范围是整型的最大值。 在创建节点的时候只需要传入节点 “/test_”,这样 之后,zookeeper 自动会给”test_”后面补充数字。
(3)EPHEMERAL 临时节点:和持久节点不同的是,临时节点的生命周期和客户端会 话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提 到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。 这里还要注意一件事,就是当你客户端会话失效后,所产生的节点也不是一下子就消失 了,也要过一段时间,大概是 10 秒以内,可以试一下,本机操作生成节点,在服务器端用 命令来查看当前的节点数目,你会发现客户端已经 stop,但是产生的节点还在。
(4) EPHEMERAL_SEQUENTIAL 临时自动编号节点:此节点是属于临时节点,不过带 有顺序,客户端会话结束节点就消失。
配置 Zookeeper
Zookeeper 在启动时默认的去 conf 目录下查找一个名称为 zoo.cfg 的配置文件。 在 zookeeper 应用目录中有子目录 conf。其中有配置文件模板:zoo_sample.cfg cp zoo_sample.cfg zoo.cfg。zookeeper 应用中的配置文件为 conf/zoo.cfg。 修改配置文件 zoo.cfg - 设置数据缓存路径
集群版
Zookeeper 集群中的角色
Zookeeper 集群中的角色主要有以下三类
Leader: Leader 是集群中的的主节点,负责响应 所有对 ZooKeeper 状态变更的请求,写操作都走 Leader。
Follower: Follower 负责处理本服务器上的 读请求,对于写请求会转发给 Leader 进行处理。
Observer: Observer 作为集群中的 观察节点,本身并 不参加选举也不响应提议、不需要将事务持久化到磁盘,但是可以提供 一定程度的读功能。
设计目的
1.最终一致性:client 不论连接到哪个 Server,展示给它都是同一个视图,这是 zookeeper 最重要的性能。
2.可靠性:具有简单、健壮、良好的性能,如果消息 m 被到一台服务器接受,那么它 将被所有的服务器接受。
3.实时性:Zookeeper 保证客户端将在一个时间间隔范围内获得服务器的更新信息,或 者服务器失效的信息。但由于网络延时等原因,Zookeeper 不能保证两个客户端能同时得到 刚更新的数据,如果需要最新数据,应该在读数据之前调用 sync()接口。
4.等待无关(wait-free):慢的或者失效的 client 不得干预快速的 client 的请求,使得每 个 client 都能有效的等待。
5.原子性:更新只能成功或者失败,没有中间状态。
6.顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息 a 在消息 b 前发布,则在所有 Server 上消息 a 都将在消息 b 前被发布;偏序是指如果一个消息 b 在消 息 a 后被同一个发送者发布,a 必将排在 b 前面。
提供应用唯一标识
在 Zookeeper 集群中,每个节点需要一个唯一标识。这个唯一标识要求是自然数。且唯 一标识保存位置是:$dataDir/myid。其中 dataDir 为配置文件 zoo.cfg 中的配置参数 在 data 目录中创建文件 myid : touch myid 为应用提供唯一标识。本环境中使用 1、2、3 作为每个节点的唯一标识。 vi myid 简化方式为: echo [唯一标识] >> myid。 echo 命令为回声命令,系统会将命令发送的 数据返回。 ‘>>‘为定位,代表系统回声数据指定发送到什么位置。 此命令代表系统回声数 据发送到 myid 文件中。 如果没有文件则创建文件。
connect host:port - 连接其他的 ZooKeeper 应用。
ls path - 列表路径下的资源。在 ZooKeeper 控制台客户端中,没有默认列表功能,必须 指定要列表资源的位置。 如: ls / ; ls /path 等。
create [-e] [-s] path data - 创建节点,如:
create /test 123 创建一个/test 节点,节点携 带数据信息 123。
create -e /test 123 创建一个临时节点/test,携带数据为 123,临时节点只 在当前会话生命周期中有效,会话结束节点自动删除。
create -s /test 123 创建一个顺序节点 /test,携带数据123,创建的顺序节点由ZooKeeper自动为节点增加后缀信息,如-/test00000001 等。-e 和-s 参数可以联合使用。
get path - 查看指定节点的数据。 如: get /test。结果如下:
set path data [version] - 设置对应位置节点的数据。如: set /test ‘test data‘。 如果要设 置的数据中有空格,则使用单引号界定数据的范围。每次修改数据后,dataVersion 属性自增。 那么在 set 命令中可以指定 version,version 数据必须与上次查询的值一致,用于保证本次修 改命令执行时,没有其他会话修改此数据。
delete path [version] - 删除指定节点,此命令不能删除有子节点的节点。如:delete /test。 其中 version 参数和 set 命令的 version 含义一致 rmr path - 删除指定结点,包括子节点。
quit - 退出控制台
//创建节点
create /zk_test
//创建带数据的节点
create /zk_test data
//创建临时节点
create -e /zk_test
//创建带数据的临时节点
create -e /zk_test data
//设置节点携带的数据
setData /zk_test data
//获取节点携带的数据
getData /zk_test
get /zk_test
//创建子节点
create /zk_test/children
//获取节点的子节点
getChildren /zk_test
//判断节点是否存在
exists /zk_test
//删除节点
delete /zk_test
基本配置
dataDir //配置内存中数据快照的目录,由 异步线程后台写入,对性能不会有太大影响,但是建议配置在空间足够大的盘中
dataLogDir //写请求的事务日志信息,同步顺序写入最好使用独立且高性能的存储
tickTime //基本实践度量单位,默认客户端会话超时时间为 tick * 2,调整该值将影响 sessionTimeout、initLimit、syncLimit 等多个关联配置值越小越早发现超时问题,但是也有 更高的心跳网络流程与更多的CPU使用率
网络配置
globalOutstandingLimit //服务器 最多能处理的客户端请求数,为了防止客户端请求量太大,服务端将请求顺序队列存储造成的内存溢出,达到上限后将 不再处理客户端的请求(没有等待请求的客户端可以继续处理),根据服务器的硬件配置合理设置
maxClientCnxns //限制客户端 单IP最大的并发请求量,可以避免DoS攻击,暴露在外网的集群建议配置
minSessionTimeout //默认为 tickTime * 2,客户端会话超时的 最小时间
maxSessionTimeout //默认为 tickTime * 20,客户端会话超时的 最大时间
集群配置
initLimit //某follower初次连接到leader后进行 初始化的超时时间,单位为tickTime的倍数,初次连接需要同步和请求大量数据
syncLimit //follower与leader进行同步的超时时间,follower超时后将会被移除,单位为tickTime的倍数
leaderServes
leader //是否为客户端提供服务,默认为yes,集群同步资源消耗大的情况下可关闭
peerType=observer //设置节点类型为观察者,适当添加观察者可以一定程度上增加集群的读负载
cat zoo.cfg
tickTime=2000
nitLimit=10
syncLimit=5
dataDir=/zookeeper/data
dataLogDir=/zookeeper/logs
clientPort=2181
server.1=zk01:2888:3888
server.2=zk02:2888:3888
server.3=zk03:2888:3888
Zookeeper 分别为开发人员提供了 同步API和异步API 使用。
在同步API中开发人员需要自己通过 while 循环来控制客户端与服务端的交互,并在交互过程中需要处理诸如 ConnectionLossException、NodeExistsException 等异常,否则客户端将 无法完整的获得 服务端传递的信息。
而在异步API中,客户端在与服务端交互过程中通过设置 回调对象 的方式来提供回调方法。
部分回调接口定义如下:
public interface StringCallback {
//rc: 调用的结果值,返回OK或者与KeeperException对应的异常编码
//path: 节点路径
//ctx: 上下文参数
//name: 节点名称
void processResult(int rc, String path, Object ctx, String name);
}
public interface StatCallback() {
//stat: 服务端返回的相关信息,如zxid时间戳、子节点个数等元数据
void processResult(int rc, String path, Object ctx, Stat stat);
}
public interface DataCallback() {
//data: 设置的节点数据
void processResult(int rc, String path, Object ctx, byte[] data, Stat stat);
}
服务端收到客户端请求(如 create)中携带的回调对象后,会将 需要响应的信息写入该对象中并回传给客户端的回调线程处理,回调线程中会自动调用开发人员设置的 processResult 回调方法。
如此一来,开发人员只需要在回调方法中 根据服务端的返回编码 做出对应的处理即可。在保证编程简洁的同时,效率也比同步API要高得多。
ZooKeeper在提供分布式锁等服务的时候需要过半数的节点可用,另外高可用的诉求来说,节点的个数必须>1,所以ZooKeeper集群需要>1的奇数节点,例如3,5,7等等,本次我们规划三个节点,操作系统选用CentOS7。
List:
CentOS7.3
zookeeper-3.4.14.tar.gz
kafka_2.12-2.3.0.tgz
注意事项:
一.初始化系统环境
1.初始化
init_security() {
systemctl stop firewalld
systemctl disable firewalld &>/dev/null
setenforce 0
sed -i ‘/^SELINUX=/ s/enforcing/disabled/‘ /etc/selinux/config
sed -i ‘/^GSSAPIAu/ s/yes/no/‘ /etc/ssh/sshd_config
sed -i ‘/^#UseDNS/ {s/^#//;s/yes/no/}‘ /etc/ssh/sshd_config
systemctl enable sshd crond &> /dev/null
rpm -e postfix --nodeps
echo -e "\033[32m [安全配置] ==> OK \033[0m"
}
init_security
init_yumsource() {
if [ ! -d /etc/yum.repos.d/backup ];then
mkdir /etc/yum.repos.d/backup
fi
mv /etc/yum.repos.d/* /etc/yum.repos.d/backup 2>/dev/null
if ! ping -c2 www.baidu.com &>/dev/null
then
echo "您无法上外网,不能配置yum源"
exit
fi
curl -o /etc/yum.repos.d/163.repo http://mirrors.163.com/.help/CentOS7-Base-163.repo &>/dev/null
curl -o /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo &>/dev/null
yum clean all
timedatectl set-timezone Asia/Shanghai
echo "nameserver 114.114.114.114" > /etc/resolv.conf
echo "nameserver 8.8.8.8" >> /etc/resolv.conf
chattr +i /etc/resolv.conf
yum -y install ntpdate
ntpdate -b ntp1.aliyun.com # 对时很重要,避免zookeeper因为时间不准找不到主机
echo -e "\033[32m [YUM Source] ==> OK \033[0m"
}
init_yumsource
# 配置主机名解析
tail -3 /etc/hosts
172.19.0.3 zk01
172.19.0.4 zk02
172.19.0.5 zk03
二. 准备Java环境并安装ZooKeeper
# 准备Java环境
tar xvf jdk-8u151-linux-x64.tar.gz -C /usr/local/
tail -3 /etc/profile
export JAVA_HOME=/usr/local/jdk1.8.0_151
export PATH=$PATH:$JAVA_HOME/bin
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
source /etc/profile
# 下载ZooKeeper包,并解压到相应目录
wget https://mirrors.tuna.tsinghua.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz
mkdir /usr/zookeeper # 创建应用目录
mkdir /zookeeper/data -p # 创建数据目录
mkdir /zookeeper/logs
tar xvf zookeeper-3.4.14.tar.gz -C /usr/zookeeper/
# 配置环境变量
tail -2 /etc/profile # profile最后增加如下内容
export ZOOKEEPER_HOME=/usr/zookeeper/zookeeper-3.4.14
export PATH=$ZOOKEEPER_HOME/bin:$PATH
# 使环境变量生效
source /etc/profile
# 查看配置结果
echo $ZOOKEEPER_HOME
/usr/zookeeper/zookeeper-3.4.14
2.4. 配置ZooKeeper
# 给原有配置文件做一个备份
cd /usr/zookeeper/zookeeper-3.4.14/conf/
cp zoo_sample.cfg zoo.cfg
# 修改配置文件
cat zoo.cfg
tickTime=2000
nitLimit=10
syncLimit=5
dataDir=/zookeeper/data
dataLogDir=/zookeeper/logs
clientPort=2181
server.1=zk01:2888:3888
server.2=zk02:2888:3888
server.3=zk03:2888:3888
# 配置节点标识
zk01机器: echo "1" > /zookeeper/data/myid
zk02机器: echo "2" > /zookeeper/data/myid
zk03机器: echo "3" > /zookeeper/data/myid
三.启动ZooKeeper
# 进入ZooKeeper bin目录
cd $ZOOKEEPER_HOME/bin
# sh $Z启动
sh zkServer.sh start
# 或者直接
zkServer.sh start
zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /opt/zookeeper-3.4.14/bin/../conf/zoo.cfg
Mode: follower
# 如果启动报错,通过以下命令查看日志
zkServer.sh start-foreground
四.测试ZooKeeper连接有不有问题
zkCli.sh -server zk1:2181
WATCHER::
WatchedEvent state:SyncConnected type:None path:null
# 输入命令测试,查看ZooKeeper根
[zk: zk1:2181(CONNECTED) 0] ls /
[zookeeper]