当前位置: 首页 > news >正文

营销类网站设计 要点抖音小程序

营销类网站设计 要点,抖音小程序,中国建设门户网登录入口,动漫制作专业调研目的文章目录 1、系统模型1.1、数据模型1.2、节点特性1.2.1、节点类型 1.3、版本——保证分布式数据原子性操作1.4、 Watcher——数据变更的通知1.5、ACL——保障数据的安全1.5.1、权限模式#xff1a;Scheme1.5.2、授权对象#xff1a;ID1.5.3、权限扩展体系 2、序列化与协议2.1… 文章目录 1、系统模型1.1、数据模型1.2、节点特性1.2.1、节点类型 1.3、版本——保证分布式数据原子性操作1.4、 Watcher——数据变更的通知1.5、ACL——保障数据的安全1.5.1、权限模式Scheme1.5.2、授权对象ID1.5.3、权限扩展体系 2、序列化与协议2.1、Jute介绍2.2、使用Jute进行序列化 3、客户端3.1、 一次会话的创建过程 4、会话4.1、会话状态4.2、会话创建4.2.1、Session 5、服务器启动5.1、单机版服务器启动5.1.1、预启动 5.2、集群版服务器启动5.2.1、预启动5.2.2、初始化5.2.3、Leader选举5.2.4、Leader和Follower启动期交互过程 6、Leader选举6.1、Leader选举概述6.1.1、服务器启动时期的Leader选举1、每个Server会发出一个投票2、 接收来自各个服务器的投票3、 处理投票4、 统计投票5、 改变服务器状态 6.1.2、服务器运行期间的Leader选举 6.2、Leader选举的算法分析6.1.1、术语解释6.1.2、算法分析 6.3、Leader选举的实现细节6.3.1、服务器状态6.3.2、投票数据结构6.3.3、QuorumCnxManager网络I/O6.3.4、消息队列6.3.5、建立连接6.3.6、消息接收与发送6.3.7、FastLeaderElection选举算法的核心部分 7、各服务器角色介绍7.1、Leader7.1.1、请求处理链 7.2、Follower7.3、Observer7.4、 集群间消息通信 从系统模型、序列化与协议、客户端工作原理、会话、服务端工作原理以及数据存储等方面来向揭示ZooKeeper的技术内幕更深入地了解ZooKeeper这一分布式协调框架。 1、系统模型 将从数据模型、节点特性、版本、Watcher和ACL五方面来讲述ZooKeeper的系统模型。 1.1、数据模型 ZooKeeper的视图结构和标准的Unix文件系统非常类似但没有引入传统文件系统中目录和文件等相关概念而是使用了其特有的“数据节点”概念我们称之为ZNode。ZNode是ZooKeeper中数据的最小单元每个ZNode上都可以保存数据同时还可以挂载子节点因此构成了一个层次化的命名空间我们称之为树。 树 首先我们来看下图所示的ZooKeeper数据节点示意图从而对ZooKeeper上的数据节点有一个大体上的认识。在ZooKeeper中每一个数据节点都被称为一个ZNode所有ZNode按层次化结构进行组织形成一棵树。ZNode的节点路径标识方式和Unix文件系统路径非常相似都是由一系列使用斜杠(/)进行分割的路径表示开发人员可以向这个节点中写入数据也可以在节点下面创建子节点。 事务ID 在《事务处理概念与技术》一书中提到事务是对物理和抽象的应用状态上的操作集合。在现在的计算机科学中狭义上的事务通常指的是数据库事务一般包含了一系列对数据库有序的读写操作这些数据库事务具有所谓的ACID特性即原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。 在ZooKeeper中事务是指能够改变ZooKeeper服务器状态的操作我们也称之为事务操作或更新操作一般包括数据节点创建与删除、数据节点内容更新和客户端会话创建与失效等操作。对于每一个事务请求ZooKeeper都会为其分配一个全局唯一的事务ID用ZXID来表示通常是一个64位的数字。每一个ZXID对应一次更新操作从这些ZXID中可以间接地识别出ZooKeeper处理这些更新操作请求的全局顺序。 1.2、节点特性 ZooKeeper的命名空间是由一系列数据节点组成的在本节中将对数据节点做详细讲解。 1.2.1、节点类型 在ZooKeeper中每个数据节点都是有生命周期的其生命周期的长短取决于数据节点的节点类型。在ZooKeeper中节点类型可以分为持久节点(PERSISTENT)、临时节点(EPHEMERAL)和顺序节点(SEQUENTIAL)三大类具体在节点创建过程中通过组合使用可以生成以下四种组合型节点类型 持久节点(PERSISTENT) 持久节点是ZooKeeper中最常见的一种节点类型。所谓持久节点是指该数据节点被创建后就会一直存在于ZooKeeper服务器上直到有删除操作来主动清除这个节点。 持久顺序节点(PERSISTENT_SEQUENTIAL) 持久顺序节点的基本特性和持久节点是一致的额外的特性表现在顺序性上。在ZooKeeper中每个父节点都会为它的第一级子节点维护一份顺序用于记录下每个子节点创建的先后顺序。基于这个顺序特性在创建子节点的时候可以设置这个标记那么在创建节点过程中ZooKeeper会自动为给定节点名加上一个数字后缀作为一个新的、完整的节点名。另外需要注意的是这个数字后缀的上限是整型的最大值。 临时节点(EPHEMERAL) 和持久节点不同的是临时节点的生命周期和客户端的会话绑定在一起也就是说如果客户端会话失效那么这个节点就会被自动清理掉。注意这里提到的是客户端会话失效而非TCP连接断开。另外ZooKeeper规定了不能基于临时节点来创建子节点即临时节点只能作为叶子节点。 临时顺序节点(EPHEMERAL_SEQUENTIAL) 临时顺序节点的基本特性和临时节点也是一致的同样是在临时节点的基础上添加了顺序的特性。 状态信息 可以针对ZooKeeper上的数据节点进行数据的写入和子节点的创建。事实上每个数据节点除了存储了数据内容之外还存储了数据节点本身的一些状态信息。 1.3、版本——保证分布式数据原子性操作 ZooKeeper中为数据节点引入了版本的概念每个数据节点都具有三种类型的版本信息对数据节点的任何更新操作都会引起版本号的变化下表中对这三类版本信息分别进行了说明。 ZooKeeper中的版本概念和传统意义上的软件版本有很大的区别它表示的是对数据节点的数据内容、子节点列表或是节点ACL信息的修改次数我们以其中的version这种版本类型为例来说明。在一个数据节点/zk-book被创建完毕之后节点的version值是0表示的含义是“当前节点自从创建之后被更新过0次”。如果现在对该节点的数据内容进行更新操作那么随后version的值就会变成1。同时需要注意的是在上文中提到的关于version的说明其表示的是对数据节点数据内容的变更次数强调的是变更次数因此即使前后两次变更并没有使得数据内容的值发生变化version的值依然会变更。 在上面的介绍中我们基本了解了ZooKeeper中的版本概念。那么版本究竟用来干嘛呢在讲解版本的作用之前我们首先来看下分布式领域中最常见的一个概念——锁。 一个多线程应用尤其是分布式系统在运行过程中往往需要保证数据访问的排他性。 悲观锁又被称作悲观并发控制(Pessimistic Concurrency ControlPCC)是数据库中一种非常典型且非常严格的并发控制策略。悲观锁具有强烈的独占和排他特性能够有效地避免不同事务对同一数据并发更新而造成的数据一致性问题。在悲观锁的实现原理中如果一个事务假定事务A正在对数据进行处理那么在整个处理过程中都会将数据处于锁定状态在这期间其他事务将无法对这个数据进行更新操作直到事务A完成对该数据的处理释放了对应的锁之后其他事务才能够重新竞争来对数据进行更新操作。也就是说对于一份独立的数据系统只分配了一把唯一的钥匙谁获得了这把钥匙谁就有权力更新这份数据。一般我们认为在实际生产应用中悲观锁策略适合解决那些对于数据更新竞争十分激烈的场景——在这类场景中通常采用简单粗暴的悲观锁机制来解决并发控制问题。 乐观锁又被称作乐观并发控制(Optimistic Concurrency ControlOCC)也是一种常见的并发控制策略。相对于悲观锁而言乐观锁机制显得更加宽松与友好。从上面对悲观锁的讲解中我们可以看到悲观锁假定不同事务之间的处理一定会出现互相干扰从而需要在一个事务从头到尾的过程中都对数据进行加锁处理。而乐观锁则正好相反它假定多个事务在处理过程中不会彼此影响因此在事务处理的绝大部分时间里不需要进行加锁处理。当然既然有并发就一定会存在数据更新冲突的可能。在乐观锁机制中在更新请求提交之前每个事务都会首先检查当前事务读取数据后是否有其他事务对该数据进行了修改。如果其他事务有更新的话那么正在提交的事务就需要回滚。乐观锁通常适合使用在数据并发竞争不大、事务冲突较少的应用场景中。 从上面的讲解中我们其实可以把一个乐观锁控制的事务分成如下三个阶段数据读取、写入校验和数据写入其中写入校验阶段是整个乐观锁控制的关键所在。在写入校验阶段事务会检查数据在读取阶段后是否有其他事务对数据进行过更新以确保数据更新的一致性。那么如何来进行写入校验呢我们首先可以来看下JDK中最典型的乐观锁实现——CAS。简单地讲就是“对于值V每次更新前都会比对其值是否是预期值A只有符合预期才会将V原子化地更新到新值B”其中是否符合预期便是乐观锁中的“写入校验”阶段。 好了现在我们再回过头来看看ZooKeeper中版本的作用。事实上在ZooKeeper中version属性正是用来实现乐观锁机制中的“写入校验”的。在ZooKeeper服务器的PrepRequestProcessor处理器类中在处理每一个数据更新(setDataRequest)请求时会进行如下清单所示的版本检查。 从上面的执行逻辑中我们可以看出在进行一次setDataRequest请求处理时首先进行了版本检查ZooKeeper会从setDataRequest请求中获取到当前请求的版本version同时从数据记录nodeRecord中获取到当前服务器上该数据的最新版本currentVersion。如果version为“-1”那么说明客户端并不要求使用乐观锁可以忽略版本比对如果version不是“-1”那么就比对version和currentVersion如果两个版本不匹配那么将会抛出BadVersionException异常。 1.4、 Watcher——数据变更的通知 ZooKeeper提供了分布式数据的发布/订阅功能。一个典型的发布/订阅模型系统定义了一种一对多的订阅关系能够让多个订阅者同时监听某一个主题对象当这个主题对象自身状态变化时会通知所有订阅者使它们能够做出相应的处理。在ZooKeeper中引入了Watcher机制来实现这种分布式的通知功能。ZooKeeper允许客户端向服务端注册一个Watcher监听当服务端的一些指定事件触发了这个Watcher那么就会向指定客户端发送一个事件通知来实现分布式的通知功能。整个Watcher注册与通知过程如下图所示 从下图中我们可以看到ZooKeeper的Watcher机制主要包括客户端线程、客户端WatchManager和ZooKeeper服务器三部分。在具体工作流程上简单地讲客户端在向ZooKeeper服务器注册Watcher的同时会将Watcher对象存储在客户端的WatchManager中。当ZooKeeper服务器端触发Watcher事件后会向客户端发送通知客户端线程从WatchManager中取出对应的Watcher对象来执行回调逻辑。 Watcher接口 在ZooKeeper中接口类Watcher用于表示一个标准的事件处理器其定义了事件通知相关的逻辑包含KeeperState和EventType两个枚举类分别代表了通知状态和事件类型同时定义了事件的回调方法process(WatchedEvent event)。 Watcher事件 同一个事件类型在不同的通知状态中代表的含义有所不同下表列举了常见的通知状态和事件类型。 上表中列举了ZooKeeper中最常见的几个通知状态和事件类型。其中针对NodeDataChanged事件此处说的变更包括节点的数据内容和数据的版本号dataVersion。因此即使使用相同的数据内容来更新还是会触发这个事件通知因为对于ZooKeeper来说无论数据内容是否变更一旦有客户端调用了数据更新的接口且更新成功就会更新dataVersion值。 NodeChildrenChanged事件会在数据节点的子节点列表发生变更的时候被触发这里说的子节点列表变化特指子节点个数和组成情况的变更即新增子节点或删除子节点而子节点内容的变化是不会触发这个事件的。 对于AuthFailed这个事件需要注意的地方是它的触发条件并不是简简单单因为当前客户端会话没有权限而是授权失败。我们首先通过下面两个清单所示的两个例子来看看AuthFailed这个事件。 上面两个示例程序都创建了一个受到权限控制的数据节点然后使用了不同的权限Scheme进行权限检查。在第一个示例程序中使用了正确的权限Schemedigest而第二个示例程序中使用了错误的Schemedigest2。另外无论哪个程序都使用了错误的Authtaokeepererror因此在运行第一个程序的时候会抛出NoAuthException异常而第二个程序运行后抛出的是AuthFailedException异常同时会收到对应的Watcher事件通知(AuthFailedNone)。 回调方法process() process方法是Watcher接口中的一个回调方法当ZooKeeper向客户端发送一个Watcher事件通知时客户端就会对相应的process方法进行回调从而实现对事件的处理。process方法的定义如下 这个回调方法的定义非常简单我们重点看下方法的参数定义WatchedEvent。WatchedEvent包含了每一个事件的三个基本属性通知状态(keeperState)、事件类型(eventType)和节点路径(path)其数据结构如下图所示。ZooKeeper使用WatchedEvent对象来封装服务端事件并传递给Watcher从而方便回调方法process对服务端事件进行处理。 提到WatchedEvent不得不讲下WatcherEvent实体。笼统地讲两者表示的是同一个事物都是对一个服务端事件的封装。不同的是WatchedEvent是一个逻辑事件用于服务端和客户端程序执行过程中所需的逻辑对象而WatcherEvent因为实现了序列化接口因此可以用于网络传输其数据结构如下图所示 服务端在生成WatchedEvent事件之后会调用getWrapper方法将自己包装成一个可序列化的WatcherEvent事件以便通过网络传输到客户端。客户端在接收到服务端的这个事件对象后首先会将WatcherEvent事件还原成一个WatchedEvent事件并传递给process方法处理回调方法process根据入参就能够解析出完整的服务端事件了。 需要注意的一点是无论是WatchedEvent还是WatcherEvent其对ZooKeeper服务端事件的封装都是极其简单的。举个例子来说当/zk-book这个节点的数据发生变更时服务端会发送给客户端一个“ZNode数据内容变更”事件客户端只能够接收到如下信息 从上面展示的信息中我们可以看到客户端无法直接从该事件中获取到对应数据节点的原始数据内容以及变更后的新数据内容而是需要客户端再次主动去重新获取数据——这也是ZooKeeper Watcher机制的一个非常重要的特性。 工作机制 ZooKeeper的Watcher机制总的来说可以概括为以下三个过程客户端注册Watcher、服务端处理Watcher和客户端回调Watcher其内部各组件之间的关系如下图所示 客户端注册Watcher 在创建一个ZooKeeper客户端对象实例时可以向构造方法中传入一个默认的Watcher 这个Watcher将作为整个ZooKeeper会话期间的默认Watcher会一直被保存在客户端ZKWatchManager的defaultWatcher中。另外ZooKeeper客户端也可以通过getData、getChildren和exist三个接口来向ZooKeeper服务器注册Watcher无论使用哪种方式注册Watcher的工作原理都是一致的这里我们以getData这个接口为例来说明。getData接口用于获取指定节点的数据内容主要有两个方法 在这两个接口上都可以进行Watcher的注册第一个接口通过一个boolean参数来标识是否使用上文中提到的默认Watcher来进行注册具体的注册逻辑和第二个接口是一致的。 在向getData接口注册Watcher后客户端首先会对当前客户端请求request进行标记将其设置为“使用Watcher监听”同时会封装一个Watcher的注册信息WatchRegistration对象用于暂时保存数据节点的路径和Watcher的对应关系具体的逻辑代码如下 在ZooKeeper中Packet可以被看作一个最小的通信协议单元用于进行客户端与服务端之间的网络传输任何需要传输的对象都需要包装成一个Packet对象。因此在ClientCnxn中WatchRegistration又会被封装到Packet中去然后放入发送队列中等待客户端发送 随后ZooKeeper客户端就会向服务端发送这个请求同时等待请求的返回。完成请求发送后会由客户端SendThread线程的readResponse方法负责接收来自服务端的响应finishPacket方法会从Packet中取出对应的Watcher并注册到ZKWatchManager中去 从上面的内容中我们已经了解到客户端已经将Watcher暂时封装在了WatchRegistration对象中现在就需要从这个封装对象中再次提取出Watcher来 在register方法中客户端会将之前暂时保存的Watcher对象转交给ZKWatchManager并最终保存到dataWatches中去。ZKWatchManager.dataWatches是一个MapStringSetWatcher类型的数据结构用于将数据节点的路径和Watcher对象进行一一映射后管理起来。整个客户端Watcher的注册流程如下图所示 通过上面的讲解相信读者已经对客户端的Watcher注册流程有了一个大概的了解。但同时我们也可以发现极端情况下客户端每调用一次getData()接口就会注册上一个Watcher那么这些Watcher实体都会随着客户端请求被发送到服务端去吗 答案是否定的。如果客户端注册的所有Watcher都被传递到服务端的话那么服务端肯定会出现内存紧张或其他性能问题了幸运的是在ZooKeeper的设计中充分考虑到了这个问题。在上面的流程中我们提到把WatchRegistration封装到了Packet对象中去但事实上在底层实际的网络传输序列化过程中并没有将WatchRegistration对象完全地序列化到底层字节数组中去。为了证实这一点我们可以看下Packet内部的序列化过程 从上面的代码片段中我们可以看到在Packet.createBB()方法中ZooKeeper只会将requestHeader和request两个属性进行序列化也就是说尽管WatchRegistration被封装在了Packet中但是并没有被序列化到底层字节数组中去因此也就不会进行网络传输了。 服务端处理Watcher 上面主要讲解了客户端注册Watcher的过程并且已经了解了最终客户端并不会将Watcher对象真正传递到服务端。那么服务端究竟是如何完成客户端的Watcher注册又是如何来处理这个Watcher的呢本节将主要围绕这两个问题展开进行讲解。 ServerCnxn存储 我们首先来看下服务端接收Watcher并将其存储起来的过程如下图所示是ZooKeeper服务端处理Watcher的序列图。 从下图中我们可以看到服务端收到来自客户端的请求之后在FinalRequest Processor.processRequest()中会判断当前请求是否需要注册Watcher 从getData请求的处理逻辑中我们可以看到当getDataRequest.getWatch()为true的时候ZooKeeper就认为当前客户端请求需要进行Watcher注册于是就会将当前的ServerCnxn对象和数据节点路径传入getData方法中去。那么为什么要传入ServerCnxn呢ServerCnxn是一个ZooKeeper客户端和服务器之间的连接接口代表了一个客户端和服务器的连接。ServerCnxn接口的默认实现是NIOServerCnxn同时从3.4.0版本开始引入了基于Netty的实现NettyServerCnxn。无论采用哪种实现方式都实现了Watcher的process接口因此我们可以把ServerCnxn看作是一个Watcher对象。数据节点的节点路径和ServerCnxn最终会被存储在WatchManager的watchTable和watch2Paths中。 WatchManager是ZooKeeper服务端Watcher的管理者其内部管理的watchTable和watch2Paths两个存储结构分别从两个维度对Watcher进行存储。 watchTable是从数据节点路径的粒度来托管Watcher。watch2Paths是从Watcher的粒度来控制事件触发需要触发的数据节点。 同时WatchManager还负责Watcher事件的触发并移除那些已经被触发的Watcher。注意WatchManager只是一个统称在服务端DataTree中会托管两个WatchManager分别是dataWatches和childWatches分别对应数据变更Watcher和子节点变更Watcher。在本例中因为是getData接口因此最终会被存储在dataWatches中其数据结构如下图所示 Watcher触发 在上面的讲解中我们了解了对于标记了Watcher注册的请求ZooKeeper会将其对应的ServerCnxn存储到WatchManager中下面我们来看看服务端是如何触发Watcher的。在下表中我们提到NodeDataChanged事件的触发条件是“Watcher监听的对应数据节点的数据内容发生变更”其具体实现如下 在对指定节点进行数据更新后通过调用WatchManager的triggerWatch方法来触发相关的事件 … 1.5、ACL——保障数据的安全 ZooKeeper作为一个分布式协调框架其内部存储的都是一些关乎分布式系统运行时状态的元数据尤其是一些涉及分布式锁、Master选举和分布式协调等应用场景的数据会直接影响基于ZooKeeper进行构建的分布式系统的运行状态。因此如何有效地保障ZooKeeper中数据的安全从而避免因误操作而带来的数据随意变更导致的分布式系统异常就显得格外重要了。所幸的是ZooKeeper提供了一套完善的ACL(Access Control List)权限控制机制来保障数据的安全。 提到权限控制我们首先来看看大家都熟悉的、在Unix/Linux文件系统中使用的也是目前应用最广泛的权限控制方式——UGOUser、Group和Others权限控制机制。简单地讲UGO就是针对一个文件或目录对创建者(User)、创建者所在的组(Group)和其他用户(Other)分别配置不同的权限。从这里可以看出UGO其实是一种粗粒度的文件系统权限控制模式利用UGO只能对三类用户进行权限控制即文件的创建者、创建者所在的组以及其他所有用户很显然UGO无法解决下面这个场景 用户U1创建了文件F1希望U1所在的用户组G1拥有对F1读写和执行的权限另一个用户组G2拥有读权限而另外一个用户U3则没有任何权限。 接下去我们来看另外一种典型的权限控制方式ACL。ACL即访问控制列表是一种相对来说比较新颖且更细粒度的权限管理方式可以针对任意用户和组进行细粒度的权限控制。目前绝大部分Unix系统都已经支持了ACL方式的权限控制Linux也从2.6版本的内核开始支持这个特性。 ACL介绍 ZooKeeper的ACL权限控制和Unix/Linux操作系统中的ACL有一些区别读者可以从三个方面来理解ACL机制分别是权限模式(Scheme)、授权对象(ID)和权限(Permission)通常使用“schemeidpermission”来标识一个有效的ACL信息。 1.5.1、权限模式Scheme 权限模式用来确定权限验证过程中使用的检验策略。在ZooKeeper中开发人员使用最多的就是以下四种权限模式。 IP IP模式通过IP地址粒度来进行权限控制例如配置了“ip192.168.0.110”即表示权限控制都是针对这个IP地址的。同时IP模式也支持按照网段的方式进行配置例如“ip192.168.0.1/24”表示针对192.168.0.*这个IP段进行权限控制。 Digest Digest是最常用的权限控制模式也更符合我们对于权限控制的认识其以类似于“usernamepassword”形式的权限标识来进行权限配置便于区分不同应用来进行权限控制。 当我们通过“usernamepassword”形式配置了权限标识后ZooKeeper会对其先后进行两次编码处理分别是SHA-1算法加密和BASE64编码其具体实现由DigestAuthenticationProvider.generateDigest(String idPassword)函数进行封装清单如下所示为使用该函数进行“usernamepassword”编码的一个实例 运行程序输出结果如下 从上面的运行结果中可以看出“usernamepassword”最终会被混淆为一个无法辨识的字符串。 World World是一种最开放的权限控制模式从其名字中也可以看出事实上这种权限控制方式几乎没有任何作用数据节点的访问权限对所有用户开放即所有用户都可以在不进行任何权限校验的情况下操作ZooKeeper上的数据。另外World模式也可以看作是一种特殊的Digest模式它只有一个权限标识即“worldanyone”。 Super Super模式顾名思义就是超级用户的意思也是一种特殊的Digest模式。在Super模式下超级用户可以对任意ZooKeeper上的数据节点进行任何操作。关于Super模式的用法本节后面会进行详细的讲解。 1.5.2、授权对象ID 授权对象指的是权限赋予的用户或一个指定实体例如IP地址或是机器等。在不同的权限模式下授权对象是不同的下表中列出了各个权限模式和授权对象之间的对应关系。 权限Permission 权限就是指那些通过权限检查后可以被允许执行的操作。在ZooKeeper中所有对数据的操作权限分为以下五大类 CREATE©数据节点的创建权限允许授权对象在该数据节点下创建子节点。DELETE(D)子节点的删除权限允许授权对象删除该数据节点的子节点。READ®数据节点的读取权限允许授权对象访问该数据节点并读取其数据内容或子节点列表等。WRITE(W)数据节点的更新权限允许授权对象对该数据节点进行更新操作。ADMIN(A)数据节点的管理权限允许授权对象对该数据节点进行ACL相关的设置操作。 1.5.3、权限扩展体系 ZooKeeper默认提供的IP、Digest、World和Super这四种权限模式在绝大部分的场景下这四种权限模式已经能够很好地实现权限控制的目的。同时ZooKeeper提供了特殊的权限控制插件体系允许开发人员通过指定方式对ZooKeeper的权限进行扩展。这些扩展的权限控制方式就像插件一样插入到ZooKeeper的权限体系中去因此在ZooKeeper的官方文档中也称该机制为“Pluggable ZooKeeper Authentication”。 。。。。。。。。。。。。 2、序列化与协议 ZooKeeper的客户端和服务端之间会进行一系列的网络通信以实现数据的传输。对于一个网络通信首先需要解决的就是对数据的序列化和反序列化处理在ZooKeeper中使用了Jute这一序列化组件来进行数据的序列化和反序列化操作。同时为了实现一个高效的网络通信程序良好的通信协议设计也是至关重要的。 2.1、Jute介绍 Jute是ZooKeeper中的序列化组件最初也是Hadoop中的默认序列化组件其前身是Hadoop Record IO中的序列化组件后来由于Apache Avro[插图]具有出众的跨语言特性、丰富的数据结构和对MapReduce的天生支持并且能非常方便地用于RPC调用从而深深吸引了Hadoop。因此Hadoop从0.21.0版本开始废弃了Record IO使用了Avro这个序列化框架同时Jute也从Hadoop工程中被剥离出来成为了独立的序列化组件。 ZooKeeper则从第一个正式对外发布的版本0.0.1版本开始就一直使用Jute组件来进行网络数据传输和本地磁盘数据存储的序列化和反序列化工作一直使用至今。其实在前些年ZooKeeper官方也一直在寻求一种高性能的跨语言序列化组件期间也多次提出要替换ZooKeeper的序列化组件。关于序列化组件的改造还需要追溯到2008年左右那时候ZooKeeper官方就提出要使用类似于Apache Avro、Thrift或是Google的protobuf这样的组件来替换Jute但是考虑到新老版本序列化组件的兼容性官方团队一直对替换序列化组件工作的推进持保守和观望态度。值得一提的是在替换序列化组件这件事上ZooKeeper官方团队曾经也有过类似于下面这样的方案服务器开启两个客户端服务端口让包含新序列化组件的新版客户端连接单独的服务器端口老版本的客户端则连接另一个端口。但考虑到其实施的复杂性这个想法设计一直没有落地。更为有趣的是ZooKeeper开发团队曾经甚至考虑将“如何让依赖Jute组件的老版本客户端/服务器和依赖Avro组件的新版本客户端/服务器进行无缝通信”这个问题作为Google Summer of Code的题目。当然另一个重要原因是针对Avro早期的发布版本ZooKeeper官方做了一个Jute和Avro的性能测试但是测试结果并不理想因此也并没有决定使用Avro——时至今日Jute的序列化能力都不曾是ZooKeeper的性能瓶颈。 总之因为种种原因以及2009年以后ZooKeeper快速地被越来越多的系统使用开发团队需要将更多的精力放在解决更多优先级更高的需求和Bug修复上以致于替换Jute序列化组件的工作一度被搁置——于是我们现在看到在最新版本的ZooKeeper中底层依然使用了Jute这个古老的并且似乎没有更多其他系统在使用的序列化组件。 2.2、使用Jute进行序列化 下面我们通过一个例子来看看如何使用Jute来完成Java对象的序列化和反序列化。假设我们有一个实体类MockReqHeader代表了一个简单的请求头其定义如下清单所示 上面即为一个非常简单的请求头定义包含了两个成员变量sessionId和type。接下来我们看看如何使用Jute来进行序列化和反序列化。 上面这个代码片段演示了如何使用Jute来对MockReqHeader对象进行序列化和反序列化总的来说大体可以分为4步: 实体类需要实现Record接口的serialize和deserialize方法。构建一个序列化器BinaryOutputArchive。序列化:调用实体类的serialize方法将对象序列化到指定tag中去。例如在本例中就将MockReqHeader对象序列化到header中去。反序列化:调用实体类的deserialize从指定的tag中反序列化出数据内容。 以上就是Jute进行序列化和反序列化的基本过程。 。。。。。。。。。。。。。。。。。。。。 3、客户端 客户端是开发人员使用ZooKeeper最主要的途径因此我们有必要对ZooKeeper客户端的内部原理进行详细讲解。ZooKeeper的客户端主要由以下几个核心组件组成 ZooKeeper实例客户端的入口。ClientWatchManager客户端Watcher管理器。HostProvider客户端地址列表管理器。ClientCnxn客户端核心线程其内部又包含两个线程即SendThread和EventThread。前者是一个I/O线程主要负责ZooKeeper客户端和服务端之间的网络I/O通信后者是一个事件线程主要负责对服务端事件进行处理。 客户端整体结构如下图所示 ZooKeeper客户端的初始化与启动环节实际上就是ZooKeeper对象的实例化过程因此我们首先来看下ZooKeeper客户端的构造方法 客户端的整个初始化和启动过程大体可以分为以下3个步骤 设置默认Watcher。设置ZooKeeper服务器地址列表。创建ClientCnxn。 如果在ZooKeeper的构造方法中传入一个Watcher对象的话那么ZooKeeper就会将这个Watcher对象保存在ZKWatchManager的defaultWatcher中作为整个客户端会话期间的默认Watcher。 3.1、 一次会话的创建过程 为了更好地了解ZooKeeper客户端的工作原理我们首先从一次客户端会话的创建过程讲起从而先对ZooKeeper的客户端及其几个重要组件之间的协作关系有一个宏观上的了解如下图所示是客户端一次会话创建的基本过程。在这个流程图中所有以白色作为底色的框图流程可以看作是第一阶段我们称之为初始化阶段以斜线底纹表示的流程是第二阶段称之为会话创建阶段以点状底纹表示的则是客户端在接收到服务端响应后的对应处理称之为响应处理阶段。 初始化阶段 初始化ZooKeeper对象通过调用ZooKeeper的构造方法来实例化一个ZooKeeper对象在初始化过程中会创建一个客户端的Watcher管理器ClientWatchManager。设置会话默认Watcher如果在构造方法中传入了一个Watcher对象那么客户端会将这个对象作为默认Watcher保存在ClientWatchManager中。构造ZooKeeper服务器地址列表管理器HostProvider。创建并初始化客户端网络连接器ClientCnxn。 ZooKeeper客户端首先会创建一个网络连接器ClientCnxn用来管理客户端与服务器的网络交互。另外客户端在创建ClientCnxn的同时还会初始化客户端两个核心队列outgoingQueue和pendingQueue分别作为客户端的请求发送队列和服务端响应的等待队列。 在后面的我们也会讲到ClientCnxn连接器的底层I/O处理器是ClientCnxnSocket因此在这一步中客户端还会同时创建ClientCnxnSocket处理器。 初始化SendThread和EventThread客户端会创建两个核心网络线程SendThread和EventThread前者用于管理客户端和服务端之间的所有网络I/O后者则用于进行客户端的事件处理。同时客户端还会将ClientCnxnSocket分配给SendThread作为底层网络I/O处理器并初始化EventThread的待处理事件队列waitingEvents用于存放所有等待被客户端处理的事件。 。。。。。。。。。。。。。。。。。。。。。。。。。 4、会话 会话(Session)是ZooKeeper中最重要的概念之一客户端与服务端之间的任何交互操作都与会话息息相关这其中就包括临时节点的生命周期、客户端请求的顺序执行以及Watcher通知机制等。 ZooKeeper客户端与服务端之间一次会话创建的大体过程。以Java语言为例简单地说ZooKeeper的连接与会话就是客户端通过实例化ZooKeeper对象来实现客户端与服务器创建并保持TCP连接的过程。在本节中我们将从会话状态、会话创建和会话管理等方面来讲解ZooKeeper连接与会话的技术内幕。 4.1、会话状态 在ZooKeeper客户端与服务端成功完成连接创建后就建立了一个会话。ZooKeeper会话在整个运行期间的生命周期中会在不同的会话状态之间进行切换这些状态一般可以分为CONNECTING、CONNECTED、RECONNECTING、RECONNECTED和CLOSE等。 如果客户端需要与服务端创建一个会话那么客户端必须提供一个使用字符串表示的服务器地址列表“host1porthost2porthost3port”。例如“192.168.0.12181”或是“192.168.0.12181192.168.0.22181192.168.0.32181”。一旦客户端开始创建ZooKeeper对象那么客户端状态就会变成CONNECTING同时客户端开始从上述服务器地址列表中逐个选取IP地址来尝试进行网络连接直到成功连接上服务器然后将客户端状态变更为CONNECTED。 通常情况下伴随着网络闪断或是其他原因客户端与服务器之间的连接会出现断开情况。一旦碰到这种情况ZooKeeper客户端会自动进行重连操作同时客户端的状态再次变为CONNECTING直到重新连接上ZooKeeper服务器后客户端状态又会再次转变成CONNECTED。因此通常情况下在ZooKeeper运行期间客户端的状态总是介于CONNECTING和CONNECTED两者之一。 另外如果出现诸如会话超时、权限检查失败或是客户端主动退出程序等情况那么客户端的状态就会直接变更为CLOSE。 下图展示了ZooKeeper客户端会话状态的变更情况。 4.2、会话创建 我们曾经介绍了会话创建过程中ZooKeeper客户端的大体工作流程。在本节中我们再一起来看看会话创建过程中ZooKeeper服务端的工作原理。 4.2.1、Session Session是ZooKeeper中的会话实体代表了一个客户端会话。其包含以下4个基本属性 sessionID会话ID用来唯一标识一个会话每次客户端创建新会话的时候ZooKeeper都会为其分配一个全局唯一的sessionID。TimeOut会话超时时间。客户端在构造ZooKeeper实例的时候会配置一个sessionTimeout参数用于指定会话的超时时间。ZooKeeper客户端向服务器发送这个超时时间后服务器会根据自己的超时时间限制最终确定会话的超时时间。TickTime下次会话超时时间点。为了便于ZooKeeper对会话实行“分桶策略”管理同时也是为了高效低耗地实现会话的超时检查与清理ZooKeeper会为每个会话标记一个下次会话超时时间点。TickTime是一个13位的long型数据其值接近于当前时间加上TimeOut但不完全相等。isClosing该属性用于标记一个会话是否已经被关闭。通常当服务端检测到一个会话已经超时失效的时候会将该会话的isClosing属性标记为“已关闭”这样就能确保不再处理来自该会话的新请求了。 。。。。。。。。。。。。。。。。。。。。 5、服务器启动 从本节开始我们将真正进入ZooKeeper服务端相关的技术内幕介绍。首先我们来看看ZooKeeper服务端的整体架构如下图所示 5.1、单机版服务器启动 ZooKeeper服务器的启动大体可以分为以下五个主要步骤配置文件解析、初始化数据管理器、初始化网络I/O管理器、数据恢复和对外服务。下图所示是单机版ZooKeeper服务器的启动流程图。 5.1.1、预启动 预启动的步骤如下 1.统一由QuorumPeerMain作为启动类。无论是单机版还是集群模式启动ZooKeeper服务器在zkServer.cmd和zkServer.sh两个脚本中都配置了使用org.apache.zookeeper.server.quorum.QuorumPeerMain作为启动入口类。 2.解析配置文件zoo.cfg。ZooKeeper首先会进行配置文件的解析配置文件的解析其实就是对zoo.cfg文件的解析。我们曾经提到在部署ZooKeeer服务器时需要使用到zoo.cfg这个文件。该文件配置了ZooKeeper运行时的基本参数包括tickTime、dataDir和clientPort等参数。 3.创建并启动历史文件清理器DatadirCleanupManager。从3.4.0版本开始ZooKeeper增加了自动清理历史数据文件的机制包括对事务日志和快照数据文件进行定时清理。 4.判断当前是集群模式还是单机模式的启动。ZooKeeper根据步骤2中解析出的集群服务器地址列表来判断当前是集群模式还是单机模式如果是单机模式那么就委托给ZooKeeperServerMain进行启动处理。 5.再次进行配置文件zoo.cfg的解析。 6.创建服务器实例ZooKeeperServer。org.apache.zookeeper.server.ZooKeeperServer是单机版ZooKeeper服务端最为核心的实体类。ZooKeeper服务器首先会进行服务器实例的创建接下去的步骤则都是对该服务器实例的初始化工作包括连接器、内存数据库和请求处理器等组件的初始化。 。。。。。。。。。。。。。。。。 5.2、集群版服务器启动 我们已经讲解了单机版ZooKeeper服务器的启动过程在本节中我们将对集群版ZooKeeper服务器的启动过程做详细讲解。集群版和单机版ZooKeeper服务器的启动过程在很多地方都是一致的因此本节只会对有差异的地方展开进行讲解。下图所示是集群版ZooKeeper服务器的启动流程图。 5.2.1、预启动 预启动的步骤如下: 统一由QuorumPeerMain作为启动类。解析配置文件zoo.cfg。创建并启动历史文件清理器DatadirCleanupManager。判断当前是集群模式还是单机模式的启动。 在集群模式中由于已经在zoo.cfg中配置了多个服务器地址因此此处选择集群模式启动ZooKeeper。 5.2.2、初始化 初始化的步骤如下: 创建ServerCnxnFactory。初始化ServerCnxnFactory。创建ZooKeeper数据管理器FileTxnSnapLog。创建QuorumPeer实例。 Quorum是集群模式下特有的对象是ZooKeeper服务器实例(ZooKeeperServer)的托管者从集群层面看QuorumPeer代表了ZooKeeper集群中的一台机器。在运行期间QuorumPeer会不断检测当前服务器实例的运行状态同时根据情况发起Leader选举。 创建内存数据库ZKDatabase:ZKDatabase是ZooKeeper的内存数据库负责管理ZooKeeper的所有会话记录以及DataTree和事务日志的存储。初始化QuorumPeer:在步骤5中我们已经提到QuorumPeer是ZooKeeperServer的托管者因此需要将一些核心组件注册到QuorumPeer中去包括FileTxnSnapLog、ServerCnxnFactory和ZKDatabase。同时ZooKeeper还会对QuorumPeer配置一些参数包括服务器地址列表、Leader选举算法和会话超时时间限制等。恢复本地数据。启动ServerCnxnFactory主线程。 5.2.3、Leader选举 Leader选举的步骤如下。 初始化Leader选举Leader选举可以说是集群和单机模式启动ZooKeeper最大的不同点。ZooKeeper首先会根据自身的SID服务器ID、lastLoggedZxid最新的ZXID和当前的服务器epoch(currentEpoch)来生成一个初始化的投票——简单地讲在初始化过程中每个服务器都会给自己投票。然后ZooKeeper会根据zoo.cfg中的配置创建相应的Leader选举算法实现。在ZooKeeper中默认提供了三种Leader选举算法的实现分别是LeaderElection、AuthFastLeaderElection和FastLeaderElection可以通过在配置文件(zoo.cfg)中使用electionAlg属性来指定分别使用数字03来表示。从3.4.0版本开始ZooKeeper废弃了前两种Leader选举算法只支持FastLeaderElection选举算法了。在初始化阶段ZooKeeper会首先创建Leader选举所需的网络I/O层QuorumCnxManager同时启动对Leader选举端口的监听等待集群中其他服务器创建连接。注册JMX服务。检测当前服务器状态。在上文中我们已经提到QuorumPeer是ZooKeeper服务器实例的托管者在运行期间QuorumPeer的核心工作就是不断地检测当前服务器的状态并做出相应的处理。在正常情况下ZooKeeper服务器的状态在LOOKING、LEADING和FOLLOWING/OBSERVING之间进行切换。而在启动阶段QuorumPeer的初始状态是LOOKING因此开始进行Leader选举。Leader选举ZooKeeper的Leader选举过程简单地讲就是一个集群中所有的机器相互之间进行一系列投票选举产生最合适的机器成为Leader同时其余机器成为Follower或是Observer的集群机器角色初始化过程。关于Leader选举算法简而言之就是集群中哪个机器处理的数据越新通常我们根据每个服务器处理过的最大ZXID来比较确定其数据是否更新其越有可能成为Leader。当然如果集群中的所有机器处理的ZXID一致的话那么SID最大的服务器成为Leader。 5.2.4、Leader和Follower启动期交互过程 到这里为止ZooKeeper已经完成了Leader选举并且集群中每个服务器都已经确定了自己的角色——通常情况下就分为Leader和Follower两种角色。下面我们来对Leader和Follower在启动期间的工作原理进行讲解其大致交互流程如下图所示 Leader和Follower服务器启动期交互过程包括如下步骤 创建Leader服务器和Follower服务器。完成Leader选举之后每个服务器都会根据自己的服务器角色创建相应的服务器实例并开始进入各自角色的主流程。Leader服务器启动Follower接收器LearnerCnxAcceptor。在ZooKeeper集群运行期间Leader服务器需要和所有其余的服务器本贴余下部分我们使用“Learner”来指代这类机器保持连接以确定集群的机器存活情况。LearnerCnxAcceptor接收器用于负责接收所有非Leader服务器的连接请求。Learner服务器开始和Leader建立连接。所有的Learner服务器在启动完毕后会从Leader选举的投票结果中找到当前集群中的Leader服务器然后与其建立连接。Leader服务器创建LearnerHandler。Leader接收到来自其他机器的连接创建请求后会创建一个LearnerHandler实例。每个LearnerHandler实例都对应了一个Leader与Learner服务器之间的连接其负责Leader和Learner服务器之间几乎所有的消息通信和数据同步。向Leader注册。当和Leader建立起连接后Learner就会开始向Leader进行注册——所谓的注册其实就是将Learner服务器自己的基本信息发送给Leader服务器我们称之为LearnerInfo包括当前服务器的SID和服务器处理的最新的ZXID。Leader解析Learner信息计算新的epoch。Leader服务器在接收到Learner的基本信息后会解析出该Learner的SID和ZXID然后根据该Learner的ZXID解析出其对应的epoch_of_learner和当前Leader服务器的epoch_of_leader进行比较如果该Learner的epoch_of_learner更大的话那么就更新Leader的epoch 发送Leader状态。计算出新的epoch之后Leader会将该信息以一个LEADERINFO消息的形式发送给Learner同时等待Learner的响应。Learner发送ACK消息。Follower在收到来自Leader的LEADERINFO消息后会解析出epoch和ZXID然后向Leader反馈一个ACKEPOCH响应。数据同步。Leader服务器接收到Learner的这个ACK消息后就可以开始与其进行数据同步了。.启动Leader和Learner服务器。当有过半的Learner已经完成了数据同步那么Leader和Learner服务器实例就可以开始启动了。 。。。。。。。。。。。。。。 6、Leader选举 了解了ZooKeeper集群中的三种服务器角色Leader、Follower和Observer。接下来我们将从Leader选举概述、算法分析和实现细节三方面来看看ZooKeeper是如何进行Leader选举的。 6.1、Leader选举概述 Leader选举是ZooKeeper中最重要的技术之一也是保证分布式数据一致性的关键所在。在本节中我们将先从整体上来对ZooKeeper的Leader选举进行介绍。 6.1.1、服务器启动时期的Leader选举 Leader选举的时候需要注意的一点是隐式条件便是ZooKeeper的集群规模至少是2台机器这里我们以3台机器组成的服务器集群为例。在服务器集群初始化阶段当有一台服务器我们假设这台机器的myid为1因此称其为Server1启动的时候它是无法完成Leader选举的是无法进行Leader选举的。当第二台机器同样我们假设这台服务器的myid为2称其为Server2也启动后此时这两台机器已经能够进行互相通信每台机器都试图找到一个Leader于是便进入了Leader选举流程。 1、每个Server会发出一个投票 由于是初始情况因此对于Server1和Server2来说都会将自己作为Leader服务器来进行投票每次投票包含的最基本的元素包括所推举的服务器的myid和ZXID我们以(myidZXID)的形式来表示。因为是初始化阶段因此无论是Server1还是Server2都会投给自己即Server1的投票为(10)Server2的投票为(20)然后各自将这个投票发给集群中其他所有机器。 2、 接收来自各个服务器的投票 每个服务器都会接收来自其他服务器的投票。集群中的每个服务器在接收到投票后首先会判断该投票的有效性包括检查是否是本轮投票、是否来自LOOKING状态的服务器。 3、 处理投票 在接收到来自其他服务器的投票后针对每一个投票服务器都需要将别人的投票和自己的投票进行PKPK的规则如下。 优先检查ZXID。ZXID比较大的服务器优先作为Leader。如果ZXID相同的话那么就比较myid。myid比较大的服务器作为Leader服务器。 现在我们来看Server1和Server2实际是如何进行投票处理的。对于Server1来说它自己的投票是(10)而接收到的投票为(20)。首先会对比两者的ZXID因为都是0所以无法决定谁是Leader。接下来会对比两者的myid很显然Server1发现接收到的投票中的myid是2大于自己于是就会更新自己的投票为(20)然后重新将投票发出去。而对于Server2来说不需要更新自己的投票信息只是再一次向集群中所有机器发出上一次投票信息即可。 4、 统计投票 每次投票后服务器都会统计所有投票判断是否已经有过半的机器接收到相同的投票信息。对于Server1和Server2服务器来说都统计出集群中已经有两台机器接受了(20)这个投票信息。这里我们需要对“过半”的概念做一个简单的介绍。所谓“过半”就是指大于集群机器数量的一半即大于或等于(n/21)。对于这里由3台机器构成的集群大于等于2台即为达到“过半”要求。 那么当Server1和Server2都收到相同的投票信息(20)的时候即认为已经选出了Leader。 5、 改变服务器状态 一旦确定了Leader每个服务器就会更新自己的状态如果是Follower那么就变更为FOLLOWING如果是Leader那么就变更为LEADING。 6.1.2、服务器运行期间的Leader选举 在ZooKeeper集群正常运行过程中一旦选出一个Leader那么所有服务器的集群角色一般不会再发生变化——也就是说Leader服务器将一直作为集群的Leader即使集群中有非Leader集群挂了或是有新机器加入集群也不会影响Leader。但是一旦Leader所在的机器挂了那么整个集群将暂时无法对外服务而是进入新一轮的Leader选举。服务器运行期间的Leader选举和启动时期的Leader选举基本过程是一致的。 我们假设当前正在运行的ZooKeeper服务器由3台机器组成分别是Server1、Server2和Server3当前的Leader是Server2。假设在某一个瞬间Leader挂了这个时候便开始了新一轮Leader选举。 变更状态当Leader挂了之后余下的非Observer服务器都会将自己的服务器状态变更为LOOKING然后开始进入Leader选举流程。每个Server会发出一个投票在这个过程中需要生成投票信息(myidZXID)。因为是运行期间因此每个服务器上的ZXID可能不同我们假定Server1的ZXID为123而Server3的ZXID为122。在第一轮投票中Server1和Server3都会投自己即分别产生投票(1123)和(3122)然后各自将这个投票发给集群中所有机器。接收来自各个服务器的投票。处理投票对于投票的处理和上面提到的服务器启动期间的处理规则是一致的。在这个例子里面由于Server1的ZXID为123Server3的ZXID为122那么显然Server1会成为Leader。统计投票。改变服务器状态。 6.2、Leader选举的算法分析 在ZooKeeper中提供了三种Leader选举的算法分别是LeaderElection、UDP版本的FastLeaderElection和TCP版本的FastLeaderElection可以通过在配置文件zoo.cfg中使用electionAlg属性来指定分别使用数字03来表示。0代表LeaderElection这是一种纯UDP实现的Leader选举算法1代表UDP版本的FastLeaderElection并且是非授权模式2也代表UDP版本的FastLeaderElection但使用授权模式3代表TCP版本的FastLeaderElection。值得一提的是从3.4.0版本开始ZooKeeper废弃了0、1和2这三种Leader选举算法只保留了TCP版本的FastLeaderElection选举算法。下文即仅对此算法进行介绍。 由于在官方文档以及一些外文资料中对于概念的描述非常的“晦涩”因此在讲解ZooKeeper的Leader选举算法的时候尽量使用一些外文的专有术语来保持一致性以便于读者理解相关内容。 6.1.1、术语解释 首先我们对ZooKeeper的Leader选举算法介绍中会出现的一些专有术语进行简单介绍以便大家更好地理解本贴内容。 SID服务器IDSID是一个数字用来唯一标识一台ZooKeeper集群中的机器每台机器不能重复和myid的值一致。ZXID事务IDZXID是一个事务ID用来唯一标识一次服务器状态的变更。在某一个时刻集群中每台机器的ZXID值不一定全都一致这和ZooKeeper服务器对于客户端“更新请求”的处理逻辑有关。Vo t e投票Leader选举顾名思义必须通过投票来实现。当集群中的机器发现自己无法检测到Leader机器的时候就会开始尝试进行投票。Quorum过半机器数。这是整个Leader选举算法中最重要的一个术语我们可以把这个术语理解为是一个量词指的是ZooKeeper集群中过半的机器数如果集群中总的机器数是n的话那么可以通过下面这个公式来计算quorum的值 例如如果集群机器总数是3那么quorum就是2。 6.1.2、算法分析 进入Leader选举 当ZooKeeper集群中的一台服务器出现以下两种情况之一时就会开始进入Leader选举。 服务器初始化启动。服务器运行期间无法和Leader保持连接。 而当一台机器进入Leader选举流程时当前集群也可能会处于以下两种状态。 集群中本来就已经存在一个Leader。集群中确实不存在Leader。 我们首先来看第一种已经存在Leader的情况。这种情况通常是集群中的某一台机器启动比较晚在它启动之前集群已经可以正常工作即已经存在了一台Leader服务器。针对这种情况当该机器试图去选举Leader的时候会被告知当前服务器的Leader信息对于该机器来说仅仅需要和Leader机器建立起连接并进行状态同步即可。 下面我们重点来看在集群中Leader不存在的情况下如何进行Leader选举。 开始第一次投票 通常有两种情况会导致集群中不存在Leader一种情况是在整个服务器刚刚初始化启动时此时尚未产生一台Leader服务器另一种情况就是在运行期间当前Leader所在的服务器挂了。无论是哪种情况此时集群中的所有机器都处于一种试图选举出一个Leader的状态我们把这种状态称为“LOOKING”意思是说正在寻找Leader。当一台服务器处于LOOKING状态的时候那么它就会向集群中所有其他机器发送消息我们称这个消息为“投票”。 在这个投票消息中包含了两个最基本的信息所推举的服务器的SID和ZXID分别表示了被推举服务器的唯一标识和事务ID。下文中我们将以“(SIDZXID)”这样的形式来标识一次投票信息。举例来说如果当前服务器要推举SID为1、ZXID为8的服务器成为Leader那么它的这次投票信息可以表示为(18)。 我们假设ZooKeeper由5台机器组成SID分别为1、2、3、4和5ZXID分别为9、9、9、8和8并且此时SID为2的机器是Leader服务器。某一时刻1和2所在的机器出现故障因此集群开始进行Leader选举。 在第一次投票的时候由于还无法检测到集群中其他机器的状态信息因此每台机器都是将自己作为被推举的对象来进行投票。于是SID为3、4和5的机器投票情况分别为(39)、(48)和(58)。 变更投票 集群中的每台机器发出自己的投票后也会接收到来自集群中其他机器的投票。每台机器都会根据一定的规则来处理收到的其他机器的投票并以此来决定是否需要变更自己的投票。这个规则也成为了整个Leader选举算法的核心所在。为了便于描述我们首先定义一些术语。 vote_sid接收到的投票中所推举Leader服务器的SID。vote_zxid接收到的投票中所推举Leader服务器的ZXID。self_sid当前服务器自己的SID。self_zxid当前服务器自己的ZXID。 每次对于收到的投票的处理都是一个对(vote_sidvote_zxid)和(self_sidself_zxid)对比的过程。 规则1如果vote_zxid大于self_zxid就认可当前收到的投票并再次将该投票发送出去。规则2如果vote_zxid小于self_zxid那么就坚持自己的投票不做任何变更。规则3如果vote_zxid等于self_zxid那么就对比两者的SID。如果vote_sid大于self_sid那么就认可当前接收到的投票并再次将该投票发送出去。规则4如果vote_zxid等于self_zxid并且vote_sid小于self_sid那么同样坚持自己的投票不做变更。 根据上面这个规则我们结合下图来分析上面提到的5台机器组成的ZooKeeper集群的投票变更过程。 每台机器都把投票发出后同时也会接收到来自另外两台机器的投票。 对于Server3来说它接收到了(48)和(58)两个投票对比后由于自己的ZXID要大于接收到的两个投票因此不需要做任何变更。对于Server4来说它接收到了(39)和(58)两个投票对比后由于(39)这个投票的ZXID大于自己因此需要变更投票为(39)然后继续将这个投票发送给另外两台机器。对于Server4来说它接收到了(39)和(58)两个投票对比后由于(39)这个投票的ZXID大于自己因此需要变更投票为(39)然后继续将这个投票发送给另外两台机器。同样对于Server5来说它接收到了(39)和(48)两个投票对比后由于(39)这个投票的ZXID大于自己因此需要变更投票为(39)然后继续将这个投票发送给另外两台机器。 确定Leader 经过这第二次投票后集群中的每台机器都会再次收到其他机器的投票然后开始统计投票。如果一台机器收到了超过半数的相同的投票那么这个投票对应的SID机器即为Leader。 如上图所示的Leader选举例子中因为ZooKeeper集群的总机器数为5台那么 也就是说只要收到3个或3个以上含当前服务器自身在内一致的投票即可。在这里Server3、Server4和Server5都投票(39)因此确定了Server3为Leader。 小结 简单地说通常哪台服务器上的数据越新那么越有可能成为Leader原因很简单数据越新那么它的ZXID也就越大也就越能够保证数据的恢复。当然如果集群中有几个服务器具有相同的ZXID那么SID较大的那台服务器成为Leader。 6.3、Leader选举的实现细节 介绍了整个Leader选举的算法设计。从算法复杂度来说FastLeaderElection算法的设计并不复杂但在真正的实现过程中对于一个需要应用在生产环境的产品来说还是有很多实际问题需要解决。在本节中我们就来看看ZooKeeper中对FastLeaderElection的实现。 6.3.1、服务器状态 为了能够清楚地对ZooKeeper集群中每台机器的状态进行标识在org.apache.zookeeper.server.quorum.QuorumPeer.ServerState类中列举了4种服务器状态分别是LOOKING、FOLLOWING、LEADING和OBSERVING。 LOOKING寻找Leader状态。当服务器处于该状态时它会认为当前集群中没有Leader因此需要进入Leader选举流程。FOLLOWING跟随者状态表明当前服务器角色是Follower。LEADING领导者状态表明当前服务器角色是Leader。OBSERVING观察者状态表明当前服务器角色是Observer。 6.3.2、投票数据结构 Leader的选举过程是通过投票来实现的同时每个投票中包含两个最基本的信息所推举服务器的SID和ZXID。现在我们来看在ZooKeeper中对Vote数据结构的定义如下图所示 我们以在org.apache.zookeeper.server.quorum.Vote类中查看其完整的定义下表中列举了Vote中的几个属性 6.3.3、QuorumCnxManager网络I/O ClientCnxn是ZooKeeper客户端中用于处理网络I/O的一个管理器。在Leader选举的过程中也有类似的角色那就是QuorumCnxManager——每台服务器启动的时候都会启动一个QuorumCnxManager负责各台服务器之间的底层Leader选举过程中的网络通信。 6.3.4、消息队列 在QuorumCnxManager这个类内部维护了一系列的队列用于保存接收到的、待发送的消息以及消息的发送器。除接收队列以外这里提到的所有队列都有一个共同点——按SID分组形成队列集合我们以发送队列为例来说明这个分组的概念。假设集群中除自身外还有4台机器那么当前服务器就会为这4台服务器分别创建一个发送队列互不干扰。 recvQueue消息接收队列用于存放那些从其他服务器接收到的消息。queueSendMap消息发送队列用于保存那些待发送的消息。queueSendMap是一个Map按照SID进行分组分别为集群中的每台机器分配了一个单独队列从而保证各台机器之间的消息发送互不影响。senderWorkerMap发送器集合。每个SendWorker消息发送器都对应一台远程ZooKeeper服务器负责消息的发送。同样在senderWorkerMap中也按照SID进行了分组。lastMessageSent最近发送过的消息。在这个集合中为每个SID保留最近发送过的一个消息。 6.3.5、建立连接 为了能够进行互相投票ZooKeeper集群中的所有机器都需要两两建立起网络连接。QuorumCnxManager在启动的时候会创建一个ServerSocket来监听Leader选举的通信端口Leader选举的通信端口默认是3888。开启端口监听后ZooKeepr就能够不断地接收到来自其他服务器的“创建连接”请求在接收到其他服务器的TCP连接请求时会交由receiveConnection函数来处理。为了避免两台机器之间重复地创建TCP连接ZooKeeper设计了一种建立TCP连接的规则只允许SID大的服务器主动和其他服务器建立连接否则断开连接。在ReceiveConnection函数中服务器通过对比自己和远程服务器的SID值来判断是否接受连接请求。如果当前服务器发现自己的SID值更大那么会断开当前连接然后自己主动去和远程服务器建立连接。 一旦建立起连接就会根据远程服务器的SID来创建相应的消息发送器SendWorker和消息接收器RecvWorker并启动他们。 6.3.6、消息接收与发送 消息的接收过程是由消息接收器RecvWorker来负责的。在上面的讲解中我们已经提到了ZooKeeper会为每个远程服务器分配一个单独的RecvWorker因此每个RecvWorker只需要不断地从这个TCP连接中读取消息并将其保存到recvQueue队列中。 消息的发送过程也比较简单由于ZooKeeper同样也已经为每个远程服务器单独分别分配了消息发送器SendWorker那么每个SendWorker只需要不断地从对应的消息发送队列中获取出一个消息来发送即可同时将这个消息放入lastMessageSent中来作为最近发送过的消息。在SendWorker的具体实现中有一个细节需要我们注意一下一旦ZooKeeper发现针对当前远程服务器的消息发送队列为空那么这个时候就需要从lastMessageSent中取出一个最近发送过的消息来进行再次发送。这个细节的处理主要是为了解决这样一类分布式问题接收方在消息接收前或者是在接收到消息后服务器挂掉了导致消息尚未被正确处理。那么如此重复发送是否会导致其他问题呢当然这里可以放心的一点是ZooKeeper能够保证接收方在处理消息的时候会对重复消息进行正确的处理。 6.3.7、FastLeaderElection选举算法的核心部分 下面我们来看Leader选举的核心算法部分的实现。在讲解之前我们首先约定几个概念。 外部投票特指其他服务器发来的投票。内部投票服务器自身当前的投票。选举轮次ZooKeeper服务器Leader选举的轮次即logicalclock。PK指对内部投票和外部投票进行一个对比来确定是否需要变更内部投票。 选票管理 我们已经讲解了在QuorumCnxManager中ZooKeeper是如何管理服务器之间的投票发送和接收的现在我们来看对于选票的管理。下图所示是选票管理过程中相关组件之间的协作关系。 sendqueue选票发送队列用于保存待发送的选票。recvqueue选票接收队列用于保存接收到的外部投票。WorkerReceiver选票接收器。该接收器会不断地从QuorumCnxManager中获取出其他服务器发来的选举消息并将其转换成一个选票然后保存到recvqueue队列中去。在选票的接收过程中如果发现该外部投票的选举轮次小于当前服务器那么就直接忽略这个外部投票同时立即发出自己的内部投票。当然如果当前服务器并不是LOOKING状态即已经选举出了Leader那么也将忽略这个外部投票同时将Leader信息以投票的形式发送出去。另外对于选票接收器还有一个细节需要注意如果接收到的消息来自Observer服务器那么就忽略该消息并将自己当前的投票发送出去。WorkerSender选票发送器会不断地从sendqueue队列中获取待发送的选票并将其传递到底层QuorumCnxManager中去。 算法核心 在上图中我们可以看到FastLeaderElection模块是如何与底层的网络I/O进行交互的其中不难发现在“选举算法”中将会对接收到的选票进行处理。下面我们就来看看这个选举过程的核心算法实现下图展示了Leader选举算法实现的流程示意图。 上图中展示了Leader选举算法的基本流程其实也就是lookForLeader方法的逻辑。当ZooKeeper服务器检测到当前服务器状态变成LOOKING时就会触发Leader选举即调用lookForLeader方法来进行Leader选举。 自增选举轮次。在FastLeaderElection实现中有一个logicalclock属性用于标识当前Leader的选举轮次ZooKeeper规定了所有有效的投票都必须在同一轮次中。ZooKeeper在开始新一轮的投票时会首先对logicalclock进行自增操作。初始化选票。在开始进行新一轮的投票之前每个服务器都会首先初始化自己的选票。在初始化阶段每台服务器都会将自己推举为Leader下表展示了一个初始化的选票。发送初始化选票。在完成选票的初始化后服务器就会发起第一次投票。ZooKeeper会将刚刚初始化好的选票放入sendqueue队列中由发送器WorkerSender负责发送出去。接收外部投票。每台服务器都会不断地从recvqueue队列中获取外部投票。如果服务器发现无法获取到任何的外部投票那么就会立即确认自己是否和集群中其他服务器保持着有效连接。如果发现没有建立连接那么就会马上建立连接。如果已经建立了连接那么就再次发送自己当前的内部投票。判断选举轮次。当发送完初始化选票之后接下来就要开始处理外部投票了。在处理外部投票的时候会根据选举轮次来进行不同的处理。a.外部投票的选举轮次大于内部投票如果服务器发现自己的选举轮次已经落后于该外部投票对应服务器的选举轮次那么就会立即更新自己的选举轮次(logicalclock)并且清空所有已经收到的投票然后使用初始化的投票来进行PK以确定是否变更内部投票关于P K的逻辑会在步骤6中统一讲解最终再将内部投票发送出去。b.外部投票的选举轮次小于内部投票如果接收到的选票的选举轮次落后于服务器自身的那么ZooKeeper就会直接忽略该外部投票不做任何处理并返回步骤4。c.外部投票的选举轮次和内部投票一致这也是绝大多数投票的场景如果外部投票的选举轮次和内部投票一致的话那么就开始进行选票PK。总的来说只有在同一个选举轮次的投票才是有效的投票。选票PK。在步骤5中提到在收到来自其他服务器有效的外部投票后就要进行选票PK了——也就是FastLeaderElection.totalOrderPredicate方法的核心逻辑。选票PK的目的是为了确定当前服务器是否需要变更投票主要从选举轮次、ZXID和SID三个因素来考虑具体条件如下在选票PK的时候依次判断符合任意一个条件就需要进行投票变更。a.如果外部投票中被推举的Leader服务器的选举轮次大于内部投票那么就需要进行投票变更。b.如果选举轮次一致的话那么就对比两者的ZXID。如果外部投票的ZXID大于内部投票那么就需要进行投票变更。c.如果两者的ZXID一致那么就对比两者的SID。如果外部投票的SID大于内部投票那么就需要进行投票变更。变更投票:通过选票PK后如果确定了外部投票优于内部投票所谓的“优于”是指外部投票所推举的服务器更适合成为Leader那么就进行投票变更——使用外部投票的选票信息来覆盖内部投票。变更完成后再次将这个变更后的内部投票发送出去。选票归档:无论是否进行了投票变更都会将刚刚收到的那份外部投票放入“选票集合”recvset中进行归档。recvset用于记录当前服务器在本轮次的Leader选举中收到的所有外部投票——按照服务器对应的SID来区分例如{(1vote1)(2vote2)…}。完成了选票归档之后就可以开始统计投票了。统计投票的过程就是为了统计集群中是否已经有过半的服务器认可了当前的内部投票。如果确定已经有过半的服务器认可了该内部投票则终止投票。否则返回步骤4。更新服务器状态。统计投票后如果已经确定可以终止投票那么就开始更新服务器状态。服务器会首先判断当前被过半服务器认可的投票所对应的Leader服务器是否是自己如果是自己的话那么就会将自己的服务器状态更新为LEADING。如果自己不是被选举产生的Leader的话那么就会根据具体情况来确定自己是FOLLOWING或是OBSERVING。 以上10个步骤就是FastLeaderElection选举算法的核心步骤其中步骤49会经过几轮循环直到Leader选举产生。另外还有一个细节需要注意就是在完成步骤9之后如果统计投票发现已经有过半的服务器认可了当前的选票这个时候ZooKeeper并不会立即进入步骤10来更新服务器状态而是会等待一段时间默认是200毫秒来确定是否有新的更优的投票。 7、各服务器角色介绍 我们已经了解到在ZooKeeper集群中分别有Leader、Follower和Observer三种类型的服务器角色。 7.1、Leader Leader服务器是整个ZooKeeper集群工作机制中的核心其主要工作有以下两个 事务请求的唯一调度和处理者保证集群事务处理的顺序性。集群内部各服务器的调度者。 7.1.1、请求处理链 使用责任链模式来处理每一个客户端请求是ZooKeeper的一大特色。服务器启动过程中我们已经提到在每一个服务器启动的时候都会进行请求处理链的初始化Leader服务器的请求处理链如下图所示 从上图中可以看到从PrepRequestProcessor到FinalRequestProcessor前后一共7个请求处理器组成了Leader服务器的请求处理链。 。。。。。。。。。。。 7.2、Follower 从角色名字上可以看出Follower服务器是ZooKeeper集群状态的跟随者其主要工作有以下三个 处理客户端非事务请求转发事务请求给Leader服务器。参与事务请求Proposal的投票。参与Leader选举投票。 和Leader服务器一样Follower也同样使用了采用责任链模式组装的请求处理链来处理每一个客户端请求由于不需要负责对事务请求的投票处理因此相对来说Follower服务器的请求处理链会简单一些其请求处理链如下图所示 。。。。。。。 7.3、Observer Observer是ZooKeeper自3.3.0版本开始引入的一个全新的服务器角色。从字面意思看该服务器充当了一个观察者的角色——其观察ZooKeeper集群的最新状态变化并将这些状态变更同步过来。Observer服务器在工作原理上和Follower基本是一致的对于非事务请求都可以进行独立的处理而对于事务请求则会转发给Leader服务器进行处理。和Follower唯一的区别在于Observer不参与任何形式的投票包括事务请求Proposal的投票和Leader选举投票。简单地讲Observer服务器只提供非事务服务通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。 另外Observer的请求处理链路和Follower服务器也非常相近如下图所示 另外需要注意的一点是虽然在上图中Observer服务器在初始化阶段会将SyncRequestProcessor处理器也组装上去但是在实际运行过程中Leader服务器不会将事务请求的投票发送给Observer服务器。 7.4、 集群间消息通信 在整个ZooKeeper集群工作过程中都是由Leader服务器来负责进行各服务器之间的协调同时各服务器之间的网络通信都是通过不同类型的消息传递来实现的。在本节中我们将围绕ZooKeeper集群间的消息通信来讲解ZooKeeper集群各服务器之间是如何进行协调的。 ZooKeeper的消息类型大体上可以分为四类分别是数据同步型、服务器初始化型、请求处理型和会话管理型。 数据同步型 数据同步型消息是指在Learner和Leader服务器进行数据同步的时候网络通信所用到的消息通常有DIFF、TRUNC、SNAP和UPTODATE四种。下表中分别对这四种消息类型进行了详细介绍。 服务器初始化型 服务器初始化型消息是指在整个集群或是某些新机器初始化的时候Leader和Learner之间相互通信所使用的消息类型常见的有OBSERVERINFO、FOLLOWERINFO、LEADERINFO、ACKEPOCH和NEWLEADER五种。下表中对这五种消息类型进行了详细介绍。 请求处理型 请求处理型消息是指在进行请求处理的过程中Leader和Learner服务器之间互相通信所使用的消息常见的有REQUEST、PROPOSAL、ACK、COMMIT、INFORM和SYNC六种。下表中对这六种消息类型进行了详细介绍。 会话管理型 会话管理型消息是指ZooKeeper在进行会话管理的过程中和Learner服务器之间互相通信所使用的消息常见的有PING和REVALIDATE两种。下表中对这两种消息类型进行了详细的介绍。
http://www.pierceye.com/news/587869/

