分类 | 博客链接 |
---|---|
Java 面试题总结之基础篇 | 待完成… |
Java 面试题总结之并发编程篇 | https://blog.csdn.net/weixin_38251871/article/details/104667961 |
Java 面试题总结之常用设计模式篇 | https://blog.csdn.net/weixin_38251871/article/details/104658445 |
Java 面试题总结之数据库篇 | 待完成… |
Java 面试题总结之 Spring 篇 | 待完成… |
Java 面试题总结之 Spring Boot 篇 | 待完成… |
Java 面试题总结 之 Spring Cloud 篇 | 待完成… |
Java 面试题总结之 Dubbo 篇 | 待完成… |
Java 面试题总结之 Redis 篇 | 待完成… |
Java 面试题总结之 Zookeeper 篇 | https://blog.csdn.net/weixin_38251871/article/details/104741902 |
Java 面试题总结之消息中间件篇 | 待完成… |
Java 面试题总结之 Nginx 篇 | 待完成… |
Java 面试题总结之分布式事务篇 | 待完成… |
Java 面试题总结之分布式锁篇 | 待完成… |
Zookeeper :
是一个开放源代码的分布式服务, 它是集群的管理者, 监视者集群中各个节点的状态并根据节点提交的反馈进行下一步的工作, 最终将简单易用的接口和性能高效、功能稳定的系统提供给用户.Zookeeper
提供了一种类似于Linux
文件系统的树形结构, 提供了对每个节点的监控与通知机制.- 分布式应用程序可以基于
Zookeeper
实现, 例如数据发布/订阅、负载均衡、命名服务、分布式协调/通知、集群管理、Master 选举、分布式锁和分布式队列等功能.
- 一个
Zookeeper
集群是基于主从复制的高可用集群, 每个服务器都有三个角色, 分别是Leader
、Follower
、Observer
- 一个
Zookeeper
集群在同一时间只有一个工作的Leader
, 它会发起并维护各个Follower
和Observer
角色之间的心跳, 所有的写操作都是通过Leader
角色完成再原子广播给其他发服务器, 只要超过半数的节点(不包括 Observer 节点)
写入成功, 该写请求就会被提交
- 一个
Zookeeper
集群可能同一时刻存在n
个Follower
角色,Follower
角色会响应Leader
的心跳,Follower
可以直接处理并返回客户端的请求, 同时会将写请求转发给Leader
处理, 并负责在Leader
处理写请求的时候对请求进行投票
Observer
角色类似于Follower
, 但是它没有投票的权利. 当Zookeeper
需要保证高可用和强一致性, 为了支持更多的客户端, 需要增加更多的Server
. 当Server
增多后, 在投票阶段延迟增大, 就会影响性能. 所以在这种情况之下就引入了Observer
角色, 该角色不参与投票. 在Observer
接收到写请求的时候, 会把该请求转发给Leader
节点. 所以加入更多的Observer
角色会提高Zookeeper
的伸缩性且不会影响吞吐率
Zookeeper
提供了一个多层级的节点命名空间(znode)
, 与文件系统不同的是, 这些节点都可以设置关联的数据, 在文件系统中只有文件节点才可以存放数据, 而目录节点不行Zookeeper
为了保证高吞吐和低延迟, 在内存中维护了这个树形结构的目录, 而这种特性使Zookeeper
不能存放大量的数据, 每个节点存放的数据最多为1M
- 在
ZAB
协议的事务编号Zxid
设计中,Zxid
是一个64
位的数字, 高32
位代表着Leader
周期的epoch
编号, 每个当选产出新的Leader
服务器, 就会从这个Leader
服务器中取出其本地日志中最大事务的Zxid
, 并读取其中的epoch
值, 然后进行加1
, 作为epoch
的新值, 低32
位代表一个递增的计数器, 主要是针对客户端每一个事务请求时, 计数器加1
,Zxid
用于标识一次更新操作的Proposal id
, 为了保证顺序性,Zxid
需要单调自增
- 指当前集群所处的年代或者周期, 每个
Leader
都有自己的一个编号, 在每次选举之后并且Leader
进行了变更, 都会在前一个基础之上加1
, 所以在旧的Leader
角色崩溃之后, 就不会有其他的Learner
角色听从该旧的Leader
角色, 因为Learner
角色只是听从于当前的Leader
角色
恢复模式(选主) :
在服务启动或者Leader
挂掉的时候,ZAB
就进入到了恢复模式广播模式(同步数据) :
当Leader
被选取出来之后, 并且多数的Server
与Leader
完成状态同步之后, 就结束恢复模式, 状态同步保证了Leader
和Server
有相同的系统状态
选举阶段 :
当服务启动之后, 节点都处于选举阶段, 只要有一个节点得到了半数节点的票数, 就会被选举为准Leader
角色, 只有到达了广播阶段, 准Leader
角色才会变成Leader
角色, 选举阶段的目的是为了选举出一个准Leader
, 然后为下一个阶段做准备发现阶段 :
各个Follower
与准Leader
进行通信, 同步Follower
最接接收到的事务Proposal
, 主要是发现当前大多数节点接收到的最新的提议, 并且准Leader
生成新的epoch
新值让各个Follower
接受并更新它们的accepted epoch
值. 一个Follower
只会连接一个Leader
, 如果有一个Follower A
认为另一个Follower B
是Leader
,Follower A
在尝试连接Follower B
时就会被拒绝, 然后就会进入选举阶段同步阶段 :
主要是利用Leader
前一阶段获取的最新Proposal
记录, 并且将其同步给所有集群中的副本, 只有当大多数节点都同步完成之后, 准Leader
就会变成的Leader
角色,Follower
只会接受Zxid
比自己的lastZxid
大的提议广播阶段 :
在处于该阶段时,Zookeeper
集群才能够正式地对外提供事务服务, 并且Leader
可以进行消息广播, 如果还有新的节点加入, 就对新节点进行同步, 在ZAB
中提交事务只需得到超过半数的节点的ACK
就可以进行操作.
Zookeeper
的核心是原子广播, 该机制保证了各个Server
之间的同步, 实现这个机制的是ZAB
协议, 在上面中有提到ZAB
协议有恢复模式和广播模式- 在服务启动或者
Leader
崩溃之后,ZAB
就进入恢复模式, 当Leader
角色被选举出来的时候, 并且大多数Server
完成了与Leader
状态同步之后, 恢复模式就结束了, 状态同步保证了Server
与Leader
的系统状态一致- 当
Leader
与大多数Learner
进行状态同步之后, 就进入广播状态. 当一个新的Server
加入到Zookeeper
服务中, 就会在恢复模式下启动, 在发现Leader
的同时就与其进行状态同步, 直到同步结束之后, 也参与到消息广播.Zookeeper Server
会一直维持在广播状态, 直到Leader
崩溃或者失去大量的Follower
角色支持- 广播模式需要保证
Proposal
被按照顺序处理, 所以Zookeeper
采用了递增的事务Zxid
来保证其顺序性, 所有的Proposal
在提出时都会加上Zxid
Zxid
的实现与之前写的一致,Zxid
是一个64
位的数字, 高32
位是epoch
用来标识Leader
关系是否改变, 每次一个Leader
被选举出来之后, 都会有一个新的epoch
值, 低32
位是个递增的计数器- 当
Leader
崩溃或者失去大部分的Follower
支持时, 这个时候Zookeeper
就进入到了恢复模式, 恢复模式又需要重新选出Leader
角色, 然后让所有的Server
恢复到正确的状态
- 每个
Server
启动之后都会询问其他Server
它需要给谁进行投票, 当对其他的Server
进行询问时, 一般Server
每次根据自己的状态都回复自己推荐的Leader
的Id
和上一次处理事务的zxid
, 所以当Server
启动的时候都会首先给自己先投票- 当收到所有的
Server
回复之后, 就计算出zxid
最大的是哪个Server
, 并将这个Server
相关的信息设置成下一次要投票的Server
- 在计算过程中获得票数最多的
Server
为获胜者, 如果获胜者的票数超过半数, 就将Server
选为Leader
, 否则继续这个过程, 直到Leader
被选举出来- 在选取出
Leader
节点之后就会开始等待Server
的连接Follower
连接Leader
, 将最大的zxid
发送给Leader
角色Leader
根据Follower
的zxid
确定同步点后就结束选举阶段- 在选举阶段完成
Leader
同步之后就通知Follower
已经成为了uptodate
状态Follower
收到uptodate
消息之后, 就可以重新接受客户端的请求进行服务
在
Zookeeper
中主要有四种状态:LOOKING、FOLLOWING、LEADING、OBSERVING
LOOKING :
寻找Leader
角色, 当服务器处于LOOKING
状态的时候, 认为当前集群中没有Leader
角色, 所以要进入Leader
选举FOLLOWING :
表示当前服务器的角色是Follower
LEADING :
表示当前服务器的角色是Leader
OBSERVING :
表示当前服务器的角色是Observer
前提 :
目前有4
台服务器, 没有服务器上都没有数据, 他们分别是A、B、C、D
, 并按照编号依次启动
选举的过程 :
- 服务器
A
启动后首先给自己投票, 然后发投票信息, 因为还没有机器启动, 所以收不到任何的反馈信息, 服务器A
的状态就一直处于LOOKING
状态;- 服务器
B
启动后也首先给自己投票, 然后和启动的服务器A
交换结果, 因为服务器B
的编号比服务器A
的编号大, 所以服务器B
胜出. 但是现在的投票数量还没有大于半数, 只有两个服务器, 所以两个服务器的状态还是处于LOOKING
;- 服务器
C
启动后同样给自己投票, 然后和启动的服务器A、B
交换结果, 由于服务器C
的编号比服务器A、B
的编号大, 所以服务器C
胜出, 此时此刻的投票数量正好大于了半数, 所以服务器C
成为了Leader
, 服务器A、B
成为了Follower
;- 服务器
D
启动并给自己投票之后, 同时和服务器A、B、C
交换结果, 虽然服务器D
的编号最大, 但是服务器C
已经成为了Leader
, 所以服务器D
只能成为Follower
;
Zookeeper
允许客户端向服务端的某个Znode
节点注册一个Watcher
监听, 当服务端的一些指定事件触发了Watcher
, 服务端会向指定的客户端发送一个通知事件来实现分布式的通知功能, 然后客户端根据Watcher
的通知状态和事件类型做出业务上的改变
- 调用
getData()、getChildren()、exist()
的API
并传入Watcher
对象- 标记请求
request
并封装Watcher
到WatchRegistration
中- 封装成
Packet
对象并向服务端发送request
- 当收到服务端的响应之后, 将
Watcher
注册到ZKWatcherManager
中进行管理- 请求返回, 完成
Watcher
的注册
- 当接受到客户端请求后, 处理请求判断是否需要注册
Watcher
, 如果需要把数据节点的节点路径和ServerCnxn
存储在WatcherManager
的WatchTable
和watch2Paths
中;
ServerCnxn :
一个客户端和服务端的连接, 实现了Watcher
的process
接口, 可以说是一个Watcher
对象- 例如服务端接收到
setData()
事务请求触发 NodeDataChanged 事件
- 首先:
Watcher
的触发的过程是将通知状态(SyncConnected)
、事件类型(NodeDataChanged)
和节点路径封装成一个WatchedEvent
对象- 其次: 从
WatchTable
中根据节点路径查找Watcher
, 如果没有找到就说明客户端在该数据节点上注册过Watcher
. 当找到的情况下就提取出来并删除WatchTable
和Watch2Paths
中对应的Watcher
- 最终: 调用
process
方法触发Watcher
事件
- 客户端
SendThread
线程接收事件通知, 交给EventThread
线程回调Watcher
, 客户端的Watcher
机制同样是一次性的, 一旦被触发之后, 该Watcher
就失效了
客户端串行执行 :
客户端Watcher
回调的过程是一个串行同步的过程一次性 :
无论是客户端还是服务端, 当有一个Watcher
被触发的时候,Zookeeper
都会将其相应存储给移除, 这样就会减轻服务端的压力, 否则对于经常更新的节点, 服务端会不断地向客户端发起事件通知, 对于网络和服务端的压力都比较大轻量 :
Watcher
机制只会告诉客户端发生了什么机制, 而不是说明事件的具体内容, 在客户端向服务端注册Watcher
的时候, 并不会把真实的Watcher
对象实体发送给服务端, 只是在客户端请求中使用布尔类型的属性进行了标记
Watcher
事件异步发送Watcher
的通知事件是从Server
发送到Client
是异步的, 那么就会出现问题, 当不同的客户端和服务端通过 socket 通信时候, 因为网络延迟或者其他的因素导致客户端在不通的情况下监听到事件, 又因为Zookeeper
本身提供了ordering guarantee
, 就是说在客户端监听了时间之后才感知到其所监视的节点发生了变化, 所以在我们使用Zookeeper
的时候, 不可能期望监控到每次节点的变化,Zookeeper
只能保证最终一致性, 不能不保证强一致性- 注册
Watcher
的情况: 调用getData()、getChildren()、exist()
的API
- 触发
Watcher
的情况 调用setData()、create()、delete()
的API
- 在一个客户端连接到新的服务端的时候,
Watcher
会用任意会话事件进行触发, 在和一个服务端失去连接的情况下, 是不能接收到watch
的. 但是当Client
重新连接的时候, 如果有需要, 之前所有的watch
都会被重新注册, 只有对于一个未创建的znode
的exist watch
, 如果在客户端断开连接的时候被创建了, 然后在客户端连接上钱删掉的情况下, 这个watch
事件可能会被丢失
- 当集群完成了
Leader
选举之后,Learner
就会在Leader
服务器中进行注册, 当Learner
服务器完成注册之后就进入数据同步环节;- 在进行数据同步之前,
Leader
服务器会完成数据同步的初始化, 在Learner
服务器注册时发送的ACKEPOCH
消息中提取lastZxid
minCommittedLog :
Learner
服务器中Proposal
缓存队列committedLog
中的最小Zxid
maxCommittedLog :
Learner
服务器中Proposal
缓存队列committedLog
中的最大Zxid
- 直接差异化同步:
- 当
peerLastZxid
介于minCommittedLog
和maxCommittedLog
之间- 先回滚再差异化同步:
- 当新的
Leader
服务器发现某个Learner
服务器包含了自己没有的事务记录的情况下, 就会让该Learner
服务器进行回滚到Leader
服务器上存在的, 也是最接近peerLastZxid
的Zxid
- 仅回滚同步:
- 当
perLastZxid
大于maxCommittedLog
的情况下- 全量同步:
- 在
peerLastZxid
小于minCommittedLog
情况下或者Leader
服务器上没有Proposal
缓存队列并且peerLastZxid
不等于lastProcessZxid
的情况下
临时节点 (EPHEMERAL) :
其生命周期和客户端的会话绑定, 一旦客户端的会话失效, 这个客户端所创建的所有的临时节点就失效持久节点 (PERSISTENT) :
节点会一直存在Zookeeper
上, 除非手动删除临时顺序节点 (EPHEMERAL_SEQUENTIAL) :
和临时节点类似, 只是加了顺序属性, 节点名称后面会追加一个父节点维护自增的整数型数字持久顺序节点 (PERSISTENT_SEQUENTIAL) :
和持久节点类似, 只是加了顺序属性, 节点名称后面会追加一个父节点维护自增的整数型数字
Zookeeper
本身是个集群, 推荐配置不少于3
个服务器,Zookeeper
自身需要保证在一个节点宕机的时, 其他节点还可以继续地提供服务, 如果是一个Follower
宕机, 还有2
台服务器提供访问, 因为Zookeeper
上的数据是多副本的, 所以数据并不会丢失, 如果是Leader
宕机,Zookeeper
会选出新的Leader
,Zookeeper
集群的机制只要超过了半数节点正常, 集群就能正常的提供服务, 只有当Zookeeper
超过半数节点宕机时, 集群才会失效
- 不是永久的, 官方网站说明一个
Watch
事件是一个一次性的触发器, 当被设置Watch
的数据发生修改的时候, 服务器就会发送这个修改给Watch
的客户端后便于进行通知.- 为什么不设置成永久的原因, 假如服务端经常进行修改, 并且有很多客户端对服务端进行监听, 每次变动都需要通知到所有的客户端, 给网络和服务器造成了很大的压力