您的位置:首页 > 其它

ZooKeeper - O'Reilly Media ----Zookeeper Internals (1)

2015-08-19 15:20 429 查看

第九章 ZooKeeper Internals

本章相对于其它章有点不同,不会详细的解释如何来构建ZooKeeper应用,而是描述ZooKeeper内部是如何工作,将会在高层面来描述内部的协议,在提供高性能的同时的容错机制。本章帮助加对ZooKeeper工作的深层理解。内在的理解对计划部署ZooKeeper来说是非常重要的,因此本章也作为下一章的背景知识。

就如我们前面几章描述的,ZooKeeper运行在一些服务器的集合上,客户端连接到这些服务器来进行操作。但当客户端请求过来时,这些服务器都具体进行了什么操作?在第二章我们提到过我们会选举一个服务器来作为leader来和其他服务器区别开来。而其它的服务器后跟随这个首领,因此称呼为跟随者followers.首领是处理所有改变ZOOKEEPER系统的请求的中心点。首领也表现为一个序列器,把对ZOOKEEPER更新状态的操作进行排序。跟随者会接受和表决首领提出的更新请求来保证状态的更新防止崩溃。

首领和跟随者是即使是崩溃情况保证状态跟新顺序的核心实体。另外还有第三类的服务器叫做观察者observer。观察者不会参与带什么请求被接受的决定进程中,他们仅仅是了解已经做出了什么决定。观察者是为了可测量行的原因添加的。

本章中,我们会描述用来实现ZOOKEEPER集合,服务器和客户端内部机制的协议。我们从一些在本章中用来关注客户端请求和事物的通用概念开始讨论。

代码引用

因为本章是关于内部机制的,我们任务如果提供代码的引用会比较有趣,因此你可以将本章的描述和源代码进行匹配,会在何时的地方提供类和方法的指引。

请求,事务和标识

ZOOKEEPER服务器会在本地处理读请求(如exists,getData和getChildren)。当服务器接受到一个客户端过来的读请求,如getData,会读取本身的状态并返回给服务端。因为是在服务器本地处理请求,因此ZOOKEEPER在主要处理读请求的工作负载下是非常快的。我们可以添加更多的服务器到ZOOKEEPER集合中来处理更多的读请求,以此来增大总的吞吐能力。

需要改变ZOOKEEPER状态的客户端请求(如create,delete和setData)会被传递到首领。首领会执行这个请求,并且生成一个我们称为事物的状态更新行为。相对于请求表示了客户端发起的操作的方式,事务包含了对应于请求操作的改变ZOOKEEPER状态的步骤。可能最直观的解释方式是提供一个简单,非ZOOKEEPER操作。比如操作是inc(i),这个操作会对变量i的值加1.一个可能的请求就是inc(i).如果i的值是10,那增加之后就是11.这里用请求和事务来描述的话,请求就是inc(i),事物就是I,11(i的值设置为11)。

我们再来看一个ZOOKEEPER的例子。假设一个客户端提交了一个/z Zookeeper节点上的setData请求。SetData应该改变节点上的数据并且提高版本数字。以此,这个请求对应的事务就包含两个重要的域:节点的新值和新的版本数字。当应用一个事务是,服务器会简单的替换事务中提供的/z的数据和版本数字,而不是进行增加。

一个事务是作为一个单元来对待的,也就是说它包含的所有改变必须是原子性的。在setData这个例子中,如果只是改变了值而没有同时改变相应的版本将会产生问题。因此,当一个ZOOKEEPER集合应用事务是,它必须保证所有的改变是事务性的,不会出现其它事物的干扰。这里并没有像传统关系数据库那样的回滚机制。相反,ZOOKEEPER会确保事务的操作不会在不同事务之间产生干扰。在很长一段时间,使用一个服务器上单线程的设计来应用事务。一个单线程会保证事务是顺序应用而不会相互干扰。最近,ZOOKEEPER已经添加了多闲扯个的支持来加快事务的应用速度。

