ZooKeeper数据存储与数据同步机制

2023-11-13

ZooKeeper中,数据存储分为两部分,内存数据(ZKDatabase)与磁盘数据(事务日志 + 事务快照)。

ZKDatabase

ZooKeeper的数据模型是一棵树。

而从使用角度看,ZooKeeper就像一个内存数据库一样,在内存数据库中,存储了整棵树的内容,包括所有的节点路径、节点数据以及ACL信息等。

ZKDatabase

ZKDatabase是ZooKeeper的内存数据库,负责管理ZooKeeper的所有会话、DataTree存储和事务日志。

ZKDatabase会定时向磁盘dump快照数据,同时在ZooKeeper服务器启动的时候,会通过磁盘上的事务日志和快照数据文件恢复成一个完整的内存数据库。

DateTree

DateTree是ZooKeeper内存数据存储的核心。

DataTree:
- nodes: ConcurrentHashMap<String, DataNode>
- ephemerals: ConcurrentHashMap<Long, HashSet<String>>
- dataWatches: WatchManager
- childWatches: WatchManager
-----------------------------------------------------
+ convertAcls(List<ACL>): Long
+ convertLong(Long): List<ACL>
+ addDataNode(String, DataNode): void
+ createNode(String, byte, List<ACL>, long, int, long, long): String
+ deleteNode(String, long)
+ setData(String, byte, int, long, long)
+ getData(String, Stat, Watcher)
+ ......

ConcurrentHashMap<String, DataNode> nodes存储所有ZooKeeper节点信息,Key为节点路径,Value为DataNode。

ConcurrentHashMap<Long, HashSet<String>> ephemerals存储所有临时节点的信息,便于实时访问和及时清理。Key为客户端SessionID,Value为该客户端创建的所有临时节点路径集合。

DataNode

DataNode 是数据存储的最小单元,内部保存节点的数据内容(data[])、ACL列表(acl)和节点状态(stat),同时记录父节点(parent)的引用和子节点列表(children)。

DataTree:
- parent: DataNode
- data: byte[]
- acl: Long
- stat: StatPersisted
- children: Set<String>
-----------------------
+ addChild(): boolean
+ removeChild(): boolean
+ setChildren(): void
+ getChildren(): Set<String>
+ copyStat(Stat): void
+ deserialize(InputArchive, String)
+ Serialize(OutputArchive, String)
+ ......

事务日志

文件存储

配置目录

事务日志文件默认存储于dataDir

也可以为事务日志单独配置文件存储目录dataLogDir

存储文件

ZooKeeper运行一段时间后,在配置的目录中将创建子目录version-2:

{dataLogDir配置目录}/version-2

version-2是当前ZooKeeper使用的事务日志格式版本号。

version-2中生成日志文件如下图:

06f1a4913ce0e8bd048b993d9ff3e5ec.png

文件名

事务日志文件的文件名是一个十六进制数字,高32位为Leader选举周期(epoch),低32为是事务ZXID。

日志格式

日志文件是二进制格式存储,ZooKeeper提供了解码工具:

Java LogFormatter 日志文件

第一行:

ZooKeeper Transactional Log File with dbid 0 txnlog format version 2

事务日志文件头信息。

第二行:

..11:07:41 session 0x144699552020000 cxid 0x0 zxid 0x300000002 createSession 3000

一次客户端会话创建的事务操作日志。
事务操作时间 + 客户端会话ID + CXID + ZXID + 操作类型 + 会话超时时间

第三行:

