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/)
更多请关注微信公众号【编程不离宗】,专注于分享服务器开发与编程相关的技术干货: