Zookeeper:基础
架构(Architecture)
- Leader: ZooKeeper 集群工作的核心。
- 事务请求(写操作)的唯一调度和处理者,保证集群事务处理的顺序性;集群内部各个服务的调度者。
- 对于 create,setData,delete 等有写操作的请求,则需要统一转发给 leader 处理,leader 需要决定编号、执行操作,这个过程称为一个事务。
- Follower: 处理客户端非事务(读操作)请求。
- 转发事务请求给 Leader;
- 参与集群;
- leader 选举投票 2n-1 台可以做集群投票。
- Observer: 观察者角色。
- 观察 ZooKeeper 集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给Leader服务器处理。
- 不会参与任何形式的投票只提供服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。
ZooKeeper集群中的节点
ZooKeeper集合中可以有不同数量的节点。那么,让我们分析一下在ZooKeeper工作流中更改节点的效果:
- 如果我们有单个节点,那么当该节点失败时,ZooKeeper集群就会失效。这就是为什么不建议在生产环境中使用它,因为它会导致“单点故障”。
- 如果我们有两个节点和一个节点故障,我们就没有多数,因为两个节点中有一个不是多数节点。
- 如果我们有三个节点而一个节点故障,那么我们有大多数,因此,这是最低要求。ZooKeeper集群在实际生产环境中必须至少有三个节点。
- 如果我们有四个节点而两个节点故障,它将再次故障。类似于有三个节点,额外节点不用于任何目的,
因此,最好添加奇数的节点,例如 3,5,7。
“为什么推荐使用奇数个节点?” 因为: 1、奇数和偶数个数的节点,对于故障转移的效果几乎是一样的; 2、但是节点越多对于集群写入操作越低效(所有节点都要写入相同数据)。 所以应该使用奇数个数的节点。
工作流
下图描述了ZooKeeper工作流:
其中:
操作或组件 | 描述及说明 |
---|---|
写入(write) | 写入过程由leader节点处理。
|
读取(read) | 读取由特定连接的znode在内部执行,因此不需要与集群进行交互。 |
领导者(Leader) | Leader是负责处理写入请求的Znode。 |
跟随者(Follower) | follower从客户端接收写入请求,并将它们转发到leader znode。 |
复制数据库(replicated database) | 它用于在zookeeper中存储数据。
|
请求处理器(request processor) | 它管理来自follower节点的写入请求。
|
原子广播(atomic broadcasts) | 负责广播从leader节点到follower节点的变化。 |
一旦ZooKeeper集合启动,它将等待客户端连接:
- 客户端将连接到ZooKeeper集合中的一个节点,它可以是领导节点或跟随节点。
- 一旦客户端被连接,节点将向特定客户端分配会话ID并向该客户端发送确认。
- 如果客户端没有收到确认,它将尝试连接ZooKeeper集合中的另一个节点。
- 一旦连接到节点,客户端将以有规律的间隔向节点发送心跳,以确保连接不会丢失。
连接之后,
- 如果客户端想要读取特定的znode:(读取)
- 它将会向具有znode路径的节点发送读取请求,并且节点通过从其自己的数据库获取来返回所请求的znode。【所以,在ZooKeeper集合中读取速度快。】
- 如果客户端想要将数据存储在ZooKeeper集合中:(写入)
- 将znode路径和数据发送到服务器;
- 连接的服务器将该请求转发给领导者,然后领导者将向所有的跟随着重新发出写入请求:
- 如果只有大部分节点(Quorum???)成功响应,而写入请求成功,则成功返回代码将被发送到客户端。
- 否则,写入请求失败。
Leader 选举
Leader 选举是保证分布式数据一致性的关键所在。
Leader 选举分为 Zookeeper 集群初始化启动时选举和 Zookeeper 集群运行期间重新选举两种情况。
- 若进行 Leader 选举,则至少需要两台机器,这里选取 3 台机器组成的服务器集群为例。
选举过程中的相关参数:
- 服务器ID(myid):在选举算法中,编号越大权重越大。
- 事务ID(zxid):在选举算法中,值越大权重越大(说明数据越新)。
- ZooKeeper 状态的每次变化都接收一个 ZXID(ZooKeeper 事务 id)形式的标记。
- ZXID 是一个 64 位的数字,由 Leader 统一分配,全局唯一,不断递增。
- ZXID 展示了所有的 ZooKeeper 的变更顺序。(如果 zxid1 小于 zxid2 说明 zxid1 在 zxid2 之前发生)
- 逻辑时钟(epoch-logicalclock):在选举算法中,同一轮投票过程中的逻辑时钟值是相同的,每投完一次值会增加。
选举过程中的节点状态:
- LOOKING:寻找 Leader 状态,处于该状态需要进入选举流程;
- LEADING:领导者状态,处于该状态的节点说明是角色已经是 Leader;
- FOLLOWING:跟随者状态,表示 Leader 已经选举出来,当前节点角色是 follower;
- OBSERVER:观察者状态,表明当前节点角色是 observer;
- OBSERVING 不参与投票。
初始化启动时选举
在集群初始化阶段,当有一台服务器 ZK1 启动时,其单独无法进行和完成 Leader 选举,当第二台服务器 ZK2 启动时,此时两台机器可以相互通信,每台机器都试图找到 Leader,于是进入 Leader 选举过程。
选举流程如下图:
节点的选举操作流程:
选举过程如下:
- 发出投票:每个Server发出一个投票。
- 由于是初始情况,ZK1 和 ZK2 都会将自己作为 Leader 服务器来进行投票,每次投票会包含所推举的服务器的 myid 和 ZXID,使用(myid, ZXID)来表示,此时 ZK1 的投票为(1, 0),ZK2 的投票为(2, 0),然后各自将这个投票发给集群中其他机器。
- 接受投票:接受来自各个服务器的投票。
- 集群的每个服务器收到投票后,首先判断该投票的有效性,如检查是否是本轮投票(根据 epoch-logicalclock)、是否来自 LOOKING 状态的服务器。
- 处理投票:针对每一个投票,服务器都需要将别人的投票和自己的投票进行比较。
- 规则如下:
- 检查 epoch;
- 先比较 zxid:zxid 比较大的服务器优先作为 Leader。
- 再比较 myid:如果 zxid 相同,myid 较大的服务器作为Leader服务器。
- 对于 ZK1 而言,它的投票是(1, 0),接收 ZK2 的投票为(2, 0),首先会比较两者的 ZXID,均为 0,再比较 myid,此时 ZK2 的 myid 最大,于是 ZK2 胜。ZK1 更新自己的投票为(2, 0),并将投票重新发送给 ZK2。
- 规则如下:
- 统计投票:每次投票后,服务器都会统计投票信息,判断是否已经有过半机器接受到相同的投票信息。
- 对于 ZK1、ZK2 而言,都统计出集群中已经有两台机器接受了(2, 0)的投票信息,此时便认为已经选出 ZK2 作为Leader。
- 改变服务器状态:一旦确定了 Leader,每个服务器就会更新自己的状态:如果是 Follower,那么就变更为 FOLLOWING,如果是 Leader,就变更为 LEADING。
- 当新的 Zookeeper 节点 ZK3 启动时,发现已经有 Leader 了,不再选举,直接将直接的状态从 LOOKING 改为 FOLLOWING。
运行期间重新选举
在 Zookeeper 运行期间,如果 Leader 节点挂了,那么整个 Zookeeper 集群将暂停对外服务,进入新一轮Leader选举。
选举流程如下图:
选举过程如下:
- 变更状态:Leader 挂后,余下的非 Observer 服务器都会讲自己的服务器状态变更为 LOOKING,然后开始进入 Leader 选举过程。
- 发出投票:每个Server会发出一个投票。
- 在运行期间,每个服务器上的 ZXID 可能不同,此时假定 ZK1 的 ZXID 为 124,ZK3 的 ZXID 为 123;在第一轮投票中,ZK1 和 ZK3 都会投自己,产生投票(1, 124),(3, 123),然后各自将投票发送给集群中所有机器。
- 接受投票:(同“初始化启动时选举”)
- 处理投票:(同“初始化启动时选举”)
- 由于 ZK1 事务 ID 大,ZK1 将会成为 Leader。
- 统计投票:(同“初始化启动时选举”)
- 改变服务器的状态:(同“初始化启动时选举”)
会话(Session)
客户端与服务端之间的连接是基于 TCP 长连接,client 端连接 server 端默认的 2181 端口,也就是 session 会话:
一旦客户端连接到服务器,将建立会话并向客户端分配'''会话ID'''。客户端以特定的时间间隔发送'''心跳'''以保持会话有效。如果ZooKeeper集合在超过服务器开启时指定的期间(会话超时)都没有从客户端接收到心跳,则它会判定客户端死机。
- 会话超时通常以毫秒为单位。当会话由于任何原因结束时,在该会话期间创建的临时节点也会被删除。
- 会话中的请求按 FIFO 顺序执行。
Session 的创建:
- sessionID:会话ID,用来唯一标识一个会话。
- 每次客户端创建会话的时候,zookeeper 都会为其分配一个全局唯一的 sessionID。
- Timeout:会话超时时间。
- 客户端在构造 Zookeeper 实例时候,向服务端发送配置的超时时间,server 端会根据自己的超时时间限制最终确认会话的超时时间。
- TickTime:下次会话超时时间点,默认 2000 毫秒。
- 可在 zoo.cfg 配置文件中配置,便于 server 端对 session 会话实行分桶策略管理。【?】
- isClosing:该属性标记一个会话是否已经被关闭,当 server 端检测到会话已经超时失效,该会话标记为“已关闭”,不再处理该会话的新请求。
Session 的状态:
- connecting:连接中,session 一旦建立,状态就是 connecting 状态,时间很短。
- connected:已连接,连接成功之后的状态。
- closed:已关闭,发生在 session 过期,一般由于网络故障客户端重连失败,服务器宕机或者客户端主动断开。
会话超时管理(分桶策略+会话激活)
从第一次连接建立开始,客户端开始会话的生命周期,客户端向服务端的ping包请求,每个会话都可以设置一个超时时间。
zookeeper 的 leader 服务器在运行期间定时进行会话超时检查,时间间隔是 ExpirationInterval,单位是毫秒,默认值是 tickTime,每隔 tickTime 进行一次会话超时检查。
ExpirationTime 的计算方式:【???】
ExpirationTime_1 = CurrentTime + SessionTimeout;
ExpirationTime_n = (ExpirationTime_n-1 / ExpirationInterval + 1) * ExpirationInterval;
在 zookeeper 运行过程中,客户端会在会话超时过期范围内向服务器发送请求(包括读和写)或者 ping 请求,俗称心跳检测完成会话激活,从而来保持会话的有效性。
会话激活流程:
激活后进行迁移会话的过程,然后开始新一轮会话超时检查:
数据同步流程
在 Zookeeper 中,主要依赖 【ZAB 协议】 来实现 【分布式数据一致性】。
ZAB 协议分为两部分:
- 消息广播
- 崩溃恢复
消息广播
Zookeeper 使用单一的主进程 Leader 来接收和处理客户端所有事务请求,并采用 ZAB 协议的原子广播协议,将事务请求以 Proposal 提议广播到所有 Follower 节点,当集群中有过半的 Follower 服务器进行正确的 ACK 反馈,那么 Leader 就会再次向所有的 Follower 服务器发送 commit 消息,将此次提案进行提交。
这个过程可以简称为 2pc 事务提交,整个流程可以参考下图:
- 注意:Observer 节点只负责同步 Leader 数据,不参与 2PC 数据同步过程。【???】
崩溃恢复
在正常情况消息广播情况下能运行良好,但是一旦 Leader 服务器出现崩溃,或者由于网络原理导致 Leader 服务器失去了与过半 Follower 的通信,那么就会进入崩溃恢复模式,需要选举出一个新的 Leader 服务器。
在这个过程中可能会出现两种数据不一致性的隐患,需要 ZAB 协议的特性进行避免。
- Leader 服务器将消息 commit 发出后,立即崩溃;
- Leader 服务器刚提出 proposal 后,立即崩溃;
ZAB 协议的恢复模式使用了以下策略:
- 选举 zxid 最大的节点作为新的 leader;(运行期间重新选举 Leader)
- 新 leader 将事务日志中尚未提交的消息进行处理;