事务同时也是一个幂等操作,也就是说我们可以对一个事务应用两次但会得到同样的结果。只要我们每一次都按照同样的顺序来进行,我们可以对不同的事务应用的多次后还能得到同样的结果。这个幂等的特性会在恢复中使用。

当首领生成一个事务,它会给事务分配一个标识符,ZOOKEEPER TRANSACTION ID(ZXID)。 Zxids可以确认事务,也就可以按照首领建立的顺序来作用到服务器的状态上。当服务器选出了一个新的首领时同样会改变zxids,因此也可以确认正常的服务器会接收到多个事务并同步他们的状态。

Zxid是一个64位的整数,包括一个epoch和一个counter.每一个部分是32位。当我们讨论用来向服务器广播状态改变的协议ZAB时,就可以理解两个部分的内容了。

首领选举

首领就是一个由服务器集合选择出来并能持续得到集合支持的服务器。首领的目的是对客户端改变ZOOKEEPER状态的请求:create,setData和delet进行排序。首领将每一个请求转变为一个事务(见上章),并推送到集合的跟随者,跟随者接受并按照首领决定的顺序来应用这些事务。

要成为一个首领,一个服务器必须得到法定数量的服务器的支持。就像我们第二章所说的,法定数量必须交叉以避免出现成为分离大脑的问题,两个不同的服务器子集分别独立的处理请求。这种情况会造成系统的不一致状态,客户端会由于连接到不同的服务器而获得不同的结果。我们在ZOOKEEPER QUORUMS提供了一个相关的例子。

选举和支持首领的组必须至少贯穿一个服务器进程,我们使用法定人数(quorum)术语来表示一个处理进程的集合。法定人数是相互交叉的。(注:也就是说不会出现相互分离的两个不同的法定人数—分离大脑)。

处理流程

对一个流程来说是必须存在一个法定人数的服务器的,ZOOKEEPER在许多服务器已经失败造成无法形成一个法定人数的情况下是无法进行处理流程的。如果服务器奔溃后最终重启是可以的,但是对于一个流程的处理,法定人数是必须最终启动。我们会在下章讨论重新配置集合放松这个限制。重新配置可以改变重新改变法定人数.

每一个服务器启动的时候都处于LOOKING状态,其必须要么推选出一个首领或找到一个现存的首领。如果现在存在一个首领,其它的服务器会通知新的服务器哪一个服务器是首领。此时新的服务器会连接到首领并确保自身的状态和首领的状态是一致的。

如果一个服务器的集合中所有的服务器都处于LOOKING状态,它们必须相互通讯来选举出一个首领。他们交换信息聚合来找到一个共同的决定来决定首领。赢得选举的服务器会进入LEADING状态,而集合中的其他服务器会进入跟随者状态。

首领选举信息被称呼为首领选举通知(leaderelection notifications),或者简单的称呼为通知(notifications)。选举的协议也是非常简单。当一个服务器进入LOOKING状态,他会发送一批通知报文,每一个服务器一条。信息中包含当前的选票,包括服务器的标识sid和最近一个执行的事务的zxid。比如,(1,5)就是一个sid为1的服务器发送的报文,其最新的zxid是5.(处于首领选举的目的,zxid是一个简单的数字,但在一些其他协议中会表现为想epoch和counter)。

当收到一个选票是,服务器会改变按照下面的规则来改变自己的选票并重新发送新的选票:

1. 用voteId和voteZxid来表示收到选票的发送服务器的sid和zxid。用myZxid和mySid来表示服务器本身的zxid和sid。

2. 如果voteZxid > myZxid) 或者(voteZxid =myAzid 并且voteId > mySid)。使用接受到的选票最为自己最新的选票并重新发送出去。

3. 否则将保持自己的选票不变。

