Zookeeper 的集群角色
集群中的 server 分为三种角色:leader
, follower
, observer
。
- 其中
observer
是配置zoo.cfg
明确定义的,角色leader
在一个zookeeper集群中有且只能有一个,是通过内部的选举机制临时产生的。 leader
是集群中最重要的角色。负责响应集群的所有对Zookeeper数据状态变更的请求。它会将每个状态更新请求进行顺序管理,以便保证整个集群内部消息处理的 FIFO,遵循了顺序一致性(Sequential Consistency)。leader
内部维护 session ,来自客户端的连接和断开连接,都会被统一follower
或observer
转发给leader
处理。leader
内部维护单调递增的 Zxid(ZooKeeper Transaction Id
),针对客户端连接,断开连接,节点的写操作都会分配一个全局唯一的Zxid,同时这些操作是原子性的,并且是严格顺序性的,遵循ZAB
原子广播一致性协议完成事务(transaction)操作。如果客户端的所有写操作,都会被follower
统一转发给leader
处理。
follower
具有选举权。负责提供给客户端读写服务,需要响应leader
的提议observer
没有选举权。主要提供给客户端读服务,不提供写服务,也不需要响应leader
的提议。也不需要日志文件,因为没有写服务,没有持久化的需要。
Server状态
LOOKING
,竞选状态。FOLLOWING
,随从状态,同步leader
状态,参与投票决策提案。OBSERVING
,观察状态,同步leader
状态,不参与投票决策提案。LEADING
,领导者状态,发起正常消息的提案。
Zookeeper 的存储
zookeeper中的znode数据都是在内存中优先维护和提供读服务,当事务被提交以及最终提交都会持久化到磁盘的日志文件中。
Zookeeper 的内部网络拓扑
Zookeeper 在内部网络中如何实现两两连接的?
这里暂且使用 10.0.2.30,10.0.2.31,10.0.2.32,10.0.2.33 替代 node1,node2,node3,node4,并依次启动 zookeeper。
zoo.cfg
配置文件
依次使用server.1=10.0.2.30:2888:3888 server.2=10.0.2.31:2888:3888 server.3=10.0.2.32:2888:3888 server.4=10.0.2.33:2888:3888
netstat
查看网络连接情况- node1
可以看出来 node1作为服务节点,由 node2,node3,node4 通过 3888端口建立连接进来。 node1 作为客户端连接 node3 (目前node3是leader
) 的2888端口。
- node2
可以看出来 node2作为服务节点,由 node3,node4 通过 3888端口建立连接进来。 但是 node2 作为客户端连接node1的3888端口。 node2 作为客户端连接 node3 (目前node3是leader
) 的2888端口。
- node3
可以看出来 node3作为服务节点,由 node4 通过 3888端口建立连接进来。 但是 node3 作为客户端连接node1,node2 的3888端口。 node3 作为leader
节点,由 node1,node2 ,node4 通过 3888端口建立连接进来。 至于为什么 node3能当选 leader 呢?可以在下面的 选举过程中 得到进一步详细的阐述。
- node4
可以看出来 node4 作为客户端连接node1,node2 ,node3 的3888端口。 node4 作为客户端连接 node3 (目前node3是leader
) 的2888端口。
- 结论 Zookeeper 在内部网络中如图所示,依据
zoo.cfg
的配置后续的server都逐个连接前面的server的3888端口,这样就形成了两两连接的拓扑,同时也不冗余。 而当leader
当选时,会开放2888端口,其他follower
连接其2888端口。
ZAB(原子广播,Zookeeper Atomic Broadcast
)
https://zookeeper.apache.org/doc/current/zookeeperInternals.html
ZAB(Zookeeper Atomic Broadcast
)原子广播是 Paxos
分布式一致性协议算法(http://zh.wikipedia.org/zh-cn/Paxos) 的一个简化版本。
首先有以下概念,我们需要了解:
- 数据包(
Packet
):通过 FIFO Channel 发送的字节数组。 - 提案(
Proposal
):协议的单位。通过与ZooKeeper中的法定server交换数据包来达成协议。大多数提案都包含消息,但是NEW_LEADER Proposal
提案是不包含消息。 - 消息(
Message
):要自动广播到所有ZooKeeper服务器的字节数组。消息被包含在一个提案(Proposal
)中,并且只有在提案(Proposal
)被通过后消息才会最终交付delivered
(提交到事务日志和更新内存的统一视图)。 - 法定人员 (
Quorum
):有 Zookeeper集群中非observer
角色的所有服务器节点组成,具有投票通过提案(Proposal
)的权力。
在 Zookeeper 中提供以下的保证数据的严格顺序:
- 传递可靠性:如果一个消息被一个server最终交付
delivered
,那么这个消息最终也被其他所有的server最终交付delivered
,这里指最终一致性。 - 顺序全局性:如果一个消息a先于b被一个server最终交付
delivered
,那么消息a也是先于b被其他所有的server最终交付delivered
。 - 顺序传递性:如果消息a先于b被发送到server,消息b先于c被发送到server,那么消息a也是先于c被server接收的。
如上所述,ZooKeeper保证消息的总顺序,也保证建议的总顺序。
ZooKeeper使用ZooKeeper transaction id
(zxid) ,这是一个全局的唯一的ID。提出提案(Proposal
)时,所有提案都将附上zxid,进而保证全局的顺序性。
leader
将提案(Proposal
)将发送到所有ZooKeeper服务器。在法定人员(
Quorum
)收到后,会确认(acknowledge
)回复这个提案(Proposal
)给leader
。
其中确认acknowledge
提案(Proposal
)表示服务器已将提案(Proposal
)持久化到日志中。
稍微注意一下:法定人员(Quorum
)收到提案(Proposal
),只存在确认(acknowledge
),或者因为网络等原因超时响应,不存在反对(reject
)。
只要一但满足半数以上(大于所有法定人员的一半)确认
acknowledge
后,leader
就会进入正式提交(Commit
)。如果提案(
Proposal
)中包含一条消息,则在提交时将最终交付delivered
该消息。
ZooKeeper消息传递包括两个阶段:
- leader选举(
Leader activation
):在此阶段,leader
被选举出来,集群开始变为对外可用状态,并准备开始提出提案(Proposal
)。 - 活动消息传递(
Active messaging
):在此阶段,leader
开始接受消息(Message
),并发起提案(Proposal
),协调和决策以提交(Commit
)提案(Proposal
)。
leader选举
leader
产生的条件:
- 具有最新的 Zxid,如果 存在多个server都有最新的Zxid,在投票过程中选取建立网络连接中 myid最大的。
leader
和其连接的follower
的个数必须满足半数以上(大于所有法定人员的一半)。
当集群中任意具有选举权的server发现leader挂了:
- 该 server 会触发
NEW_LEADER Proposal
提案,给自己投票,并通过 ZAB 广播给所有连接的 server。 - 接受到
NEW_LEADER Proposal
提案的server,如果有被选举权,则会触发它的投票行为:- 先比较zxid,最新的胜出,如果zxid相同,再比较myid,最大的胜出。
- 最后将胜出的内容,通过 ZAB 广播给所有连接的 server。
- 最终满足
leader
条件的server,将被选出,同时follower
也被广播获得Proposal
的提交。
以上中的 网络拓扑 为什么 node3能当选 leader 呢?
- node1 启动时,给自己投票,因为其他server尚没启动,因为 node1 依然在
LOOKING
竞选状态。 - node2 启动完,给自己投票,同时与 node1 交换了Zxid和myid,node2 胜出,但因为没有达到半数以上法定人员,所以node1,node2 依然处于
LOOKING
竞选状态。 - node3 启动完,给自己投票,同时与 node1 ,node2 交换了Zxid和myid,node3 胜出,也达到半数以上法定人员(3 > 4/2),因此 node3 被选举为
leader
。 - node4 启动完,给自己投票,同时与 node1 ,node2 ,node3交换了Zxid和myid,node3的Zxid最新,因此 node4 追随 node3。
活动消息传递
消息的传递一般指写请求。
- 当
follower
接收到 客户端的 写请求后,会转发给leader
顺序处理。 leader
收到写请求,会检查数据问题,如无问题,创建一个新的提案proposal
加入toBeApplied
FIFO 队列,内容是写请求的消息,并附上全局的ZXid。leader
每次toBeApplied
FIFO 队列头部取到一个提案proposal
,通过 ZAB 广播给所有的follower
,处于 pending 等待回复。follower
收到提案proposal
后,记录提案proposal
持久化到磁盘的日志文件中,然后确认(acknowledge
)回复这个提案(Proposal
)给leader
。leader
处于 pending 等待回复,一旦收到follower
加上自己的确认(acknowledge
)超过半数法定人员(Quorum
),就会触发Commit
阶段,发送commit
请求给所有的follower
,发送info
请求所有的observer
。 同时,leader
将提案proposal
放入 committedRequest 队列,并从toBeApplied
FIFO 队列移出该 提案proposal
。follower
收到Commit
后,会更新自己的内存数据,统一数据视图。observer
收到info
后,会更新自己的内存数据,统一数据视图。
针对客户端的读请求,则不需要转发给leader
处理。 当然如果是客户端的sync
命令,则会触发客户端连接的follower
或observer
向leader
请求同步数据状态。
@SvenAugustus(https://www.flysium.xyz/) 更多请关注微信公众号【编程不离宗】,专注于分享服务器开发与编程相关的技术干货: