目录
一、由来
Zookeeper最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。
关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的Pig项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家RaghuRamakrishnan(罗摩克里希纳)开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而Zookeeper正好要用来进行分布式环境的协调一一于是,Zookeeper的名字也就由此诞生了
二、简介
官网:https://zookeeper.apache.org/
ApacheZooKeeper是一项致力于开发和维护开源服务器的工作,它能够实现高度可靠的分布式协调。
ZooKeeper是一个集中的服务,用于维护配置信息、命名( 服务注册和发现)、提供分布式同步以及提供组服务。
三、应用场景
概述:提供了文件系统(存储信息)和通知机制(watch)
命名服务
常见的就是一些分布式服务框架(如RPC、RMI)中的服务地址列表,通过使用命名服务,客户端应用能够根据指定名字来获取资源的实体、服务地址和提供者的信息等
如dubbo把zookeeper做注册中心时:
/dubbo
/provider:存放服务地址
/consumer:存放消费地址
/conf:存放配置信息限制
…
consumer通过监听provider节点的内容修改实现动态读取地址,并且支持集群,只需要在provider中存放多个地址然后程序中通过代码实现随机调用即可
配置管理
实现思路:
1、将公共的配置存放在Zookeeper的节点中
2、应用程序可以连接到Zookeeper中并对Zookeeper中配置节点进行读取或者修改(对于写操作可以进行权限验证设置)
集群管理
思路:
监控系统在/clusterServers节点上注册一个Watcher监听,那么进行动态添加机器的操作,就会在/clusterServers节点下创建一个临时节点:/clusterServer/[Hostname],如果该机器故障,临时节点就会被取消。这样一来,监控系统就能够实时检测到机器的变动情况
如下管理:
希望知道当前集群中究竟有多少机器在工作。
对集群中每台机器的运行时状态进行数据收集。
对集群中机器进行上下线操作。
分布式锁
在我们日常的开发中,如果是单个进程中对共享资源的访问,我们只需要用synchronized或者lock就能实现互斥操作。
但是对于跨进程、跨主机、跨网络的共享资源似乎就无能为力了,(分布式锁)
思路:
1, 首先zookeeper中我们可以创建一个/distributed_lock持久化节点
2,然后再在/distributed_lock节点下创建自己的临时顺序节点,比如:/distributed_lock/task_00000000000 001 007 006 009 ....00020
3,获取所有的/distributed_lock下的所有子节点,并排序
4,判断自己创建的节点是否最小值(第一位)
5,如果是,则获取得到锁,执行自己的业务逻辑,最后删除这个临时节点。(如果出错可以让会话断开,创建临时节点消失,有效防止阻塞或者死锁)
6,如果不是最小值,则需要监听自己创建节点前一位节点的数据变化,并阻塞。
7,当前一位节点被删除时,我们需要通过递归来判断自己创建的节点是否是最小的,如果是则执行5);如果不是则执行6)(就是递归循环的判断)
队列管理
思路:
A生产者 硬件配置好,并发量非常大 1000->MQ-> B消费者 配置不好,并发量小 100 (A请求的并发量太大 B卡死)
1,首先利用Zookeeper中临时顺序节点的特点
2,当生产者创建节点生产时,需要判断父节点下临时顺序子节点的个数,如果达到了上限,则阻塞等待;如果没有达到,就创建节点。 上限比如:10000
3,当消费者获取节点时,如果父节点中不存在临时顺序子节点,则阻塞等待(没有消息消费);如果有子节点,则获取执行自己的业务,执行完毕后删除该节点即可
4,获取时获取最小值,保证FIFO(first input first output)特性。
四、配置详解
tickTime:CS通信心跳时间
Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime 时间就会发送一个心跳。tickTime以毫秒为单位。
initLimit=10: 对于从节点最初连接到主节点时的初始化时间,单位为tick值的倍数。
syncLimit=5:对于主节点与从节点进行同步操作时的超时时间,单位为tick值的倍数。
dataDir=/tmp/zookeeper: 用于配置内存数据库保存的模糊快照的目录。即刚刚创建的data文件夹就是在此目录中。文件信息都存放在data目录下。本机数据存放目录
clientPort=2181: 表示客户端所连接的服务器所监听的端口号,默认是2181。即zookeeper对外提供访问的端口号。
#server.1=127.0.0.1:2888:3888 不是集群可以不加 127.0.0.1也可以是主机名称
server.1 本机标识
2888是leader和follower的通信端口
3888是选举投票端口
当前zookeeper与其他zookeeper通信端口
五、集群搭建
apache-zookeeper-3.6.2-bin.tar.gz:zookeeper-bin.tar
集群规划
192.168.37.152 cluster1
192.168.37.152 cluster2
192.168.37.152 cluster3
集群基础
Zookeeper集群中节点个数一般为奇数个2N+1(N>0) 大于等于3,若集群中leader挂掉,剩余follower节点个数在半数以上时,就可以推举新的主节点,继续对外提供服务。
解压下载的安装包
tar -xzvf /software/apache-zookeeper-3.6.2-bin.tar.gz -C /usr/
重命名目录并配置环境变量
mv /usr/apache-zookeeper-3.6.2-bin/ /usr/zookeeper
vim /etc/profile
export ZK_HOME=/usr/zookeeper
export PATH=$ZK_HOME/bin:$PATH
source /etc/profile
复制配置文件(启动默认加载conf/zoo.cfg文件)
cp /usr/zookeeper/conf/zoo_sample.cfg /usr/zookeeper/conf/zoo.cfg
#单机版已经完成,可以使用 zkServer.sh start 启动 zkCli.sh 连接
修改zoo.cfg
vim /usr/zookeeper/conf/zoo.cfg
server.1=cluster1:2888:3888
server.2=cluster2:2888:3888
server.3=cluster3:2888:3888
添加集群配置
注意:cluster1,cluster2,cluster3是机子名称,需要配置本地的/etc/hosts配置,如果没有配置,需要用ip地址,不能写机子名称,2888是集群节点的通信端口3888是集群投票端口
测试三台机子的连通性
#分别在三台机子互相ping以下,看看能不能连通
ping cluster1 2 3
比如:cluster1去测试ping cluster2和cluster2
按照dataDir配置路径新建目录
mkdir /usr/zookeeper/data
新建myid文件
在data配置的目录下新建myid文件里面和server.1,server.2,server.3对应
vim /usr/zookeeper/data/myid 并写入1 保存退出 自动生成该文件
题外话
以上操作都是需要zookeeper1、zookeeper2、zookeeper3同时进行,都需要相同的操作,也可以现在zookeeper1上配置好之后,复制到2和3上
就需要做一个免密
在zk1上执行
#免密配置
ssh-keygen -t rsa 回车3次ssh-copy-id cluster1
ssh-copy-id cluster2
ssh-copy-id cluster3
scp -r /usr/zookeeper/ cluster2:/usr/
scp -r /usr/zookeeper/ cluster3:/usr/
在zk2和zk3上执行
vim /usr/zookeeper/data/myid //myid改为2和3
cat /usr/zookeeper/data/myid //在all-session中查看
#分发/etc/profile和/etc/hosts
scp /etc/profile cluster2:/etc/
scp /etc/profile cluster3:/etc/
cat /etc/profile //在all-session中查看
source /etc/profile //在all-session中查看
echo $ZK_HOME //在all-session中查看
scp /etc/hosts cluster2:/etc/
scp /etc/hosts cluster2:/etc/
cat /etc/hosts //在all-session中查看
启动集群
zkServer.sh start #启动集群,使用不同启动顺序来观察leader
zkServer.sh stop #关闭集群
zkServer.sh status #查看集群状态,可以看到谁是leader 谁是follower
分别启动1,2,3进行查看
jps 相当于 ps -ef|grep java 查看以java为基础运行的进程
jps //看到QuorumPeerMain进程,说明集群启动成功
关闭任意一台,集群照样使用,leader和follower也发生变化(半数关闭,集群失效)
六、集群投票选举leader原理
概念
SID:
服务器ID (myid=1 myid=2.....)
SID是一个数字,用来唯一标识一台ZooKeeper集群中的机器,每台机器不能重复,和myid的值一致
事务ID (ZXID):
事务是指能够改变Zookeeper服务器状态的操作,一般包括数据节点的创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每个事务请求,zk都会为其分配一个全局唯一的事务ID,即ZXID,是一个64位的数字,高32位表示该事务发生的集群选举周期(集群每发生一次leader选举,值加1),低32位表示该事务在当前选择周期内的递增次序(leader每处理一个事务请求,值加1,发生一次leader选择,低32位要清0)。
Vote:投票(核心原则,zxid一样时投票给sid大的服务器)
Leader选举,就是通过投票来实现,当集群中的机器发现自己无法检测到Leader机器的时候,就会开始尝试进行投票。一旦选出一个Leader,那么所有服务器的集群角色一般不会再发生变化,也就是说,Leader服务器将一直作为集群的Leader,即使集群中有非Leader挂了或有新机器加入集群也不会影响Leader。但是一旦Leader所在机器挂了,那么整个集群将暂时无法对外提供服务,而是进入新一轮的Leader选举。服务器运行期间的Leader选举和启动时期的Leader选举基本过程一致的。
Quorum:过半机器数(过半原则,防脑裂)
这个是整合Leader选举算法中最重要的一个术语,我们可以理解为一个量词,指的是ZooKeeper集群中过半的机器数,公式为quorum=(n/2+1)。例如:如果集群机器总数为3,那么quorum就是2 1, 2 ,3 ,4,5
Zookeeper节点状态
LOOKING:寻找Leader状态,处于该状态需要进入选举流程
LEADING:领导者状态,处于该状态的节点说明是角色已经是Leader
FOLLOWING:跟随者状态,表示Leader已经选举出来,当前节点角色是follower
OBSERVER:观察者状态,表明当前节点角色是observer,不参与选举
选举过程
Zookeeper集群初始化启动时Leader选举
若进行Leader选举,则至少需要两台机器,这里选取3台机器组成的服务器集群为例。
在集群初始化阶段,当有一台服务器ZK1启动时,其单独无法进行和完成Leader选举,当第二台服务器ZK2启动时,此时两台机器可以相互通信,每台机器都试图找到Leader,于是进入Leader选举过程。选举过程开始,过程如下:
(1) 每个Server发出一个投票。由于是初始情况,ZK1和ZK2都会将自己作为Leader服务器来进行投票,每次投票会包含所推举的服务器的myid和ZXID,使用(myid, ZXID)来表示,此时ZK1的投票为(1, 0),ZK2的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
(2) 接受来自各个服务器的投票。集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票、是否来自LOOKING状态的服务器。
(3) 处理投票。针对每一个投票,服务器都需要将别人的投票和自己的投票进行比较,规则如下
· 优先检查ZXID。ZXID比较大的服务器优先作为Leader。
· 如果ZXID相同,那么就比较myid。myid较大的服务器作为Leader服务器。
对于ZK1而言,它的投票是(1, 0),接收ZK2的投票为(2, 0),首先会比较两者的ZXID,均为0,再比较myid,此时ZK2的myid最大,于是ZK2胜。ZK1更新自己的投票为(2, 0),并将投票重新发送给ZK2。
(4) 统计投票。每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息,对于ZK1、ZK2而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出ZK2作为Leader。
(5) 改变服务器状态。一旦确定了Leader,每个服务器就会更新自己的状态,如果是Follower,那么就变更为FOLLOWING,如果是Leader,就变更为LEADING。当新的Zookeeper节点ZK3启动时,发现已经有Leader了,不再选举,直接将直接的状态从LOOKING改为FOLLOWING。
Zookeeper集群运行期间Leader重新选
(1) 变更状态。Leader挂后,余下的非Observer服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举过程。
(2) 每个Server会发出一个投票。在运行期间,每个服务器上的ZXID可能不同,此时假定ZK1的ZXID为124,ZK3的ZXID为123;在第一轮投票中,ZK1和ZK3都会投自己,产生投票(1, 124),(3, 123),(4, 120)然后各自将投票发送给集群中所有机器。
(3) 接收来自各个服务器的投票。与启动时过程相同。
(4) 处理投票。与启动时过程相同,由于ZK1事务ID大,ZK1将会成为Leader。
(5) 统计投票。与启动时过程相同。
(6) 改变服务器的状态。与启动时过程相同。
七、扩展——脚本启动所有
#!/bin/bash
zkpsnum=`ps -ef|grep zookeeper-root-server-cluster|grep -v grep|wc -l`
if [ $zkpsnum -eq 0 ];then
echo 'zk集群开始启动'
for i in {1..3}
do
ssh cluster$i "source /etc/profile;zkServer.sh start"
done
echo 'zk集群启动完毕'
echo 'zk集群状态为:'
for i in {1..3}
do
ssh cluster$i "source /etc/profile;zkServer.sh status"
done
else
echo 'zk集群开始关闭'
for i in {1..3}
do
ssh cluster$i "source /etc/profile;zkServer.sh stop"
done
echo 'zk集群关闭完毕'
fi