@TOC
2 Zookeeper的实现
2.1 主要组成
Zookeeper服务主要由几大部分组成,请求处理器,原子广播,可复制的数据库等组件组成主要的请求处理如下图所示
可复制的数据库
可复制的数据库是一个包含整个数据树的内存数据库,更新被记录到磁盘上以保证可恢复性,写操作在更新内存数据库之前会先将数据序列化到磁盘来做持久化。
读请求处理
客户端只连接到一个服务器来提交请求。读取请求由每个服务器数据库本地副本提供服务。
写请求处理
作为协议的一部分,所有来自客户端的写请求都被转发到一个名为leader的服务器上,Zookeeper上的其他服务器角色,被称为Follower或者Observer,Follower参与投票并与Leader脸色同步,Observer是不参与投票的。
自定义的原子消息协议
2.2 Zookeeper性能?
Zookeeper被设计成高性能的,在读的数量超过写的应用程序中性能会更好,写操作还涉及到其他操作比如同步所有服务器的状态,而读操作则可以直接读取内存中数据。
Zoookeeper的吞吐量受写操作的影响,随着读写比的变化而变化,接下来我们可以看下Zookeeper release3.2运行在双2GHZ Xeon和两个SATA 15K RPM驱动器上的吞吐量图写请求是1 k写读请求是1k读 Servers表示Zookeeper服务器数量
从一开始的读写比例相同情况下zookeeper三台服务器可以处理2000多个请求,请求量低的时候增加服务器反而降低了每秒请求数量,可以看到横轴随着读比例的变多每秒处理的请求数量越来越多,看服务器数量请求数量比较低的情况下服务器数量越少性能越高,随着请求数量变大在某个点上服务器数量越多性能越来越好,也就是说我们请求量低的时候不早配置太多服务器。
2.3 Zookeeper Session
Zookeeper服务器启动后可以监听客户端的连接, Zookeeper客户端建立通过创建服务句柄来建立与Zookeeper的会话,句柄创建后状态切换至CONNECTING状态,开始尝试连接到组成zookeeper服务的其中一个服务器,
然后切换到CONNECTED状态,如果发生了不可恢复的错误,例如会话过期或身份验证失败,或者应用程序显式的关闭了句柄,则将切换到CLOSED状态,如果连接成功则切换到CONNECTED状态
当客户端(会话)从ZK服务集群中分区时,它将开始搜索在会话创建期间指定的服务器列表。最终,当客户端和至少一个服务器之间的连接重新建立时,会话将再次转换到“已连接”状态(如果在会话超时值内重新连接),或者转换到“过期”状态(如果在会话超时后重新连接)。不建议在断开连接时创建一个新的会话对象(一个新的zookeeper .class或c绑定中的zookeeper句柄)。ZK客户端库将为您处理重新连接。特别地,我们在客户端库中内置了启发式来处理“群体效应”等事情。仅在收到会话过期通知时才创建新会话(强制)。
Session过期由ZooKeeper集群本身管理,而不是由客户端管理。当ZK客户机与集群建立会话时,它会提供上面详细描述的“超时”值。集群使用此值来确定客户端会话何时过期。当集群在指定的会话超时时间内没有收到来自客户机的消息(即没有心跳)时,就会发生失效。在会话到期时,集群将删除该会话拥有的任何/所有临时节点,并立即通知任何/所有连接的客户端(任何监视这些znode的人)。此时,过期会话的客户端仍然与集群断开连接,它将不会收到会话过期的通知,直到/除非它能够重新建立到集群的连接。客户端将保持断开连接状态,直到TCP连接与集群重新建立,此时过期会话的观察者将收到“会话过期”通知。
会话通过客户端发送的请求保持活跃。如果会话空闲了一段时间,将使会话超时,客户端将发送一个PING请求以使会话保持活跃。这个PING请求不仅可以让ZooKeeper服务器知道客户端仍然处于活动状态,还可以让客户端验证自己与ZooKeeper服务器的连接是否仍然处于活动状态。PING的时间足够保守,以确保有合理的时间检测死连接并重新连接到新的服务器。
客户端通过逗号分隔的host:port列表字符串来连接zookeeper,每条信息对应一个ZooKeeper服务器。该函数调用一个概率负载平衡算法,该算法可能导致客户机断开与当前主机的连接,目标是在新列表中实现每个服务器的预期统一连接数。如果客户端连接到的当前主机不在新列表中,此调用将始终导致连接被删除。否则,决策取决于服务器的数量是增加了还是减少了,以及减少了多少
2.4 原子广播
一致性保证
1.1 原子广播说明
保证
ZooKeeper使用的消息传递系统提供的特定保证如下:
可靠的传递:如果消息m是由一台服务器传递的,则该消息m最终将由所有服务器传递。
总顺序:如果一台服务器a先b于一条消息传递一条消息,则所有服务器a将先传递一条消息b。
因果顺序:如果在发件人发送b消息之后a发送b消息,则a必须在消息之前对其进行排序b。如果发送方在发送c后发送b,则c必须在之后订购b。
依赖于TCP的以下属性
有序传递:m仅按照发送之前发送的所有消息的传递顺序,发送数据和发送消息的顺序相同m。(这的必然结果是,如果消息m丢失,则此后的所有消息m都将丢失。)
关闭后无消息:关闭FIFO通道后,将不会再收到任何消息。
名词解释:
Packet(数据包):通过FIFO通道发送的字节序列。
Proposal(提案):协议单位。通过与法定的ZooKeeper服务器交换数据包来达成协议。大多数投标都包含消息,但是NEW_LEADER投标是不包含在消息中的投标的示例。
Message(消息):要自动广播到所有ZooKeeper服务器的字节序列。传递到提案中并已达成共识的消息。
ZooKeeper保证消息的总顺序,也保证提案的总顺序。ZooKeeper使用ZooKeeper事务ID(zxid)公开总排序
提出提案时,所有提案都将盖上zxid,并准确反映总排序。提案将发送到所有ZooKeeper服务器,并在法定人数确认提案后提交。如果投标中包含一条消息,则在提交投标时将传递该消息。确认表示服务器已将建议记录到持久性存储中。我们的法定人数要求任何一对法定人数必须至少有一个共同的服务器。我们通过要求所有仲裁均具有大小(n / 2 + 1),其中n是组成ZooKeeper服务的服务器数量。
zxid有两个部分:时期和计数器。在我们的实现中,zxid是一个64位数字。我们将高阶32位用于纪元,将低阶32位用于计数器。由于zxid由两部分组成,因此zxid既可以表示为数字,也可以表示为整数对(epoch,count)。纪元数代表领导层的变化。每次新领导人上台时,都会有自己的时代号。我们有一个简单的算法为提案分配唯一的zxid:组长只需增加zxid即可为每个提案获得唯一的zxid。领导者激活将确保只有一个领导者使用给定的纪元,因此我们的简单算法可确保每个提案都将具有唯一的ID。
ZooKeeper消息传递包括两个阶段:
领导者激活:在这个阶段,领导者建立了正确的系统状态,并准备开始提出建议。
主动消息传递:在此阶段,领导者接受消息以提议和协调消息传递。
选举领导者后,会将一台服务器指定为领导者,并开始等待关注者进行连接。其余服务器将尝试连接到领导者。领导者将通过发送追随者丢失的任何提案来与追随者同步,或者如果追随者缺少太多提议,则会将状态的完整快照发送给追随者。
在一个极端的情况下,具有领导者看不到的提案的追随者到达。提案按顺序显示,因此的提案的zxid高于领导者看到的zxid。跟随者一定是在领导人选举后到达的,否则,跟随者将被视为领导者,因为它具有较高的zxid。由于必须通过一定数量的服务器来查看已提交的提案,并且看不到选举组长的服务器法定数量,因此尚未提交的建议,因此可以将其丢弃。当跟随者连接到领导者时,领导者将告诉跟随者放弃。
激活消息:
领导者激活完成了所有繁重的工作。领导加冕后,他就可以开始提出建议了。只要他仍然是领导者,就不会有其他领导者出现,因为没有其他领导者能够获得一定数量的追随者。如果确实出现了新的领导者,则意味着该领导者失去了法定人数,并且该新领导者将清除激活她的领导者过程中留下的任何混乱情况。
ZooKeeper消息传递的操作类似于经典的两阶段提交。
todo 这里有个图
所有通信通道均为FIFO,因此所有操作均按顺序进行。具体而言,遵守以下操作限制:
领导者使用相同的顺序将提案发送给所有关注者。此外,该顺序遵循已接收请求的顺序。因为我们使用FIFO通道,所以这意味着关注者还会按顺序接收投标。
关注者按照收到消息的顺序处理消息。这意味着,由于具有FIFO通道,消息将按顺序被ACK,并且领导者将按顺序从跟随者接收ACK。这也意味着,如果m已将消息写入非易失性存储,则之前提案的所有消息都已写入非易失性存储。
一旦有一定数量的关注者确认了消息,领导者就会向所有关注者发出COMMIT。由于消息是按顺序确认的,因此领导者将按照跟随者的顺序发送COMMIT。
COMMIT按顺序处理。提交提案后,关注者会发送提案消息。
一致性保证
ZooKeeper的一致性保证介于顺序一致性和线性化之间。在本节中,我们将说明ZooKeeper提供的确切一致性保证。
ZooKeeper中的写操作是可线性化的。换句话说,write在客户端发出请求和接收到相应响应之间的某个时间点,每个元素似乎都自动生效。这意味着ZooKeeper中所有客户端执行的写入可以按照尊重这些写入的实时顺序的方式进行完全排序。但是,仅声明写操作可线性化是没有意义的,除非我们也谈到读操作。
ZooKeeper中的读取操作无法线性化,因为它们可以返回可能过时的数据。这是因为readZooKeeper中的a不是法定操作,并且服务器将立即响应执行a的客户端read。ZooKeeper之所以这样做,是因为在读取用例时,它会优先考虑性能而不是一致性。但是,ZooKeeper中的读取顺序上是一致的,因为read操作似乎会以某种顺序顺序生效,从而进一步尊重每个客户端操作的顺序。解决此问题的常见模式是在发行sync之前发行read。这也并不能严格保证了最新的数据,因为syncIS目前还没有达到法定人数的操作。为了说明这一点,请考虑一个场景,其中两个服务器同时认为它们是领导者,如果TCP连接超时小于,则可能会发生这种情况syncLimit * tickTime。请注意,这在实践中不太可能发生,但在讨论严格的理论保证时仍应牢记。在这种情况下,sync“领导者”可能会将过时的数据提供给,从而使后面的内容read也过时。如果write在a之前执行了实际的定额运算(例如a ),则可以提供更好的线性化保证read。
总体而言,ZooKeeper的一致性保证是通过有序的顺序一致性或OSC(U)确切地说是精确的概念来捕获的,该概念介于顺序一致性和线性化之间。
法定人数
原子广播和领导者选举使用法定概念来保证系统的一致视图。默认情况下,ZooKeeper使用多数仲裁,这意味着在这些协议之一中进行的每次投票都需要多数投票。一个示例是确认领导者提议:领导者只有在收到来自法定服务器数量的确认后才能进行提交。
如果我们从多数使用中提取出我们真正需要的属性,那么我们只需要保证用于通过投票(例如,确认领导提议)来验证操作的过程组在至少一台服务器中成对相交。使用多数保证了这种性质。但是,还有其他一些不同于多数的构造仲裁的方法。例如,我们可以为服务器的投票分配权重,并说某些服务器的投票更为重要。要获得法定人数,我们需要获得足够的选票,以使所有选票的权重之和大于所有权重之和的一半。
一种使用权重并且在广域部署(同一位置)中有用的不同构造是一种分层构造。通过这种构造,我们将服务器分成不相交的组,并为进程分配权重。为了形成法定人数,我们必须从大多数组G中获得足够的服务器,这样对于G中的每个组g,来自g的票数之和要大于g权重之和的一半。有趣的是,这种构造可以实现较小的仲裁。例如,如果我们有9台服务器,则将它们分成3组,并为每台服务器分配权重1,那么我们就可以形成大小为4的仲裁。请注意,进程的两个子集构成了每个进程的多数。大多数组中的每个服务器都必须具有非空交集。
借助ZooKeeper,我们为用户提供了配置服务器以使用多数仲裁,权重或组层次结构的能力。
常见问题处理:
我应该如何处理CONNECTION_LOSS错误?
我应该如何处理SESSION_EXPIRED?
有没有一种简便的方法可以使会话过期以进行测试?
为什么NodeChildrenChanged和NodeDataChanged监视事件不返回有关更改的更多信息?
升级ZooKeeper的选项过程是什么?
我如何确定ZooKeeper集成体(集群)的大小?
我可以在负载均衡器后面运行集成集群吗?
群集关闭时ZK会话会发生什么情况?
https://cwiki.apache.org/confluence/display/ZOOKEEPER/FAQ
技术咨询与支持,可以扫描微信公众号进行回复咨询
Zookeeper16, Zookeeper源码解析16