zookeeper

一、概述

特点

  1. 一个leader多个fllower组成的集群
  2. 集群中只要有半数以上节点存货,zk集群就能正常服务。所以zk适合安装奇数台服务器
  3. 全局数据一致性,每隔server保存一份相同的数据副本,client无论连接到哪个Server,数据都是一致的
  4. 跟新请求顺序执行,来自同一个client的更新请求按其发送顺序一次执行
  5. 数据更新原子性,一次数据更新要么成功要么失败
  6. 实时性:在一定时间范围内,client能读取到i最新的数据

数据结构

zookeeper数据结构与unix文件系统很类似,整体上可以看作是一棵树,每隔节点称作一个ZNode。每个ZNode默认能够存储1MB的数据,每个ZNode都可以通过其路径唯一标识。

应用场景

  1. 统一命名服务:在分布式环境下,经常需要对应用/服务进行统一命名,便于识别‘
  2. 统一配置管理:在分布式环境下,配置文件同步非常常见
  3. 统一集群管理:分布式环境中,实时掌握每个节点的状态是必要的;zk可以实现实时监控节点状态变化
  4. 服务器动态上下线:客户端实时洞察到服务器上下线的变化
  5. 软负载均衡:zk中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求

CAP和BASE理论

一个分布式系统必然会存在一个问题:因为分区容忍性(partition tolerance)的存在,就必定要求我们需要在系统可用性(availability)和数据一致性(consistency)中做出权衡。这就是著名的CAP定理。

CAP理论中,P(分区容忍性)是必然要满足的,因为毕竟是分布式,不能把所有的应用全放到一个服务器里面,这样服务器是吃不消的。所以,只能从AP(可用性)和CP(一致性)中找平衡。Eureka保证了AP,zookeeper保证了CP。

BASE理论:即使无法做到强一致性,但分布式系统可以根据自己的业务特点,采用适当的方式来使系统达到最终的一致性。BASE理论由:Basically Avaliable 基本可用、Soft state 软状态、Eventually consistent 最终一致性组成。

  • 基本可用(Basically Available):基本可用是指分布式系统在出现故障的时候,允许损失部分可用性,即保证核心可用。例如,电商大促时,为了应对访问量激增,部分用户可能会被引导到降级页面,服务层在该页面只提供降级服务。
  • 软状态(Soft State):软状态是指允许系统存在中间状态,而该中间状态不会影响系统整体可用性。分布式存储中一般一份数据至少会有多个副本,允许不同节点间副本同步的延时就是软状态的体现。
  • 最终一致性(Eventual Consistency):最终一致性是指系统中的所有数据副本经过一定时间后,最终能够达到一致的状态。弱一致性和强一致性相反,最终一致性是弱一致性的一种特殊情况。

ACID是传统数据库常用的设计理念,追求强一致性模型。BASE支持的是大型分布式系统,通过牺牲强一致性获得高可用性。实现BASE理论的算法由2PC、3PC、Paxos、Raft、ZAB,解决的问题全部都是:在分布式环境下,怎么让系统尽可能的高可用,而且数据最终能达到一致。

二、安装

启动和退出

/bin/zkServer.sh start|stop|status|start-foreground  // 服务端管理
/bin/zkCli.sh  // 客户端连接

配置

tickTime=2000;   通信心跳时间,zk服务器与客户端心跳时间,单位毫秒
initLimit=10;    Leader和Follower之间初始连接时能容忍的最多心跳数量。
syncLimit=5;     Leader和Follower之间通信时间如果超过syncLimit*tickTime,则表示掉线,从服务器列表中删除Follower
dataDir=/opt/zk/;数据快照保存目录
clientPort=2181; 客户端连接端口,通常不修改

三、集群

配置

配置文件新增
#server.1=zookeeper1:2888:3888
#server.2=zookeeper2:2888:3888
#server.3=zookeeper3:2888:3888
其中1|2|3表示集群中服务的唯一ID
zookeeper1|2|3是host,也是IP地址
2888是LF之间通信的端口
3888是Leader挂了之后重新选举的端口

