目录
zk的数据模型
树结构,/是根节点,节点叫做znode,一个znode对应一个文件目录。
有四种类型的znode
- PERSISTENT 持久,znode创建后一直保留
- PERSISTENT_SEQUENTIAL 持久、顺序编号
- EPHEMERAL 临时,创建znode的zk client断开与zk server的连接后,zk server会自动删除该znode
- EPHEMERAL_SEQUENTIAL 临时、顺序编号
临时节点常用于实现分布式锁。
zk server的集群模式
zk server有3种角色
- leader 负责处理zk client的写(、读)请求,一个zk集群只有一个leader
- follower 同步leader的数据,负责处理zk client的读请求,并参与leader的选举
- observer 负责处理zk client的读请求,但不参与leader的选举
zk集群是读写分离的模式,leader负责写(也可以处理读),follower负责读。
有follower就够了,为什么还要使用observer?
1、提高并发负载
observer可以协助 follower 处理更多的读请求,提高读请求的吞吐量
2、减少选举的时间花销
eg. follower5个、observer6个,选举leader只需要等待3台机器同意;如果11个全是follower,需要等待6个同意,大大增加了选举的时间花销。
3、提高集群可用性
down掉一半集群不可用,指的是followerdown掉一半,observer不参与数量统计,就算observer全部宕掉,也没关系。
eg. follower5个、observer8个,down掉所有observer、2个follower总共10个,集群仍然可用;如果13个全是follower,宕掉7个集群就不可用,显然使用observer可用性更高。
zk server集群至少需要3个zk server,为什么官方推荐参与集群的zk server数量为奇数?
因为单数、双数的容灾能力一样,少用一台机器还可以节约成本。
eg. zk server数量为5,down掉3个集群就不可用;数量为6,还是down3个集群就不可用。容灾能力(可用性)一样,少用1台机器还节约了成本。
zk的工作原理
zk的核心机制是原子广播,这个机制保证了节点之间的数据同步。
zk使用Zab协议实现原子广播,Zab协议有2种模式
- 恢复模式:leader故障后自动进入恢复模式,将集群恢复到正常工作状态。先从follower中选出一个新leader,其它follower从新leader处同步数据,大多数follower完成同步恢复模式就结束了。
- 广播模式:leader处理写请求时,广播通知follower发起投票,半数以上的follower同意后,leader执行写请求(修改自身的znode),执行后将修改、更新广播给follower、observer,完成数据同步。
leader的选举机制
zk server在集群中的4种状态
- LOOKING:leader宕掉了,正在选举新的leader
- LEADING:当前节点是leader
- FOLLOWING:当前节点是follower,同步leader的数据
- OBSERVING:当前节点是observer,同步leader的数据
相关概念
- myid:唯一标识zk集群中的一个节点
- zxid:zookeeper transaction id,zookeeper事务id,唯一标识zk的一个写操作,可以区分数据版本。前32位表示epoch(纪元、时代),一个leader对应一个epoch(时代),换了新leader,epoch会自动使用新值;后32位表示这个时代中的事务id,即xid。
- Logicalclock:逻辑时钟。逻辑时钟是选举时的一个概念,其值就是zxid中epoch的值,标识一个leader的领导时代。
leader的选举时机
- 集群启动还没有leader时
- leader宕机时
leader的选举机制、过程
- 集群没有leader,所有zk server都处于LOOKING状态
- 所有follower都参与投票,投的都是自己,写上自己的myid、zxid,广播出去
- 比较zxid,先选epoch大的(纪元新的);如果有相同的从里面选xid大的(同步数据多的)。这2项都是为了保证新leader的数据更齐全。如果还有相同的,选myid大的。
- 选出新leader后,其它节点从新leader处同步数据,leader状态变为LEADING,follower转态变为FOLLOWING,observer状态变为OBSERVING。
zk的节点同步流程
第一次是全量同步,后续是增量同步
- follower、observer连接到leader,并将自身的zxid发送给leader;
- leader根据zxid确定同步点,发送同步消息;
- follower、observer完成同步后通知follower,并修改自身状态为uptodate
同步期间,follower、observer不对外提供服务,同步完成、变成uptodate状态后才对外提供服务、接受client的请求。
zk server如何处理客户端的请求
- 读请求:所有的zk server都可以处理读请求,leader收到读请求也会处理;
- 写请求:由leader处理,follower、observer收到写请求时转交给leader处理。
leader使用paxos算法来处理写请求
-
leader将写请求都放到一个队列中,并给每个写请求分配唯一的编号,编号从小到大依次递增,按照先进先出的顺序处理,保证了事务执行的顺序一致性。
-
执行某个写请求时,leader先执行,执行完后leader广播通知follower、observer同步这个写操作,整个流程作为一个事务来处理。如果半数以上follower、observer完成同步,就提交事务;如果在指定时间内完成同步的zk server数量没达到一半,就回滚这个写操作。
-
follower、observer同步数据(写操作)时,会与前面已同步的写请求的编号对比,如果编号小于之前已同步的写请求的编号,说明自己数据同步出了问题,数据不一致了,立刻停止对外服务,从leader同步全量数据。
zk的特点
- 顺序一致性:zk server使用paxos算法来处理请求,将请求放在队列中,先进先出,请求的执行顺序与发送顺序一致。可用此特点实现队列
- 数据更新原子性:一次写操作即一个事务,要么成功(都应用、同步到所有节点),要么失败(所有节点都不使用这次数据更新)
- 单一视图: zk client无论连接到哪个zk server,读到的数据都是相同的
- 实时性: zk client读取到的是zk server上最新的数据
- 高性能:zk server将全量数量存储在内存中,性能极高,尤其是处理读请求的时候。处理写请求(更新znode)时,要把更新从内存同步到文件,同步到follower、observer,性能稍低
zk的心跳机制
zk通过心跳机制维护各个zk client的状态。
zk cli每隔一定时间(默认2000ms)发送一个心跳包给zk server,如果zk server连续多少次(默认10次)没有收到某个zk cli的心跳包,就认为该zk cli挂了,自动销毁对应的session、删除本次会话中创建的临时节点。
zk的watcher机制
也叫作观察/通知机制,zk client可以watch某个znode,指定的znode发生改变时,zk server会通知watch了此znode的zk client。
这其实是一种特殊的发布/订阅,zk client订阅znode的改变事件,指定的znode发生改变时zk server发布消息,订阅了该znode改变事件的zk client会收到消息。
watcher是观察者模式的体现,观察到某种现象发生时自动做一些事情。
有2种watcher
# get、stat这2种方式设置的watcher,都只监听节点本身的数据变化,不监听子孙节点
get -w /mall
stat -w /mall
# ls方式设置的watcher只监听子节点的数量变化,不监听节点本身的数据变化,也不监听孙节点
ls -w /mall
watcher是一次性的,触发1次后就销毁,如果要继续监听,需要重新设置watcher。
zk实现分布式通知、协调
通过watcher机制实现:节点中存储需要的数据,客户端watch节点,节点数据变化时zk server自动通知对应的客户端,客户端获取更新的数据。
acl 权限控制
可以给znode设置用户权限,限制用户对znode可以进行的操作,提高数据安全性。
znode的acl权限
- c:create,可创建子节点
- d:delete,可删除子节点
- r:read,可读取节点数据
- w:write,可写入|更新节点数据
- a:admin,可管理此节点,配置acl权限
acl权限操作
# 查看指定节点的acl权限设置,默认所有用户的权限都是cdrwa(全部权限)
getAcl /mall
# 统一设置所有用户的权限,world:anyone:权限
setAcl world:anyone:cdrw
# 设置某个用户的权限
# auth:用户名:密码:权限
auth:chy:abcd:cdrw
# 或者 digest:用户名:BASE64(SHA1(密码)):权限
digest:chy:BASE64(SHA1(abcd)):cdrw
# auth、digest设置的密码都是以密文方式存储在zk server上,但验证密码时auth是输入明文、digest是输入SHA1加密后的密文。
# auth更简单,digest更安全、但也更麻烦
# 根据ip设置权限
ip:192.168.1.1:cdrw
# 给znode设置用户权限后,操作该znode时需要验证用户名、密码
addauth digest chy:abcd
设置超级用户
超级用户具有所有的acl权限,可以操作所有的znode。
先在IDEA中获取密文
//用户名:密码,此处设置的用户名、密码都是super
String str = DigestAuthenticationProvider.generateDigest("super:super");
System.out.println(str);
编辑bin目录下的zkServer.sh
vim zkServer.sh
# 找到以下代码
nohup "$JAVA" $ZOO_DATADIR_AUTOCREATE "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" \
"-Dzookeeper.log.file=${ZOO_LOG_FILE}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}" \
# 在第二行末尾的\之前加上以下代码,superDigest=后面是IDEA输出的密文
"-Dzookeeper.DigestAuthenticationProvider.superDigest=super:gG7s8t3oDEtIqF6DM9LlI/R+9Ss="
超级用户可以管理所有的znode,但如果在设置超级用户之前,就已创建了某些znode、并手动给这些znode设置了acl权限,则超级用户对这些之前创建的znode没有管理权限。