小目标

V1

2022/11/13阅读:43主题:简

ZooKeeper学习笔记

大家好,我是二营长,日拱一卒无有尽,功不唐捐终入海。这里是Java学习小站,关注我,每天进步一点点!


ZooKeeper是什么

Zookeeper 是一个开源的分布式的,为分布式框架提供协调服务的 Apache 项目。 树形文件系统 + 监听通知机制

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

Zookeeper的特点

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

ZooKeeper保证的是CP

(1)ZooKeeper不能保证每次服务请求的可用性。(注:在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果)。所以说,ZooKeeper不能保证服务可用性。

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

CAP理论告诉我们,一个分布式系统不可能同时满足以下三种
CAP理论

⚫ 一致性(C:Consistency)
⚫ 可用性(A:Available)
⚫ 分区容错性(P:Partition Tolerance)
这三个基本需求,最多只能同时满足其中的两项,因为P是必须的,因此往往选择就在CP或者AP中。
1)一致性(C:Consistency)
在分布式环境中,一致性是指数据在多个副本之间是否能够保持数据一致的特性。在一致性的需求下,当一个系统在数
据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。
2)可用性(A:Available)
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
3)分区容错性(P:Partition Tolerance)
分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络
环境都发生了故障。

Zookeeper的数据结构

zk提供的命名空间非常像一个树形的文件系统(类似Unix文件系统)。每一个文件路径都是唯一的,zk命名空间中所有节点都是通过路径来进行识别的。

节点类型

zk节点有1M的存储空间,可以存储协调数据:配置、状态、位置等信息。

znode节点维护了一个stat结构,包括了数据变化和版本号、访问控制列表变化和时间戳。可以缓存验证和协调更新。

持久节点:客户端断开连接,节点不会被删除;

临时节点:客户端断开连接,节点立刻被删除;

有序节点:在znode的名称后米娜附加了一个顺序号,是一个递增的计数器;

无序节点:znode名称没有序号区分。

两两组合就是:持久节点、临时节点、持久顺序节点、临时顺序节点四种节点类型。

Zookeeper的操作API

> create-在树形结构的位置中创建节点
> delete-删除一个节点
> exists-测试节点是否存在
> get data-从节点上读取数据
set data-向节点中写入数据
> get chilren-检索一个节点的子节点列表
> sync-等待同步操作传输数据

Zookeeper的实现机制

ZK组件中,除了请求异常之外,组成Zookeeper 服务的节点都会复制自己组件的副本。

Replicated database是一个内存数据库,包含了全部的而数据库树。所有的更新记录都会持久化到磁盘中,写入操作在到内存数据库之前就被序列化到了磁盘中,保障可恢复性。

每一个ZK服务节点都可以正常的接收到来自客户端的请求。每个节点的本地内存数据库提供读请求的服务。写请求的时候被一个协议保护,将所有的写请求都传递给Leader来进行写处理。在Leader下线的时候,需要重新选举出新的Leader,这个时候服务是不可用的,选举完成之后恢复可用。

ZK是CAP原理中的CP,即保障一致性和分区容错性。是因为它自定义了一个原子消息协议,Leader接受到写请求,会原子的将写操作同步到过半的服务节点中。这样保证每个节点的本地副本从不出现偏差的情况。

客户端访问时的处理顺序

  • Leader才具有写权限;

  • 过半节点同步成功才能认为数据保证了一致性要求;

  1. 客户端的写流程直接发送到Leader
  1. 客户端的写流程发送给了Follower

性能问题:

写操作的时候,需要进行服务器的状态同步,所有在读多写少的时候,zk的性能是很高的。

Zookeeper选举流程

EPOCH:

每个Leader任期的代号,每投完一轮这个数据就会增加

机器id:

myid,全局唯一,手动配置;

事务id:

zxid,每一个更新请求都分配一个事务id;Leader统一分配,全局唯一,不断递增;

集群中服务器的角色:

Leader(主);

Follower(从,参与投票);

Observer(观察者,不参与投票)

集群中的服务器状态:

LOOKING:寻找 Leader 状态,当服务器处于该状态时,表示当前集群没有 Leader,因此会进入 Leader 选举状态。