选举机制

  • SID:服务器ID,用来唯一标识一台zk集群中的机器,和myid一致
  • ZXID:事务ID。ZXID用来标识一次服务器状态的变更。在某一时刻,集群中的每台机器的ZXID值不一定完全一致,这和zk服务器对于客户端更新请求的处理逻辑有关。
  • Epoch:每个Leader任期的代号。
服务器选举状态
  1. LOOKING 不确定Leader状态。该状态下的服务器认为当前集群中没有Leader,会发起Leader选举。
  2. **FOLLOWING **跟随者状态。表明当前服务器角色是Follower,并且它知道Leader是谁。
  3. LEADING 领导者状态。表明当前服务器角色是Leader,它会维护与Follower间的心跳。
  4. **OBSERVING **观察者状态。表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票。
首次选举
  • 服务器1启动,发起一次选举。服务器1投自己一票,此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持未LOOKING
  • 服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息;次数服务器1发现服务器2的myid比自己目前投票选举的(服务器1)大,更改选票为推举服务器2。此时服务器1票数0,服务器2票数2,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING
  • 服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数超过半数,当选leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING
  • 服务器4启动,发起一次选举。次数服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING
  • 服务器5启动,同4一样更改状态为FOLLOWING
非首次选举

当zk集群中的一台服务器出现一下两种情况之一时,就会开始进入Leader选举:

  • 服务器初始化启动
  • 服务器运行期间无法和leader保持连接

当一台机器进入Leader选举流程时,当前集群也可能会处于一下两种状态:

  • 集群中本来就存在一个leader
  • 集群中确实不存在leader

假设zk集群由5台服务器组成,SID分别为1、2、3、4、5,ZXID分别为8、8、8、7、7,并且此时SID为3的服务器时Leader。某一时刻,3和5服务器出现故障,因此开始进行Leader选举。此时服务器124的(epoch, zxid, sid)分别为(1,8,1)(1,8,2)(1,7,4)。选举规则:

  1. epoch大的直接胜出
  2. epoch相同,sxid大的胜出
  3. sxid相同,sid大的胜出

四、操作命令

节点

节点类型
  • 持久化节点PERSISTENT:客户端与zookeeper断开连接后,该节点依旧存在。
  • 持久化顺序编号节点PERSISTENT_SEQUENTIAL:客户端与zookeeper断开连接后,该节点依旧存在,只是Zookeeper给该节点名称进行顺序编号。
  • 临时节点EPHEMERAL:客户端与zookeeper断开连接后,该节点被删除。
  • 临时顺序编号节点EPHEMERAL_SEQUENTIAL:客户端与zookeeper断开连接后,该节点被删除,只是Zookeeper给该节点名称进行顺序编号。
节点信息
znode 状态信息 解释
cZxid create ZXID,即该数据节点被创建时的事务 id
ctime create time,znode 被创建的毫秒数(从1970 年开始)
mZxid modified ZXID,znode 最后更新的事务 id
mtime modified time,znode 最后修改的毫秒数(从1970 年开始)
pZxid znode 最后更新子节点列表的事务 id,只有子节点列表变更才会更新 pZxid,子节点内容变更不会更新
cversion znode 子节点变化号,znode 子节点修改次数,子节点每次变化时值增加 1
dataVersion znode 数据变化号,节点创建时为 0,每更新一次节点内容(不管内容有无变化)该版本号的值增加 1
aclVersion znode 访问控制列表(ACL )版本号,表示该节点 ACL 信息变更次数
ephemeralOwner 如果是临时节点,这个是 znode 拥有者的 sessionid。如果不是临时节,则 ephemeralOwner=0
dataLength znode 的数据长度
numChildren znode 子节点数量
节点操作
ls /zk						查看节点的子节点
ls2 /zk						查看节点的子节点和该节点的详细信息
get /zk						查询该节点的数据
create /zk/server-1 "s1"	创建节点
create -s /zk/server-2 "s2"	创建带序号的节点
create -e /zk/server-3 "s3" 创建临时节点
set /zk/server-1 "s0"		修改节点的数据
stat /zk					查看节点的状态
rmr /zk/server-3			删除某个节点
delete /zk/server-2			删除某个子节点

监听

