1.系统模型1.数据模型zookeeper 的视图结构和标准的Unix文件系统非常相似,但没有引入传统文件系统中目录和文件等相关概念,而是使用了其特有的 '数据节点'概念,我们称之为 ZNode。ZNode 是 zookeeper 中数据的最小单元,每个ZNode上都可以保存数据,同时还可以挂载子节点,因此构成了一个层次化的命名空间,我们称之为树。树:在zookeeper中,每一个数据节点都被称为 ZNode.所有的 ZNode按层次化结构进行组织,形成一棵树。ZNode的节点路径标识方式和Unix文件系统路径非常相似,都是由一系列使用斜杠'/'进行分割的路径表示,开发人员可以向这个节点写入数据,也可以在节点下面创建节点。事务ID:在 <<事务处理:概念与技术>>一书中提到,事务是对物理和抽象的应用状态上的操作集合。狭义上的事务通常是指数据库事务,一包包含了一系列对数据库有序的读写操作,这些事务具有ACID。在 zookeeper 中,事务是指能够改变 zookeeper 服务器状态的操作,我们也称之为事务操作或者更新操作,一般包括数据节点创建与删除,数据节点内容更新和客户端会话创建于失效等操作。对于每一个事务请求,zookeeper 都会为其分配一个全局唯一的事务ID,用 ZXID来表示,通常是一个64位的数字。每一个 ZXID 对应一次更新操作,从这些 ZXID 中可以间接的识别出 zookeeper 处理这些更新操作请求的全局顺序。2.节点特性节点类型:在zookeeper 中,每个数据节点都是有生命周期的,其生命周期的长短取决于数据节点的节点类型。在 zookeeper 中,节点类型可以分为持久类型(PERSISTENT),临时节点(EPHEMERAL)和顺序节点(SEQUENTIAL)三大类,具体在节点创建过程,通过组合使用,可以生成以下四种组合型节点类型:1.持久节点(PERSISTENT)持久节点是 zookeeper 中最常见的。所谓持久节点,是指该数据节点别创建后,一直存在于zookeeper服务上,直到有删除操作来主动清除这个节点。2.持久顺序节点(PERSISTENT_SEQUENTIAL)持久顺序节点的基本特性和持久节点是一致的,额外的特性表现在顺序性上。在 zookeeper 中,每个父节点都会为它的第一级子节点维护一份顺序,用于记录下每个子节点创建的先后顺序。基于这个顺序特性,在创建子节点的时候,可以设置这个标记,那么在创建节点过程中,zookeeper 会自动为给定节点名加上一个数字后缀,作为一个新的,完整的节点名。这个数字后缀的上限是整型的最大值。3.临时节点(EPHEMERAL)和持久节点不同,临时节点的生命周期和客户端的会话绑定在一起,也就是说,如果客户端会话失效,那么这个节点就会被自动清理。注意,这里提到的客户端会话失效,而非tcp连接断开。另外,zookeeper 规定了不能基于临时节点来创建子节点,即临时节点只能作为叶子节点。4.临时顺序节点(EPHEMERAL_SEQUENTIAL)临时顺序节点的基本特性和临时节点也是一样的,在临时节点的基础上,添加了顺序的特性。状态信息:我们可以针对zookeeper上的数据节点进行数据的写入和节点的创建。事实上,每个数据节点除了存储数据内容之外,还存储数据内容本身的一些状态信息。get 获取数据节点的内容。3.版本zookeeper 中为数据节点引入了版本的概念,每个数据节点都具有三种类型的版本信息,对数据节点的任何更新操作都会引起版本号的变化。version: 当前数据节点数据内容的版本号cversion: 当前数据节点子节点的版本号aversion: 当前数据节点 ACL 变更版本号zookeeper 的版本表示的是对数据节点的数据内容,子节点列表,或者是节点ACL信息的修改次数。例如version,在数据节点被创建之后,version是0,表示’当前节点被创建以后,被更新过0次‘。version 表示的是对数据节点内容的变更次数,即使内容不变,version的值依然会变。悲观锁:又被称为悲观并发控制(Pessimistic Concurrency Control, PCC),是数据库中一种非常典型且非常严格的并发控制策略。悲观锁具有强烈的独占和排他特性,能够有效的避免不同事务对同一数据并发更新而造成的数据一致性问题。在悲观锁的实现原理中,如果一个事务(假定事务A)正在对事务进行处理,那么在整个事务处理过程中,都会将数据处于锁定状态,在这期间,其他事务将无法对这个数据进行更新操作,直到事务A完成了对该数据的处理,释放了对应的锁之后,其他事务才能重新竞争来对数据进行更新操作。也就是说,对于一份独立的数据,系统只配备了一把唯一的钥匙,谁获得了这把钥匙,谁就有权利更新数据。在生产环境中,悲观锁策略适合解决那些对于数据更新竞争十分激烈的场景。乐观锁:又被称为乐观并发控制(Optimistic Concurrency Control,OCC),也是一种常见的并发控制策略。相对于悲观锁而言,乐观锁机制显得宽松与友好。悲观锁假定不同的事务之间的处理一定会出现互相干扰,从而需要在一个事务从头到尾的过程中都对数据进行加锁处理。而乐观锁刚好相反,它假定多个事务在处理过程中不会彼此影响,因此在事务处理的绝大部分时间里不需要进行加锁处理。当然,既然有并发,就一定会存在数据更新冲突的可能。在乐观锁机制中,在更新请求提交之前,每个事务都会首先检查事务读取数据后,是否有其他事务对该数据进行了修改。如果其他事务有更新的话,那么正在提交的事务就需要回滚。乐观锁通常适用在数据并发竞争不大,事务冲突比较少的场景。我们可以把一个乐观锁的事务分成如下三个阶段:数据读取,写入校验和数据写入,其中写入校验是整个乐观锁控制的关键所在。在写入校验阶段,事务会检查数据在读取阶段后是否有其他事务对数据进行过更新,以确保数据更新的一致性。CAS : 对于值 V,每次更新之前都会对其值是否符合预期值A,只有符合预期值,才会将 V 原子化的更新到值B.其中是否符合预期便是乐观锁中的'写入校验'。事实上,在 zookeeper 中,version 的属性正是用来实现乐观锁机制中的'写入校验的'。4.Watcherzookeeper 的 watcher 机制主要包括客户端线程,客户端WatchManager和 zookeeper服务器三部分。工作流程为,客户端向 zookeeper服务器注册Watcher的同时,会将 Watcher对象存储在客户端的 WatchManager中。当zookeeper服务端触发Watcher事件后,会向客户端发送通知,客户端线程从WatchManager中取出对应的Watcher对象来执行回调逻辑。工作机制:客户端注册Watcher,服务端处理Watcher,客户端回调WatcherWatcher 特性:1.一次性无论是服务端还是客户端,一旦个watcher被触发,zookeeper都会将其从相应的存储中移除。因此,开发人员在 watcher 的使用上要记住的一点是需要反复注册,这样的设计有效的减轻了服务端的压力。试想,如果注册一个watcher之后一直有效,那么,针对那些更新非常频繁的节点,服务端会不断的向客户端发送事件通知,无论对于网络还是服务端的性能影响都非常大。2.客户端串行执行客户端watcher回调的过程是一个串行同步的过程,这为我们保证了顺序,同时,需要开发注意的是,千万不要因为一个watcher的处理逻辑影响了整个客户端的watcher回调。3.轻量WatchedEvent 是zookeeper 整个watcher 通知机制的最小通知单元,整个数据结构中只包含三部分内容:通知状态,事件类型和节点路径。也就是说,watcher通知非常简单,只会告诉客户端发生了事件,而不会说明事件的具体内容。例如,针对NodeDataChanged 事件,zookeeper 的 watcher 只会通知客户端指定数据节点的数据内容发生了变更,而对于原始数据以及变更后的新数据都无法从这个事件中直接获取到,而是需要客户端主动重新去获取数据。另外,客户端向服务端注册watcher的时候,并不会把客户端真实的watcher对象传递到服务端,仅仅只是在客户端请求中使用boolean类型属性进行了标记,同时服务端也仅仅只是保存了当前连接的 ServerCnxn 对象。5.ACLzookeeper 作为一个分布式协调框架,其内部存储的都是一些关乎分布式系统运行时状态的元数据,尤其是一些设计分布式锁,master选举和分布式协调等应用场景的数据,会直接影响基于zookeeper进行构建的分布式系统的运行状态。因此,有效的保障zookeeper中数据的安全,避免误操作带来的系统异常。ACL 介绍:权限模式(Scheme),授权对象(ID)和权限(Permission),通常使用 'scheme:id:permission' 来标识一个有效的ACL信息。1.权限模式:Scheme权限模式用来确定权限验证过程中使用的校验策略。在zookeeper中最常使用的权限模式4种:1.IPIP 模式通过ip地址粒度来进行权限控制,例如配置了 'ip:192.168.0.1',即表示权限控制都是针对这个ip地址的。同时,ip模式也支持网段的方式进行配置,'ip:192.168.0.1/24' 表示针对 192.168.0.* 这个ip段进行权限控制。2.DigestDigest 是最常用的权限控制模式,其类似于'username:password' 形式的权限标识进行权限配置,以便区分不同应用来进行权限控制。当我们通过'username:password' 形式配置了权限标识后,zookeeper会对其先后进行2次编码处理,分别是 SHA-1和base64编码。3.Worldworld 是一种最开放的权限控制模式,事实上这种权限控制方式几乎没有任何作用,数据节点发访问权限对所有用户都开放,即所有的用户都可以在不进行任何权限校验的情况下操作 zookeeper 上的数据。另外,world 可以看做是特殊的 Digest 模式,它只有一个标识 'world:anyone'。4.SuperSuper 就是超级用户的意思,也是一种特殊的 Digest 模式。在 Super 模式下,超级用户可以对任意 zookeeper 上的数据节点做任何操作。2.授权对象:ID授权对象指的是权限赋予的用户或者一个指定实体,例如ip地址或机器等。不同的权限模式下,授权对象是不同的。3.权限:Permission权限就是指那些通过权限检查后可以被允许执行的操作。create,delete,read,write,admin权限扩展体系:zookeeper 提供了特殊的权限控制插件体系,允许开发通过指定的方式对zookeeper 的权限进行扩展。ACL 管理:设置ACL:通过 zkCli 脚本登录 zookeeper 服务器后,可以通过2种方式设置 ACL 权限。1.通过数据节点创建的同时进行 ACL 权限的设置create [-s] [-e] path data acl //create -e /zk-boook init digest:foo:密码:cdrwa2. 另外一种是使用 setAcl 命令单独对已经存在的数据节点进行 acl 设置setAcl path acl //setAcl /zo-book digest:foo:密码:cdrwaSuper 模式的用法:如果一个持久节点包含了 acl 权限,一旦创建者客户端退出或者已经不再使用,如何清理?就需要在 ACL 的 Super 模式下,进行处理了。2.序列化与协议1.Jute一个网络通信,首先需要解决的就是对数据的序列化和反序列化处理,还有良好的通信协议的设计。通信协议:基于 tcp/ip 协议,zookeeper 实现了自己的通信协议来完成客户端与服务端,服务端与服务端之间的网络通信。3.客户端zookeeper 的客户端主要由以下几个核心组件组成:1.ZooKeeper 实例:客户端入口2.ClientWatchManager : 客户端 Watcher 管理器3.HostProvider : 客户端地址列表4.ClientCnxn : 客户端核心线程,其内部又包含2个线程,即 SendThread 和 EventThread。前者是一个IO线程,负责zookeeper客户端和服务端之间的网络IO通信;后者是一个事件线程,主要负责对服务端事件进行处理。一次会话的创建过程:1.初始化阶段1.初始化 zookeeper 对象创建一个客户端的 Watcher 管理器:ClientWatchManager2.设置默认的 Watcher在构造方法中传入一个 Watcher 对象,客户端会将这个对象作为默认的 Watcher保存在 ClientWatchManager 中。3.构造 zookeeper 服务器列表地址管理器:HostProvider构造方法中传入的服务器地址,客户端将其放在服务器管理器 HostProvider 中。4.创建并初始化客户端网络连接器:ClientCnxnzookeeper 客户端首先创建一个网络连接器 ClientCnxn,用来管理客户端与服务器的网络交互。另外,还会初始化客户端两个核心队列:outgoiingQueue 和 pendingQueue,分别作为客户端的请求发送队列和服务端响应的等待队列。5.初始化SendThread 和 EventThread客户端会创建2个核心的网络线程 SendThread 和 EventThread,前者用于管理客户端和服务端之间的所有网络IO,后者用于进行客户端的事件处理。同时,客户端还会将 ClientCnxnSocket 分配给 SendThread 作为底层网络IO处理器,并初始化 EventThread 的待处理事件队列 waitingEvents,用于存放所有等待被客户端处理的事件。2.会话创建阶段6.启动 SendThread 和 EventThreadSendThread 会首先判断当前客户端的状态,进行一系列清理工作,为客户端发送 '会话创建' 请求做准备。7.获取一个服务器地址创建 tcp 连接之前,SendThread 首先获取服务器地址,通常是从 HostProvider 中随机提供,然后委托 ClientCnxnSocket 去创建 tcp 连接。8.创建tcp连接获取服务器地址后,ClientCnxnSocket 负责和服务器创建一个 tcp 连接9.构造 ConnectRequest 请求tcp 连接只是纯粹的从网络tcp层完成了客户端与服务端之间的 Socket 连接,但远未完成 zookeeper 客户端的会话创建。SendThread 会根据当前客户端的实际设置,构造出一个 ConnectRequest 请求,该请求代表了客户端试图与服务端创建一个会话。同时,zookeeper 客户端还会进一步将该请求包装成网络IO层的Packet 对象,放入请求发送队列 outgoingQueue 中去。10.发送请求   当客户端请求准备完毕后,就可以开始向服务端发送请求了。ClientCnxnSocket 负责从 outgoingQueue 中取出一个待发送的 Packet 对象,将其序列化为ByteBuffer 后,向服务端发送。3.响应处理阶段11.接收服务端响应ClientCnxnSocket 接收到服务端的响应后,会首先判断当前的客户端状态是否是 '已初始化',如果尚未完成初始化,那么就认为该响应一定是会话创建请求的响应,直接交由 readConnectResult 方法来处理响应。12.处理 ResponseClientCnxnSocket 会接收到服务端的响应进行反序列化,得到ConnectResponse 对象,并从中获取到 zookeeper 服务端分配的 sessionId。13.连接成功连接成功后,一方面需要通知 SendThread 线程,进一步对客户端进行会话参数的设置,包括 readTimeout 和 connectTimeout 等,并更新客户端状态,另一方面,需要通知地址管理器 HostProvider 当前成功连接的服务器地址。14.生成事件: SyncConnected-None为了能够让上层应用感知到会话的成功创建,SendThread 会生成一个事件 SyncConnected-None,代表客户端与服务端会话创建成功,并将该事件传递给EventThread 线程。15.查询 WatcherEventThread 线程收到事件后,会从 ClientWatchManager 管理器中查询出对应的 Watcher,针对 SyncConnected-None 事件,那么就直接找出步骤2中存储的默认 Watcher,然后将其放到 EventThread 中的 waitEvents 队列中去。16.处理事件EventThread 不断从 waitingEvents 队列中取出待处理的 Watcher 对象,然后直接调用该对象的 process 接口方法,以达到触发 Watcher 的目的。服务器地址列表:zookeeper 客户端允许我们将服务器的所有地址都配置在一个字符串上。192.168.0.1:2181,192.168.0.1:2181,192.168.0.1:2181Chroot:客户端隔离命名空间Chroot 特性,该特性允许每个客户端为自己设置一个命名空间。如果一个zookeeper客户端设置了Chroot,那么该客户端对服务端的任何操作,都会被限制在其自己的命名空间下。举个例子,如果我们希望为应用X分配 /apps/X 下的所有节点,那么该应用可以将其所有 zookeeper 客户端的 Chroot 设置为 /apps/X 的。一旦设置了Chroot之后,那么对这个客户端来说,所有的节点路径都以 /apps/X 为根节点,它和 zookeeper 发起的所有请求中相关的节点路径,都将是一个相对路径 --- 相对于/apps/X 的路径。例如,通过 zookeeper 的客户端API创建节点 /test_chroot,那么实际上在服务端创建的节点是 /apps/X/test_chroot。通过 Chroot,我们能够将一个客户端应用与 zookeeper 服务端的一个子树相对应,在那些多个应用共用一个 zookeeper 集群的场景下,这对于实现不同应用之间的互相隔离非常有帮助。客户端可以通过在 connectString 中添加后缀的方式来设置 Chroot : 192.168.0.1:2181,192.168.0.1:2181,192.168.0.1:2181/apps/XHostProvider : 地址列表管理器StaticHostProvider : zookeeper 客户端对 HostProvider 的默认实现。对 HostProvider 的几个设想:1.配置文件方式2.动态变更的地址列表管理器3.实现同机房优先策略  ClientCnxn : 网络IOClientCnxn 是 zookeeper 客户端的核心工作类,负责维护客户端与服务端之间的网络连接并进行一系列网络通信。ClientCnxnSocket : 底层 Socket 通信层SendThread : 客户端 ClientCnxn 内部的一个核心IO调度线程,用于管理客户端和服务端之间的所有网络IO操作。在 zookeeper 客户端的实际运行过程中,一方面,SendThread 维护了客户端与服务端之间的会话生命周期,其通过在一定的周期频率内向服务端发送一个 PING 包来实现心跳检测。同时,在会话周期内,如果客户端与服务端之间出现tcp连接断开的情况,那么就会自动且透明的完成重连操作。另一方面,SendThread 管理了客户端所有的请求发送和响应接收操作,其将上层客户端api操作转换成相应的请求协议并发送到服务端,并完成对同步调用的返回和异步调用的回调。同时,SendThread 还负责将来自服务端事件传递给 EventThread去。EventThread : EventThread 是客户端 ClientCnxn 内部的另外一个核心的线程,负责将客户端的事件处理,并触发客户端注册的 Watcher 监听。EventThread 中有一个 waitingEvents 队列,用于临时存放那些需要被触发的 Object,包括那些客户端注册的 Watcher 和异步接口中注册的回调器 AAsyncCallback。同时,EventThread 会不断从 waitingEvents 这个队列中取出 Object,识别出具体类型(Watcher 或者 AsyncCallback),并分别调用 process 和 processResult 接口方法来实现对事件的触发和回调。4.会话客户端与服务端之间的任何交互操作都与会话息息相关,这其中就包括临时节点的生命周期,客户端请求的顺序执行及Watcher通知机制等。zookeeper 的连接与会话就是客户端通过实例化zookeeper 对象来实现与客户端与服务器创建并保持tcp连接过程。1.会话状态zookeeper 会话在整个运行期间的生命周期中,会在不同的会话状态之间进行切换,这些状态一般可以分为 connecting,connected,reconnecting,reconnected,和close.一旦客户端开始创建zookeeper对象,那么客户端的状态就会变成 connecting,同时客户端开始从上述服务器地址列表中逐个选取ip地址来尝试网络连接,直到成功连接上服务器,然后客户端状态变成 connected。伴随着网络闪断或者是其他原因,客户端与服务端之间的连接会出现断开情况。一旦碰到这种情况,zookeeper 客户端会自动进行重连操作,同时客户端的状态变成connecting,直到重新连接上后,状态变成connected。因此,通常情况下,在 zookeeper 运行期间,客户端的状态总是介于 connecting 和 connected 之间。如果出现诸如会话超时,权限检查失败或是客户端主动退出程序等,那么客户端的状态为 close。2.会话创建Session 是 zookeeper 中的会话实体,代表了一个客户端会话,其包含以下4个基本属性:1.sessionID会话id,用来标识一个会话,每次客户端创建新会话的时候,zookeeper 都会为其分配一个全局唯一的 sessionID.2.TimeOut会话超时时间。客户端在创造zookeeper实例的时候,会配置一个sessionTimeout 参数用于指定会话的超时时间。zookeeper客户端向服务端发送这个超时时间后,服务器会根据自己的超时时间限制最终确定会话的超时时间。3.TickTime下次会话超时时间点。为了便于 zookeeper 对会话实行 '分桶策略' 管理,同时也是为了高效低耗的实现会话的超时检查与清理,zookeeper 会为每个会话标记下一个会话超时时间点。TickTime 是一个13位的long型数据,其值接近于当前时间加上 TimeOut,但不完全相等。4.isClosing该属性用于标记一个会话是否已经被关闭。通常当服务端检测到一个会话已经超时失效的时候,会将该会话的 isClosing 属性标记为 '已关闭',这样就能确保不再处理来自该会话的新请求了。sessionID 的生成算法:1.获取当前时间的毫秒表示2.左移24位3.右移8位4.添加机器标识: SID这里的id 就是 zookeeper 服务器的 SID,配置在 myid 文件中的值。然后,左移56位。 5.将步骤3和步骤4得到的2个64位表示的数值进行 '|' 操作经过上面5个步骤,就完成了一个 sessionID 的初始化。因为 ID 是一个机器编号,比如1,2,3,因此经过上面算法计算之后,我们就可以得到一个单机唯一的序列号。简单的讲,高8位确定了所在机器,后56位使用当前时间的毫秒数表示进行随机。为什么左移24位 : 为了防止负数出现。SessionTracker:是zookeeper服务端的会话管理器,负责会话的创建,管理和清理等工作。整个会话的生命周期都离不开 SessionTracker 的管理。每一个会话在 SessionTracker 内部都保留了3份,具体如下:1.sessionsById : 用于根据 sessionID 来管理 Session 实体2.sessionsWithTimeout : 用于根据 sessionID 来管理会话的超时时间。该数据结构和 zookeeper 内存数据库相连通,会被定期持久化到快照文件中。3.sessionSets : 用于根据下次会话超时时间点来归档会话,便于进行会话管理和超时检查。创建连接:服务端对于客户端的 '会话创建' 请求的处理,大体分为4个步骤:处理 ConnectRequest请求,会话创建,处理器链路处理和会话响应。3.会话管理分桶策略:zookeeper 的会话管理主要是由 SessionTracker 负责的,其采用了一种特殊的会话管理方式,我们称之为'分桶策略'。所谓分桶策略是指,将类似的会话放在同一区块中进行管理,以便于zookeeper 对会话进行不同区块的隔离处理以及同一区块的统一处理。zookeeper 将所有的会话都分配在了不同的区块之中,分配的原则是每个会话的'下次超时时间点'(ExpirationTime)。ExpirationTime是指该会话最近一次可能超时的时间点。对于一个新创建的会话而言,其会话创建完毕后,zookeeper 就会为其计算 ExpirationTime ,计算方式如下:ExpirationTime = CurrentTime + SessionTimeoutCurrentTime 指的是当前时间,毫秒;SessionTimeout 指的是指该会话设置的超时时间,毫秒。在 zookeeper 的实现中,还做了一个处理。zookeeper的leader服务器在运行期间会定时的进行会话超时检查,其时间间隔是 ExpirationInterval,单位是毫秒,默认值是 tickTime的值,默认情况下是2000毫秒进行一次会话超时检查。为了方便多个会话同时进行超时检查,完整的 ExpirationTime 的计算方式如下:ExpirationTime = CurrentTime + SessionTimeoutExpirationTime = (ExpirationTime_/ExpirationInterval + 1) * ExpirationInterval也就是说,ExpirationTime 值总是 ExpirationInterval 的整数倍。会话激活: 为了保持客户端会话的有效性,在 zookeeper 的运行过程中,客户端会在会话超时时间过期范围内向服务端发送ping请求来保持会话的有效性,我们俗称的'心跳检测'。同时,服务端需要不断的接收来自客户端的这个心跳检测,并且需要重新激活对应的客户端会话,我们将这个重新激活的过程称为 TouchSession。会话激活的过程,不仅能够使服务端检测到对应客户端的存活性,同时也能让客户端自己保持连接状态。Leader服务器激活客户端会话流程:1.检测该会话是否已经被关闭Leader 会检查该会话是否已经被关闭,如果该会话已经被关闭,那么不再继续激活该会话。2.计算该会话新的超时时间ExpirationTime_New如果该会话尚未关闭,那么就开始激活会话。首先需要计算出该会话下一次超时时间点。3.定位该会话的当前区块获取该会话老的超时时间 ExpirationTIme_Old,并根据该超时时间来定位到其所在的区块。4.迁移会话将该会话从老的区块中取出,放入 ExpirationTime_New 对应的新区块中。通过以上步骤,就基本完成会话激活的过程。只要客户端发送心跳检测,那么服务端就会进行一次会话激活。心跳检测是由客户端主动发起的,以ping的形式向服务端发送。但实际上,在 zookeeper 服务端的设计中,只要客户端有请求发送到服务端,那么就会触发一次会话激活。因此,总体来说,会出现2种情况的会话激活:1.只要客户端向服务端发送请求,包括读或者写请求2.如果客户端发现在 sessionTimeout/3 时间内尚未和服务器进行过任何通信,即没有向服务端发送任何请求,那么就会主动发起一次ping请求。会话超时检查:在 zookeeper 中,会话超时检查同样是 SessionTracker 负责的。SessionTracker 中有一个单独的线程专门进行会话超时检查,称之为'超时检查线程',其工作机制的核心思路是:逐个一次的对会话桶中剩下的会话进行清理。如果一个会话被激活,那么 zookeeper 会将其从上一个会话桶迁移到下一个会话桶中,例如 session.n 逐个会话,由于触发了会话激活,因此 zookeeper 会将其从 expirationTime 1 桶迁移到 expirationTime n 桶中去。于是,expirationTime 1 中留下的都是尚未被激活的会话。因此,超时检查线程的任务就是定时检查出这个会话桶中所有剩下的未被迁移的会话。那么超时检测线程是如何做到定时检查的?这里就和 zookeeper 会话的分桶策略紧密联系起来了。在分桶策略中,我们将 ExpirationInterval 的倍数作为时间点来分布会话,因此,超时检查线程只要在这些特定的时间点上进行检查即可,这样即提高了会话的检查效率,而且由于是批量清理,因此性能非常好 --- 这也是为什么 zookeeper 要通过分桶策略管理客户端会话的最主要原因。因为在实际生产中,一个 zookeeper 集群的客户端会话数可能非常多,逐个检查会话的方式非常耗费时间。4.会话清理大概7个步骤:1.标记会话状态为 '已关闭'由于整个会话清理的过程需要一段时间,因此为了保证在此期间不再处理来自该客户端的新请求,SessionTracker 会首先将该会话的 isClosing 属性标记为 true,这样,即使在整个会话清理期间收到该客户端的新请求,也无法继续处理了。2.发起 '会话关闭' 请求为了使得该会话的关闭操作在整个服务端集群中都生效,zookeeper 使用了提交 '会话关闭' 的请求方式,并立即交付给 PrepRequestProcessor 处理器进行处理。3.收集需要清理的临时节点在 zookeeper 中,一旦某个会话失效了,那么和该会话相关的临时节点都要被释放。因此,在清理节点之前,首先需要将服务器上所有和该会话相关的临时节点都整理出来。在 zookeeper 的内存数据库中,为每个会话都单独保存一份由该会话维护的所有临时节点集合,因此在会话清理阶段,只需要根据当前即将关闭的会话的sessionID从内存数据库中获取到这份临时节点列表即可。但是,在实际应用场景中,情况并没有那么简单,有如下的细节需要处理:在 zookeeper 处理会话关闭请求之前,正好有以下两类请求到达了服务端并正在处理中。1.节点删除请求,删除的目标节点正好是上述临时节点中的一个2.临时节点创建请求,创建的目标正好是上述临时节点中的一个对于这2类请求,其共同特点是事务处理尚未完成,因此还没有应用到内存数据库中,所以上述获取到的临时节点列表在遇到这2类事务请求的时候,会存在不一致的情况。假定我们当前获取的临时节点列表是 ephemerals,那么针对第一类请求,我们需要将所有的这些请求对应的数据节点路径从 ephemerals 中移除,以避免重复删除。针对第二类请求,我们需要将所有的请求对应的数据节点路径添加到 ephemerals 中去,以删除这些即将会被创建但是尚未保存到内存数据库中去的临时节点。4.添加 '节点删除' 事务变更完成该会话相关的临时节点收集后,zookeeper 会逐个将这些临时节点转换成 '节点删除' 请求,并放入事务变更队列 outstandingChanges 中去。5.删除临时节点在上面的步骤中,我们已经收集了所有需要删除的临时节点,并创建了对应的'节点删除'请求,FinalRequestProcessor 处理器会触发内存数据库,删除该会话对应的所有临时节点。6.移除会话完成节点删除后,需要将会话从 SessionTracker 中移除。主要就是从上面提到的三个数据结构(sessionById,sessionWithTimeout和sessionSets)中将该会话移除。7.关闭 NIOServerCnxn最后,从NIOServerCnxnFactory 找到该会话对应的 NIOServerCnxn,将其关闭。5.重连当客户端和服务端之间的网络连接断开的时,zookeeper 客户端会自动进行反复的重连,直到最终成功连接上zookeeper 集群中的一台机器。这种情况下,再次连接上服务端的客户端有可能处于以下2种状态之一:1.connected: 如果在会话超时时间内重新连接上zookeeper集群中的任意一台机器,那么被视为重连成功。2.expired: 如果是在会话超时时间以外重新连接上,那么服务端其实已经对该会话进行了会话清理操作,因此再次连接上的会话将被视为非法会话。在 zookeeper 中,客户端与服务端之间维持的是一个长连接,在 sessionTimeout 时间内,服务端会不断的检测该客户端是否还处于正常连接 --- 服务端会将客户端的每次操作视为一次有效的心跳检测来反复的进行会话激活。因此,在正常情况下,客户端的会话是一直有效的。然后,当客户端与服务端之间的连接断开之后,用户在客户端可能主要会看到2类异常:connection_loss(连接断开)和session_expired(会话过期)。连接断开:connection_loss有时候网络闪断,或者客户端与服务端之间出现问题导致连接断开,称为'客户端与服务器连接断开'现象,即 connection_loss。在这种情况下,zookeeper 客户端会自动从地址列表中重新逐个选取新的地址并尝试进行重新连接,直到最终成功连上服务器。会话失效: session_expiredsession_expired 指的是会话过期,通常发生在 connection_loss 期间。客户端和服务器连接断开之后,由于重新期间耗时过长,超过了会话超时时间(sessionTimeout)限制后还没有成功连接上服务器,那么服务器会认为这个会话已经结束了,就会开始进行会话清理。但另一方面,该客户端本身不知道会话已经失效了,并且其客户端状态还是disconnected。之后,如果客户端重新连接上了服务器,那么很不幸,服务端会告诉客户端该会话已经失效(sessioin_expire)了。在这种情况下,用户就需要重新实例化一个ZooKeeper 对象,并且看应用的复杂情况,重新恢复临时数据。会话转移:session_moved会话转移是指客户端会话从一台服务器转移到了另外一台服务器。假设客户端和服务器s1之间的连接断开之后,如果通过重连,成功连接上了新的服务器s2并且延续了有效会话,那么就可以说会话从s1转移到了s2。在 3.2.0 版本之后,zookeeper 明确提出了会话转移的概念,同时封装了 SessionMovedExceptioin 异常。之后,在处理客户端的请求的时候,会首先检查会话的所有者(Owner):如果客户端请求的会话 Owner 不是当前服务器的话,那么就会直接抛出 SessionMovedExceptioin。当然,由于客户端已经和这个服务器断开了连接,因此无法收到这个异常的响应。只有多个客户端使用相同的sessionId/sessionPasswd 创建会话时,才会收到这个异常。因为一旦有一个客户端会话创建成功,那么zookeeper服务器就会认为该 sessionId 对应的那个会话已经发生了转移,于是,等第二个客户端连接上服务器后,就被认为是'会话转移'了。5.服务器启动1.单机版服务器启动过程zookeeper 服务器的启动,大体可以分为5个步骤:配置文件解析,初始化数据管理器,初始化网络IO管理器,数据恢复和对外服务。预启动:1.统一由 QuorumPeerMain 作为启动类无论是单机还是集群启动zookeeper服务器,在 zkServer.sh 中,都配置使用了 org.apache.zookeeper.server.quorum.QuorumPeerMain 作为启动入口。2.解析配置文件 zoo.cfgzookeeper 首先会进行配置文件的解析,就是对 zoo.cfg 文件的解析。3.创建并启动历史文件清理器 DatadirCleanupManager从3.4.0版本开始,zookeeper 增加了自动清理历史数据文件的机制,包括对事务日志和快照数据文件进行定时清理。4.判断当前是集群还是单机模式zookeeper 根据步骤2解析出的集群服务器地址列表来判断当前是集群模式还是单机模式,如果是单机模式,那么就委托给ZooKeeperServerMain进行启动处理。5.再次进行配置文件 zoo.cfg 解析6.创建服务器实例 ZooKeeperServerorg.apache.zookeeper.server.ZooKeeperServer 是单机版 zookeeper 服务端最为核心的实体类。zookeeper服务器首先会进行服务器实例的创建,接下去的步骤则都是对服务器实例的初始化工作,包括连接器,内存数据库和请求处理器等组件的初始化。初始化:1.创建服务器统计器 ServerStatsServerStats 是 zookeeper 服务器运行时的统计器,包含了最基本的运行时信息。2.创建 zookeeper 数据管理器 FileTxnSnapLogFileTxnSnapLog 是 zookeeper 上层服务器和底层数据存储之间的对接层,提供了一系列操作数据文件的接口,包括事务日志和快照数据文件。zookeeper根据 zoo.cfg 文件中解析出来的快照数据目录 dataDir 和事务日志目录 dataLogDir 来创建 FileTxnSnapLog。3.设置服务器 tickTime 和会话超时时间限制4.创建 ServerCnxnFactory早期版本中,zookeeper 都是自己实现 NIO 框架,从3.4.0开始,引入了Netty。可以通过配置系统属性 zookeeper.serverCnxnFactory 来指定使用zookeeper 自己实现的 NIO 还是 Netty 框架作为 zookeeper 的服务端网络连接工厂。5.初始化 ServerCnxnFactoryzookeeper 首先会初始化一个Thread,作为整个 ServerCnxnFactory 的主线程,然后再初始化 NIO 服务器。6.启动 ServerCnxnFactory 主线程步骤5中已经初始化的主线程 ServerCnxnFactory 是主逻辑(run 方法)。需要注意的是,虽然这里zookeeper的NIO服务器已经对外开放端口,客户端能够访问到zookeeper的客户端服务端口2181,但是此时zookeeper服务器还是无法正常处理客户端的请求。7.恢复本地数据每次在zookeeper启动的时候,都需要从本地快照数据文件和事务日志文件中进行数据恢复。8.创建并启动会话管理器在zookeeper启动阶段,会创建一个会话管理器SessionTracker。它主要负责zookeeper服务端的会话管理,创建SessionTracker的时候,会初始化expirationInterval,nextExpirationTime和sessionWithTimeout(用于保存每个会话的超时时间),同时还会计算出一个初始化的sessionID。SessionTracker初始化完毕之后,zookeeper就会立即开始会话管理器的会话超时检查。9.初始化 zookeeper 的请求处理链zookeeper 的请求处理方式是典型的责任链模式的实现,在zookeeper服务器上,会有多个请求处理器依次来处理一个客户端请求。在服务器启动的时候,会将这些请求处理器串联起来形成一个请求处理链。单机版服务器的请求处理链主要包括PreRequestProcessor,SyncRequestProcessor和FinalReqeustProcessor三个请求处理器。10.注册 JMX 服务zookeeper 会将服务器运行时的一些信息以 JMX 的方式暴露给外部。11.注册 zookeeper 服务器实例在步骤6中,zookeeper已经将ServerCnxnFactory主线程启动,但是同时我们提到此时zookeeper依旧无法处理客户端请求,原因就是此时网络层尚不能访问zookeeper服务器实例。在经过后续步骤的初始化后,zookeeper服务器实例已经初始化完毕,只需要注册ServerCnxnFactory即可,之后,zookeeper 就可以对外提供正常的服务了。2.集群版服务器启动预启动:1.统一由 QuorumPeerMain 作为启动类2.解析配置文件 zoo.cfg3.创建并启动历史文件清理器DatadirCleanupManager4.判断当前是集群还是单机在集群模式种,由于已经在 zoo.cfg 中配置了多个服务器地址,因此此处选择集群模式启动zookeeper初始化:1.创建 ServerCnxnFactory2.初始化 ServerCnxnFactory3.创建zookeeper数据管理器FileTxnSnapLog4.创建QuorumPeer实例Quorum 是集群模式下特有的对象,是zookeeper服务器实例(ZooKeeperServer)的托管者,从集群层面看,QuorumPeer代表了zookeeper集群中的一台机器。在运行期间,QuorumPeer 会不断检测当前服务器实例的运行状态,同时根据情况发起 Leader选举。5.创建内存数据库ZKDatabaseZKDatabase 是 zookeeper 的内存数据库,负责管理zookeeper的所有会话记录以及DataTree和事务日志的存储。6.初始化QuorumPeerQuorumPeer是ZooKeeperServer的托管者,因此需要将一些核心组件注册到QuorumPeer中去,包括FileTxnSnapLog,ServerCnxnFactory和 ZKDatabase。同时 zookeeper 还会对 QuorumPeer配置一些参数,包括服务器地址列表,Leader选举算法和会话超时时间限制等。7.恢复本地数据8.启动ServerCnxnFactory主线程Leader 选举:1.初始化Leader选举Leader选举可以说是集群和单机模式启动zookeeper最大的不同点。zookeeper首先会根据自身的 SID(服务器ID),lastLoggedZxid(最新的ZXID)和当前服务器epoch(currentEpoch)来生成一个初始化的投票---简单的说,在初始化过程中,每个服务器都会给自己投票。然后,zookeeper会根据zoo.cfg中的配置,创建相应的Leader选举算法实现。在zookeeper中,默认提供了3种leader选举算法,分别是LeaderElection,AuthFastLeaderElection和FastLeaderElection,可以通过配置文件zoo.cfg中使用 electionAlg属性来指定,分别使用数字0~3表示。从3.4.0开始,zookeeper废弃了前2种leader选举算法,只支持FastLeaderElection选举算法了。在初始化阶段,zookeeper会首先根据leader选举所需的网络IO层QuorumCnxManager同时启动对 Leader选举端口的监听,等待集群中其他服务器创建连接。2.注册JMX服务3.检测当前服务器状态在运行期间,QuorumPeer的核心工作就是不断的检测当前服务器的状态,并作出相应的处理。在正常情况下,zookeeper服务器的状态在looking,leading 和 following/observing 之间进行切换。而在启动阶段,QuorumPeer的初始状态是looking,因此开始进行leader选举。4.Leader选举zookeeper的leader选举过程,简单的说,就是一个集群中所有的机器互相之间进行一系列的投票,选举产生最合适的机器成为leader,同时其他机器成为follower或者是Observer的集群机器角色的初始化过程。关于leader选举算法,简而言之,就是集群中哪个机器处理的数据越新(通常我们根据每个服务器处理过的最大ZXID来确定其数据是否更新),其越有可能成为leader。当然,如果集群中的所有机器处理的ZXID一致的话,那么SID最大的服务器成为leader。Leader和Follower启动期交互过程:1.创建leader服务器和follower服务器完成leader选举之后,每个服务器都会根据自己的服务器角色创建相应的服务器实例,并开始进入各自角色的主流程。2.leader服务器启动follower接收器 LearnerCnxAcceptor在zookeeper集群运行期间,leader服务器需要和所有其余的服务器保持连接以确定集群的机器存活情况。LearnerCnxAcceptor 接收器用于负责接收所有非leader服务器的连接请求。3.Learner服务器开始和Leader建立连接所有的 learner 服务器在启动完毕后,会从leader选举的投票结果中找到当前的leader服务器,并与之建立连接。4.Leader服务器创建 LearnerHandlerleader 接收到来自其他机器的连接创建请求后,会创建一个 LearnerHandler 实例。每个LearnerHandler实例都对应了一个leader与 learner 服务器之间的连接,其负责leader和 learner服务器之间几乎所有的消息通信和数据同步。5.向Leader注册当和leader建立起连接后,learner就会开始向leader进行注册 --- 所谓的注册,其实就是learner服务器将自己的基本信息发送给leader服务器,我们称之为LearnerInfo,包括当前服务器的SID和服务器处理的最新的ZXID.6.Leader解析Learner信息,计算新的epochleader 服务器在接收到learner的基本信息后,会解析出该 learner的 SID 和 ZXID,然后根据该 learner的 ZXID 解析出其对应的 epoch_of_learner,和当前leader服务器的 epoch_of_leader 进行比较,如果该 learner 的 epoch_of_learner 更大的话,那么就更新leader的 epoch:epoch_of_leader = epoch_of_learner + 1然后,LearnerHandler 会进行等待,直到过半的 learner 已经向 leader 进行了注册,同时更新了 epoch_of_leader 之后,leader就可以确定当前集群的 epoch 了。7.发送Leader状态计算出新的epoch之后,leader会将该信息以一个 LEADERINFO 消息的形式发送给 learner,同时等待learner 响应。8.Learner发送ACK信息follower 在收到来自leader的 LEADERINFO 消息后,会解析出 epoch 和 ZXID,然后向 leader反馈一个ACKEPOCH 响应。9.数据同步leader服务器接收到learner的这个ack消息后,就可以开始与其进行数据同步了。10.启动Leader和 Learner服务器当有过半的learner已经完成了数据同步,那么leader和learner服务器实例就可以开始启动了。Leader和Follower启动:1.创建并启动会话管理器2.初始化zookeeper的请求处理链和单机版服务器一样,集群模式下,每个服务器都会在启动阶段串联请求处理链,只是根据服务器的角色不同,会有不同的请求处理链路。3.注册JMX服务6.Leader 选举1.Leader选举概述服务器启动时期的Leader选举我们讲leader选举的时候,隐式条件便是 zookeeper 的集群规模至少是3台。在服务器集群初始化的时候,当有一台服务器启动,它是无法完成leader选举的。当第二台服务器启动的时候,此时两台机器已经能够进行互相通信,每台机器都试图找到一个leader,于是便进入了leader选举流程。1.每个Server会发出一个投票对于server1和server2,都会将自己作为leader服务器进行投票,每次投票包含的最基本的元素包括:所推举的服务器的myid和ZXID,我们以(myid,ZXID)的形式表示。因为是初始化阶段,因此无论是server1,还是server2,都会投给自己,即server1的投票为(1,0),server2的投票为(2,0),然后各自将这个投票发给集群中其他所有机器。2.接收来自各个服务器的投票每个服务器都会接收来自其他服务器的投票。集群中的每个服务器在接收到投票后,首先会判断该投票的有效性,包括检查是否是本轮投票,是否来自looking状态的服务器。3.处理投票在接收到来自其他服务器的投票后,针对每一个投票,服务器都需要将别人的投票和自己的投票进行pk,pk的规则如下:1.优先检查ZXID。ZXID比较大的服务器优先作为Leader2.如果ZXID相同的话,那么比较myid。myid比较大的服务器作为Leader服务器。4.统计投票每次投票之后,服务器都会统计所有的投票,判断是否已经有过半的机器收到相同的投票信息。对于server1和 server2服务器来说,都统计出集群中已经有2台机器接受了(2,0)这个投票信息。所谓的'过半'就是指大于集群机器数量的一半,即大于或者等于(n/2+1)。对于这里的3台机器构成的集群,大于等于2即达到过半的要求。那么,当server1和server2都收到相同的投票信息(2,0)的时候,即认为已经选出了leader。5.改变服务器状态一旦确定了Leader,每个服务器就会更新自己的状态:如果是Follower,那么就变更为FOLLOWING,如果是Leader,那么就变更为LEADING。服务器运行期间的Leader选举:在 zookeeper 集群正常运行期间,一旦选出一个leader,那么所有的服务器的集群角色一般不会再发生变化 --- 也就是说,leader服务器将一直作为集群的leader,即使集群中有非leader集群挂了或者是新机器加入集群也不会影响leader。但是一旦leader所在的机器挂了,那么整个集群将暂时无法对外服务,而是进入新一轮的leader选举。服务器运行期间的leader选举和启动时期的leader选举基本过程是一致的。1.变更状态当leader挂了之后,余下的非Observer服务器都会将自己的服务器状态变更为Llooking,然后开始进入leader选举流程。2.每个server会发出一个投票在这个过程中,需要生成投票信息(myid,ZXID)。因为是运行期间,因此每个服务器上的ZXID可能不同,我们假定server1的ZXID为123,而server3的ZXID为122。在第一轮的投票中,server1和server3都会投给自己,即分别产生投票(1,123),(3,122),然后各自将这个投票发给集群中的所有机器。3.接收来自各个服务器的投票4.处理投票对于投票处理,和服务器启动期间的处理规则一样。在这个例子中,server1的ZXID为123,server3的ZXID为122,那么显然,server1会成为leader。5.统计投票6.改变服务器状态2.Leader选举的算法分析在zookeeper中,提供了3种leader选举算法,分别是LeaderElection,UDP版本的FastLeaderElection和TCP版本的FastLeaderElection,可以通过配置文件zoo.cfg中使用 electionAlg 属性来指定,分别使用数字 0~3 来表示。0代表LeaderElection,这是一种纯udp实现的leader选举算法;1代表udp版本的FastLeaderElection,并且是非授权模式;2也代表udp版本的FastLeaderElection,但是使用授权模式;3代表tcp版本的FastLeaderElection。值得一提的是,从3.4.0版本开始,zookeeper废弃了0,1,2这3种leader选举算法,只保留了tcp版本的FastLeaderElection算法。术语解释:SID:服务器IDSID是一个数字,用来唯一标识一台zookeeper集群中的机器,每台机器不能重复,和myid的值是一致的。ZXID:事务IDZXID是一个事务id,用来唯一标识一次服务器状态的变更。在某一个时刻,集群中的每台机器的ZXID值不一定全部一致,这和zookeeper服务器对客户端'更新请求'的处理逻辑有关。Vote:投票leader选举,顾名思义必须通过投票来实现。当集群中的机器发现自己无法检测到leader机器的时候,就会开始尝试进行投票。Quorum:过半数机器这是整个leader选举算法中最重要的一个术语,我们可以理解为一个量词,指的是整个zookeeper集群中过半的机器数。quorum = (n/2 + 1)算法分析:进入Leader选举:当zookeeper集群中的一台服务器出现以下2种情况的时候,就会开始进入leader选举:1.服务器初始化启动2.服务器运行期间无法和leader保持连接当一台机器进入leader选举流程的时候,当前集群也可以处于以下2种情况:1.集群中本来就已经存在一个leader2.集群中确实不存在leader我们先看第一种情况。这种情况通常是集群中的某一台机器启动比较晚,在它启动之前,集群已经可以正常工作了,即已经存在一台leader服务器。针对这种情况,当该机器试图去选举leader的时候,会被告知当前服务器的leader信息。对于该机器来说,仅仅需要和leader机器建立连接,并进行状态同步即可。开始第一次投票:通常有2种情况会导致集群中不存在leader,一种情况是整个服务器刚刚初始化启动时,此时尚未产生一台leader服务器;另外一种情况是在运行期间当前leader所在的服务器挂了。无论是哪种情况,此时集群中所有机器都处于一种试图选举出一个leader的状态,我们把这种状态称为'looking',意思是说正在寻找leader。当一台服务器处于looking状态的时候,那么它就会向集群中所有的其他机器发送消息,我们称这个消息为'投票'。在这个投票消息中包含两个最基本的信息:所推举的服务器SID和ZXID,分别表示了被推举服务器的唯一标识和事务ID。投票变更:集群中的每台机器发出自己的投票后,也会接收到来自集群中其他机器的投票。每台机器都会根据一定的规则,来处理收到的其他机器的投票,并以此来决定是否需要变更自己的投票。这个规则也成为了整个Leader选举算法的核心所在。vote_sid:接收到的投票中所推举leader服务器的SIDvote_zxid:接收到的投票中所推举的leader服务器的ZXIDself_sid:当前服务器自己的sidself:zxid:当前服务器自己的zxid对于每次投票的处理,都是一个对(vote_sid,vote_zxid) 和(self_sid,self_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,那么同样坚持自己的投票,不做任何修改。确定Leader:经过这第二次投票后,集群中的每台机器都会再次收到其他机器的投票,然后开始统计投票。如果一台机器收到了超过半数的相同的投票,那么这个投票对应的SID机器即成为leader.上面例子,因为 zookeeper 集群的总机器数为5台,那么quorum = (5/2 + 1) = 3只要收到3个或以上的一致投票即可。在这里,server3,server4,server5 都投票(3,9),因此确定了server3为leader。小结:简单的说,通常哪台服务器上的数据越新,那么越有可能成为leader,原因很简单,数据越新,那么它的zxid越大,也就能够越能保证数据的恢复。如果集群中有几个服务器的zxid相同,那么sid较大的那台服务器成为leader。3.Leader选举的实现细节服务器状态:LOOKING:寻找leader状态FOLLLOWING:跟随者状态LEADING:领导者状态OBSERVING:观察者状态投票数据结构:Vote属性说明:1.id:被推举的leader的sid值2.zxid:被推举的leader的事务id3.electionEpoch:逻辑时钟,用来判断多个投票是否在同一轮选举周期中。该值在服务端是一个自增序列,每次进入新一轮投票后,都会对该值进行加1操作4.peerEpoch:被推举的leader的epoch5.state:当前服务器的状态QuorumCnxManager:网络IO每台服务器启动的时候,都会启动一个QuorumCnxManager,负责各台服务器之间的底层leader选举过程中的网络通信。消息队列:QuorumCnxManager 这个类内部维护了一系列的队列,用于保存接收到的,待发送的消息,以及消息的发送器。除接收队列以外,这里提到的所有队列都有一个共同特点---按sid分组形成队列集合,我们以发送队列为例来说明这个分组的概念。假设集群中除自身外还有4台机器,那么当前服务器就会为这4台服务器分别创建一个发送队列,互不干扰。1.recvQuoue:消息接收队列,用于保存那些从其他服务器接收到的消息2.queueSendMap:消息发送队列,用于保存那些待发送的消息。queueSendMap是一个Map,按照sid进行分组,分别为集群中的每台机器分配了一个单独队列,从而保证各台机器之间的消息发送互不影响。3.senderWorkerMap:发送器集合。每个SendWorker消息发送器,都对应一台远程zookeeper服务器,负责消息的发送。同样,在senderWorkerMap中,也按照sid进行了分组。4.lastMessageSent:最近发送过的消息。在这个集合中,为每个sid保留最近发送过的一个消息。建立连接:为了能够进行互相投票,zookeeper 集群中的所有机器都需要两两机建立起网络连接。QuorumCnxManager在启动的时候,会创建一个ServerSocket来监听Leader选举的通信端口(Leader选举的通信端口默认是3888)。开启端口监听后,zookeeper 就能够不断的接收到来自其他服务器'创建连接'请求,在接收到其他服务器的tcp连接请求时,会交由 receiveConnection 函数来处理。为了避免两台机器之间重复创建tcp连接,zookeeper 设计了一种建立tcp连接的规则:只允许sid大的服务器和其他服务器建立连接,否则断开连接。在ReceiveConnection函数中,服务器通过对比自己和远程服务器的sid值,来判断是否接受连接请求。如果当前服务器发现自己的sid更大,那么就会断开连接,然后自己主动去和远程服务器建立连接。一旦连接建立,就会根据远程服务器的sid来创建相应的消息发送器 SendWorker 和消息接收器 RecvWorker,并启动他们。消息接收与发送:消息的接收过程是由消息接收器RecvWorker来负责的。zookeeper 会为每个远程服务器分配一个单独的RecvWorker.因此,每个RecvWorker只需要不断的从tcp连接中读取消息,并将其保存到recvQueue队列中。由于zookeeper同样也为每个远程服务器单独分别分配了消息发送器SendWorker,那么每个SendWorker只需要不断的从对应的消息发送队列中获取出一个消息发送即可,同时将这个消息放入lastMessageSent中来作为最近发送过的消息。在 SendWorker的具体实现中,有一个细节:一旦zookeeper发现针对远程服务器的消息发送队列为空,那么这个时候就需要从lastMessageSent中取出最近发送过的消息来再次发送。这个细节主要是为了解决这样一类分布式问题:接收方在消息接收前,或者是在接收到消息后服务器挂了,导致消息尚未被正确处理。zookeeper能够保证接收方在处理消息的时候,会对消息重复进行正确的处理。FastLeaderElection:选举算法的核心部分:外部投票:特指其他服务器发来的投票内部投票:服务器自身当前的投票选举轮次:zookeeper服务器leader选举的轮次,即 logicalclockpk:指对内部投票和外部投票进行一个对比来确定是否需要改变内部投票投票管理:sendqueue:选票发送队列,用于保存待发送的选票recvqueue:选票接收队列,用于保存接收到的外部选票WorkerReceiver:选票接收器。该接收器会不断的从QuorumCnxManager中获取其他服务器发来的选举信息,并将其转换成一个选票,然后保存到recvqueue队列中去,在选票的接收过程中,如果发现该外部投票的选举轮次小于当前服务器,那么就直接忽略这个外部投票,同时立即发送自己的内部投票。当然,如果当前服务器并不是looking 状态,即已经选出了leader,那么也将忽略这个外部投票,同时将leader信息以投票的形式发送出去。另外,对于选票接收器,还有一个细节,如果接收到的消息来自Observer服务器,那么就忽略该消息,并将自己当前的投票发送出去。WorkerSender:选票发送器,会不断的从sendqueue队列中获取待发送的选票,并将其传递到底层QuorumCnxManager中去。算法核心:当zookeeper服务器检测到当前服务器状态变成looking时,就会触发leader选举,即调用lookForLeader方法来进行leader选举。1.自增选举轮次在FastLeaderElection中,有一个logicalclock属性,用于标识当前leader的选举轮次,zookeeper规定了所有有效的投票都必须在同一轮次中。zookeeper在开始新一轮的投票时,会首先对 logicalclock 进行自增操作。2.初始化选票在开始新一轮的投票之前,每个服务器都会首先开始初始化自己的选票。3.发送初始化选票   在完成选票的初始化后,服务器就会发起第一次投票。zookeeper会将刚刚初始化好的投票放入sendqueue队列中,由发送器WorkerSender负责发送出去。4.接收外部投票每台服务器都会不断的从recvqueue队列中获取外部投票。如果服务器发现无法获取任何外部投票,那么就会立即确认自己是否和集群中其他服务器保持这有效连接。如果发现没有建立连接,那么就会马上建立连接。如果已经建立连接了,那么就再次发送自己当前的内容投票。5.判断选举轮次当发送完初始化选票之后,接下来就要开始外部投票处理了。在处理外部投票的时候,会根据选举轮次来进行不同的处理。外部投票的选举轮次大于内部投票:会立即更新自己的选举轮次(logicalclock),并且清空所有已经收到的选票,然后使用初始化的投票进行pk以确定是否变更内部投票,最终将内部投票发送出去。外部投票的选举轮次小于内部投票:zookeeper会直接忽略该外部投票,不做任何处理,并返回步骤4.外部投票的选举轮次等于内部投票:开始pk。只有在同一个选举轮次的投票才是有效的投票。6.选票pkFastLeaderElection.totalOrderPredicate 方法的核心逻辑。选票pk是为了确定当前服务器是否需要变更投票,主要从选举的轮次,zxid和sid三个因素来考虑。具体条件如下:在选票pk的时候依次判断,符合任意一个条件就需要进行投票变更。1.如果外部投票中被推举的leader服务器的选举轮次大于内部投票,那么就需要进行投票变更。2.如果选举轮次一致的话,那么就对比两者的zxid。如果外部投票的zxid大于内部投票,那么就需要进行投票变更。3.如果两者的zxid一致的话,就对比sid。如果外部投票的sid大的话,就进行投票变更。7.变更投票通过投票pk后,如果确定了外部投票优于内部投票,那么就进行投票变更---使用外部投票的选票信息来覆盖内部投票。变更完成后,再次将这个变更后的内部投票发送出去。8.选票归档无论是否进行了投票变更,都会将刚刚收到的那份外部投票放入'选票集合'recvset中进行归档。recvset用于记录当前服务器在本次的leader选举中收到的所有外部投票。按照服务器对应的sid来区分,如 {(1,vote1),(2,vote2)...}9.统计投票完成了选票归档之后,就可以进行投票统计了。统计投票是为了统计集群中是否已经有过半的服务器认可了当前的内部投票。如果确定已经有过半的服务器认可了内部投票,则终止投票。否则返回步骤4.10.更新服务器状态统计投票后,如果已经确定可以终止投票,那么就开始更新服务器状态。服务器会首先判断当前被过半服务器认可的投票对应的leader信息是否是自己,如果是的话,那么就会将自己的服务器状态变更为 leading。如果不是的话,那么就会根据情况来确定是following还是observing。有一个细节需要注意的是,在完成步骤9之后,如果统计投票发现已经有过半的服务器认可了当前的选票,这个时候,zookeeper并不会立即进入步骤10来更新服务器状态,而是会等待一段时间(默认是200毫秒)来确定是否有新的更优的选票。7.各服务器角色介绍1.Leaderleader 服务器是整个zookeeper集群工作机制的核心,主要工作有2个:1.事务请求的唯一调度和处理者,保证集群事务处理的顺序性2.集群内部各服务器的调度者请求处理链:使用责任处理链来处理每一个客户端请求是zookeeper的一大特色。前后一共7个请求处理器组成了leader服务器的请求处理链。1.PrepRequestProcessor2.ProposalRequestProcessor3.SyncRequestProcessor4.AckRequestProcessor5.CommitProcessor6.ToBeCommitProcessor7.FinalRequestProcessorLearnerHandler:为了保持整个集群内部的实时通信,同时也是为了确保可以控制所有的follower/observer 服务器,leader服务器会与每一个 follower/observer 服务器都建立一个tcp 长连接,同时也会为了每个follower/observer服务器都创建一个名为 LearnerHandler 的实体。LearnerHandler 是 zookeeper 集群中 Learner 服务器的管理器,主要负责 follower/observer 服务器和 Leader 服务器之间的一系列网络通信,包括数据同步,请求转发和Proposal 提议的投票等。Learner 服务器中保存了所有 follower/observer 对应的 LearnerHandler。2.FollowerFollower 是zookeeper 集群状态的跟随者,其主要工作有以下3个:1.处理客户端非事务请求,转发事务请求给Leader服务器2.参与事务请求Proposal的投票3.参与Leader选举投票和Leader服务器的请求处理链最大的不同点在于,Follower 服务器的第一个处理器换成了 FollowerRequestProcessor 处理器,同时由于不需要处理事务请求的投票,因此也没有了 ProposalRequestProcessor 处理器。1.FollowerReqeustProcessor2.SendAckRequestProcessor3.Observer从字面意思看该服务器充当了一个观察者的角色 --- 其观察zookeeper集群的最新状态变化并将这些状态变更都同步过来。observer服务器在工作原理上和follower基本是一致的。对于非事务请求,都可以进行独立的处理,而对于事务请求,则会转发给Leader服务器进行处理。和follower唯一的区别在于,observer不参与任何形式的投票,包括事务请求Proposal的投票和Leader选举投票。简单的说,Observer服务器只提供非事务服务,通常用于在不影响集群事务处理能力的前提下提升集群的非事务处理能力。4.集群间消息通信在整个 zookeeper 集群的工作过程中,都是由leader服务器来负责进行各服务器之间的协调,同时,各服务器之间的网络通信,都是通过不同类型的消息传递来实现的。zookeeper 的消息类型大体上可以分为4类:数据同步型,服务器初始化型,请求处理型和会话管理型。1.数据同步型指的是Learner和 Leader服务器进行数据同步的时候,网络通信所用到的消息,通常有 DIFF,TRUNC,SNAP和UPTODATE 4种。2.服务器初始化型指的是在整个集群或是某些新机器初始化的时候,Leader和 Learner之间互相通信所使用的消息类型,常见的有 OBSERVERINFO,FOLLOWERINFO,LEADERINFO,ACKEPOCH和NEWLEAAR 5种。3.请求处理型指的是在进行请求处理的过程中,Leader和Learner服务器之间互相通信所使用的消息类型,常见的有REQUEST,PROPOSAL,ACK,COMMIT,INFORM 和 SYNC 6种。4.会话管理型指zookeeper在进行会话管理的过程中,和Learner服务器之间互相通信所使用的消息,常见的有PING 和 REVALIDATE 2种。8.请求处理zookeeper 服务端对于会话创建的处理,大体可以分为请求接收,会话创建,预处理,事务处理,事务应用和会话响应 6 大环节。1.会话创建请求1.请求接收1.IO层接收来自客户端的请求2.判断是否是客户端'会话创建'请求3.反序列化ConnectRequest请求4.判断是否是ReadOnly客户端5.检查客户端ZXID6.协商sessionTimeout7.判断是否需要重新创建会话2.会话处理8.为客户端生成sessioinID9.注册会话10.激活会话11.生成会话密码3.预处理12.将请求交给zookeeper的PreRequestProcessor 处理器进行处理13.创建请求事务头14.创建请求事务体15.注册于激活会话4.事务处理16.将请求交给 ProposalRequest 处理器1.Sync流程2.Proposal流程1.发起投票2.生成提议Proposal3.广播提议4.收集投票5.将请求放入toBeApplied队列6.广播COMMIT消息3.Commit流程1.将请求交付给CommitProcessor处理器2.处理queueRequests队列请求3.标记nextPending4.等待Proposal投票5.投票通过6.提交请求5.事务应用17.交给FinalRequestProcessor 处理器18.事务应用19.将事务请求放入队列:commitProposal6.会话响应20.统计处理21.创建响应ConnectResponse22.序列化ConnectResponse23.IO层发送响应给客户端2.SetData请求服务端对于SetData请求的处理,大致分为4个步骤,分别是请求的预处理,事务处理,事务应用和请求响应。预处理1.IO层接收来自客户端的请求2.判断是否是客户端'创建会话'请求3.将请求交给zookeeper的PrepRequestProcessor处理器进行处理4.创建请求事务头5.会话检查6.反序列化请求,并创建ChangeRecord记录7.ACL检查8.数据版本检查9.创建请求事务体SetDataTxn10.保存事务操作到outstandingChanges队列中去事务处理事务应用11.交付给 FinalRequestProcessor 处理器12.事务应用13.将事务请求放入队列:commitProposal请求响应14.统计处理15.创建响应体SetDataResponse16.创建响应头17.序列化响应18.IO层发送响应给客户端3.事务请求转发在事务请求的处理过程中,为了保证事务请求被顺序执行,从而确保zookeeper集群的数据一致性,所有的事务请求必须由Leader服务器来处理。zookeeper 实现了非常特别的事务请求转发机制:所有非leader服务器如果接收到了来自客户端的事务请求,那么必须将其转发给Leader服务器处理。4.GetData请求GetData 非事务请求的处理流程。大致分为3个步骤,分别是请求的预处理,非事务处理和请求响应。预处理1.IO层接收来自客户端的请求2.判断是否是客户端'会话创建'请求3.将请求交给zookeeper的PrepRequestProcessor 处理器进行处理4.会话检查非事务处理5.反序列化GetDataRequest请求6.获取数据节点7.ACL检查8.获取数据内容和stat,注册Watcher请求响应9.创建响应体GetDataResponse10.创建响应头11.统计处理12.序列化响应13.IO层发送响应给客户端9.数据与存储在 zookeeper 中,数据存储分为两部分:内存数据存储与磁盘数据存储。1.内存数据zookeeper 就像一个内存数据库一样。在这个内存数据库中,存储了整颗树的内容,包括所有的节点路径,节点数据以及ACL信息等,zookeeper 会定时将这个数据存储到磁盘上。DataTree:DataTree 是 zookeeper 内存数据存储的核心,是一个'树'的数据结构,代表了内存中的一份完整的数据。DataTree不包含任何与网络,客户端连接以及请求处理等相关的业务逻辑。DataNode:DataNode 是数据存储的最小单元,内部除了保持节点的数据内容(data[]),ACL列表(acl)和节点状态(stat)之外,正如最基本的数据结构中对树的描述,还记录了父节点(parent)的引用和子节点列表(children)2个属性。同时,DataNode 还提供了对子节点列表操作的各个接口。ZKDatabase:ZKDatabase 是 zookeeper 的内存数据库,负责管理 zookeeper 的所有会话,DataTree存储和事务日志。ZKDatabase 会定时向磁盘dump快照数据,同时在 zookeeper 服务器启动的时候,会通过磁盘上的事务日志和快照数据文件恢复成一个完整的内存数据库。2.事务日志文件存储:在部署 zookeeper 集群的时候需要配置一个目录:DataDir,这个目录是zookeeper 中默认存储事务日志文件的,其实zookeeper中可以为事务日志单独分配一个文件存储目录:dataLogDir。如果我们确定 dataLogDir 为 /home/admin/zkData/zk_log,那么zookeeper在运行的时候就会在该目录下创建一个名为 version-2 的子目录,该目录确定了当前zookeeper使用的事务日志格式版本号。也就是说,等到下次zookeeper版本对事务日志格式进行变更,这个目录也会所有变更。log.300000001log.300000002不难发现这些文件都具有以下2个特点:1.文件大小都出奇的一致,都是64mb2.文件名后缀非常有规律,都是一个十六进制数字,同时随着文件修改时间的推移,这个十六进制后缀变大。该后缀其实是一个事务id:zxid,并且是写入该事务日志第一条事务记录的zxid。使用zxid作为文件后缀,可以帮助我们迅速定位到某一事务操作所在的事务日志。同时,使用zxid作为事务日志的后缀的另外一个好处是,zxid由2部分组成,高32位代表当前leader周期(epoch),低32位则是真正的操作序列号。因此,将zxid作为文件后缀,我们就可以清楚的看出当前运行 zookeeper 的 leader 周期。日志格式:java LogFormatter 事务日志文件日志写入:FileTxnLog 负责维护事务日志对外的接口,包括事务日志的写入和读取等。将事务操作写入事务日志的工作主要由 append 方法来负责:public synchronized boolean append(TxnHeader hdr, Record txn)事务日志的写入大概分为6个步骤:1.确定是否有事务日志可写2.确定事务日志文件是否需要扩容(预分配)3.事务序列化4.成成Checksum5.写入事务日志文件流6.事务日志刷入磁盘日志截断:在 zookeeper 的运行过程中,可能会出现这样的情况,非leader机器上记录的事务id(我们称之为peerLastZxid)比leader服务器大,这是一个非法的运行时状态,同时,zookeeper遵守一个原则:只要集群中存在leader,那么所有的机器都必须与该leader的数据保持同步。因此,一旦碰到上述情况,leader 就会发送TRUNC 命令给这个机器,要求其进行日志截断。learner服务器在接收到该命令后,就会删除所有包含或大于peerLastZxid的事务日志。3.snapshot --- 数据快照数据快照用来记录 zookeeper 服务器上某一时刻的全量内存数据内容,并将其写入到指定的磁盘文件中。文件存储和事务日志类似,zookeeper 的快照数据也使用特定的磁盘目录进行存储,也可以用 dataDir 属性进行配置。在 version-2 下面会看到:snapshot.2c021384c4该后缀标识了本次数据快照开始时刻的服务器最新zxid,这个后缀非常重要,在数据恢复阶段,zookeeper 会根据该 zxid 来确定数据恢复的起始点。和事务日志不同,数据快照文件没有采用'预分配'机制,因此不会像事务日志那样内容中可能包含大量的'0'。每个快照数据文件的所有内容都是有效的,因此该文件大小一定程度上能够反馈当前 zookeeper 内存中全量数据的大小。存储格式java SnapshotFormatter 快照数据文件数据快照FileSnap 负责维护快照数据对外的接口,包括快照数据的写入和读取等。数据的写入过程 --- 将内存数据库写入快照数据文件中其实是一个序列化过程。针对客户端的每一次事务操作,zookeeper 都会将它们记录到事务日志中,当然,zookeeper 同时也会将数据变更应用到内存数据库中。另外,zookeeper会在进行若干次事务日志记录之后,将内存数据库的全量数据dump到本地文件中,这个过程就是数据快照。可以使用snapCount参数来配置每次数据快照之间的事务操作次数,即zookeeper会在 snapCount次事务日志记录后进行一次数据快照。数据快照的过程:1.确定是否需要进行数据快照2.切换事务日志文件3.创建数据快照异步线程4.获取全量数据和会话信息5.生成快照数据文件名6.数据序列话4.初始化在 zookeeper 服务器启动期间,首先会进行数据初始化工作,用于将存储在磁盘上的数据文件加载到zookeeper服务器内存中。初始化流程:数据的初始化工作,其实就是从磁盘中加载数据的过程,主要包括了从快照文件中加载快照数据和根据事务日志进行数据订正的两个过程。1.初始化FileTxnSnapLog2.初始化ZKDatabase3.创建PlayBackListerner监听器4.处理快照文件5.获取最新的100个快照文件6.解析快照文件7.获取最新的zxid8.处理事务日志9.获取所有的 zxid_for_snap 之后提交的事务10.事务应用11.获取最新的zxid12.校验epochPlayBackListener:是一个事务应用监听器,用于在事务应用过程中的回调:每当成功将一条事务日志应用到内存数据库中后,就会调用这个监听器。5.数据同步整个集群完成leader选举之后,learner会向leader服务器进行注册。当learner服务器向leader完成注册后,就进入数据同步环节。简单的说,数据同步过程就是leader服务器将那些没有在learner服务器上提交过的事务请求同步给learner服务器。获取learner状态:在注册learner的最后阶段,learner服务器会发送给 leader服务器一个 ACKEPOCH 数据包,leader 会从这个数据包中解析出该learner的 currentEpoch和lastZxid。数据同步初始化:在开始数据同步之前,leader服务器会进行数据同步初始化,首先会从zookeeper的内存数据库中提取出事务对应的提议缓存队列:orioisaks,同时完成以下三个ZXID值的初始化:1.peerLastZxid: 该learner服务器最后处理的zxid2.minCommittedLog: leader服务器提议缓存队列 committedLog 中的最小 zxid3.maxCommittedLog: leader服务器提议缓存队列 committedLog 中的最大zxidzookeeper 集群数据同步分为4类,分别是 直接差异化同步(DIFF同步),先回滚再差异化同步(TRUNC+DIFF同步),仅回滚同步(TRUNC同步)和全量同步(SNAP同步)。在初始化阶段,leader服务器会优先初始化以全量同步方式同步数据 --- 当然,这并非最终的数据同步方式,在以下步骤种,会根据leader和learner服务器之间的数据差异情况来决定最终的数据同步方式。直接差异化同步(DIFF 同步):先回滚再差异化同步(TRUNC+DIFF同步):仅回滚同步(TRUNC同步):全量同步(SANP同步):