FOLLOWING:跟随者状态,表示当前服务器角色是 Follower。

LEADING:领导者状态,表示当前服务器角色是 Leader。

OBSERVING:观察者状态,表示当前服务器角色是 Observer。

选举的两种场景:

  1. ZK集群启动时初始化进行选举;

  2. ZK集群Leader宕机失联下线时重新选举;

选举的前提条件:

  1. ZK服务器处于LOOKING竞选状态时,才会进行投票选举,OBSERVING观察者状态,不参与选举投票;

  2. ZK集群的节点数量需要为奇数,至少为3台,因为ZK选举机制是过半选举,超过半数的节点服务正常集群才能正常提供服务。

选举原则:

  1. EPOCH(Leader的任期数) 大的胜出;
  2. EPOCH 相同的,事务zxid大的胜出(事务id大表示写请求是最近的,数据最新);
  3. 事务id相同的,选择服务器id(myid)大的胜出。

## 选举流程:

第一种情况:集群初始化

  1. 服务器1启动,发起一次选举,S1投自己一票,此时选举不过半,S1状态仍然为LOOKING;

  2. 服务器2启动,发起选举,S2投自己1票,LOOKING 的S1投票给myid更大的S2,此时S2有2票,依然不过半,选举未完成。S1、S2都是LOOKING状态;

  3. 服务器3启动,发起选举,此时S1、S2的投票更改为S3,S3投给自己,此时3票过半,选举成功,S3变成Leader,S1、S2状态变成FOLLOWER,不再参与投票;

  4. 服务器4启动,发起选举,S1、S2状态为FOLLOWER,不投票,选举失败,S4少数服从多数,归顺为S3的FOLLOWER;

  5. 服务器5启动,和S4的结果一样。

第二种情况:Leader失联

服务器出现下面两种情况时就会发起选举:

  • 服务器初始化启动

  • 服务器运行期间与Leader失联

当服务器进入选举流程时,也存在两种情况:

  • Leader是正常服务的,是自己失联:尝试选举的时候,会由其他节点告知集群的Leader信息,然后自己尝试和Leader建立连接即可;

  • Leader确实挂了,不存在:此时每个节点都有自己的服务器ID(myid)事务id(zxid)和epoch。

投票的时候,先投自己,然后发现其他的LOOKING状态的节点时,对比epoch、zxid、myid。选举规则:epoch大的胜出;epoch相同时,zxid大的胜出;zxid相同时,myid大的胜出。

监听器原理

常见的监听:

  • 监听节点数据变化: get path [watch]

  • 监听节点数量增减变化: ls path [watch]

  1. main函数作为主线程程序入口;
  2. main函数中创建zk的客户端对象,这时会创建两个线程,一个复制网络通信,一个负责监听;
  3. 通过Connect线程将注册的监听事件发送给zk;
  4. 在zk的注册监听器列表中添加刚才的监听事件;
  5. zk中的数据或者路径发生变化时,就会将这个消息发送给listener线程;
  6. listener线程内部调用了process()方法来处理变化的业务逻辑。

Zookeeper的使用场景

通过ZK丰富的数据节点类型进行交叉使用,配合Watcher事件通知机制,可以非常方便的构建分布式中涉及的一些核心功能。如:数据发布/订阅、命名服务、集群管理、Mater选举、分布式锁和分布式队列等。

1. 分布式锁

单机锁锁的是线程获取锁,分布式可以理解成只有一个JVM获取锁成功(本质依然是线程)。保证有序的访问竞态资源。

zk的分布式锁原理就是临时顺序节点+监听机制;定义一个持久节点/lock为锁(根节点),获取锁是在lock节点下创建临序节点。最小序号为持有锁。

客户端加锁成功之后,实际上是成功创建了临时顺序节点,后续有其他服务去抢锁时,在这个临时节点之后创建临序节点,并监听上一个节点。释放锁的时候临序节点删除,监听的节点判断自己是不是序号最小的节点,是的话就是成功获取到锁。

Curator框架实现分布式锁

原生Java API的问题:

  • 会话连接是异步的,需要自己去处理,比如使用CountDownLatch;
  • Watch需要重复注册,不然不能生效;
  • 开发的复杂性比较高;
  • 不支持多节点删除和创建,需要自己去递归