Watcher 监听机制是 Zookeeper 中非常重要的特性,我们基于 Zookeeper上创建的节点,可以对这些节点绑定监听事件,比如可以监听节点数据变更、节点删除、子节点状态变更等事件,通过这个事件机制,可以基于 Zookeeper 实现分布式锁、集群管理等多种功能,它有点类似于订阅的方式,即客户端向服务端 注册 指定的 watcher ,当服务端符合了 watcher 的某些事件或要求则会 向客户端发送事件通知 ,客户端收到通知后找到自己定义的 Watcher 然后 执行相应的回调方法 。

当客户端在Zookeeper上某个节点绑定监听事件后,如果该事件被触发,Zookeeper会通过回调函数的方式通知客户端,但是客户端只会收到一次通知。如果后续这个节点再次发生变化,那么之前设置 Watcher 的客户端不会再次收到消息(Watcher是一次性的操作),可以通过循环监听去达到永久监听效果。

Watcher机制可以分为三个过程:

  1. 客户端注册Watcher,注册watcher有三种方式:getData、exists、getChildren
  2. 服务器处理Watcher
  3. 客户端回调Watcher客户端

写数据原理

每个follower节点都会有一个先进先出的队列用来存放收到的事务请求,保证执行事务的顺序。所以:

  • 可靠提交由ZAB的事务一致性协议保证
  • 全局有序由TCP协议保证
  • 因果有序由follower的历史队列保证
通过Leader写数据
  1. leader从客户端收到一个写请求
  2. leader生成一个新的事务并为这个事务生成一个唯一的ZXID
  3. leader将这个事务发送给所有的follows节点,将带有 zxid 的消息作为一个提案(proposal)分发给所有 follower。
  4. follower节点将收到的事务请求加入到历史队列(history queue)中,当 follower 接收到 proposal,先将 proposal 写到硬盘,写硬盘成功后再向 leader 回一个 ACK
  5. 当leader收到大多数follower(超过一半)的ack消息,leader会向follower发送commit请求(leader自身也要提交这个事务)
  6. 当follower收到commit请求时,会判断该事务的ZXID是不是比历史队列中的任何事务的ZXID都小,如果是则提交事务,如果不是则等待比它更小的事务的commit(保证顺序性)
  7. Leader将处理结果返回给客户端

注意:

  • leader并不需要得到observer的ack,即observer无投票权
  • leader不需要得到所有follower的ack,只要收到过半的ack即可,同时leader本身对自己有一个ack
  • observer虽然无投票权,但仍需同步leader的数据从而在处理读请求时可以返回尽可能新的数据
通过follower/observer写数据

follower/observer接受写请求以后,不能直接处理,需要将写请求转发到leader处理

  • Follower/Observer接受写请求以后,不能直接处理,而需要将写请求转发给Leader处理
  • 除了多了一步请求转发,其它流程与直接写Leader无任何区别
  • Leader处理写请求是通过上面的消息广播模式,实质上最后所有的zkServer都要执行写操作,这样数据才会一致

五、应用

分布式锁

a、基于临时节点方案
  1. AB等多线程都创建/lock节点
  2. 如果A创建成功,则A持有锁
  3. B创建节点失败阻塞,同事对/lock节点设置监听
  4. A线程执行完操作,删除/lock节点释放锁
  5. B线程此时收到监听回调,解除阻塞,重新去创建/lock节点获取锁

缺点:当线程数庞大时会发生"惊群"现象,zk节点可能会运行缓慢甚至宕机。每次的争抢锁都会消耗资源,且性能大打折扣

b、基于临时顺序节点方案
  1. AB等多线程在/locks路径下创建一个带序号的临时节点
  2. 判断自己创建的节点是不是/locks路径下序号最小的节点
  3. 如果是,则获取锁
  4. 如果不是,则监听自己的前一个节点
  5. 获取锁的线程处理完业务逻辑,删除自己创建的节点
  6. 监听它的后一个节点收到通知后循环步骤5

服务器动态上下线

  1. 服务器上线后去注册信息(创建的都是临时节点)
  2. 客户端获取到当前在线服务器列表并注册监听(监听父级节点子节点变动)
  3. 服务器节点下载(临时节点被删除)
  4. 服务器上下线回调客户端通知
posted @ 2024-01-18 14:08  無花無酒鋤作田  阅读(3)  评论(0编辑  收藏  举报