ZAB协议
如何实现读操作
相比写操作,读操作的处理要简单很多,因为接收到度请求的节点只需要查询本地数据,然后响应数据给客户端就可以了。读操作的核心流程如图所示。
- 1.跟随者在FollowerRequestProcessor.processRequest()中接收到度请求。
- 2.跟随者在FinalRequestProcessor.processRequest()中查询本地数据,也就是dataTree中的数据,如代码所示
java
// 处理读请求
case OpCode.getData: {
......
// 查询本地dataTree中的数据
rsp = handleGetDataRequest(getDataRequest, cnxn, request.authInfo);
......
break;
}
- 3.然后跟随者响应查询到的数据给客户端,如代码所示
java
case OpCode.getdata: {
......
// 响应查询到的数据给客户端
cnxn.sendResponse(hdr, rsp, "response", path, stat, opCode);
break;
}
至此,ZooKeeper就完成了读操作的处理。这里补充一点,可以将dataTree理解成Raft的状态机,提交的数据最终都存放在dataTree中
ZAB协议与Raft算法
在我看来,ZAB协议和Raft算法很类似,比如主备模式(也即领导者、跟随者模型)、日志必须是连续的、以领导者的日志为准来实现日志一致等。为什么它们比较类似呢?
我的看法是,"英雄所见略同"。比如ZAB协议要实现操作的顺序性,而Raft算法不仅要实现操作的顺序性,还要实现线性一致性,这两个目标决定了它们不能允许日志不连续,且必须按照顺序提交日志,素以,它们要通过上面的方法实现日志的顺序性,并保证达成共识(即提交后的日志不会再改变)。
最后,就ZAB协议和Raft算法做个对比,来具体说说二者的异同。既然要做对比,那么首先要定义对比标准,我们可以这么考虑:你应该有这样的体会,同一个功能,不同的人实现的代码会不一样(比如数据结构、代码逻辑),所以过于细节的比较,尤其是偏系统实现方面的比较,意义不大(比如比较跟随者是否转发写请求到领导者,不仅意义不大,而且这是ZAB协议和Raft算法都没有约定的,是集群系统需要考虑的)。我们可以从核心原理上做对比。
- 1.领导者选举:ZAB协议采用的是"见贤思齐、相互推荐"的快速领导者选举(Fast Leader Election)算法,Raft算法采用的是"一张选票、先到先得"的自定义算法。在我看来,Raft算法的领导者选举需要通信的消息数更少、选举也更快
- 2.日志复制:Raft算法和ZAB协议都是以领导者的日志为准来实现日志一致,而且日志必须是连续的,也必须按照顺序提交
- 3.读操作和一致性:ZAB协议的设计目标是操作的顺序性,在ZooKeeper中默认实现的是最终一致性,读操作可以在任何节点上执行,而Raft算法的设计目标是强一致性(也就是线性一致性),所以Raft算法更灵活,它既可以提供强一致性,也可以提供最终一致性
- 4.写操作:Raft算法和ZAB协议的写操作都必须在领导者节点上处理
- 5.成员变更:Raft算法和ZAB协议都支持成员变更(其中ZAB协议是以动态配置的方式实现的),所以在节点变更时,你不需要重启及其,因为集群是一直运行的,服务也不会中断。
- 6.其他:相比ZAB协议,Raft算法的设计更为简洁,比如Raft算法没有引入类似ZAB协议的成员发现和数据同步阶段,而是当节点发起选举时递增任期编号,在选举结束后广播心跳,直接建立领导关系,然后向各节点同步日志,来实现数据副本的一致性。在我看来,ZAB协议的成员发现可以和领导者选举合到一起,类似Raft算法,在领导者选举结束后直接建立领导者关系,而不是再引入一个新的阶段;数据同步阶段是一个冗余的设计,可以去除。因为ZAB协议无须先实现数据副本的一致性,才可以处理写请求,而且这个设计是没有额外的意义和价值的。
- 7.另外,ZAB协议与ZooKeeper强耦合,无法在实际系统中独立使用;而Raft算法的实现(比如Hashicorp Raft算法)是可以独立使用的,编程友好
思维拓展
- 1.在ZAB协议中,主节点是基于TCP协议来广播消息的,且保证了消息接收的顺序性。那么你不妨想想,如果ZAB采用的是UDP协议,能保证消息接收的顺序性吗?为什么呢?
答案:ZAB(ZooKeeper Atomic Broadcast)协议是ZooKeeper分布式协调服务中用于实现分布式系统间一致性的一种协议。在ZAB协议中,主节点(Leader)负责将消息广播给所有从节点(Followers),确实保证了消息接收的顺序性,这是通过TCP协议的连接性和确认机制来实现的。
如果ZAB协议采用UDP协议来广播消息,那么消息接收的顺序性将无法得到保证。这是因为UDP(用户数据报协议)是一种无连接的协议,它不保证数据保的顺序、可靠传输或者数据的完整性。在UDP中,数据包(datagrams)可能会丢失、重复或乱序到达。这些特性使得UDP在高速传输但可以容忍一定数据丢失的应用场景中非常有用,比如视频流或在线游戏。
在分布式系统中消息的顺序性是非常重要的,因为它涉及到系统的一致性和状态同步。如果消息顺序无法保证,可能会导致系统状态的不一致,从而影响整个分布式系统的正确性。
因此,如果ZAB协议基于UDP来实现,就需要引入额外的机制来确保消息的顺序性,例如:
1.1 序列号:为每个消息分配一个序列号,接收方根据序列号重新排序消息
1.2 确认和重传:接收方对于收到的消息进行确认,发送方对于未确认的消息进行重传
1.3 选择性重传:只重传哪些确认丢失的消息
这些机制会增加协议的复杂性,并且可能会降低系统的性能。因此,在设计分布式协议时,通常会根据应用的需求来选择合适的传输协议,对于需要强一致性的系统,如ZooKeeper,使用TCP是更合适的选择
- 2.ZAB协议是通过快速领导者选举来选举出新的领导者的。那么选举中会出现选票被瓜分、选举失败的问题吗?为什么?
答案:因为存在任期编号大的优先、zxid较大节点优先、zxid相同,服务器id较大的节点优先 - 3.提案提交的大多数原则和领导者选举的大多数原则,确保了被复制到大多数节点的提案不再改变。那么你不妨思考和推演一下,这是为什么?
答案:"大多数"原则在提案提交和领导者选举中都起到了确保系统一致性、容错能力和稳定性的关键作用 - 4.ZooKeeper提供的是最终一致性,读操作可以在任何节点上执行。如果读操作访问的是备份节点,为什么无法保证每次都能读到最新的数据呢?
答案:有可能备份节点还没有收到领导者的提交响应,所以存在延迟
重点总结
- 1.ZAB协议是通过"一切以领导者为准"的强领导者模型和严格按照顺序处理、提交提案来实现操作的顺序性的
- 2.领导者选举的目标是选举出大多数节点中数据最完整的节点,也就是大多数节点中事务标识符值最大的节点。任期编号、事务标识符、集群ID的值的大小决定了哪个节点更适合作为领导者,按照顺序,值最大的节点更适合作为领导者
- 3.数据同步是通过以领导者的数据为准的方式来实现各节点数据副本的一致性的。需要注意的是,基于"大多数"的提交原则和选举原则能确保被复制到大多数节点并提交的提案不再改变
- 4.在ZooKeeper中,写请求只能在领导者节点上处理,读请求可以在所有节点上处理,即ZooKeeper实现的是最终一致性。而与领导者"失联"的跟随者(比如发生分区故障时)既不能处理写请求、也不能处理读请求。
你可能会问Paxos算法、Raft算法也都有领导者,难道实现一致性就必须要领导者吗?没有领导者就无法实现一致性吗?其实有些没有领导者的算法也能实现一致性