zookeeper

zookeeper工作机制

Zookeeper从设计模式角度来理解

​ 是一个基于观察者模式设计的分布式服务管理框架,它负责存储和管理大家都关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Zookeeper就 将负责通知已经在Zookeeper上注册的那些观察者做出相应的反应。

zookeeper的特点

​ 1)Zookeeper:一个领导者(Leader),多个跟随者(Follower)组成的集群。

​ 2)集群中只要有半数以上节点存活,Zookeeper集群就能正常服务。所 以Zookeeper适合安装奇数台服务器。

​ 3)全局数据一致:每个Server保存一份相同的数据副本,Client无论连接到哪个Server,数据都是一致的。

​ 4)更新请求顺序执行,来自同一个Client的更新请求按其发送顺序依次执行。

​ 5)数据更新原子性,一次数据更新要么成功,要么失败。

​ 6)实时性,在一定时间范围内,Client能读到最新数据。

zookeeper数据结构

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

请添加图片描述

zookeeper应用场景

提供的服务包括:统一命名服务、统一配置管理、统一集群管理、服务器节点动态上下线、软负载均衡等。

  • 统一命名服务:在分布式环境下,经常需要对应用/服务进行统一命名,便于识别。
  • 统一配置管理:
    • 分布式环境下,配置文件同步非常常见
      • 一般要求一个集群中,所有节点的配置信息是一致的,比如 Kafka 集群。
      • 对配置文件修改后,希望能够快速同步到各个节点上。
    • 配置管理可交由ZooKeeper实现
      • 可将配置信息写入ZooKeeper上的一个Znode
      • 各个客户端服务器监听这个Znode。
      • 一 旦Znode中的数据被修改,ZooKeeper将通知各个客户端服务器。
  • 统一集群管理
    • 分布式环境中,实时掌握每个节点的状态是必要的。
      • 可根据节点实时状态做出一些调整
    • ZooKeeper可以实现实时监控节点状态变化
      • 可将节点信息写入ZooKeeper上的一个ZNode。
      • 监听这个ZNode可获取它的实时状态变化
  • 服务器节点动态上下线
  • 软负载均衡
    • 在Zookeeper中记录每台服务器的访问数,让访问数最少的服务器去处理最新的客户端请求

配置参数解读

  • tickTime = 2000:通信心跳时间,Zookeeper服务器与客户端心跳时间,单位毫秒

  • initLimit = 10:**LF **初始通信时限 Leader和Follower初始连接时能容忍的最多心跳数(tickTime的数量)

  • syncLimit = 5LF同步通信时限 Leader和Follower之间通信时间如果超过syncLimit * tickTime,Leader认为Follwer死掉,从服务器列表中删除Follwer。

  • server.A=B:C:D

    A 是一个数字,表示这个是第几号服务器;

    集群模式下配置一个文件 myid,这个文件在 dataDir 目录下,这个文件里面有一个数据

    就是 A 的值,Zookeeper 启动时读取此文件,拿到里面的数据与 zoo.cfg 里面的配置信息比

    较从而判断到底是哪个 server。

    B 是这个服务器的地址;

    C 是这个服务器 Follower 与集群中的 Leader 服务器交换信息的端口;

    D 是万一集群中的 Leader 服务器挂了,需要一个端口来重新进行选举,选出一个新的

    Leader,而这个端口就是用来执行选举时服务器相互通信的端口。

Zookeeper选举机制

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

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

SID:服务器ID。用来唯一标识一台ZooKeeper集群中的机器,每台机器不能重复,和myid一致。

ZXID:事务ID。ZXID是一个事务ID,用来标识一次服务器状态的变更。在某一时刻,集群中的每台机器的ZXID值不一定完全一致,这和ZooKeeper服务器对于客户端“更新请求”的处理逻辑有关。

Epoch:每个Leader任期的代号。没有Leader时同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加

第一次启动

(1)服务器1启 动,发起一次选举。服务器1投自己一票。此时服务器1票数一票,不够半数以上(3票),选举无法完成,服务器1状态保持为 LOOKING;

​ (2)服务器2启动,再发起一次选举。服务器1和2分别投自己一票并交换选票信息:此时服务器1发现服务器2的myid比自己目前投票推举的(服务器1) 大,更改选票为推举服务器2。此时服务器1票数0票,服务器2票数2票,没有半数以上结果,选举无法完成,服务器1,2状态保持LOOKING

(3)服务器3启动,发起一次选举。此时服务器1和2都会更改选票为服务器3。此次投票结果:服务器1为0票,服务器2为0票,服务器3为3票。此时服务器3的票数已经超过半数,服务器3当选Leader。服务器1,2更改状态为FOLLOWING,服务器3更改状态为LEADING;LOOKING LOOKING

(4)服务器4启动,发起一次选举。此时服务器1,2,3已经不是LOOKING状态,不会更改选票信息。交换选票信息结果:服务器3为3票,服务器4为 1票。此时服务器4服从多数,更改选票信息为服务器3,并更改状态为FOLLOWING;

(5)服务器5启动,同4一样当小弟。

非第一次启动

  • EPOCH大的直接胜出

  • EPOCH相同,事务id大的胜出

  • 事务id相同,服务器id大的胜出

演示命令

请添加图片描述
请添加图片描述
请添加图片描述

  • czxid:创建节点的事务 zxid

    每次修改 ZooKeeper 状态都会产生一个 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 子节点数量

节点类型

  • 持久(Persistent):客户端和服务器端断开连接后,创建的节点不删除

  • 短暂(Ephemeral):客户端和服务器端断开连接后,创建的节点自己删除(不会立刻删除,是有变量可以进行控制的)

注意:在分布式系统中,顺序号可以被用于为所有的事件进行全局排序,这样客户端可以通过顺序号推断事件的顺序

监听器原理

​ 客户端注册监听它关心的目录节点,当目录节点发生变化(数据改变、节点删除、子目录节点增加删除)时,ZooKeeper 会通知客户端。监听机制保证 ZooKeeper 保存的任何的数据的任何改变都能快速的响应到监听了该节点的应用程序。

  • 首先要有一个main()线程

  • 在main线程中创建Zookeeper客户端,这时就会创建两个线程,一个负责网络连接通信(connet),一个负责监听(listener)。

  • 通过connect线程将注册的监听事件发送给Zookeeper。

  • 在Zookeeper的注册监听器列表中将注册的监听事件添加到列表中。

  • Zookeeper监听到有数据或路径变化,就会将这个消息发送给listener线程。

  • listener线程内部调用了process()方法。

请添加图片描述

因为注册一次,只能监听一次。想再次监听,需要再次注册

ZooKeeper 分布式锁案例

什么叫做分布式锁呢?

​ 比如说"进程 1"在使用该资源的时候,会先去获得锁,"进程 1"获得锁以后会对该资源保持独占,这样其他进程就无法访问该资源,"进程 1"用完该资源以后就将锁释放掉,让其他进程来获得锁,那么通过这个锁机制,我们就能保证了分布式系统中多个进程能够有序的访问该临界资源。那么我们把这个分布式环境下的这个锁叫作分布式锁

请添加图片描述

5.2 Curator 框架实现分布式锁案例

1) 原生的 Java API 开发存在的问题

(1)会话连接是异步的,需要自己去处理。比如使用 CountDownLatch

(2)Watch 需要重复注册,不然就不能生效

(3)开发的复杂性还是比较高的

(4)不支持多节点删除和创建。需要自己去递归

2Curator 是一个专门解决分布式锁的框架,解决了原生 JavaAPI 开发分布式遇到的问题。

​ 详情请查看官方文档:https://curator.apache.org/index.html

Paxos 算法

Paxos算法:一种基于消息传递且具有高度容错特性的一致性算法

Paxos算法解决的问题:就是如何快速正确的在一个分布式系统中对某个数据值达成一致,并且保证不论发生任何异常,都不会破坏整个系统的一致性。
请添加图片描述

在一个Paxos系统中,首先将所有节点划分为Proposer(提议者),Acceptor(接受者),和Learner(学习者)。(注意:每个节点都可以身兼数职)。

  • 一个完整的Paxos算法流程分为三个阶段:
    • Prepare准备阶段
      • Proposer向多个Acceptor发出Propose请求Promise(承诺)
      • Acceptor针对收到的Propose请求进行Promise(承诺)
    • Accept接受阶段
      • Proposer收到多数Acceptor承诺的Promise后,向Acceptor发出Propose请求
      • Acceptor针对收到的Propose请求进行Accept处理
    • Learn学习阶段:Proposer将形成的决议发送给所有Learners

(1)Prepare: Proposer生成全局唯一且递增的Proposal ID,向所有Acceptor发送Propose请求,这里无需携带提案内容,只携

带Proposal ID即可。

(2)Promise: Acceptor收到Propose请求后,做出“两个承诺,一个应答”。

➢ 不再接受Proposal ID小于等于(注意:这里是<= )当前请求的Propose请求。

➢ 不再接受Proposal ID小于(注意:这里是< )当前请求的Accept请求。

➢ 不违背以前做出的承诺下,回复已经Accept过的提案中Proposal ID最大的那个提案的Value和Proposal ID,没有则返回空值。

(3)Propose: Proposer收到多数Acceptor的Promise应答后,从应答中选择Proposal ID最大的提案的Value,作为本次要发起的提 案。如果所有应答的提案Value均为空值,则可以自己随意决定提案Value。然后携带当前Proposal ID,向所有Acceptor发 送Propose请求。

(4)Accept: Acceptor收到Propose请求后,在不违背自己之前做出的承诺下,接受并持久化当前Proposal ID和提案Value。

(5)Learn: Proposer收到多数Acceptor的Accept后,决议形成,将形成的决议发送给所有Learner。

Paxos 算法缺陷:在网络复杂的情况下,一个应用 Paxos 算法的分布式系统,可能很久无法收敛,甚至陷入活锁的情况

ZAB 协议

请添加图片描述

ZooKeeper保证的是CP

1ZooKeeper不能保证每次服务请求的可用性。(注:在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要

重新请求才能获得结果)。所以说,ZooKeeper不能保证服务可用性。

(2)进行Leader选举时集群都是不可用

源码解读

Leader 和 Follower 中的数据会在内存和磁盘中各保存一份。所以需要将内存中的数据持久化到磁盘中。

请添加图片描述请添加图片描述
请添加图片描述

ZK 服务端初始化源码解析

请添加图片描述

请添加图片描述

主启动类: QuorumPeerMain.main()

  • 解析zoo.cfg配置文件,获取myid绑定serverid

  • 清理快照(默认是关闭的)

  • 通信组件初始化(默认是NIO服务,绑定的2181) 将zoo.cfg中的配置设置到QuorumPeer中

  • 启动zk,并阻塞

    • 开始恢复操作日志以及快照

      • 在磁盘中最多查找100个有效快照,然后一个个循环,将快照中的数据恢复到DataTree
      • 然后恢复操作日志 ,是从最后读取到快照的事务+1的位置开始恢复,不断循环,通过不同类型,创建或删除node
    • 开始进行leader选举通信准备

      • 创建选票(开始选票的时候都是选自己)
      • 创建选举算法
      • QuorumCnxManager 负责选举过程中的所有网络通信
        • 创建接收队列recvQueue,创建发送队列queueSendMap,并且启动WorkerSender线程用来发送消息 以及WorkerReceiver线程,用于接收消息
      • 启动监听线程
        • 创建一个ServerSocket, 绑定服务器地址,进入死循环,阻塞等待处理请求
      • 准备开始选举
        • 初始化发送队列 sendqueue ,接收队列 recvqueue
        • 每3秒在recvqueue队列中拉取消息
        • 判断请求是否合规,至少28个byte
    • 选举执行

      • 启动时,默认的状态都是Looking状态,所以switch 分支选择的是Looking

      • 开始寻找Leader(FastLeaderElection.java lookForLeader()),更新选票,创建选票,并将选票放入发送队列中

        • WorkerSender 会每隔几秒钟从发送队列中拉取消息,然后通过包装选票,然后通过QuorumCnxManager的toSend方法
          • 创建Socket ,然后连接其他服务器
          • 通过socket 阻塞获取对方发过来的选票
          • 如果对方的id比我的大,我是没有资格给对方发送连接请求的,直接关闭自己的客户端
          • 创建并启动发送器SendWorker线程和接收器RecvWorker线程
            • 不断从发送队列SendQueue 中,获取发送消息,并执行发送
            • RecvWorker不断接收消息将接收到的消息,放入接收消息队列 放入接收消息队列recvQueue
        • 更新身份,确定leader的节点
      • 如果始终都是Looking状态,每隔几秒就会在接收队列WorkerReceiver中拉取消息,如果消息为空,就不停的发送消息,如果不为空 就更新选票,获取身份

      • 这个和上一步都是不断循环的,从第二步里面更新完身份后,进入FOLLOWING 或者 LEADING 选项中,执行follower.followLeader(); 或者leader.lead();进行状态同步

        • leader.lead();

          • 等待其他follower节点向leader节点发送同步状态
          • 创建一个LearnerHandler对于每一个follower,然后向已经注册的follower发送信息
          • Leader根据从Follower获取sid和旧的epoch(follower注册时获取的),构建新的epoch
          • Leader向Follower发送信息(包含:zxid和newEpoch) 等待确认
          • 接收到Follower应答的ack epoch
          • 判断Leader和Follower是否要同步(syncFollower()) 将需要同步的状态添加到LearnerHandler 中的消息队列中,然后由LearnerHandler 发送给follower该状态
          • 接收到应答然后开始同步
          • 然后即是zk leader 的启动
            • 开始设置客户端发送过来的request 请求类型,来创建不同的 request
            • 通过启动线程不断的消耗PrepRequestProcessor的submittedRequests消息,然后进行相关的处理
        • follower.followLeader();

          • 查找leader,然后向leader中注册自己

          • 接收leader的epoch(上面第四步)

          • 发送ackepoch给leader(包含了自己的:epoch和 zxid)

          • 然后根据状态进行同步的数据同步DIFF、TRUNC、SNAP,然后同步结果返回给leader

          • 发送ackepoch给leader(包含了自己的:epoch和zxid)

          • follower接收该状态,进行确认,对leader进行应答

          • processPacket()将请求注册到

          • 然后即是zk follower 的启动

          • follower 通过processPacket不断的往PrepRequestProcessor的submittedRequests队列中存放消息

客户端

​ ZooKeeperMain.java main()

  • ZooKeeperMain main = new ZooKeeperMain(args);
    • connectToZK 通过该方法去连接zk的服务端
      • 赋值watcher给默认的defaultWatcher
      • 客户端与服务器端通信的终端
      • 解析连接地址
      • 通过反射获取clientCxnSocket对象
      • 然后创建两个线程 sendThread(发送通过命令行包装的request)和 eventThread(将发送的事件交还给观察者,进行观察) 并且启动.
      • 接收服务端响应,并处理
  • main.run();
    • 就是一行行解析命令
    • connectToZK连接服务器通过sendThread 线程发送命令,与后端交互

ZK服务端加载数据源码解析

请添加图片描述

(1)zk 中的数据模型,是一棵树,DataTree,每个节点,叫做 DataNode

(2)zk 集群中的 DataTree 时刻保持状态同步

(3)Zookeeper 集群中每个 zk 节点中,数据在内存和磁盘中都有一份完整的数据。

  • 内存数据:DataTree

  • 磁盘数据:快照文件 + 编辑日志

ZK选举源码解析

请添加图片描述
请添加图片描述

Follower和Leader状态同步源码解析

请添加图片描述

请添加图片描述

客户端初始化源码解析

请添加图片描述

注册一次,监听一次
只要有半数的节点写完了,就可以往客户端发送确认通知,之后的节点,再由leader 逐个发送,逐个接收确认
客户端发送写请求,发送给 follower ,然后 follower 发送给leader 写请求,leader发送确认,follower写完,往leader发送确认写完,然后超过半数,follower 既往客户端发送确认通知,剩余的节点将有leader逐个发送,然后逐个确认,只要应答数超过了半数,就会发送确认
分布式锁
一旦用完,就直接释放(不一定,这个是可以通过配置文件进行设置的)
zookeeper 保持数据一致性

paxos算法,是一种基于消息传递且具有高度容错特性的一致性算法
解决的问题,就是如何快速正确的在一个分布式系统中对某个数据值达成一致,并且保证不论发生什么异常,都不会破坏整个系统的一致性
一份在内存,一份在硬盘

在这里插入图片描述
https://download.csdn.net/download/weixin_42403127/37010007
这个并不需要积分下载

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