ZooKeeper学习笔记
一、简介
1.1 概括
- 管理大量主机的协同服务。
- 分布式应用,实现分布式读写技术。
- zookeeper提供的服务
- Naming service:按名称区分集群中的节点
- Configuration management:对加入节点的最新化处理
- Cluster management:实时感知集群中节点的增减
- Leader election:leader选举
- Locking and synchronization service:修改时锁定数据,实现容灾
- Highly reliable data registry:节点宕机数据也是可用的
1.2 工作机制
Zookeeper从设计模式角度来看:是一个基于观察者模式的设计的分布式管理框架,它负责存储和管理大家大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,zookeeper就将负责通知已经在zookeeper注册的那些观察者做出相应的反应。
- 一个领导者(leader)多个跟随着(follower)组成的集群
- 集群中只要有半数以上节点存活,zookeeper集群就能正常服务
- 全局数据一致:每个server保存一份相同的数据副本,client无论连接到哪一个server,数据都是一致的
- 更新请求顺序进行,来自用一个client的更新请求按其发送的顺序依次执行
- 数据更新原子性,依次数据更新要么成功,要么失败
- 实时性在一定时间范围内,client能读取到最新消息(数据量少,主要放配置信息,同步速度快)
二、安装
2.1 单机版
jdk安装
下载zookeeper-3.4.9.tar.gz
tar开包,到指定目录下
Code$tar -zxvf zookeeper-3.4.9.tar.gz
$sudo mv zookeeper-3.4.9 /soft/zookeeper-3.4.9创建链接
Code$ln -s zookeeper-3.4.9 zookeeper
配置环境变量
Code# ZooKeeper
export ZK_HOME=/soft/zookeeper
export PATH=$PATH:$ZK_HOME/bin
# 更新资源
$source /etc/profile配置zoo.cfg
Code[wbw@s201 /soft/zookeeper/conf]$cp zoo_sample.cfg zoo.cfg
[wbw@s201 /soft/zookeeper/conf]$vi zoo.cfg
# 修改数据目录,其余配置不用动
dataDir=/home/wbw/zookeeper自定义日志目录
修改 /bin/zkEnv.sh文件
Code[wbw@s201 /soft/zookeeper/bin]$vi zkEnv.sh
# 修改以下两个地方的目录,将日志文件输出到安装目录
if [ "x${ZOO_LOG_DIR}" = "x" ]
then
ZOO_LOG_DIR="${ZOOKEEPER_PREFIX}/logs"
fi
if [ "x${ZOO_LOG4J_PROP}" = "x" ]
then
ZOO_LOG4J_PROP="INFO,ROLLINGFILE"
fi修改/conf/log4j.properties文件
Code[wbw@s201 /soft/zookeeper/conf]$vi log4j.properties
# 修改以下几个地方
zookeeper.root.logger=INFO, ROLLINGFILE
zookeeper.log.dir=/soft/zookeeper/logs
zookeeper.tracelog.dir=/soft/zookeeper/logs
log4j.appender.ROLLINGFILE=org.apache.log4j.DailyRollingFileAppender
#log4j.appender.ROLLINGFILE.MaxFileSize=10MB (把这句话注释掉)启动zk服务器
Code$zkServer.sh start
验证zk
Code$jps
1606 QuorumPeerMain
$netstat -anop | grep 2181
3.2 完全分布式
首先我们规划s201,s202,s203三台主机作为zookeeper节点
在每台机子上都安装zk,修改环境变量(和单机版一样,可以先做第3步,然后分发)
配置所有zk节点的配置文件zoo.cfg,末尾追加:
xml# server.n=host:port1:port2,数字n必须是myid中的值
# port1:leader端口, 作为leader时,供follower连接的端口
# port2:选举端口,选举leader时,供其他follower连接的端口
server.1=s201:2888:3888
server.2=s202:2888:3888
server.3=s203:2888:3888在每台主机的ZK数据存放目录中添加myid
Code[wbw@s201 /home/wbw/zookeeper]$echo 1 > myid
[wbw@s202 /home/wbw/zookeeper]$echo 2 > myid
[wbw@s203 /home/wbw/zookeeper]$echo 3 > myid启动服务集群
Code[wbw@s201 /home/wbw/zookeeper]$zkServer.sh start
[wbw@s202 /home/wbw/zookeeper]$zkServer.sh start
[wbw@s203 /home/wbw/zookeeper]$zkServer.sh start查看每台服务器状态
Code[wbw@s201 /home/wbw/zookeeper]$zkServer.sh status
Mode: leader
[wbw@s202 /home/wbw/zookeeper]$zkServer.sh status
Mode: follower
[wbw@s203 /home/wbw/zookeeper]$zkServer.sh status
Mode: follower测试选举
Code[wbw@s201 /home/wbw/zookeeper]$zkServer.sh stop
[wbw@s202 /home/wbw/zookeeper]$zkServer.sh status
Mode: follower
[wbw@s203 /home/wbw/zookeeper]$zkServer.sh status
Mode: leader
三、访问zk
3.1 客户端
zkCli.sh 连接
$zkCli.sh -server s201:2181 |
如果是连接本机,那么可以简写:
$zkCli.sh |
help 帮助
help |
quit 退出
quit |
create 创建节点
创建节点(zk中以目录形式存在,逻辑上的 ):一般放配置信息,大小一般<1M。
# 就像MAP那样,KV对。 |
ls 列表
[zk: localhost:2181(CONNECTED) 1] ls / |
get 查看节点
[zk: localhost:2181(CONNECTED) 2] get /a |
set 设置数据
[zk: localhost:2181(CONNECTED) 3] set /a tom2 |
delete 删除节点
[zk: localhost:2181(CONNECTED) 5] delete /a |
rmr 递归删除节点
[zk: localhost:2181(CONNECTED) 7] create /a tom |
3.2 通过API
导入依赖
xml<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>编写代码
java/**
* 显示指定路径下的节点
*/
public void testLs() throws IOException, KeeperException, InterruptedException {
ZooKeeper zk = new ZooKeeper("192.168.174.201:2181", 5000, null);
List<String> children = zk.getChildren("/a", null);
for (String child : children) {
System.out.println(child);
}
}
/**
* 递归显示节点
*/
public void testLsR() throws IOException, KeeperException, InterruptedException {
ZooKeeper zk = new ZooKeeper("192.168.174.201:2181", 5000, null);
lsAll(zk,"/");
}
private void lsAll(ZooKeeper zk, String path) throws KeeperException, InterruptedException {
System.out.println(path);
List<String> children = zk.getChildren(path, null);
if (null != children && !children.isEmpty()) {
for (String child : children) {
if (path.equals("/")) {
lsAll(zk, path + child);
} else {
lsAll(zk, path + "/" + child);
}
}
}
}
/**
* 设置数据、查看数据
* NOTE:每次更新数据,版本号都会+1,如果有2个人同时修改数据,那么版本号一旦有问题,将修改失败
*/
public void testSet() throws IOException, KeeperException, InterruptedException {
ZooKeeper zk = new ZooKeeper("192.168.174.201:2181", 5000, null);
System.out.println(new String(zk.getData("/a", false, null)));
// 这里的版本号要特别注意,如果错误了。设不成功!!!!
zk.setData("/a", "tomtom".getBytes(), 0);
System.out.println(new String(zk.getData("/a", false, null)));
}
四、zk架构
4.1 Client
从server获取信息,周期性发送数据给server,表示自己还活着。
client连接时,server回传ack信息。
如果client没有收到reponse,自动重定向到另一个server。
4.2 Server
zk集群中的一员,向client提供所有service,回传ack信息给client,表示自己还活着。
4.3 Ensemble
一组服务器,最小节点数是3。
最小节点数是3。
4.4 Leader
如果连接的节点失败,自定恢复,zk服务启动时,完成leader选举。
4.5 Follower
追寻leader指令的节点。
五、节点
5.1 znode
zk中的节点,维护了stat,由Version number, Action control list (ACL), Timestamp,Data length.构成。
每一个ZNode默认能够存储1MB数据,每个ZNode都可以通过其路径唯一标识(zookeeper数据模型结构和Unix文件系统类似)
5.2 节点类型
持久节点:client结束,还存在。使用create创建的就是。
临时节点:在client活动时有效,断开自动删除。临时节点不能有子节点。leader推选时使用。
Code[zk: localhost:2181(CONNECTED) 26] create -e /c haha
Created /c
[zk: localhost:2181(CONNECTED) 27] ls /
[b0000000003, a, c, zookeeper]
[zk: localhost:2181(CONNECTED) 28] quit
$zkCli.sh
[zk: localhost:2181(CONNECTED) 1] ls /
[b0000000003, a, zookeeper]java/**
* 创建临时节点
*/
public void testEphemeral() throws IOException, KeeperException, InterruptedException {
ZooKeeper zk = new ZooKeeper("192.168.174.201:2181", 5000, null);
zk.create("/c", "haha".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL);
// 断点!
System.out.println("Debug");
}
(持久/临时)序列节点:在节点名之后附加10个数字,主要用于同步和锁。
Code[zk: localhost:2181(CONNECTED) 21] create -s /b heihei
Created /b0000000003
六、Session 会话
Session中的请求以FIFO执行,一旦client连接到server,session就建立了。sessionid分配client。
client以固定间隔向server发送心跳,表示session是valid的,zk集群如果在超时时候,没有收到心跳,判定为client挂了,与此同时,临时节点被删除。
注意:网络连接断开和Session过期是两种机制。网络连接断开,客户端会尝试到集群重连,但是Seesion并不会消失。只有重连时间超过Seesion的超时时间才会被清理。
七、Watches 观察者
7.1 监听器原理
- 在main线程中创建ZK客户端,然后创建两个线程,一个负责通信,一个负责监听。
- 通过connect线程将注册的监听事件发送给zk
- 在zk中,将监听事件添加到注册监听器列表中
- 当监听到数据或路径变化的适合,将消息发送到listener线程
- listener线程内部调用了process方法
7.2 JAVA API
client能够通过watch机制在数据发生变化时收到通知。
client可以在read 节点时设置观察者。watch机制会发送通知给注册的客户端。
观察模式只触发一次(watcher时间是一次性的触发器)。
session过期,watch机制删除了。
/** |
八、zk工作流程
zk集群启动后,client连接到其中的一个节点,这个节点可以leader,也可以follower。
连通后,node分配一个id给client,发送ack信息给client。
如果客户端没有收到ack,连接到另一个节点。
client周期性发送心跳信息给节点保证连接不会丢失。
如果client读取数据(读流程),发送请求给node,node读取自己数据库,返回节点数据给client。
如果client存储数据(写流程),将路径和数据发送给server,server转发给leader。
leader再补发请求给所有follower。只有大多数(超过半数)节点成功响应,则写操作成功。
九、leader推选
9.1 选举机制
- 半数机制:集群中半数以上机器存活,集群可用。所以zookeeper适合安装在奇数台服务器。
9.2 选举流程
- 所有节点在同一目录下创建临时序列节点。
- 节点下会生成/xxx/xx000000001等节点。
- 序号最小的节点就是leader,其余就是follower.
- 每个节点观察小于自己节点的主机。(注册观察者)
- 如果leader挂了,对应znode删除了。
- 观察者收到通知。
十、使用场景
在HBase中主要是:
- Master高可用管理
- RegionServer宕机异常检测
- 分布式锁
其他的什么负载均衡、服务器上下线等等。