Curator是一个专门解决分布式锁得框架,解决了原生Java API开发分布式遇到的问题。其可以实现五种类型的锁:

Recipes实现的锁有五种
Shared Reentrant Lockf分布式可重入锁
官网地址:http://curator.apache.org/curator-recipes/shared-reentrant-lock.html
Shared Lock 分布式非可重入锁
官网地址:http://curator.apache.org/curator-recipes/shared-lock.html
Shared Reentrant Read Write Lock可重入读写锁
官网地址:http://curator.apache.org/curator-recipes/shared-reentrant-read-write-lock.html
Shared Semaphore共享信号量
官网地址:http://curator.apache.org/curator-recipes/shared-semaphore.html
Multi Shared Lock 多共享锁
官网地址:http://curator.apache.org/curator-recipes/multi-shared-lock.html

2. 服务动态上下线

分布式系统中,主节点可以有多台,可以动态上下线,任意=一台客户端都能感知到主节点的上下线状态。

原理就是利用zk的临时顺序节点,服务端注册的时候创建临序节点,下线的时候节点自动删除。客户端监听客户端目录下的节点数量/数据变化。发生变化的时候做相应的处理,比如说不调用下线的服务、负载均衡等。

3.统一命名服务

在分布式环境下,需要经常对应用/服务进行统一命名,便于识别。将难以记住的ip和对应的域名做映射。

4.统一配置管理

与配置中心类似。希望对配置修改后,对所有节点都生效。对配置进行统一管理,方便维护。

实现逻辑:

  1. 将配置信息接入到ZK的一个节点中;
  2. 每个客户端都监听这个znode;
  3. 当配置数据有任何的变化,zk就会通知所有的客户端,客户点进行配置的重新读取。

5.统一集群管理

分布式环境中,实时监控每一个节点的状态,根据节点的实时状态做一些调整;类似注册中心。

实现:

  1. 将节点信息写入ZK的一个节点中;
  2. 监听这个znode的数据变化;比如上下线。

6.软负载均衡

ZK中用节点记录每一天服务器的访问数量,然后优先将请求分配给访问量少的服务器。

拜占庭将军问题

拜占庭将军问题是一个协议问题,拜占庭帝国军队的将军们必须全体一致的决定是否攻击某一支敌军。问题是这些将军在地理上是分隔开来的,并且将军中存在叛徒。叛徒可以任意行动以达到以下目标:欺骗某些将军采取进攻行动;促成一个不是所有将军都同意的决定,如当将军们不希望进攻时促成进攻行动;或者迷惑某些将军,使他们无法做出决定。如果叛徒达到了这些目的之一,则任何攻击行动的结果都是注定要失败的,只有完全达成一致的努力才能获得胜利。

互联网领域的解释:在缺少可信任的中央节点和可信任的通道的情况下,分布在网络中的各个节点应如何达成共识

Paxos算法

解决什么问题? 一种基于消息传递的一致性算法

Paxos 算法解决的问题是一个分布式系统如何就某个值(决议)达成一致。一个典型的场景是,在一个分布式数据库系统中,如果各节点的初始状态一致,每个节点执行相同的操作序列,那么他们最后能得到一个一致的状态。

Prxos算法描述: prxos算法中,将所有节点划分为Proposer(提议者),Acceptor(接受者),Learner(学习者),并且每个节点都可以身兼数职。

消息广播

(1)客户端发起一个写操作请求。

(2)Leader服务器将客户端的请求转化为事务Proposal提案,同时为每个Proposal分配一个全局的ID,即zxid。

(3)Leader服务器为每个Follower服务器分配一个单独的队列,然后将需要广播的 Proposal依次放到队列中去,并且根据FIFO策略进行消息发送。

(4)Follower接收到Proposal后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向Leader反馈一个Ack响应消息。

(5)Leader接收到超过半数以上Follower的Ack响应消息后,即认为消息发送成功,可以发送commit消息。

(6)Leader向所有Follower广播commit消息,同时自身也会完成事务提交。Follower 接收到commit消息后,会将上一条事务提交。

(7)Zookeeper采用Zab协议的核心,就是只要有一台服务器提交了Proposal,就要确保所有的服务器最终都能正确提交Proposal。