..11:08:40 session 0x144699552020000 cxid 0x2 zxid 0x300000003 create `/test_log,#7631,v{s{31,s{'world',anyone}}},F,2

节点创建操作的事务操作日志。
事务操作时间 + 客户端会话ID + CXID + ZXID + 操作类型 + 节点路径 + 节点数据内容

以后几行都类似。

日志写入

事务写入事务日志的操作由FileTxnLogappend方法完成:

public synchronized boolean append(TxnHeader hdr, Record txn)

确定是否有事务日志可写

ZooKeeper第一次写入事务日志,或者上一个事务日志写满时,服务器没有和任何日志文件关联, 此时需要使用当前待写入事务的ZXID作为后缀创建新的事务日志文件,并写入。

确定事务日志文件是否需要扩容

为了避免开辟新磁盘块的开销,ZooKeeper使用事务文件预分配的方式。

文件初创建时,会预分配64MB磁盘块,并且当检测到当前事务文件剩余空间不足4KB时,文件大小将被增加64MB,并使用0填充被扩容的文件空间。

zookeeper.preAllocSize设置预分配大小。

写入文件

事务序列化、计算Checksum后,事务头、事务体和Checksum值将被写入文件流,放入streamsToFlush中。

zookeeper.forceSync设置是否强制将streamsToFlush中的字节流马上写入磁盘。

日志截断

在ZooKeeper中,Leader服务器上的事务ID(Zxid)必须大于或等于非Leader服务器上的事务ID(peerLastZxid)。

当发现非Leader服务器上的Zxid比Leader服务器上的Zxid大时,Leader会发送TRUNC命令给该机器,进行日志截断,删除所有包含或大于peerLastZxid的事务日志文件,并重新与Leader进行同步。

snapshot数据快照

数据快照用来记录ZooKeeper服务器上某一时刻的全量内存数据内容,并将其写入到指定的磁盘文件中。

文件存储

快照数据的存储和事务日志文件类似。

  1. 通过dataDir属性配置文件存储位置

  2. 建立版本目录

  3. 文件名高32位为Leader选举纪元(epoch),低32位为快照开始时最新ZXID。

  4. 二进制存储,提供SnapshotFormatter解码工具

snapshot数据快照因为是一次全量写入,因此不需要预分配机制。

快照过程

FileSnap负责维护快照数据的接口,包括快照数据写入和读取。

确定是否需要进行数据快照

ZooKeeper每隔若干次事务日志记录后,进行一次数据快照。通过snapCount参数进行配置。

如果当前已经记录的事务日志数量logCount满足以下“过半随机”条件时,进行一次快照:

randRoll = random(1, snapCount / 2);
logCount > (snapCount / 2 + randRoll);

snapCount默认为100000,那么ZooKeeper会在50000到100000次事务日志记录后进行一次快照。

  1. 切换事务日志文件

重新创建一个新的事务日志

事务文件不能无限制增加(按64M增量),当事务执行数目满足snapCount过半随机时,会切换新的事务文件。

因此快照和事务文件其实是相互影响的一体的,并不是独立的。

  1. 创建数据快照异步线程

  2. 生成快照数据文件名

ZooKeeper根据当前Leader纪元(epoch)及当前ZXID生成快照数据文件名。

  1. 序列化ZKDatabase中DataTree及会话信息,生成Checksum,写入快照文件。

内存数据初始化

ZooKeeper服务器启动时,会进行数据初始化工作,将磁盘上的数据文件加载到ZooKeeper服务器内存中。

3ce536f6c0f51ea780cfb004a2a4db1c.png

初始化FileTxnSnapLog

FileTxnSnapLog是ZooKeeper事务日志和快照数据访问层。包括FileTxnLog和FileSnap分别为事务日志管理器和快照数据管理器。

初始化ZKDatabase

初始化DataTree,创建默认节点//zookeeperzookeeper/quota

初始化sessionsWithTimeouts会话超时时间记录器。

创建PlayBackListener监听器

在ZooKeeper数据恢复后期,会有一个事务订正的过程,在这个过程中,会回调PlayBackListener监听器进行对应的数据订正。

获取并解析快照文件

从所有的快照文件中,按时间逆序对快照文件进行反序列化,生成DataTree对象和sessionsWithTimeouts集合,并且进行checkSum校验。

只有当最新的文件不可用时,才会解析下一个,直到有一个文件通过校验,恢复完成。

如果读取至第100个快照文件仍然不可用,则认为无法从磁盘中加载数据,服务启动失败。

生成快照最新的ZXID:zxid_for_snap

根据4中的快照文件名低32位得到快照文件恢复数据对应的最新的ZXID: zxid_for_snap。

解析事务日志

由于快照文件是依据每隔一段时间才生成,包含的数据只是近似全量数据,剩余的增量数据需要从事务日志中获

事务应用

从事务日志中获取所有ZXID大于zxid_for_snap的事务,并逐个应用到DataTree和sessionsWithTimeouts中。

对每个应用的事务回调PlayBackListener监听器,将事务转换成Proposal保存至提议缓存队列ZKDatabase.committedLog中,以便Follower进行快速同步。

获取最新ZXID

所有待提交事务被完整应用后,获取此时最大ZXID。

校验epoch

从最新ZXID中解析出事务处理的Leader周期epochOfZxid,同时从磁盘的currentEpoch和acceptedEpoch文件中读取上次记录的最新epoch值,进行校验。

数据同步

集群完成Leader选举后,Learner会向Leader服务器进行注册,当Learner服务器向Leader完成注册后,就进入数据同步环节。

数据同步过程就是Leader服务器将那些没有在Learner服务器上提交过的事务请求同步给Learner服务器。

be2d01da7b8f6a9ecbcded89f21d3b97.png

数据同步初始化

Learner向Leader注册的最后阶段,Learner向Leader发送ACKEPOCH,包含Learner的currentEpoch和lastZxid。

Leader服务器从ZooKeeper内存中提取出提议缓存队列(committedLog),同时初始化三个ZXID值:

committedLog: ZooKeeper会保存最近一段时间内执行的事务请求议案,个数限制默认为500个议案。
  • peerLastZxid:Learner服务器的lastZxid。

  • minCommittedLog:Leader服务器提议缓存队列committedLog中的最小ZXID。

  • maxCommittedLog:Leader服务器提议缓存队列committedLog中的最大ZXID。

Leader服务器根据peerLastZxid、minCommittedLog、maxCommittedLog的值决定数据同步类型:

  • 差异化同步(DIFF同步)

  • 回滚同步(TRUNC同步)

  • 先回滚再差异化同步(TRUNC + DIFF同步)

  • 全量同步(SNAP同步)

差异化同步(DIFF同步)

当 minCommittedLog <= peerListZxid <= maxCommittedLog时,进行差异化同步。

c31d2e59c4b78994efe5d3ae1bcccf4b.png

Leader向Learner发送DIFF指令。

通知Learner进入差异化数据同步阶段,Leader即将把Proposal同步给自己。

Leader针对每个Proposal,先后发送PROPOSAL内容数据包和COMMIT指令数据包

Learner依次Proposal应用到内存数据库中。

Leader发送完差异事务数据后,立即向Learner发送NEWLEADER指令

NEWLEADER指令通知Learner,已经将committedLog中的Proposal都同步给Learner。

Learner向Leader反馈ACK消息

Learner向Leader反馈完成了对committedLog中Proposal的同步。

Leader进入“过半策略”等待阶段

Leader会和其他所有Learner服务器进行同样的数据同步流程,直到集群中由过半的Learner响应并反馈ACK消息。

向所有已经完成数据同步的Learner发送UPTODATE指令

当收到过半Learner的ACK消息后,通知Learner集群中已经有过半机器完成了数据同步,已经具备对外服务的能力。

Learner再次向Leader反馈ACK。

先回滚再差异化同步(TRUNC + DIFF同步)

当Leader服务器发现某个Learner包含一条自己没有的事务记录,就需要让该Learner进行事务回滚–回滚到Leader服务器上存在的,最接近peerLastZxid的ZXID。

86e9158449c5ea426ada3ae451446c18.png

在minCommittedLog <= peerLastZxid <= maxCommittedLog时,有一种特殊的情况:

1. 假设有A、B、C三台机器,此时B是Leader服务器,Leader_Epoch为5,当前已经被集群中绝大部分机器都提交的ZXID为:0x500000001和0x500000002。
2. 此时Leader正要处理ZXID: 0x500000003并且已经写入Leader本地事务日志,但是在要将该Proposal发送给其他Follower投票时Leader服务器宕机,Proposal没有被同步出去。
3. 此时ZooKeeper集群进行新一轮选举,产生的新的Leader是A,同时Leader_Epoch变更为6。
4. A和C继续提供服务,并提交了0x600000001和0x600000002两个事务。
5. 此时,服务器B再次启动,作为Follower连接至新的LeaderA,并开始同步数据。

此时,数据同步各值为:
- minCommittedLog: 0x500000001
- maxCommittedLog: 0x600000002
- peerLastZxid: 0x500000003
这种情况就需要进行TRUNC + DIFF同步,让Learner先TRUNC回滚到0x50000002,在DIFF同步至0x50000003。

仅回滚同步(TRUNC同步)

当peerLastZxid比Leader中maxCommittedLog大时,Leader会要求Learner回滚到ZXID值为maxCommittedLog对应的事务操作。

全量同步(SNAP同步)

当peerLastZxid小于minCommittedLog时,或者Leader服务器上没有提议缓存队列时,无法直接使用提议缓存队列和Learner进行数据同步。

只能进行全量同步(SNAP同步),将本机上的全量内存数据都发送给Learner。

  1. Leader服务器向Learner发送SNAP指令。通知Learner即将进行全量数据同步。

  2. Leader从内存数据库中获取到全量数据节点和会话超时时间记录器,序列化后传输给Learner。

  3. Learner接收到全量数据后,反序列化并载入。

ca36a3a3e789c43d8eb59656875c5bcb.png

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ZooKeeper数据存储与数据同步机制 的相关文章

  • Eclipse 中的 Java 构建路径问题

    在 Eclipse 中 我有一个与我的构建路径相关的错误 错误 Project XX is missing required library middlegen 2 1 jar 但该库在构建路径配置之前被删除 是不是缓存或者其他方面有问题
  • 将 Java 对象图保存为 XML 文件

    将任意 Java 对象图保存为 XML 文件 并能够在以后重新水合对象 的最简单易用的技术是什么 这里最简单的方法是序列化对象图 Java 1 4 内置了对 XML 序列化的支持 我成功使用的一个解决方案是 XStream http x s
  • 如何知道 HTTP 服务器何时完成发送数据

    我正在开发一个面向浏览器 代理的项目 我需要下载网页 向 Web 服务器发送自定义 HTTP 请求后 我开始监听服务器响应 读取响应时 我检查响应标头中的 Content Length row 如果我得到其中之一 很容易确定服务器何时完成发
  • SharePoint 2010 Web 服务上的 Java JBoss 401 错误

    我的代码在 Eclipse IDE 中测试时运行成功 我正在使用生成的 Copy wsdl 通过 Web 服务连接到 MS SharePoint 2010 当我在 JBoss 服务器上部署代码 运行 Adob e LifeCycle 时 我
  • 有没有一种干净的方法将泛型类型的类分配给变量?

    鉴于此代码 List
  • android新手需要了解“?android:attr/actionBarSize”

    我正在经历拉尔斯 沃格尔的教程 http www vogella com articles AndroidFragments article html在使用 Fragments 时 我遇到了以下代码 android layout margi
  • Android 服务 START_STICKY START_NOT_STICKY

    我需要让我的服务始终在后台运行 并使用 startService 函数启动我的服务 无论应用程序的状态如何 我都不想重新启动服务 这是我的观察 START STICKY gt 如果应用程序启动 则服务正在重新启动 当应用程序关闭时 服务也会
  • 从 Windows Batch (cmd.exe) 中的文件读取环境变量

    我正在尝试从批处理文件中读取变量 以便稍后在批处理脚本 Java 启动器 中使用 理想情况下 我希望所有平台 Unix Windows 上的设置文件都具有相同的格式 并且也是有效的 Java 属性文件 也就是说 它应该看起来像这样 sett
  • 将 Swing 集成到简单的文本冒险游戏中

    我对 Java 中的一些中级概念相当陌生 最近 我制作了一款名为 DazzleQuest 的文本冒险游戏 它完全在开发者控制台 终端中运行 它涉及到我的朋友作为角色 所以我想向他们展示它 并通过将命令行的功能和控制台的输出转移到一个简单的
  • 如何在休眠中持久保存实体期间验证实体的约束

    我有一个带有字段名称的实体 我希望它不超过255 所以我这样定义它 Entity public class A implements Serializable NotNull Size max 255 private String name
  • 在 Selenium Grid 中注册 PhantomJS 节点时出错

    我有以下问题 我成功启动了 Selenium Grid hub java jar selenium server standalone 2 53 0 jar role hub 之后我尝试使用以下命令启动 PhantomJS 节点 phant
  • Java TCP Echo 服务器 - 广播

    我有一个简单的回显服务器 我希望当连接的用户向服务器键入任何内容时 所有其他客户端和该客户端都会收到消息 MOD 它现在不会发送给所有客户端 但它应该发送 而且我只是不知道我的代码出了什么问题 所以现在它只会将消息 MOD 发送给发送消息的
  • Runtime.getRuntime().exec(cmd) 挂起

    我正在执行一个命令 该命令返回文件的修订号 文件名 但如果执行命令时出现问题 应用程序就会挂起 我可以做什么来避免这种情况 请在下面找到我的代码 String cmd cmd C si viewhistory fields revision
  • 批量将Dictionary中的数据设置到Redis中

    我正在使用 StackExchange Redis DB 插入键值对字典Batch如下 private static StackExchange Redis IDatabase database public void SetAll
  • 为什么在大多数开源java项目中局部变量没有被声明为final?

    如果我查看 OpenJDK Hibernate 或 Apache 中的 java 源代码 我还没有看到任何声明为 Final 的局部变量 这表明一些最广泛使用的 java 软件库的开发人员 不要相信final关键字可以提高可读性 不相信它会
  • 在字节数组上进行右位旋转/循环移位的最快方法是什么

    如果我有数组 01101111 11110000 00001111 111 240 15 移位 1 位的结果是 10110111 11111000 00000111 183 248 7 数组大小不固定 移位范围为 1 到 7 含 目前我有以
  • Apache Camel - 路由中的事务

    我有一个关于 Apache Camel 的一般性问题 我无法找到聚合器是否已进行交易 如果是交易 交易是如何实现的 聚合的速度有多快 将消息发送到聚合器可以在事务中运行 您需要一个带有聚合器的持久存储来让传出消息充当事务 请参阅有关持久性的
  • 在 x64 系统上使用 skype-java-api

    我正在使用 skype java api 在 Java 中使用 Skype 我需要的唯一功能是点击即可拨打电话号码 它在 Windows XP x86 上运行良好 但我刚刚在 Windows 7 x64 上测试它 但失败了 错误是 线程 T
  • 在地图中的图块上实现鼠标单击事件

    我正在尝试在 JPanel 上实现图像 基本上是地图上的图块 的鼠标单击事件 我只是不知道该怎么做 我有一个扩展 JPanel 的 Main 类 我正在从图块服务器检索图块 并根据特定的缩放级别在 Main 类的 PaintComponen
  • Cassandra 会话与集群 有什么可分享的?

    考虑 Cassandra 的 Session 和 Cluster 类 Java 驱动程序 我想知道有什么区别 在 Hibernate 中 每次都会创建一个会话并共享会话工厂 从许多来源我了解到 它被认为是创建一个会话并在多个线程之间共享它

随机推荐

  • Hololens入门开发(二)unity项目设置及MR开发包导入

    一 新建一个u3d项目 二 将unity的平台切换为Universal Windows Platform 三 MR开发包导入 开发包链接来自Github 根据需要的版本进行选择 https github com microsoft Mixe
  • Exchange Server 2016 安装部署

    目录 0x01 简介 0x02 环境准备 0x03 添加角色与功能 配置Active Directory 域服务 0x04 安装ExchangeServer 点击 setup 不检查更新 点击 下一步 接受许可 不使用推荐设置 选择邮件角色
  • element-ui 整体替换主题色

    自定义主题 Element 默认提供一套主题 CSS 命名采用 BEM 的风格 方便使用者覆盖样式 我们提供了三种方法 可以进行不同程度的样式自定义 仅替换主题色 如果仅希望更换 Element 的主题色 推荐使用在线主题生成工具 Elem
  • MySQL锁之一:锁详解

    一 什么是死锁 死锁是指两个或两个以上的进程在执行过程中 因争夺资源而造成的一种互相等待的现象 若无外力作用 它们都将无法推进下去 此时称系统处于死锁状态或系统产生了死锁 这些永远在互相等的进程称为死锁进程 二 死锁产生的四个必要条件 互斥
  • Go-cli框架Cobra

    Cobra是前go语言负责人spf13开发的一款go cli框架 可以轻松规范的处理cli应用场景 项目地址 https github com spf13 cobra作者博客 https spf13 com 所谓官网 https cobra
  • len()函数

    len 函数 描述 len 函数返回对象 字符 列表 元组等 长度或项目个数 语法 len 方法语法 len s 参数 s 对象 返回值 返回对象长度 实例 以下实例展示了 len 的使用方法 str 1111111111111 print
  • Python 11 (paramiko模块,ssh密钥,进程线程,锁,信号量)

    本节内容 paramiko模块 基于SSH用于连接远程服务器并执行相关操作 paramiko模块 SSHClient 用于连接远程服务器并执行基本命令 基于用户名密码连接 Author yyqian import paramiko 创建SS
  • android studio f5,AndroidStudio 快捷鍵

    8种机械键盘轴体对比 本人程序员 要买一个写代码的键盘 请问红轴和茶轴怎么选 符號說明 gt option alt gt shift gt control gt command gt esc 注 与F6 F7 F12等F功能键开头的组合的快
  • java中String、StringBuffer、StringBuilder的区别

    java中String StringBuffer StringBuilder是编程中经常使用的字符串类 他们之间的区别也是经常在面试中会问到的问题 现在总结一下 看看他们的不同与相同 三者共同之处 都是final类 不允许被继承 主要是从性
  • msvcp140.dll丢失如何修复?win7丢失msvcp140.dll的修复方法

    有win7用户在操作电脑的过程中 遇到msvcp140 dll丢失的情况 这是怎么回事 一般是由于系统中丢失dll文件导致的 下面就和小编一起来看看具体的解决办法吧 MSVCP文件的版本 这是我电脑上安装的各版本 VC 版本间互相不重复 并
  • javac不是内部或外部命令,也不是可运行的程序 或批处理文件的细节问题(window10)

    目录 步骤一 找到JDK下的bin目录 运行cmd 输入javac 能提示 说明环境配置有问题 步骤二 cmd输入 path 步骤三 看看下载的JDK包解压后目录对不对 步骤四 检查是否有环境冲突 最后 环境变量配置 描述 打开cmd 输入
  • 前后台交互时客户端请求的中文字变成问号

    今天在写form表单的时候发现提交的中文字在后台接收的时候变成了 如下图 经过检查发现是编码格式的不统一的问题 前台给数据设置了编码格式 而后台没有设置编码格式 就造成了中文字不能识别
  • spring boot中web容器配置

    web容器配置 spring boot 默认的web容器是 tomcat 如果需要换成其他的 web 容器 可以如下配置
  • windows下nginx配置为服务

    1 下载winswx 下载地址 winsw下载 2 解压后将其重命名为 nginx service 并将其放到nginx目录下 3 新建一个文本文档内容如下 里面的路径根据自己的情况修改
  • mysql 8 窗口函数

    使用mysql8 遇到一个问题 分区之后排序 得到的结果不是预想中的值 而把排序取消之后 能够得到正确的结果 如图所示 搞不懂 大佬解释下
  • hadoop datanode没有启动

    datanode启动不了 即执行命令 bin hadoop namenode format 后 默认地址tmp hadoop 1 dfs data current VERSION namenod 和datanode 的clusterid不一
  • Qt5.9中QMessageBox输出变量的用法(或者是字符串添加变量的方法):tr("%1").arg(variable)

    本文主要总结在Qt5 9的Creator4 4 1中 在弹出框QMessageBox中输出变量的方法之一tr 1 arg variable 具体的示例如下所示 str tr 这是变量 QMessageBox warning this tr
  • 基于Vue项目的富文本vue-quill-editor的使用

    基于Vue项目的富文本vue quill editor的使用 文章目录 基于Vue项目的富文本vue quill editor的使用 一 背景 二 Vue Quill Editor使用 1 简介 2 安装 3 挂载到项目 1 全局挂载 2
  • CentOS8 Keepalived 高可用测试

    keepalived 安装与使用 介绍 软件原理 准备工作 软件安装 配置文件 启动 介绍 Keeplived 是一款系统插件 工作在TCP IP 层 通过虚拟IP对外提供服务 当主节点出现故障时 能自动切换到从节点 达到一个高可用的目的
  • ZooKeeper数据存储与数据同步机制

    ZooKeeper中 数据存储分为两部分 内存数据 ZKDatabase 与磁盘数据 事务日志 事务快照 ZKDatabase ZooKeeper的数据模型是一棵树 而从使用角度看 ZooKeeper就像一个内存数据库一样 在内存数据库中