1 概述:
1.开源的,分布式的,为分布式服务提供协调服务的Apache 项目。
2.基于观察者模式设计的分布式服务管理框架, 负责存储和管理数据,接受观察者的注册, 一旦数据状态变化, 将通知已经注册的观察者做出反应
2 特点
- Zookeeper 由一个领导者(leader, 有且只有一个), 多个跟随者(follower)组成的集群
- 集群中只有半数以上的节点存活,Zookeeper集群才能正常服务,所以zookeeper 适合安装奇数台服务器
- 全局数据一致性:每个存活的节点保存一份相同的数据副本, client无论链接到哪个节点,数据都是一致的
- 更新请求顺序执行, 来自同一个client的更新请求按其发送的顺序依次执行
- 数据更新原子性,一次数据更新要么成功,要么失败
- 实时性: 在一定时间范围内,client能读取到最新的数据
3 节点
zookeeper的数据模型和结构与unix文件系统类似, 整体可以看作是一棵树,每个节点称作为ZNode, 每个ZNode 默认能存储1MB的数据, 每个ZNode都可以通过其路径唯一标识
3.1 节点类型:
节点可分为持久节点,临时节点,持久顺序节点,临时顺序节点
- 持久节点(PERSISTENT):
客户端和服务器断开链接后,创建的节点不会被删除 - 持久顺序节点(PERSISTENT_SEQUENTIAL):
客户端和服务器断开链接后,创建的节点不会被删除, 而且对该节点进行顺序编号 - 临时节点(EPHEMERAL):
客户端和服务器断开链接后,创建的节点会被删除 - 临时顺序节点(EPHEMERAL_SEQUENTIAL):
对该节点进行顺序编号,客户端和服务器断开链接后,创建的节点会被删除
3.2 节点信息
例:创建一个值为test持久节点 /abcd
[zk: localhost:2181(CONNECTED) 28] create /abcd "test"
Created /abcd
查看/abcd 节点信息
[zk: localhost:2181(CONNECTED) 29] get -s /abcd
test.
cZxid = 0x10000000b
ctime = Thu Feb 10 20:12:28 CST 2022
mZxid = 0x10000000b
mtime = Thu Feb 10 20:12:28 CST 2022
pZxid = 0x10000000b
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 4
numChildren = 0
名称 | 含义 |
---|---|
test | 对应节点的值 |
cZxid | 创建节点的事务id |
ctime | 创建节点的时间(ms), 从1970开始 |
mZxid | 节点最后更新的事务id |
mtime | 节点最后更新的时间(ms),从1970开始 |
pZxid | 节点对应子节点事务ID |
cversion | 节点对应子节点的版本号,子节点的变化数 |
dataVersion | 节点数据版本号 |
aclVersion | 节点权限版本号 |
ephemeralOwner | 判断是否是临时节点,如果不是,则为0 ,如果是则为对应的sessionId |
dataLength | 节点数据长度 |
numChildren | 节点对应的子节点数 |
3.3 节点监听(watch)
使用watcher机制实现了数据的发布订阅功能功能,当订阅的对象发生变化时会异步通知客户端
- watch 特性
特性 | 说明 |
---|---|
一次性触发 | watcher 是一次性触发的,一旦被触发就会被移除,再次使用时需要重新注册 |
客户端顺序回调 | watcher 回调是顺序串行化执行的,只有回调后客户端才能看到最新的数据状态,一个watcher回调逻辑不应该太多,以免影响别的watcher执行 |
轻量级 | watchEvent 是最小的通信单元,结构上只包含通知状态,事件类型和节点路径,并不会告诉数据节点前后变化的具体内容 |
实效性 | watcher只有在当前的session彻底失效才会无效,若session有效期间内快速重新连接成功,则watcher依然有效,仍可收到通知 |
- watchEvent
当数据有变化时,服务端会返回对应的watchEvent对象给客户端, 该对象中包括了通知的状态(keepState)和事件的类型(eventType)
-. 通知状态(keepState):
客户端和服务端连接状态发生变化时的对应通知类型
属性 | 说明 |
---|---|
SyncConnected | 客户端与服务器正常连接 |
Disconnected | 客户端与服务器断开连接时 |
Expired | 回话session失效时 |
AuthFailed | 身份认证失效时 |
-. 事件类型(eventType)
节点(znode)发生变化时对应的通知类型,EventType变化时keepState都会处于SyncConnected的通知状态下;当keepState发生变化时,EventType永远为None
类型属性 | 说明 |
---|---|
None | 无 |
NodeCreated | Watcher监听的数据节点被创建 |
NodeDeleted | Watcher监听的数据节点被删除 |
NodeDataChanged | Watcher监听的数据节点内容发生变更时(不论内容数据是否变化) |
NodeChildrenChanged | Watcher监听节点的子节点列表发生变化 |
注:客户端接收到的相关事件通知中只包含状态及类型等信息,不包括数据节点变化前后的具体内容,变化后的数据需要调用get等方法重新获取
- 捕获相应的事件
在zookeeper 中采用zk.getChildren(path,watch), zk.exists(path,watch), zk.getData(path,watch)方式为某个节点注册监听
注册方式 | Created | ChildrenChanged | Changed | Deleted |
---|---|---|---|---|
zk.getChildren(path,watch) | 可监控 | 可监控 | ||
zk.exists(path,watch) | 可监控 | 可监控 | 可监控 | |
zk.getData(path,watch) | 可监控 | 可监控 |
3.4 节点权限(ACL)
ACL:Access Control List 访问控制列表
使用 scheme:id:perm 来标识,主要涵盖 3 个方面:
权限模式(Scheme):授权的策略
授权对象(ID):授权的对象
权限(Permission):授予的权限
其特性如下:
-.ZooKeeper的权限控制是基于每个znode节点的,需要对每个节点设置权限
-.每个znode支持设置多种权限控制方案和多个权限
-.子节点不会继承父节点的权限,客户端无权访问某节点,但可能可以访问它的子节点
3.4.1 权限模式
scheme 采用何种方式授权
- world:默认方式,相当于全部都能访问
- auth:代表已经认证通过的用户(cli中可以通过addauth digest
user:pwd 来添加当前上下文中的授权用户) - digest:即用户名:密码这种方式认证,这也是业务系统中最常用的。 用 username:password 字符串来产生一个MD5串,然后该串被用来作为ACL ID。认证是通过明文发送username:password 来进行的,当用在ACL时,表达式为username:base64 ,base64是password的SHA1摘要的编码。
- ip:使用客户端的主机IP作为ACL ID 。这个ACL表达式的格式为addr/bits ,此时addr中的有效位与客户端addr中的有效位进行比对。
- super: 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa)
3.4.2 授权对象
授权模式 | 授权对象 |
---|---|
world | 只有一个用户:anyone, 代表登陆zookeeper的所有人(默认) |
ip | 通常是一个ip地址或者段,例如:192.169.19.22 或者 192.169.19.1/24 |
auth | 使用已添加的认证的用户认证 username:password |
digest | 与auth类似,只不过password需要加密处理,username:BASE64(SHA-1(username:password)) |
super | 与digest一致 |
3.4.3 授予的权限
CREATE、READ、WRITE、DELETE、ADMIN 也就是 增、删、改、查、管理权限,这5种权限简写为crwda
注意:这5种权限中,delete是指对子节点的删除权限,其它4种权限指对自身节点的操作权限
更详细的如下:
权限 | 缩写 | 含义 |
---|---|---|
CREATE | c | 可以创建子节点 |
DELETE | d | 可以删除子节点(仅下一级节点) |
READ | r | 可以读取节点数据及显示子节点列表 |
WRITE | w | 可以设置节点数据 |
ADMIN | a | 可以设置节点访问控制列表权限 |
4 选举机制
首先了解如下几个概念:
- SID: 服务器id,用来表示集群中该机器,每台机器不能重复,与myid一致
- ZXID:事物id ,用来标识一次服务器状态的变更,在某一时刻,集群中的每一台服务器的ZXID不一定相同
- Epoch:每个leader任期的代号,没有leader时,同一轮投票选举逻辑时钟是一致的,每投完一轮,这个数据就会增加
4.1 初次启动选举机制
假设有5台服务器组成的的zookeeper集群,server ID为1~5,同时他们是最新启动的,且没有历史数据,在存放数据量上面是一样的
此时依次按顺序启动服务,
1.服务器1 启动,此时没有leader节点,服务器1 发起选举投票,并投给自己,此时投票不超过半数(3票),选举失败,此时服务1处于looking状态,
- 服务器2 启动,此时再次发起leader选举,服务器1和2 分别投票给自己,并互相交换选举结果,此时服务器1 发现自己的myid小于服务器2的,并且都没有历史数据,服务器1就会更改投票给服务器2,此时服务器2 有2 票,服务器1 有0票,都没有超过半数投票,选举失败,服务器1 和2 都处于looking状态
- 服务器3 启动,如果服务器2 启动时一样,再次选举,由于服务器3的myid最大,最终服务器3 有3 票,满足超过半数投票,服务器3 就选举为leader节点,服务器1,2为follower几点,集群启动成功
- 服务器4 启动,leader节点已经存在,此时服务器4 成为follwer节点
- 服务器5 启动,和服务器4一样,也成为follower节点
4.2 非第一次启动选举
当集群中的一台服务器出现以下情况时,此时会进入选举机制
- 服务器初始化启动
- 服务器运行期间和leader节点丢失通信
当一台机器开始选举时,会出现如下几种情形之一:
1.集群中已经有leader了,此时该机器发起选举时,会被告知leader节点信息,此时该机器只要和leader节点建立连接并完成同步就行
- 集群中leader节点挂了,
假设 5台服务器的sid为1,2,3,4,5, ZXID为8,8,8,7,7。
此时服务器3(leader)和服务器5(follower)挂了,此时剩下的1,2,4 服务器重新开始选举.
选举规则: Epoch大的直接胜出, Epoch相同,ZXID大的胜出,Epoch和ZXID都相同的,服务器id大的胜出
由于1,2,4的Epoch 相同, 但是2的ZXID,serverID 相对于最大,此时服务器2 就被选举为新的leader
5 安装(Ver 3.5.7)
5.1 配置参数解读:
参数名 | 含义 |
---|---|
clientPort | 客户端连接server的端口,即对外服务端口,一般设置为2181 |
dataDir | 存储快照文件snapshot的目录。默认情况下,事务日志也会存储在这里。建议同时配置参数dataLogDir, 事务日志的写性能直接影响zk性能 |
tickTime | ZK中的一个时间单元。ZK中所有时间都是以这个时间单元为基础,进行整数倍配置的。例如,session的最小超时时间是2*tickTime |
dataLogDir | 事务日志输出目录。尽量给事务日志的输出配置单独的磁盘或是挂载点,这将极大的提升ZK性能 |
globalOutstandingLimit | 最大请求堆积数。默认是1000。ZK运行的时候, 尽管server已经没有空闲来处理更多的客户端请求了,但是还是允许客户端将请求提交到服务器上来,以提高吞吐性能。当然,为了防止Server内存溢出,这个请求堆积数还是需要限制下的 |
preAllocSize | 预先开辟磁盘空间,用于后续写入事务日志。默认是64M,每个事务日志大小就是64M。如果ZK的快照频率较大的话,建议适当减小这个参数 |
snapCount | 每进行snapCount次事务日志输出后,触发一次快照(snapshot), 此时,ZK会生成一个snapshot.文件,同时创建一个新的事务日志文件log.。默认是100000.(真正的代码实现中,会进行一定的随机数处理,以避免所有服务器在同一时间进行快照而影响性能) |
traceFile | 用于记录所有请求的log,一般调试过程中可以使用,但是生产环境不建议使用,会严重影响性能 |
maxClientCnxns | 单个客户端与单台服务器之间的连接数的限制,是ip级别的,默认是60,如果设置为0,那么表明不作任何限制。请注意这个限制的使用范围,仅仅是单台客户端机器与单台ZK服务器之间的连接数限制,不是针对指定客户端IP,也不是ZK集群的连接数限制,也不是单台ZK对所有客户端的连接数限制。指定客户端IP的限制策略,这里有一个patch,可以尝试一下:[http://rdc.taobao.com/team/jm/archives/1334](http://rdc.taobao.com/team/jm/archives/1334) |
clientPortAddress | 对于多网卡的机器,可以为每个IP指定不同的监听端口。默认情况是所有IP都监听 clientPort 指定的端口 |
minSessionTimeoutmaxSessionTimeout | 事务日志输出时,如果调用fsync方法超过指定的超时时间,那么会在日志中输出警告信息。默认是1000ms |
fsync.warningthresholdms | 用于记录所有请求的log,一般调试过程中可以使用,但是生产环境不建议使用,会严重影响性能 |
autopurge.purgeInterval | 在上文中已经提到,3.4.0及之后版本,ZK提供了自动清理事务日志和快照文件的功能,这个参数指定了清理频率,单位是小时,需要配置一个1或更大的整数,默认是0,表示不开启自动清理功能 |
autopurge.snapRetainCount | 这个参数和上面的参数搭配使用,这个参数指定了需要保留的文件数目。默认是保留3个 |
electionAlg | 在之前的版本中, 这个参数配置是允许我们选择leader选举算法,但是由于在以后的版本中,只会留下一种“TCP-based version of fast leader election”算法,所以这个参数目前看来没有用了,这里也不详细展开说了 |
initLimit | Follower在启动过程中,会从Leader同步所有最新数据,然后确定自己能够对外服务的起始状态。Leader允许F在 initLimit 时间内完成这个工作。通常情况下,我们不用太在意这个参数的设置。如果ZK集群的数据量确实很大了,F在启动的时候,从Leader上同步数据的时间也会相应变长,因此在这种情况下,有必要适当调大这个参数了 |
syncLimit | 在运行过程中,Leader负责与ZK集群中所有机器进行通信,例如通过一些心跳检测机制,来检测机器的存活状态。如果L发出心跳包在syncLimit之后,还没有从F那里收到响应,那么就认为这个F已经不在线了。注意:不要把这个参数设置得过大,否则可能会掩盖一些问题 |
leaderServes | 默认情况下,Leader是会接受客户端连接,并提供正常的读写服务。但是,如果你想让Leader专注于集群中机器的协调,那么可以将这个参数设置为no,这样一来,会大大提高写操作的性能 |
server.x=[hostname]:nnnnn[:nnnnn] | 这里的x是一个数字,与myid文件中的id是一致的。右边可以配置两个端口,第一个端口用于F和L之间的数据同步和其它通信,第二个端口用于Leader选举过程中投票通信。 |
group.x=nnnnn[:nnnnn]weight.x=nnnnn | 对机器分组和权重设置,可以 参见这里 |
cnxTimeout | Leader选举过程中,打开一次连接的超时时间,默认是5s |
zookeeper.DigestAuthenticationProvider.superDigest | ZK权限设置相关,具体参见 《 使用super **身份对有权限的节点进行操作 **》 和 《 ZooKeeper **权限控制 **》 |
skipACL | 对所有客户端请求都不作ACL检查。如果之前节点上设置有权限限制,一旦服务器上打开这个开头,那么也将失效 |
forceSync | 这个参数确定了是否需要在事务日志提交的时候调用 [FileChannel ](http://rdc.taobao.com/team/%5C/java%5C/jdk1.6.0_22%5C/jre%5C/lib%5C/rt.jar%3Cjava.nio.channels(FileChannel.class%E2%98%83FileChannel).force来保证数据完全同步到磁盘 |
jute.maxbuffer | 每个节点最大数据量,是默认是1M。这个限制必须在server和client端都进行设置才会生效 |
5.2. 单机节点
- 解压安装包
tar -zxvf apache-zookeeper-3.5.7-bin.tar.gz
- 修改配置文件
将 解压后文件夹下的conf 路径下的zoo_sample.cfg 改为zoo.cfg,
设置配置文件中参数 dataDir
配置文件:
# The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
# do not use /tmp for storage, /tmp here is just
# example sakes.
dataDir=/root/zookeeper/apache-zookeeper-3.5.7-bin/data
# the port at which the clients will connect
clientPort=2181
# the maximum number of client connections.
# increase this if you need to handle more clients
#maxClientCnxns=60
- 启动
在解压文件的bin目录中
[root@iZuf6g3hri8hvnuqng6id7Z bin] ./zkServer.sh --config ../conf start
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: ../conf/zoo.cfg
Starting zookeeper ... 、STARTED
- 停止
[root@iZuf6g3hri8hvnuqng6id7Z bin] ./zkServer.sh stop
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /root/zookeeper/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Stopping zookeeper ... STOPPED
- 查看状态
[root@iZuf6g3hri8hvnuqng6id7Z bin]# ./zkServer.sh status
/usr/bin/java
ZooKeeper JMX enabled by default
Using config: /root/zookeeper/apache-zookeeper-3.5.7-bin/bin/../conf/zoo.cfg
Client port found: 2181. Client address: localhost.
Mode: standalone
5.3. 集群
示例: 在三个服务器上部署三个zookeeper节点,组成集群
- 解压安装:
和单机节点安装一样,在三台服务器上分别解压设置dataDir目录 - 配置服务器编号
在参数dataDir 目录下创建myid文件, 此文件说明当前zookeeper在集群中的唯一编号,在文件中填写对应server的编号(例如1,2,3) - 配置集群文件
在每个节点的配置文件中添加集群的信息
# server.A=B:C:D
#A: serverId 与myid文件的编号对应
#B:服务器地址
#C:follower 与leader 通信的端口
#D:leader节点挂了之后,重新选举时的端口号
server.1=139.196.58.234:2888:3888
server.2=139.196.58.235:2888:3888
server.3=139.196.58.236:2888:3888
- 分别启动zookeeper 节点
6 客户端操作
6.1 常用节点操作
语法 | 含义 |
---|---|
ls path | 查看当前节点的子节点。-w 监听子节点变化, -s 附加次级信息 |
create path | 创建节点 -e 创建临时节点, -s 创建顺序节点 |
get path | 获得节点对应的值 -w 监听节点内容变化 -s 附加次级信息 |
set path value | 设置节点具体值 |
state path | 查看节点状态 |
delete path | 删除节点 |
deleteall path | 递归删除节点 |
6.2 权限操作
- 获取节点权限
客户端操作指令:getAcl path
#创建一个/abcd节点, 观察节点的权限,此时默认是world:anyone:cdrwa 的权限
[zk: localhost:2181(CONNECTED) 4] create /abcd "test"
Created /abcd
[zk: localhost:2181(CONNECTED) 5] getAcl /abcd
'world,'anyone
: cdrwa
- 设置节点权限
指令 setAcl path acl
#world 权限设置 setAcl path world:anyone:<perm>
#设置/abcd 节点没有创建子节点的权限,此时就不能创建子节点了
[zk: localhost:2181(CONNECTED) 6] setAcl /abcd world:anyone:drwa
[zk: localhost:2181(CONNECTED) 8] getAcl /abcd
'world,'anyone
: drwa
[zk: localhost:2181(CONNECTED) 7] create /abcd/a "a"
Authentication is not valid : /abcd/a
#ip 权限设置 setAcl path ip:<ip>:<perm>
[zk: localhost:2181(CONNECTED) 9] setAcl /abcd ip:127.0.0.1:cdrwa
[zk: localhost:2181(CONNECTED) 10] getAcl /abcd
'ip,'127.0.0.1
: cdrwa
# 此时/abcd 节点的cdrwa 权限只能是ip为127.0.0.1的客户端才拥有
# 可同时支持设置多个ip地址的权限, 以逗号分隔
[zk: localhost:2181(CONNECTED) 11] setAcl /abcd ip:127.0.0.1:cdrwa,ip:192.162.12.11:cdrwa
[zk: localhost:2181(CONNECTED) 12] getAcl /abcd
'ip,'127.0.0.1
: cdrwa
'ip,'192.162.12.11
: cdrwa
- 添加认证用户
指令 addauth digest <auth>
#添加 用户名为gagaya 密码为123456的认证用户
[zk: localhost:2181(CONNECTED) 0] addauth digest gagaya:123456
# auth授权模式, 需要现通过addauth 添加认证用户(登陆)
[zk: localhost:2181(CONNECTED) 1] setAcl /abcd auth:gagaya:cdrwa
[zk: localhost:2181(CONNECTED) 2] getAcl /abcd
'digest,'gagaya:MiCKcSLnJ5sLUpkr1wj81iMhnKQ=
: cdrwa
#digest 授权模式,
#setAcl path digest username:password(加密处理):<perm>,
# 在Linux中可用以下指令获取加密密码
#echo -n <username>:<password> | openssl dgst -binary -sha1 |openssl base64
[root@iZuf6g3hri8hvnuqng6id7Z ~]# echo -n gagaya:123456 | openssl dgst -binary -sha1 |openssl base64
MiCKcSLnJ5sLUpkr1wj81iMhnKQ=
# 使用digest授权模式相比于auth 模式需要提供用户密码的加密密钥
[zk: localhost:2181(CONNECTED) 7] setAcl /abcd digest:gagaya:MiCKcSLnJ5sLUpkr1wj81iMhnKQ=:cdrwa
[zk: localhost:2181(CONNECTED) 8] getAcl /abcd
'digest,'gagaya:MiCKcSLnJ5sLUpkr1wj81iMhnKQ=
: cdrwa
- 多种授权模式
同一个节点可以设置多种授权的模式
# 用逗号分隔
[zk: localhost:2181(CONNECTED) 11] setAcl /abcd digest:gagaya:MiCKcSLnJ5sLUpkr1wj81iMhnKQ=:cdrwa,ip:127.0.0.1:cdrwa
[zk: localhost:2181(CONNECTED) 12] getAcl /abcd
'digest,'gagaya:MiCKcSLnJ5sLUpkr1wj81iMhnKQ=
: cdrwa
'ip,'127.0.0.1
: cdrwa
- 超级管理员(super)
超级管理员,可以访问任何权限的节点,只能在启动服务器的时候添加。
假设这个超管是:super:admin
先获取加密后的密码:
[root@iZuf6g3hri8hvnuqng6id7Z ~]# echo -n super:admin | openssl dgst -binary -sha1 |openssl base64
xQJmxLMiHGwaqBvst5y6rkB6HQs=
打开zk目录下的/bin/zkServer.sh服务器脚本文件,找到如下一行:
nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"
这就是脚本中启动zk的命令,默认只有以上两个配置项,我们需要加一个超管的配置项
"-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="
那么修改以后这条完整命令变成了:
nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" "-Dzookeeper.DigestAuthenticationProvider.superDigest=super:xQJmxLMiHGwaqBvst5y6rkB6HQs="\
-cp "$CLASSPATH" $JVMFLAGS $ZOOMAIN "$ZOOCFG" > "$_ZOO_DAEMON_OUT" 2>&1 < /dev/null &
之后启动zk集群, 然后在客户端链接集群后,添加对应的超级用户,此时客户端就是超级管理员了
addauth digest super:admin
7. 共识协议
7 应用场景
提供的服务包括:统一命名服务, 统一配置管理,通知集群管理等,服务节点动态上下线,软负载均衡等
4.1 统一命名服务
4.2 统一配置管理
,