简单的说,最新的服务器会赢得选举,因为该服务器有最近的zxid。接下来我们会看到这样会简化当一个首领宕机后重启一个法定人数的过程。如果多个服务器都有最近的zxid,而sid最大的服务器将当选。

当一个服务器接收到达到法定人数的服务器的相同选票是,该服务器会认为首领已经选择出来了,如果首领就是服务器本身,则会开始执行首领的角色,否则,该服务器会成为一个跟随者并尝试去连接当选的首领。注意到这里并不能保证跟随者会连接到当选的首领,因为当选的首领是可能宕机。当连接建立起来后,跟随者会和首领同步他们的状态,并且只有当同步完成之后,该跟随者才开始处理请求。

寻找首领

ZooKeeper中实现选举的Java类是Quorumpeer。它的run方法实现了服务器的主要循环。当处于LOOKING状态,会执行lookForLeader来选举一个首领,这个方法基本上就是实现了我们上面描述的协议。在这个方法返回之前,会设置服务器的状态要么是LEADING,要么是FOLLOWING。OBSERVING也是一个选择,下面我们在讨论。如果服务器是首领,它会创建一个新的Leader并云习惯。如果是跟随者会创建一个新的Fellower并执行。

让我们来看一个这个协议执行的例子。图9-1显示了3个服务器,每一个都由一个包含服务标识和自身最新zxid的选票来开始。每一个服务器都会接受到另外两台服务器的选票,在第一轮之后,服务器s2和s3都将他们的选票改为(1,6),然后重新发出新的通知。再接收到这些新的通知后,所有服务器都有了一个法定人数的相同的选票,最终选择了服务器s1来作为首领。

并不是所有的操作都想图9-1那边很好的执行的,在图9-2种,我们展示了服务器s2做了一个较早的决定,选择了一个和服务器s1,s2决定不同的首领。这个情况是由于网络中信息从s1到s2时较长的延迟造成的,(s1到s2的信息会提示s1有更高的zxid),同时,s2选择了s3,结果就是s1和s3形成了一个法定人数,而移除了s2.

S2选择了一个不同的首领并不会造成Zookeeper的服务不正确,是因为s3不会以首领的身份来相应s2。最终,s2会在尝试连接自己选择的首领s3时出现超时并重新尝试,重新尝试,也意味着在这段时间s2无法作为一个可用的服务器来处理客户端请求,这本身就是不正确的。

这个例子中一个简单的结论是如果s2等待再长一点时间做出首领的决定,就会做出正确的决定。图9-3展示的就是这样的情况。但是很那知道该等待多长时间。当前的实现是FastLeaderElection,首领选举的默认实现,使用了一个固定的200ms作为等待时间(常量finalizeWait)。这个值比当代的数据中心期望的延迟长(一般都是小于1毫秒到几毫秒之间),但是在恢复时候也没有出现本质的区别。在这种情况下这个延迟(或其他选择的延迟)会不够长,一个或多个服务器会很快的选择出一个没有足够跟随者的首领,这些服务器不定不重新进行首领选举。错误的选择一个首领可能会造成整个恢复时间变长,因为这些服务器会进行不必要的连接和同步,并需要发送更多的信息来选择其他的首领。

FastLeaderElection的快指什么

如果你希望弄明白这点,我们把 当前默认首领选举算法称呼为fast是有历史原因的。最初的首领选举算法实现是基于拉模型的,而一个服务器用来拉选票的间隔是1秒,这个方法增加了恢复的延迟。当我们使用当前的实现是,我们是可以更快的选择一个首领的。

如果需要实现一个新的首领选举算法,我们需要实现quoram包中的Election接口。为了是的用户可以在可用的首领选举算法中进行选择,代码使用了简单的整数标识来区别(见Quorumpeer.createElectionAlgorithm())。当前另外的两个可用实现是LeaderElection和AuthFastLeaderElection,但 他们在3.4.0版本中都被推荐不适用了,可能在将来的版本中就看不到了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: