zookeeper原理篇-Zookeeper的数据存储与恢复原理,java面试通关手册

zookeeper原理篇-Zookeeper的数据存储与恢复原理,java面试通关手册,第1张

zookeeper原理篇-Zookeeper的数据存储与恢复原理,java面试通关手册

而比较值得注意的是这些文件的大小,都是一样的67108880KB,这大小换算成MB刚好是64MB大小,除此之外,可以看到log文件的命名都是log.作为前缀,后面的名字都是十六进制的数字,那么这个是什么呢?其实这个是使用了一个ZXID作为后缀,而选择的则是当前日志中的第一条事务的ZXID,而ZXID我们前面也了解过,是由两个部分组合而成,高32位代表当前Leader的选举周期–epoch的大小,而低32位则是该周期内的 *** 作序列号,因此我们可以根据事务日志的名称快速的解析读取出来对应的epoch信息和先后顺序。

我们随便选择一个日志文件打开,发现里面的内容无法阅读,都是序列化后的事务日志:

可以看到里面的内容只能隐约的看到一些节点的路径以外,其他的几乎分辨不出来了,而在zookeeper中提供了一个格式化日志的命令–org.apache.zookeeper.Server.LogFormatter,使用方式只需要在目录下输入:

  1. JavaLogFormatter日志文件

我们随便找一个日志文件输入命令,看看格式化后的内容:

第一行日志:

  1. ZooKeeperTransactionalLog F ile with dbid 0 txnlog format version 2

可以看到这句日志是日志记录的开始,告诉我们日志的当前版本号是2,以及当前的dbid是0,接着我们看下一行日志:

  1. 01:07:41 session 0x144699552020000 cxid 0x0 zxid 0x300000002 createSession 30000

而第二行从左到右分别记录了事务的发生时间、当前事务的会话id、客户端序列号cxid、事务id–zxid以及当前触发事务的动作是创建 *** 作,接着我们来看第三行日志的内容:

  1. 01:08:40 session 0x144699552020000 cxid 0x2 zxid 0x300000003 create

  2. /test_log,#7631,v{s{31,s{/w orld,'anyone}}},F,2

这一行日志我们看到,不仅有和第二行记录一样的以外,还记录了节点的路径,节点的数据内容,这里需要注意的是这里记录的方式的#+值的ASSCII的码值,节点的ACL信息以及是否为临时节点,这里使用了F/T方式记录,F代表是临时节点,T为持久化节点,以及版本号,基本上一个事务大体上记录的内容就这么多,其他的日志大体上和这些类似,因此不再详细介绍

FileTxnLog

FileTxnLog负责维护事物日志相关的 *** 作,包括事物日志的写入和读取以及数据恢复等。首先我们来看事物写入的方法:

  1. publicsynchronizedboolean append(TxnHeader hdr, Record txn);

从方法的定义可以看出来,如果要写入日志,需要传入两个参数,分别是事物头和事物消息体,而整个方法的大概过程如下:

1.当整个Zookeeper启动完成后第一次进行日志的写入或者是上一次日志刚好写满以后,都会处于一个与日志文件断开的状态。因此,在进行日志写入之前,Zookeeper会先判断FileTxnLog组件是否已经关联一个事物日志文件,如果没有关联的日志文件,那么就会使用该事物关联的ZXID作为后缀创建一个新的事物日志文件,同时会去创建事物日志头信息**(其中包括magic,事物日志的版本号version和dbid)**,并且立即写入到这个事物日志文件中去,然后将文件流存入一个集合中–StreamsToFlush。

2.在客户端触发每一次的事物 *** 作的时候,会进行一次空间大小检测 *** 作,当发现事物日志的剩余空间不足4096字节(4KB)大小的时候,就会进行一次扩容 *** 作,而每一次扩容(包括第一次分配大小)都是65536KB(64MB)大小,而这些扩容的内容,还没使用的情况下,会预先使用0进行占满,这里涉及到一个IO性能优化的地方,如果Zookeeper不预先分配空间大小,可能会导致事物日志在写入的过程中,频繁的触发Seek,开辟新的空间,导致写入I

《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》

【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享

O性能缓慢。当然默认的预分配大小64MB,如果需要调节大小,可以设置系统参数:

zookeeper.preAllocSize来改变大小

3.在写入事物之前,会进行一次事物序列化,分别是对TxnHeader和Record的序列化,其中包括创建会话事物、节点创建事物、删除节点事物和更新节点事物等,序列化完成以后,为了保证事物写入的完整性和准确性,会根据序列化生成的字节数组计算一个Checksum,在Zookeeper中默认使用的是Adler32算法来计算Checksum值。

4.将序列化后的事物头、事物体消息以及checkSum的值一起写入到文件流中, 此时使用的是BufferedOutputStream,因此会等待缓存区填充满以后才会真正的写入日志文件中,当事物日志写入到BufferedOutputStream以后,因为文件流都存入了stramToFlush,因此我们会从中提取文件流,并且调用**FileChannel.force(boolean metaData)**方法进行强制刷盘 *** 作,至此Zookeeper的一次事物日志 *** 作写入完成。

注意:在Zookeeper运行过程中,由于会出现leader机器出现异常等情况,最后变成非leader机器,重新选举出来的leader发现非leader机器上记录的事物ID大于自身的,那么由于遵循前面文章说过的,Zookeeper要求所有的follower机器在Leader存在的过程中,必须和Leader保持一致,因此这个时候Leader就会发送一个TRUNC命令给这个follower机器,强制对这部分日志进行截断,follower机器在收到请求以后,会将这部分大于Leader事物ID的日志信息删除。

Snapshot

在Zookeeper中,除了事物日志以外,还有一个核心的数据存储组件–Snapshot(数据快照),与事物日志不同的是,数据快照用于记录某一时刻的zookeeper上的全量数据内容,并且存入磁盘文件中。和事物日志相同的一点是,数据快照也支持指定dataDir属性进行配置存储的目录,我们打开对应的存储目录,查看一下快照文件的格式,如下:

  1. -rw-rw-r-- 1 admin admin 125807203-0117:49 snapshot.2c021384ce

可以看到和事物日志很像的一点是,快照的数据文件命名格式也是使用ZXID的十六进制作为文件后缀,同样的,在数据恢复的阶段,会根据ZXID来确定和进行数据恢复。当然与事物日志不同的是,快照文件并没有预分配空间的机制,因此也可以认为快照文件中的数据都是当时全量数据的有效数据。

当我们打开一个快照文件以后,发现和事物日志差不多,里面的内容也是被序列化后的,当然,Zookeeper也提供了一个格式化工具 org .apache.zookeeper.server.SnapshotFormatter,使用的方式也和前面的事物日志格式化工具差不多,在快照所在的目录下,使用如下命令:

  1. JavaSnapshotFormatte快照

这个时候我们再去读取内容,会发现,已经能成功看到每个节点的状态信息,虽然看不到具体的数据内容,但是已经对我们运维很有帮助了,大概信息如下:

  1. CZxid» 0x00000000000000

  2. ctiffle » ThuJan0108:00:00 C S T 1970

  3. mZxid - OxOOOOOOGOOQOOOO

  4. mtime = ThuJttxi0108:0D:0© C S T 1972

  5. pZxid » 0*00000300000003

  6. cversion = 2

  7. dataVersion = 0

  8. aclVersion = 0

  9. ephemeralOwner = 0x00000000000000

  10. dataLength = 0

而在Zookeeper中,负责快照相关 *** 作的类是FileSnap,包括处理快照的写入和读取等 *** 作。我们知道,Zookeeper的每一次事物 *** 作,都会写入到事物日志中,当然同时也会写入到内存数据库中,而在触发了多次事物写入日志的 *** 作以后,就会触发一次快照的数据写入 *** 作,而这个次数snapCount参数则是可以在zookeeper参数中进行配置,接下来我们来看看快照的大概写入过程:

1.每一次事物日志写入完毕以后,Zookeeper都会检测一次是否需要写入到快照中的 *** 作,理论上达到snapCount次数以后的事物日志就要触发快照的demp *** 作,但是考虑整体性能,Zookeeper并不是每一次都会执行demp,而是选择使用了过半随机的原则,即:

  1. logCount > (snapCount /2+ randRoll)

这里的logCount指的是当前记录的日志数量,snapCount指的是配置的多少次事物日志触发一次快照,randRoll则是1 - snapCount/2之间的一个随机数,如果我们配置的事物日志的数量为10000,那么则会在一半 + 随机值的次事物日志以后才开始写入快照。

2.当事物日志数量刚好达到半数随机值以后,Zookeeper会进行一次事物日志文件切换(即事物日志已经需要写入snapCount个事物日志),需要重新创建一个新的事物日志文件出来,这个时候为了保证性能稳定,会创建一个单独的线程用来处理demp快照的 *** 作

3.而生成快照的过程则是将所有节点和会话信息保存到本地磁盘文件中,而文件的命名规则则是根据当前已经提交的最大ZXID来生成数据快照文件名。接下来会进行序列化 *** 作,首先序列化文件头信息,这里包含了magic,事物日志的版本号version和dbid,然后再对会话信息和DataTree分别序列化,同样序列化完成后会生成一个CheckSum,一并写入到快照文件中,至此快照文件写入完成

数据初始化与数据同步

前面我们有学习过,Zookeeper的启动流程,其中有两个步骤,一个是初始化启动的时候,会去磁盘中加载数据,另外一个则是集群启动后,会有follower机器与leader机器进行数据同步的过程,接下来我们来看看这两个过程是如何进行数据之间的恢复与同步的。

初始化数据

1.在Zookeeper中,进行数据恢复或者数据同步使用的是FileTxnSnapLog类,这个类属于衔接业务与下层数据存储的类,其中包含类事物日志的 *** 作,以及快照 *** 作,因此FileTxnSnapLog的初始化就是事物日志 *** 作类–FileTxnSnapLog和快照管理类–FileSnap的初始化过程。

2.在FileTxnSnapLog类初始化完成后,会将其交给ZKDatabase,完成初始化 *** 作,包括创建初始化的一些节点,例如**/,/zookeeper和/zookeeper/quota节点,除此之外,还会创建 一个保存所有会话超时时间的记录器–sessionsWithTimeouts,初始化完成后,会去创建一个PlayBackListener**监听器,这个监听器用来接受事务应用过程中的回调,会在数据恢复的过程中,进行数据修正 *** 作。

3.完成内存数据库的初始化以后,就要读取快照文件,进行全量数据恢复了,这个时候会默认读取最多一百个最新的快照文件,然后从ZXID最大的快照文件开始,进行逐个解析,进行反序列化 *** 作,然后生成DataTree和sessionWithTimeout,并且根据checkSum校验完整性,如果校验失败,会放弃这个快照文件,选择第二个ZXID最大的快照文件,继续解析,依次类推,如果读取到的最多一百个快照文件都失败了,那么就直接启动失败,如果有校验成功的,则使用该文件进行全量恢复。

4.当快照文件恢复全量数据完成后,此时已经创建了DataTree实例和sessionsWithTimeOuts集合了,这个时候我们也知道快照文件对应的最新的ZXID,而这个时候我们就需要找到比snap中的ZXID大的事物日志,进行增量恢复和数据修正,每一条事务日志被恢复后,就会应用到快照恢复出来的DataTree和sessionsWithTimeOuts中,并且会回调PlayBackListener 监听器,将这一 事务 *** 作记录转换成 Proposal , 并保存到ZkDatabase.committedLog 中,以便 Follower 进行快速同步 *** 作。
恢复出来的DataTree和sessionsWithTimeOuts中,并且会回调PlayBackListener 监听器,将这一 事务 *** 作记录转换成 Proposal , 并保存到ZkDatabase.committedLog 中,以便 Follower 进行快速同步 *** 作。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5683545.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存