什么是Zookeeper?
Zookeeper是一个开源的分布式的,为分布式应用提供协调服务的Apache项目。
ZooKeeper是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务
分布式应用程序可以基于ZooKeeper实现数据发布与订阅、负载均衡、命名服务、分布式协调与通知、集群管理、Leader选举、分布式锁、分布式队列等功能。
zookeeper的一些特点
- 顺序一致性:从同一个client客户端发来的请求,会按其发送的顺序来执行
- 原子性:一次数据处理要么全部成功,要么全部失败
- 数据一致性:每个Server保存一份相同的数据,客户端无论连接到哪个Server,数据都是一致的
- 实时性:在一定时间范围内,客户端能够读取到最新的数据
Zookeeper的数据模型
ZK会维护一个具有层次关系的树状的数据结构,每个树节点称为一个ZNode。每个ZNode默认能够存储1MB的数据,每个ZNode都可以通过路径唯一标识
一个ZNode既能在它下面创建子节点,作为路径标识的一部分,同时该节点也能存储数据;主要存放分布式应用的配置信息和状态信息等
每个ZNode节点都有各自的版本号,当节点数据发生变化是,那该节点的版本号也会累加(乐观锁的机制)
节点类型
- 持久(Persistent):客户端和服务器断开连接后,创建的节点不会被删除
- 短暂(Ephemeral):客户端和服务器断开连接后,创建的节点会自动删除
创建ZNode节点的时候可以设置顺序标识,ZNode名称后会附加一个顺序号,这个顺序号是单调递增的计数器,并且是由父节点来维护的
注意:在分布式系统中,顺序号可以被用于所有事件的全局排序;客户端可以通过顺序号来推断事件的执行顺序
Zookeeper的应用场景
统一命名服务
在分布式环境下,对应用/服务进行统一的命名,会便于识别
对外只显示服务的名称,通过节点去访问对应IP的服务
统一配置管理
集群中一般要求所有节点的配置信息是一致的,例如Kafka集群。并且对配置文件修改后,能够快速更新到各个节点上
可以将配置信息写入ZNode中,各个客户端监听该配置信息的状态,一旦ZNode中的数据发生改变,可以及时通知各个客户端将最新的配置信息更新到系统中
统一集群管理
服务节点动态上下线,当ZK中注册的服务下线时,客户端能够实时的得到下线通知;这里可以通过ZK的监听器去监听节点的动态新增/删除
分布式锁
软负载均衡
ZK记录节点上的服务,可以让访问数最少的服务器去处理最新的客户端请求
ZK安装
注意:下面操作没有设置环境变量,如果设置的环境变量,那么可以在全局环境下直接使用zkServer.sh或者zkCli.sh
设置方法:
1
2
3
4 vim /etc/profile
export ZOOKEEPER_HOME=/opt/zookeeper
export PATH=$PATH:$ZOOKEEPER_HOME/bin
单机模式
解压tar.gz文件到指定目录下(/opt)
1
tar -zxvf zookeeper-3.4.10.tar.gz
复制conf下的zoo_sample.cfg为新文件zoo.cfg,并且在zookeeper的主目录下创建data文件夹,并在配置文件中设置data目录和dataLog目录
1
2
3
4
5
6
7cd /opt/zookeeper/conf
cp zoo_sample.cfg zoo.cfg
cd /opt/zookeeper
mkdir data
vim /opt/zookeeper/conf/zoo.cfg
dataDir=/opt/zookeeper/data
dataLogDir=/opt/zookeeper/dataLog启动zk
1
2
3
4
5
6
7
8
9
10
11
12启动zk
bin/zkServer.sh start
关闭zk
bin/zkServer.sh stop
#查看zk的状态
bin/zkServer.sh status
#查看zk进程是否启动
jps
4020 Jps
4001 QuorumPeerMain
分布式部署
在data目录下创建myid文件,在文件上添加ZK编号
1
2
3touch myid
1
#其他ZK的机子上需要添加不同的编号修改 zoo.cfg 配置文件
1
2
3
4
5
6
7
8
9
10vim zoo.cfg
######################cluster##########################
server.1=zk1:2888:3888
server.2=zk2:2888:3888
server.3=zk3:2888:3888
######################cluster##########################
server.1=localhost:2881:3881
server.2=localhost:2882:3882
server.3=localhost:2883:3883配置文件解析:
- server后面的数字就是 myid 文件制定的编号
- zk1 是你服务器的 ip 地址
- 2888 是zk集群的信息交换端口(不一定是2888,可自行指定)
- 3888 是zk集群中Leader节点挂了之后重新选择Leader节点时进行通信的端口(同样可自行选择其他端口)
深入学习Zookeeper
ZK配置文件
tickTime
通信心跳数,Zookeeper服务器与客户端心跳时间,单位毫秒
Zookeeper使用的基本时间,服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个tickTime时间就会发送一个心跳,时间单位为毫秒
它用于心跳机制,并且设置最小的session(会话)超时时间为两倍心跳时间(session的最小超时时间是2*tickTime)
initLimit
集群中主从服务器之间的初始通信时限
集群中的Follower跟随者服务器与Leader领导者服务器之间初始连接时能容忍的最多心跳数(tickTime的数量),用它来限定集群中的Zookeeper服务器连接到Leader的时限
syncLimit
集群中主从服务器之间的同步通信时限
集群中Leader与Follower之间的最大响应时间单位,假如响应超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer
dataDir
数据文件目录+数据持久化路径
dataLogDir
日志文件目录,如果不配置则使用dataDir的目录进行日志的存放
clientPort
监听客户端连接的端口,默认是2181
ZK集群
选举机制中的基础概念
服务器ID
即myid文件中的编号;编号越大,权重越大
Zxid,数据ID
服务器中存放的最大数据ID;值越大说明该数据越新,权重越大
Epoch:逻辑时钟
投票的次数(轮数),同一轮投票过程中的逻辑时钟值是相同的
每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比,根据不同的值做出不同的判断
Server状态:选举状态
- LOOKING,竞选状态
- FOLLOWING,随从状态,同步leader状态,参与投票
- OBSERVING,观察状态,同步leader状态,不参与投票
- LEADING,领导者状态
选举简易流程
目前有5台服务器,每台服务器均没有数据,它们的编号分别是1,2,3,4,5,按编号依次启动,它们的选择举过程如下:
- 服务器1启动,给自己投票,然后发投票信息,由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直属于Looking(竞选状态)
- 服务器2启动,给自己投票,同时与之前启动的服务器1交换结果;由于服务器2的编号比服务器1的大,所以服务器2胜出;但此时投票数没有大于半数,所以两个服务器的状态依然是LOOKING
- 服务器3启动,给自己投票,同时与之前启动的服务器1和2交换信息,由于服务器3的编号最大,所以服务器3胜出,此时投票数正好大于半数,所以服务器3成为Leader,服务器1和2成为Follower,状态变成FOLLOWING
- 服务器4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息,尽管服务器4的编号大,但服务器3的状态已经是Leading,所以服务器4也是Follower
- 服务器5启动,逻辑同服务器4
几种情况的选举
一台宕机重启的机器加入已有环境,如果已有环境中已经存在Leader,那么该机器会变成Follwoer
一台机器加入正在投票中的环境
所有server都会接受优先级最高的投票,最高优先级最高的选票当选,选举结束
当集群中多数机器宕机重启
存活的服务发现不满足多数派,改变状态为LOOKING,投票轮数+1,然后重新开始投票,会按照优先级的选举投票直至结束
- 逻辑时钟小的选举结果被忽略,重新投票
- 统一逻辑时钟后,数据 version 大的胜出
- 数据 version 相同的情况下,server id 大的胜出
以上,只要有超过半数的机器存活,最终会完成投票
选举机制(半数机制)
集群中半数以上机器存活,集群可用。所以Zookeeper适合安装奇数台服务器
Zookeeper虽然在配置文件中并没有指定Master和Slave。Zookeeper工作时只有一个节点为Leader,其他则为Follower,Leader是通过内部的选举机制临时产生的
zkClient
1 | 启动zk客户端 |
常用操作命令
命令基本语法 | 功能描述 |
---|---|
help | 显示所有操作命令 |
ls path [watch] | 使用 ls 命令来查看当前znode中所包含的内容 |
ls2 path [watch] | 查看当前节点数据并能看到更新次数等数据(详细数据) |
create [选项] | 普通创建一个zNode -s :含有序列 -e:临时(重启或者超时消失) |
get path [watch] | 获得节点的值 |
set path data [version] | 设置(修改)节点的具体值,可根据版本号对节点的值进行修改(推荐使用版本号修改,乐观锁机制) |
stat | 查看节点状态 |
delete path data [version] | 删除节点,可根据版本号对节点进行删除(推荐使用版本号删除,乐观锁机制) |
rmr | 递归删除节点 |
Stat结构体
1 | [zk: localhost:2181(CONNECTED) 1] ls2 / |
cZxid:创建节点的事务zxid
每次修改ZooKeeper状态都会收到一个zxid形式的时间戳,也就是ZooKeeper事务ID
事务ID是ZooKeeper中所有修改总的次序。每个修改都有唯一的zxid,如果zxid1小于zxid2,那么zxid1在zxid2之前发生
ctime:znode被创建的毫秒数(从1970年开始)
mzxid:znode最后更新的事务zxid
mtime:znode最后修改的毫秒数(从1970年开始)
pZxid:znode最后更新的子节点zxid
cversion:znode子节点变化版本号,znode子节点修改次数
dataversion:znode数据变化版本号
aclVersion:znode访问控制列表的变化版本号
ephemeralOwner:如果是临时节点,这个是znode拥有者的session id。如果不是临时节点则是0
- dataLength:znode的数据长度
- numChildren:znode子节点数量
watcher机制
watcher是zk中的监听器机制,父节点或者子节点的增删改操作都能够触发watcher事件
事件类型
- 父节点创建:NodeCreated
- 父节点数据修改:NodeDataChanged
- 父节点删除:NodeDeleted
- 创建了子节点:NodeChildrenChanged
- 删除子节点:NodeChildrenChanged
- 修改子节点不触发任何事件
watcher机制的使用场景
统一的配置管理,可以监听配置信息的节点,当配置信息的节点数据发生变化的时候触发客户端更新配置的操作
ACL权限控制
ACL(access control lists),可以针对节点设置读写等权限,可以保障数据的安全性;如果没有权限,则会抛出异常
zk的acl通过 [scheme : id : permissions] 的形式来构成权限的列表
- scheme:代表采用的某种权限机制
- id:代表允许访问的用户
- permissions:权限组合字符串(有crdwa)
- c:CREATE,创建子节点
- r:READ,获取节点/子节点
- d:DELETE,删除子节点
- w:WRITE,设置节点数据
- a:ADMIN,设置权限
权限示例:
world:world:anyone:[permissions]
auth:auth:user:password:[permissions] 代表认证登录,需要注册的用户有操作权限即可
digest:digest:username:BASE64(SHA1(password)):[permissions] 表示需要对密码进行加密才可以访问
ip:ip:ip地址:[permissions] 可以限制指定ip才能访问该节点
ACL的命令行操作
getAcl:获取某个节点的acl权限信息
setAcl:设置某个节点的acl权限信息
示例:(1和2是等价的)
- setAcl /path auth:dai:dai:cdrwa
- setAcl /path digest:dai:password:cdrwa
上面两个操作后需要进行addauth操作后才能够对 /path 进行操作
- setAcl /path ip:192.168.1.1:cdrwa
设置ip后,只有指定ip的客户端才有权限去访问该节点
addauth:输入认证授权信息,注册时输入明文密码,在zk系统中,密码都是以加密的形式存在的
参照2的示例: 执行 addauth digest:dai:dai 登录后能获取上面设置节点的操作权限
注意:要使用 dai 用户前需要先注册 dai 用户才可以设置成功,注册用户同样是addAuth命令:addauth digest dai:dai
注意:使用 digest 来设置权限时,查看加密后的password可以通过getAcl,比如:
getAcl /dh
‘digest,’dai:password(此处的password是加密后的显示)
Java使用ZK
原生ZK的API
引入POM
1 | <!-- zookeeper --> |
连接ZK
1 | public class ZKDemo implements Watcher { |
ZK的节点操作
1 | public class ZKNode implements Watcher { |
CallBack回调
1 | //父节点的watcher机制回调 |
ACL权限
1 | public class ZKNodeAcl implements Watcher { |
Apache Curator
ZK连接及节点操作
1 | public class ZKCurator { |
ACL权限
1 | public class ZKCurator { |