7.从Paxos到Zookeeper分布式一致性原理与实践---Zookeeper 技术内幕相关推荐

  1. 《从Paxos到zookeeper分布式一致性原理与实践》笔记

    <从Paxos到zookeeper分布式一致性原理与实践>笔记 文章目录 <从Paxos到zookeeper分布式一致性原理与实践>笔记 一.概念 二.一致性协调 2.1 2P ...

  2. 《从Paxos到zookeeper分布式一致性原理与实践》

    <从Paxos到zookeeper分布式一致性原理与实践> 一.概念 ACID: Automaticy.consistency.isolation. Durability CAP: con ...

  3. [201502][从 Paxos 到 ZooKeeper][分布式一致性原理与实践][倪超][著]

    [201502][从 Paxos 到 ZooKeeper][分布式一致性原理与实践][倪超][著] http://zookeeper.apache.org 第 1 章 分布式架构 1.1 从集中式到分 ...

  4. 《从Paxos到Zookeeper 分布式一致性原理与实践》

    第1章 分布式架构 1.1 从集中式到分布式 1.1.1 集中式的特点 集中式的特点:部署结构简单(因为基于底层性能卓越的大型主机,不需考虑对服务多个节点的部署,也就不用考虑多个节点之间分布式协调问题 ...

  5. 《从Paxos到ZooKeeper 分布式一致性原理与实践》读书笔记

    一.分布式架构 1.分布式特点 分布性 对等性.分布式系统中的所有计算机节点都是对等的 并发性.多个节点并发的操作一些共享的资源 缺乏全局时钟.节点之间通过消息传递进行通信和协调,因为缺乏全局时钟,很 ...

  6. 《从paxos到zookeeper分布式一致性原理与实践》读书笔记--第二章一致性协议--二阶段提交

    在分布式系统中,每一台机器节点虽然能够知道自己在进行事务操作过程中的结果是成功还是失败但无法直接获取其他分布式系欸但的操作结果.因此,当一个事务需要跨越多个分布式节点的时候为了保持事务的ACID特性, ...

  7. 从Paxos到Zookeeper:分布式一致性原理与实践

    网站 更多书籍点击进入>> CiCi岛 下载 电子版仅供预览及学习交流使用,下载后请24小时内删除,支持正版,喜欢的请购买正版书籍 电子书下载(皮皮云盘-点击"普通下载" ...

  8. Zookeeper分布式一致性原理(四):Zookeeper简介

    zookeeper是一个典型的分布式数据一致性的解决方案,分布式应用程序可以基于它实现数据发布/订阅.负载均衡.命名服务.分布式协调/通知.集群管理.master选举.分布式锁和分布式队列等.Zook ...

  9. Zookeeper分布式一致性原理(八):Zookeeper典型应用场景

    1. 简介 Zookeeper是一个高可用的分布式数据管理和协调框架,并且能够很好的保证分布式环境中数据的一致性.在越来越多的分布式系统(Hadoop.HBase.Kafka)中,Zookeeper都 ...

  10. Zookeeper分布式一致性原理(二):一致性协议

    为了解决分布式一致性问题,在长期的研究过程中,提出了一大批经典的一致性协议和算法,其中最著名的就是2PC和3PC以及Paxos算法了. 1. 2PC和3PC 在分布式系统中,每个节点都明确知道自己事务 ...

最新文章

  1. SQL 性能起飞了!
  2. Prometheus 如何做到“活学活用”,大牛总结的避坑指南
  3. HTML5学习笔记简明版(10):过时的元素和属性
  4. hdu4503 概率
  5. 面试官:说出几个你熟悉的 Zookeeper 命令
  6. JS键盘事件(非常详细)
  7. every function in scala is an instance of a class
  8. 自定义AlertDialog控件的使用(AndroidStudio)
  9. 游戏一般用什么编程语言开发?
  10. 词法分析器【编译原理】
  11. 嵌入式Linux使用TFT屏幕:使用TinyDRM点亮ST7789V屏幕
  12. PX4 mixer load
  13. html怎么拼图没有缝隙,【答疑】ps怎么拼图不留缝隙?急需答案!! - 视频教程线上学...
  14. linux中关于磁盘配额的描述,Linux中的磁盘配额
  15. 微信小程序+.NET(九) 小程序之简单的广告拦截
  16. 关于PostgreSQL执行计划中的Bitmap Heap Scan、Bitmap Index Scan、Recheck Cond
  17. 微信公众号之错误返回码
  18. windows 2008 server 服务器远程桌面连接会话自动注销,在服务器上开掉的软件全部自动关闭的解决办法...
  19. YII模板(前台显示)详细分析
  20. 使用JDBC的基本步骤

热门文章

  1. 你一点要认真了解 C++中的模式匹配介绍
  2. 来教你用什么泡脚好,泡脚的好处有那些?
  3. OFbiz--HelloWorld
  4. TOMCAT如何配置域名,可以用本地服务进行测试
  5. java中的正则操作总结
  6. EXPLAIN PLAN用法小议
  7. 全网最雕10名月薪超过5W的程序员,和他们的公众号!
  8. Julia语言初体验
  9. 一、公安备案与经营性备案
  10. 把数字倒序的几种方法(二更,增加了负数反序的情况)(c++)