zk的数据模型

Zookeeper 理论总结_zookeeper
树结构,/是根节点,节点叫做znode,一个znode对应一个文件目录。
 

有四种类型的znode

  • PERSISTENT 持久,znode创建后一直保留
  • PERSISTENT_SEQUENTIAL 持久、顺序编号
  • EPHEMERAL 临时,创建znode的zk client断开与zk server的连接后,zk server会自动删除该znode
  • EPHEMERAL_SEQUENTIAL 临时、顺序编号

临时节点常用于实现分布式锁。

 

zk server的集群模式

Zookeeper 理论总结_zookeeper_02
 

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的请求。

Zookeeper 理论总结_数据_03

 

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没有管理权限。