Zookeeper笔记
Zookeeper Note
-
什么是 Zookeeper?
Zookeeper是一个开源的分布式应用程序协调系统, 是Google的Chubby的一个开源实现(两者有所不同), 他是集群的管理者, 监视着集群中各个节点的状态(源码中有使用Observer[观察者]模式), 并根据节点提交的反馈进行下一步合理操作。最后, zookeeper将简单易用的接口和性能高效, 功能稳定的系统提供给用户。
客户端的读取请求可以被集群中的任意一台机器处理, 如果读取请求在节点上注册了监听器, 此监听器也是有所连接的zookeeper机器来处理。对于写入请求, 这些请求会同时被发给其他zookeeper机器并且达成一致后, 请求才会返回成功。因此, 随着zookeeper集群机器增多, 读取请求的吞吐量会提高但是写请求的吞吐量会下降。
Zookeeper的有序性: 所有的更新都是全局有序的, 每个更新都有一个唯一的时间戳(zxid[Zookeeper Transaction Id]), 而读取请求只会相对于更新有序, 也就是读取请求的返回结果中会带有该Zookeeper最新的zxid。
-
Zookeeper 提供了什么?
-
文件系统
-
通知机制
-
-
Zookeeper 文件系统
Zookeeper提供一个多层级的节点命名空间(namespace节点名: znode)。这些节点都可以设置关联的数据, 而文件系统中只有文件节点可以存放数据而目录节点不行。
Zookeeper为了保证高吞吐量和低延迟, 在内存中维护了树状的目录结构, 该特性使得Zookeeper不能用于存放大量的数据, 每个节点的存放数据2上限为1M。
-
四种znode 类型
-
persistent - 持久化目录节点
- 客户端与Zookeeper断开连接后, 该节点依旧存在
-
persistent_sequential-持久化顺序编号目录节点
- 客户端并与zookeeper断开连接后, 该节点依旧存在, 只是Zookeeper给该节点名称进行顺序编号
-
ephemeral - 临时目录节点
- 客户端与zookeeper断开连接后, 该节点被删除
-
ephemeral_sequential - 临时顺序编号目录节点
- 客户端与zookeeper断开连接后, 该节点被删除, 只是Zookeeper给该节点名称进行顺序编号
-
-
Zookeeper 通知机制
客户端(client)会对某个znode建立一个watcher事件, 当该node发生变化时, 这些client会受到Zookeeper的通知, 然后client可以根据znode变化来做出业务上的改变等。
-
Zookeeper 功能
-
命名服务
-
配置管理
-
集群管理
-
分布式锁
-
队列管理
-
-
Zookeeper 命名服务(文件系统)
命名服务: 通过指定的名字来获取资源或服务的地址, 利用Zookeeper创建一个全局的路径, 即使唯一的路径, 这个路径就可以作为一个名字, 指向集群中的集群, 提供的服务的地址, 甚至可以是一个远程的对象etc......
-
Zookeeper 的配置管理(文件系统, 通知机制)
程序分布式地部署在不同的机器上, 将程序的配置信息放在Zookeeper的znode下, 当有配置发生改变时, 也就是znode发生变化时, 可以通过改变Zookeeper集群中某个节点的内容, 利用watcher通知给各个客户端, 从而更改配置。
-
Zookeeper 集群管理(文件系统, 通知机制)
集群管理:
(1) 监测是否有机器退出和加入
- 所有机器都约定在父目录下创建临时目录节点, 然后监听父目录节点的子节点变化消息。一旦有机器挂掉, 该机器与Zookeeper的连接断开,其所创建的临时目录节点被删除, 所有其他机器都收到通知: 某个兄弟目录被删除(新机器加入同理)。
(2) 选举master主节点
- 所有机器创建临时顺序编号目录节点, 每次选取编号最小的机器作为master。
-
Zookeeper 分布式锁(文件系统, 通知机制)
锁服务:
(1). 保持独占
- 将zookeeper上的一个znode看作是一把锁, 通过createznode方式来实现。所有客户端都去创建/distribute_lock 节点, 最终成功创建的那个客户端也拥有了这把锁。用完删除掉自己创建的distribute_lock 节点 就释放出了锁。
(2) 控制时序
- /distribute_lock 已经预先存在, 所有客户端在它下面创建临时顺序编号目录节点, 和选取master一样, 编号最小的获得锁, 用完删除。
-
获取分布式锁的流程
-
获取分布式锁时会在locker(注意: 这边的locker只是个指代)节点下创建临时顺序节点, 释放锁时会删除该临时节点。客户端调用createNode方法在locker下创建临时顺序节点, 然后调用getChildren("locker")来获取locker下面的所有子节点, 而此时不用设置任何Watcher。
-
客户端获取到所有的子节点路径之后, 如果发现自己创建的节点在所有创建的子节点序号最小, name就认为该客户端获取到了锁。如果发现自己创建的节点并非locker所有子节点中最小的,则说明自己还没有获取到锁, 此时客户端需要找到比自己小的那个节点, 然后对其调用exists()方法, 同时对其注册事件监听器。之后, 让这个被关注的节点删除, 则客户端的Watcher会收到相应通知, 此时再次判断自己创建的节点是否是locker子节点中序号最小的, 如果是则获取到了锁, 如果不是则重复执行以上步骤直到获取到比自己小的一个节点并注册监听。
- 获取分布式锁的重点在于BaseDistributedLock, 实现了基于Zookeeper实现分布式锁的细节(后续会使用maven项目导入Curator客户端来实现一下, 这里先挖个坑, 后面填)。
-
-
Zookeeper 队列管理(文件系统, 通知机制)
队列类型:
(1) 同步队列
-
当一个队列的成员都聚齐时, 这个队列才可用, 否则要一直等待所有队员到达。
-
管理方式: 在约定目录下创建临时目录节点, 监听节点数目是否是我们要求的数目。
(2) FIFO(First In First Out)队列
- 传统queue, 先进先出, 后进后出
- 管理方式: 在分布式锁服务中的控制时序场景基本原理一直, 入队有编号, 出列按编号。 在特定的目录下创建persistent_sequential节点, 创建成功是Watcher通知等待的队列, 队列删除序列号最小的节点用以消费。在该场景下, Zookeeper的znode用于消息存储, znode存储的数据就是消息队列中的消息内容, sequential序列号就是消息的编号, 按该顺序取出就ok。
- 由于创建的节点是持久化的, 所有不需要担心队列消息的丢失问题。
-
-
Zookeeper 数据复制
作为一个为集群提供一致的数据的服务, zookeeper自然需要在所有机器间做数据复制。
数据复制的优点:
- 容错: 一个节点出错, 不会导致整个系统停止工作, 别的节点可以接管它的工作;
- 提高系统的扩展能力: 把负载分部到多个节点上, 或者增加节点来提高系统的负载能力;
- 提高性能: 让客户端本地访问就近的节点, 提高用户访问速度。
以客户端读写访问的透明度来区分数据复制集群系统可分为以下两类:
- 主写(WriteMaster): 对数据的修改提交给指定的节点
- 读无此限制, 可以读取任意一个节点
- 该情况下客户端需要对读与写进行区别, 俗称读写分离
- 任意写(Write Any): 对数据的修改可提交给任意的节点, 跟读一样
- 该情况下, 客户端对集群节点的角色与变化透明
Zookeeper采用的是任意写(Write Any)。随着机器数量增加, 他的读吞吐能力和相应能力扩展性非常好, 但写吞吐能力会逐渐下降(so建立observer观察), 而相应能力则取决于具体实现方式, 延迟复制保持最终一致性还是立刻复制快速响应。
-
Zookeeper 工作原理
Zookeeper的核心是原子广播, 这个机制保证了各个server之间的同步。实现该机制的协议为Zab协议, 它有两种模式: 恢复模式(选主) 和 广播模式(同步)。
- 当服务启动或者在领导者崩溃后, Zab就进入恢复模式
- 当领导者被选举出来, 且大多数Server完成了和leader的状态同步之后, 恢复模式就结束了
状态同步保证了leader 和 Server 具有相同的系统状态。
-
Zookeeper 如何保证事务的顺序一致性?
Zookeeper采用了递增的事务Id来标识, 所有的提议(proposal)都在被提出来的时候加上了zxid(
-
一个64位的数字
-
高32位是epoch, 用来标识leader是否发生改变, 如果有新的leader产生, epoch会自增
-
低32位用来递增计数
)
-
当新产生proposal的时候, 会依据数据库的两阶段过程, 首先会向其他的server发出事务执行请求, 如果超过半数的机器都能执行并且能够成功, 那么就会开始执行。
-
-
Zookeeper 下 Server工作状态
每个Server在工作过程中的三种状态:
-
looking: 当前Server不知道leader是谁, 正在搜寻
-
leading: 当前Server即为选举出来的leader
-
following: leader已经选举出来, 当前Server与之同步
-
-
Zookeeper如何选取主节点(leader)?
当leader崩溃或者leader失去大多数的follower, Zookeeper进入恢复模式, 在该模式下会重新选举出一个新的leader, 让所有的Server都恢复到一个正确的状态。
Zookeeper的选举算法有两种:
- basic paxos
- fast paxos(系统默认)
(1) Zookeeper选主流程(basic paxos)
-
选举线程由当前Server发起选举的线程担任, 其主要功能是对投票结果进行统计, 并选出推荐的Server
-
选举线程首先向所有Server发起一次询问(包括自己)
-
选举线程收到回复后, 验证是否是自己发起的询问(验证zxid是否一致), 然后获取对方的myid, 并存储到当前询问对象列表中, 最后获取对方提议的leader相关信息(myid, zxid), 并将这些信息存储到当次选举的投票记录表中
-
收到所有Server回复之后, 就会计算出zxid最大的那个Server, 并将这个Server相关信息设置成下一次要投票的Server
-
线程将当前zxid最大的Server设置为当前Server要推荐的leader, 如果此时获胜的Server获得n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server, 将根据获胜的Server相关信息设置自己的状态。否则, 迭代这个流程, 直到leader被选举出来。
恢复模式下, 如果是刚从崩溃状态恢复的或者是刚启动的server还会从磁盘快照中恢复数据和会话信息,
zookeeper会记录事务日志并定期进行快照, 方便在恢复时进行状态恢复。
(2) Zookeeper选主流程(fast paxos)
-
在选举过程中, 某Server首先向所有Server提议自己要成为leader, 当其他Server收到提议之后,解决epoch 和 zxid的冲突, 并接受对方的提议, 然后想对方发送接收提议完成的消息, 重复这个流程直到选举出leader。
-
Zookeeper同步流程
选举出leader后, Zookeeper就进入状态同步过程。
- leader等待server连接
- follower连接leader, 将最大的zxid发送给leader
- leader根据follower的zxid确定同步点
- 完成同步后通知follower已经成为uptodate状态
- follower收到uptodate消息后, 又可以重新接受client的请求进行服务了
-
分布式通知和协调
-
从系统调度的角度来看, 操作人员发送通知实际上是通过控制台改变某个节点的状态, 然后Zookeeper将这些变化发送给注册了这个节点的watcher的所有客户端。
-
对于执行情况汇报: 每个工作进程都在某个目录下创建一个临时节点。并携带工作的进度数据, 这样汇总的进程可以监控目录子节点的变化获得工作进度的实时的全局状况。
-
-
机器中为什么会有leader?
在分布式环境中, 有些业务逻辑只需要集群中的某一台机器进行执行, 其他的机器就可以共享这个结果, 这样可以大大减少重复计算, 提高性能。
-
Zookeeper节点宕机如何处理?
Zookeeper集群推荐配置不少于3个服务器, 它需要保证当一个节点宕机时, 其他节点会继续提供服务。
-
如果是一个follower(从节点)宕机, 仍有多台服务器提供访问, 而Zookeeper上的数据是有多个副本的,数据不会丢失
-
如果是一个leader(主节点)宕机, Zookeeper会选举出新的leader
-
只要挂掉的服务器数量不到总数的一半, 集群仍能运行。
-
-
Zookeeper负载均衡和nginx负载均衡区别
-
Zookeeper的负载均衡是可以调控的, 而nginx只是能调权重, 其他需要可控的都需要自己写插件
-
nginx的吞吐量比Zookeeper大很多, 技术选型应该由业务需求决定
-
-
Zookeeper watch机制
一个Watch事件是一个一次性的触发器, 当贝设置Watch的数据发生了改变的时候, 则服务器将这个改变发送给设置了Watch的客户端, 以便通知它们。
watch机制的特点:
-
一次性触发数据发生改变时, 一个watch event会被发送到client, 但是client只会收到一次这样的信息。
-
watcher event异步发送watcher的通知事件从server发送到client是异步的, 由于不同的客户端和服务器之间通过socket进行通信, 由于网络延迟或其他因素导致客户端在不通的时刻监听到事件, 由于Zookeeper本身提供了ordering guarantee(客户端监听事件后, 才会感知它所监视的znode发生了变化)。所以使用Zookeeper不能期望能够监控到节点每次的变化。Zookeeper只能保证最终, 而无法保证强一致性。
-
数据监视: Zookeeper有数据监视和子数据监视
- getData() 和 exists()设置数据监视
- getChildren()设置子节点监视
-
注册watcher: getData, exists, getChildren
-
触发watcher: create, delete, setData
-
setData()会触发znode上设置的data watch(如果设置成功的话)。 一个成功的create操作会触发被创建的znode上的数据watch, 以及其父节点上的child watch。
一个成功的delete()操作将会同时触发一个znode的data watch 和 child watch(因为这样就没有子节点了), 同时也会触发其父节点的child watch
-
当一个客户端连接到一个新的服务器上时, watch将被任意会话事件触发。当与一个服务器失去连接的时候, 是无法接收到watch的。而当client重新连接时, 所有先前注册过的watch, 都会被重新注册(if needed)。只有在该状况下, watch可能丢失: 对于一个未创建的znode的exist watch, 如果在客户端断开连接期间被创建了, 并且随后在客户端连接上之前又删除了, 这种情况下, 这个watch事件可能会被丢失。
-
Watch是轻量级的, 其实就是本地JVM的Callback, 服务器端只是存了是否有设置了Watcher的boolean类型