相关文章:

  • 淄博网站制作高端网站后台任务
  • 营销型网站源码成都网站建设seo
  • 天津网上商城网站建设专业的猎头公司
  • 西平县住房城乡建设局网站西部数码网站管理助手3.0
  • 承德市网站建设WordPress电影资源分享下载站
  • 专注于网络推广及网站建设wordpress离线发布功能
  • 营销型网站案例提高wordpress打开速度
  • 怎么样做一个网站自己个人网站后台怎么做
  • 源码站免费找客户网站
  • idc空间商网站源码知名的网站建设
  • 什么叫网站降权建设网站租服务器
  • 网站后台模板怎样使用站长平台
  • 写一个app需要多少钱龙岩seo包年系统排行榜
  • 科技公司企业网站建设手机360网站seo优化
  • 做翻译 英文网站黑色时尚橱柜网站源码
  • wordpress 主机要求珠海百度推广优化
  • 台山网站建设哈尔滨网站建设收费
  • 卖主机 服务器的网站wordpress自动标签内联
  • 28创业商机网seo在线优化技术
  • 建设银行网站查询余额世界杯球队最新排名
  • 网站对联广告做戒指网站的logo照片
  • 网站开发 项目计划书网页设计产品介绍页面的制作
  • 专做正品 网站青岛 网站制作
  • wordpress建站镜像杭州网站开发公司排名
  • 网站都需要什么类别网站首页seo关键词布局
  • 泰安千橙网站建设北京活动策划公司黄页
  • 网页网站模板北京市工商注册网上服务系统
  • 企业网站建设报价明细表免费ppt模板下载哪个网站好
  • 佛山做公司网站全球域名
  • 网站建设陆金手指谷哥7邢台企业做网站找谁