基础入门
Zookeeper概述
ZooKeeper是一种分布式协调服务
,所谓分布式协调服务可以在分布式系统中共享配置,协调锁资源,提供命名服务。
zookeeper是基于内存同步数据的,所以集群内的节点其内存中的数据结构是完全相同的,因此效率非常高。
下载地址:https://zookeeper.apache.org/releases.html#download
Zookeeper特性
- 顺序一致性:从同一个客户端发起的事务请求,最终将会严格按照其发起顺序被应用到zookeeper中
- 可靠性:消息message被到一台服务器接受,那么它到任何服务器都被接受。
- 实时性:zk保证在一个时间间隔范围内获得服务器的更新信息,或者服务器失效信息。但是由于网络延时等一些其他原因,zk不能保证两个客户端同事得到跟新或者失效信息。
- 原子性:更新只能成功或者失败,没有其他中间信息。
- 单一视图:无论客户端连接的是哪个zookeeper服务器,其看到的服务端数据模型都是一致的
Zookeeper数据模型
Zookeeper的数据模型很像数据结构当中的树,也很像文件系统的目录。树是由节点所组成,Zookeeper的数据存储也同样是基于节点,这种节点叫做Znode
。但是,不同于树的节点,Znode的引用方式是路径引用,类似于文件路径,这样的层级结构,让每一个Znode节点拥有唯一的路径,就像命名空间一样对不同信息作出清晰的隔离。
Znode包含了数据、子节点引用、访问权限等等:
data
:Znode存储的数据信息。
ACL
:记录Znode的访问权限,即哪些人或哪些IP可以访问本节点。
stat
:包含Znode的各种元数据,比如事务ID、版本号、时间戳、大小等等。
child
:当前节点的子节点引用,类似于二叉树的左孩子右孩子
注意:Zookeeper是为读多写少的场景所设计。Znode并不是用来存储大规模业务数据,而是用于存储少量的状态和配置信息,每个节点的数据最大不能超过1MB。
节点类型:
ZooKeeper中的节点有两种,分别为临时节点和永久节点。节点的类型在创建时即被确定,并且不能改变。
- 临时节点:该节点的生命周期依赖于创建它们的会话。一旦会话(Session)结束,临时节点将被自动删除,当然可以也可以手动删除。虽然每个临时的Znode都会绑定到一个客户端会话,但他们对所有的客户端还是可见的。另外,ZooKeeper的临时节点不允许拥有子节点。
- 永久节点:该节点的生命周期不依赖于会话,并且只有在客户端显示执行删除操作的时候,他们才能被删除。
- 顺序节点:当创建Znode的时候,用户可以请求在ZooKeeper的路径结尾添加一个递增的计数。这个计数对于此节点的父节点来说是唯一的,它的格式为"%10d"(10位数字,没有数值的数位用0补充,例如"0000000001")。
- 监视器:客户端可以在节点上设置watch,我们称之为监视器。当节点状态发生改变时(Znode的增、删、改)将会触发watch所对应的操作。当watch被触发时,ZooKeeper将会向客户端发送且仅发送一条通知,因为watch只能被触发一次,这样可以减少网络流量。
Zookeeper的基本操作和事件通知
那么我们如何来操作这些节点呢?
zookeeper为我们提供了一些简单的API
,甚至还提供了触发器机制。
常用API:
create:创建节点
delete:删除节点
exists:判断节点是否存在
getData:获得一个节点的数据
setData:设置一个节点的数据
getChildren:获取节点下的所有子节点
这其中,exists
,getData
,getChildren
属于读操作。Zookeeper客户端在请求读操作的时候,可以选择是否设置Watch
。
Watch:
可以理解成是注册在特定Znode上的触发器。当这个Znode发生改变,也就是调用了create,delete,setData方法的时候,将会触发Znode上注册的对应事件,请求Watch的客户端会接收到异步通知。
Watcher数据变更通知:
Zookeeper使用Watcher机制实现分布式数据的发布/订阅
功能。
Zookeeper的Watcher机制主要包括客户端线程、客户端WatcherManager、Zookeeper服务器三部分。客户端在向Zookeeper服务器注册的同时,会将Watcher对象存储在客户端的WatcherManager当中。当Zookeeper服务器触发Watcher事件后,会向客户端发送通知,客户端线程从WatcherManager中取出对应的Watcher对象来执行回调逻辑。
Zookeeper的一致性
身为分布式系统的协调服务,为了防止单机挂掉的情况,zookeeper维护了一个集群:
Zookeeper Service集群是一主多从结构。在更新数据时,首先更新到主节点(这里的节点是指服务器,不是Znode),再同步到从节点。在读取数据时,直接读取任意从节点。
集群角色:
角色 | 说明 |
---|---|
Leader | 集群内部各服务器的调度者,事务请求的唯一调度和处理者,保证集群事务处理的顺序性。负责投票的发起和决议,更新系统状态,处理写请求。 |
Follower | 参与客户端非事务请求,转发事务请求给Leader服务器,参与请求的Proposal投票,参与Leader选举投票。 |
Observer | ObServer可以接受客户端连接,将写请求转发给leader节点,但ObServer不参加投票过程,只同步leader状态。ObServer的目的是为了扩展系统,提高读取速度。 |
Client | 执行读写请求的发起方 |
ZAB协议:
为了保证主从节点的数据一致性,Zookeeper采用了ZAB协议
,这种协议非常类似于一致性算法Paxos和Raft。
ZAB
即Zookeeper Atomic Broadcast
,有效解决了zookeeper集群崩溃恢复,以及主从同步数据的问题。
在学习ZAB之前,我们需要首先了解ZAB协议所定义的三种节点状态:
Looking :选举状态。
Following :Follower节点(从节点)所处的状态。
Leading :Leader节点(主节点)所处状态。
我们还需要知道最大ZXID
的概念:最大ZXID也就是节点本地的最新事务编号,包含epoch和计数两部分。epoch是纪元的意思,相当于Raft算法选主时候的term。
崩溃恢复:
假如Zookeeper当前的主节点挂掉了,集群会进行崩溃恢复。ZAB的崩溃恢复分成三个阶段:
1.Leader election
:选举阶段
此时集群中的节点处于Looking
状态。它们会各自向其他节点发起投票,投票当中包含自己的服务器ID和最新事务ID(ZXID);
接下来,节点会用自身的ZXID和从其他节点接收到的ZXID做比较,如果发现别人家的ZXID比自己大,也就是数据比自己新,那么就重新发起投票,投票给目前已知最大的ZXID所属节点;
每次投票后,服务器都会统计投票数量,判断是否有某个节点得到半数以上的投票。如果存在这样的节点,该节点将会成为准Leader,状态变为Leading。其他节点的状态变为Following。
2.Discovery
:发现阶段
用于在从节点中发现最新的ZXID和事务日志。或许有人会问:既然Leader被选为主节点,已经是集群里数据最新的了,为什么还要从节点中寻找最新事务呢?
这是为了防止某些意外情况,比如因网络原因在上一阶段产生多个Leader的情况。
所以这一阶段,Leader集思广益,接收所有Follower发来各自的最新epoch值。Leader从中选出最大的epoch,基于此值加1,生成新的epoch分发给各个Follower。
各个Follower收到全新的epoch后,返回ACK给Leader,带上各自最大的ZXID和历史事务日志。Leader选出最大的ZXID,并更新自身历史日志。
3.Synchronization
:同步阶段
把Leader刚才收集得到的最新历史事务日志,同步给集群中所有的Follower。只有当半数Follower同步成功,这个准Leader才能成为正式的Leader。
Broadcast:
ZAB是怎么实现写入数据的呢?
写入数据涉及到ZAB协议的Broadcast阶段。
什么是Broadcast呢?简单来说,就是Zookeeper常规情况下更新数据的时候,由Leader广播到所有的Follower。其过程如下:
- 客户端发出写入数据请求给任意Follower。
- Follower把写入数据请求转发给Leader。
- Leader采用二阶段提交方式,先发送Propose广播给Follower。
- Follower接到Propose消息,写入日志成功后,返回ACK消息给Leader。
- Leader接到半数以上ACK消息,返回成功给客户端,并且广播Commit请求给Follower。
Zab协议既不是强一致性,也不是弱一致性,而是处于两者之间的
单调一致性
。它依靠事务ID和版本号,保证了数据的更新和读取是有序的
。
Zookeeper应用场景
1.分布式锁
:这是雅虎研究员设计Zookeeper的初衷。利用Zookeeper的临时顺序节点,可以轻松实现分布式锁。
2.服务注册和发现
:利用Znode和Watcher,可以实现分布式服务的注册和发现。最著名的应用就是阿里的分布式RPC框架Dubbo。
3.共享配置和状态信息
:Redis的分布式解决方案Codis,就利用了Zookeeper来存放数据路由表和 codis-proxy 节点的元信息。同时 codis-config 发起的命令都会通过 ZooKeeper 同步到各个存活的 codis-proxy。
此外,Kafka、HBase、Hadoop,也都依靠Zookeeper同步节点信息,实现高可用。
。。。
环境部署
ZooKeeper的工作模式有三种:单机模式、集群模式、伪集群模式。
- 单机模式:Zookeeper只运行在一台服务器上,适合测试环境;
- 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例;
- 集群模式:Zookeeper运行于一个至少有三个节点以上集群中,适合生产环境;
伪集群模式
在同一台机器上部署3个zookeeper server时,需要为每个server创建独立的配置文件,并且保证每个配置文件中的端口不能冲突,除了clientPort不同之外,另外,还要在dataDir所对应的目录中创建myid文件来指定对应的zookeeper server 实例。
注意事项:
clientPort端口
:如果在1台机器上部署多个server,那么每台机器都要不同的 clientPort,比如 server1是2181,server2是2182,server3是2183
dataDir和dataLogDir
:dataDir和dataLogDir也需要区分下,将数据文件和日志文件分开存放,同时每个server的这两变量所对应的路径都是不同的
server.X和myid
: server.X 这个数字就是对应,data/myid中的数字。在3个server的myid文件中分别写入了0,1,2,那么每个server中的zoo.cfg都配 server.0 server.2,server.3就行了。因为在同一台机器上,后面连着的2个端口,3个server都不要一样,否则端口冲突
启动命令:
bin/zkServer.sh start zoo1.cfg
bin/zkServer.sh start zoo2.cfg
bin/zkServer.sh start zoo3.cfg
集群模式
实验环境:(搭建elk平台的kafka集群,本节先部署zookeeper)
IP | Server | myid |
---|---|---|
192.168.20.201 | Kafka+ZooKeeper | 1 |
192.168.20.203 | Kafka+ZooKeeper | 2 |
192.168.20.56 | Kafka+ZooKeeper | 3 |
zk服务器集群规模不得小于3个节点
要求各服务器之间系统时间要保持一致
1.在三个节点上解压程序,创建数据库目录,安装JDK:
#解压程序:
tar xf zookeeper-3.4.12.tar.gz -C /usr/local/
#创建数据库目录:
mkdir /data/zookeeper/
#安装JDK
rpm -ivh jdk-8u45-linux-x64.rpm
echo -e 'export JAVA_HOME=/usr/java/jdk1.8.0_45\nexport CLASSPATH=$CLASSPATH:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$CLASSPATH\nexport PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH:$HOMR/bin'>>/etc/profile
source /etc/profile
2.在192.168.20.201节点上修改配置文件,然后同步到其他两个节点上:
cd /usr/local/zookeeper-3.4.12/conf/
cp zoo_sample.cfg zoo.cfg
#编辑修改配置文件:
# grep -Ev "^$|#" zoo.cfg
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/data/zookeeper/
clientPort=2181
server.1=192.168.20.201:2888:38888
server.2=192.168.20.203:2888:38888
server.3=192.168.20.56:2888:38888
#同步配置文件到其他两台节点:
scp zoo.cfg root@192.168.20.203:/usr/local/zookeeper-3.4.12/conf/
scp zoo.cfg root@192.168.20.56:/usr/local/zookeeper-3.4.12/conf/
注: zookeeper 集群,每个节点的配置文件都是一样的。所以直接同步过去,不需要做任何修改。
3.创建myid文件
#192.168.20.201
echo 1 > /data/zookeeper/myid
#192.168.20.203
echo 2 > /data/zookeeper/myid
#192.168.20.56
echo 3 > /data/zookeeper/myid
4.启动服务&查看节点状态
#192.168.20.201
/usr/local/zookeeper-3.4.12/bin/zkServer.sh start
/usr/local/zookeeper-3.4.12/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper-3.4.12/bin/../conf/zoo.cfg
Mode: follower
#192.168.20.203
/usr/local/zookeeper-3.4.12/bin/zkServer.sh start
/usr/local/zookeeper-3.4.12/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper-3.4.12/bin/../conf/zoo.cfg
Mode: leader
#192.168.20.56
/usr/local/zookeeper-3.4.12/bin/zkServer.sh start
/usr/local/zookeeper-3.4.12/bin/zkServer.sh status
ZooKeeper JMX enabled by default
Using config: /usr/local/zookeeper-3.4.12/bin/../conf/zoo.cfg
Mode: follower
配置文件详解
tickTime=2000 # 这个时间是作为Zookeeper服务器之间或客户端与服务器之间维持心跳的时间间隔,每隔tickTime时间就会发送一个心跳;最小的session过期时间为2倍tickTime
initLimit=10 # 此配置表示,允许follower(相对于Leaderer言的“客户端”)连接并同步到Leader的初始化连接时间,以tickTime为单位。当初始化连接时间超过该值,则表示连接失败。
syncLimit=5 # 此配置项表示Leader与Follower之间发送消息时,请求和应答时间长度。如果follower在设置时间内不能与leader通信,那么此follower将会被丢弃。
dataDir=/data/zookeeper/ # 运行数据的存放路径
dataLogdDir=/path1 # 指定事务日志的存储路径,可以和dataDir在不同设备,这意味着可以使用一个日志的专用磁盘,避免日志IO和快照竞争。
clientPort=2181
server.1=192.168.20.201:2888:38888
server.2=192.168.20.203:2888:38888
server.3=192.168.20.56:2888:38888
# server.myid=ip:leader_port:inner_port
# myid 为服务器编号,用于标识服务器,这个值必须和dataDir目录下myid文件中的值保证一致
# ip 为当前服务器IP,
# leader_port Leader的端口
# inner_port zk服务器之间内部通信端口
# 同一个集群内的服务器,需要把该集群内的服务器列表信息都写在配置文件中。
基本操作
zookeeper常用操作指令
conf: 输出相关服务配置的详细信息
cons: 列出所有连接到服务器的客户端的完全的连接会话的详细信息。包括“接受/发送”的包数量、会话id、操作延迟、最后的操作执行等信息
dump:列出未经处理的会话和临时节点
envi:输出关于服务环境的详细信息(区别conf命令)
reqs:列出未经处理的请求
ruok:测试服务是否处于正确状态。是返回“imok”,否则不做任何响应
stat:输出关于性能和连接的客户端列表
wchs:列出服务器watch的详细信息
wchc:通过session列出服务器的watch的详细信息,它的输出是一个与watch 相关的会话的列表
wchp:通过路径列出服务器watch的详细信息。它输出一个与session相关的路径。
客户端可以通过
nc
或telnet
连接ZooKeeper Server提交指令
简单示例:
#检查服务器状态是否正常
# echo ruok |nc localhost 2181
imok
#输出服务器配置信息
# echo conf |nc localhost 2181
clientPort=2181
dataDir=/data/zookeeper/version-2
dataLogDir=/data/zookeeper/version-2
tickTime=2000
maxClientCnxns=60
minSessionTimeout=4000
maxSessionTimeout=40000
serverId=1
initLimit=10
syncLimit=5
electionAlg=3
electionPort=38888
quorumPort=2888
peerType=0
ZooKeeper Client 简单操作
create:创建Znode(父zonde必须存在)
delete:删除znode(zonde没有子节点)
exists:测试Znode是否存在,并获取它的元数据
getACL/setACL:为Znode获取/设置ACL
getData/setData:获取/设置Znode的相关数据
sync:使客户端的Znode视图与zookeeper同步
简单示例:
# 连接 ZooKeeper 服务器
bin/zkCli.sh -server localhost:2181
# 查看根下有哪些节点
[zk: localhost:2181(CONNECTED) 1] ls /
[controller_epoch, controller, brokers, zookeeper, admin, isr_change_notification, consumers, config]
# 查看 brokers 下有哪些子节点
[zk: localhost:2181(CONNECTED) 4] ls /brokers
[ids, topics, seqid]
# 在根下创建一个 "tuchao" 节点,并设置数据为 "hello zookeeper"
[zk: localhost:2181(CONNECTED) 5] create /tuchao "hello zookeeper"
# 查看是否创建成功
[zk: localhost:2181(CONNECTED) 6] ls /
[controller_epoch, controller, brokers, zookeeper, tuchao, admin, isr_change_notification, consumers, config]
# 查看 /tuchao 中的数据
[zk: localhost:2181(CONNECTED) 7] get /tuchao
hello zookeeper
cZxid = 0x20049ab24
ctime = Sun Oct 09 15:45:02 CST 2016
mZxid = 0x20049ab24
mtime = Sun Oct 09 15:45:02 CST 2016
pZxid = 0x20049ab24
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 15
numChildren = 0
# 可以看到刚刚设置的数据。
# 修改 /tuchao 中的数据为 "Happy birthday"
[zk: localhost:2181(CONNECTED) 8] set /tuchao "Happy birthday"
cZxid = 0x20049ab24
ctime = Sun Oct 09 15:45:02 CST 2016
mZxid = 0x20049b2cc
mtime = Sun Oct 09 15:51:17 CST 2016
pZxid = 0x20049ab24
cversion = 0
dataVersion = 1 # 可以发现,当数据变更时,数据版本号增加了。
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 14
numChildren = 0
# 再查看一次 /tuchao 中的数据
[zk: localhost:2181(CONNECTED) 9] get /tuchao
Happy birthday
cZxid = 0x20049ab24
ctime = Sun Oct 09 15:45:02 CST 2016
mZxid = 0x20049b2cc
mtime = Sun Oct 09 15:51:17 CST 2016
pZxid = 0x20049ab24
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 14
numChildren = 0
# 给 /tuchao 创建一个子节点 /tuchao/abc1
[zk: localhost:2181(CONNECTED) 10] create /tuchao/abc1 "abc1 node"
Created /tuchao/abc1
[zk: localhost:2181(CONNECTED) 11] ls /tuchao
[abc1]
# 删除节点 /tuchao/abc1
[zk: localhost:2181(CONNECTED) 0] delete /tuchao/abc1
[zk: localhost:2181(CONNECTED) 1] ls /tuchao
[]
# 查看命令帮助,输入 h
[zk: localhost:2181(CONNECTED) 2] h
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
参考地址:http://blog.51cto.com/tchuairen/1859494