ZAB协议针对事务请求的处理过程类似于一个两阶段提交过程:

(1)广播事务阶段

(2)广播提交操作

这两阶段提交模型如下,有可能因为Leader宕机带来数据不一致,比如

(1) Leader 发 起 一 个 事务Proposal1后就宕机,Follower都没有Proposal1

(2)Leader收到半数ACK宕机,没来得及向Follower发送Commit 怎么解决呢?ZAB引入了崩溃恢复模式

崩溃恢复-异常假设

一旦Leader服务器出现崩溃或者由于网络原因导致Leader服务器失去了与过半 Follower的联系,那么就会进入崩溃恢复模式。

1)假设两种服务器异常情况:

(1)假设一个事务在Leader提出之后,Leader挂了。

(2)一个事务在Leader上提交了,并且过半的Follower都响应Ack了,但是Leader在Commit消息发出之前挂了。

2)Zab协议崩溃恢复要求满足以下两个要求:

(1)确保已经被Leader提交的提案Proposal,必须最终被所有的Follower服务器提交。 (已经产生的提案,Follower必须执行)

(2)确保丢弃已经被Leader提出的,但是没有被提交的Proposal。(丢弃还没有产生的提案)

崩溃恢复——Leader选举

崩溃恢复主要包括两部分:Leader选举和数据恢复

Leader选举:根据上述要求,Zab协议需要保证选举出来的Leader需要满足以下条件: (1)新选举出来的Leader不能包含未提交的Proposal。即新Leader必须都是已经提交了Proposal的Follower服务器节点

(2)新选举的Leader节点中含有最大的zxid。这样做的好处是可以避免Leader服务器检查Proposal的提交和丢弃工作。

崩溃恢复——数据恢复

崩溃恢复主要包括两部分:Leader选举和数据恢复

Zab如何数据同步:

(1)完成Leader选举后,在正式开始工作之前(接收事务请求,然后提出新的Proposal),Leader服务器会首先确认事务日志中的所有的Proposal 是否已经被集群中过半的服务器Commit

(2)Leader服务器需要确保所有的Follower服务器能够接收到每一条事务的Proposal,并且能将所有已经提交的事务Proposal应用到内存数据中。等到Follower将所有尚未同步的事务Proposal都从Leader服务器上同步过,并且应用到内存数据中以后,Leader才会把该Follower加入到真正可用的Follower列表中

崩溃恢复——异常提案处理

Zab数据同步过程中,如何处理需要丢弃的Proposal?

在Zab的事务编号zxid设计中,zxid是一个64位的数字。其中低32位可以看成一个简单的单增计数器,针对客户端每一个事务请求,Leader在产生新的Proposal事务时,都会对该计数器加1。而高32位则代表了Leader周期的epoch编号。

epoch编号可以理解为当前集群所处的年代,或者周期。每次Leader变更之后都会在 epoch的基础上加1,这样旧的Leader 崩溃恢复之后,其他Follower也不会听它的了,因为** Follower只服从epoch最高的Leader命令**。

每当选举产生一个新的 Leader,就会从这个Leader服务器上取出本地事务日志从最大编号Proposal的zxid,并从zxid中解 析得到对应的epoch编号,然后再对其加1,之后该编号就作为新的epoch 值,并将低32位数字归零,由0开始重新生成zxid。

Zab协议通过epoch编号来区分Leader变化周期,能够有效避免不同的Leader错误的使用了相同的zxid编号提出了不一样的 Proposal的异常情况。

基于以上策略,当一个包含了上一个Leader周期中尚未提交过的事务Proposal的服务器启动时,当这台机器加入集群中, 以Follower角色连上Leader服务器后,Leader 服务器会根据自己服务器上最后提交的 Proposal来和Follower服务器的Proposal进行比对,比对的结果肯定是Leader要求Follower进行一个回退操作,回退到一个确实已经被集群中过半机器Commit的最新Proposal。


时间总是过去了,才恍然过得飞快。日拱一卒无有尽,功不唐捐终入海。这里是Java学习小站,关注我,每天进步一点点!

分类:

后端

标签:

后端

作者介绍

小目标
V1