zookeeper原理篇-Zookeeper的数据存储与恢复原理

Java LogFormatter 日志文件

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

第一行日志:

ZooKeeper Transactional Log F ile with dbid 0 txnlog format version 2

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

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

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

01:08:40 session 0x144699552020000 cxid 0x2 zxid 0x300000003 create
/test_log,#7631,v{s{31 ,s{/w orld,'anyone}}},F,2

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

FileTxnLog

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

public synchronized boolean append(TxnHeader hdr, Record txn);

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

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

2.在客户端触发每一次的事物操作的时候,会进行一次空间大小检测操作,当发现事物日志的剩余空间不足4096字节(4KB)大小的时候,就会进行一次扩容操作,而每一次扩容(包括第一次分配大小)都是65536KB(64MB)大小,而这些扩容的内容,还没使用的情况下,会预先使用0进行占满,这里涉及到一个IO性能优化的地方,如果Zookeeper不预先分配空间大小,可能会导致事物日志在写入的过程中,频繁的触发Seek,开辟新的空间,导致写入IO性能缓慢。当然默认的预分配大小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属性进行配置存储的目录,我们打开对应的存储目录,查看一下快照文件的格式,如下:

-rw-rw-r-- 1 admin admin 1258072 03-01 17:49 snapshot.2c021384ce

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

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

Java SnapshotFormatte 快照

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

CZxid » 0x00000000000000
ctiffle » Thu Jan 01 08:00:00 C S T 1970
mZxid - OxOOOOOOGOOQOOOO
mtime = Thu Jttxi 01 08:0D:0 © C S T 1972
pZxid » 0*00000300000003
cversion = 2
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x00000000000000
dataLength = 0

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

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

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最大的快照文件开始,进行逐个解析,进行反序列化操作,然后生成DataTreesessionWithTimeout,并且根据checkSum校验完整性,如果校验失败,会放弃这个快照文件,选择第二个ZXID最大的快照文件,继续解析,依次类推,如果读取到的最多一百个快照文件都失败了,那么就直接启动失败,如果有校验成功的,则使用该文件进行全量恢复。

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

5.当事务日志恢复完毕后,数据的初始化过程基本结束,这个时候再去获取一个ZXID,用来作为上次服务器正常阶段提交的最大事务ID,这个时候根据ZXID解析出来上一次leader的周期-epochOfZxid,同时在磁盘的currentEpochacceptedEpoch文件读取上次记录的epoch进行校验,至此数据初始化流程完成

数据同步

当zookeeper初始化完成后,集群选举后,Learner服务器会向Leader完成注册以后,就会触发数据同步环节。在前面的文章中,我们学习过,注册Learner的最后阶段,会发送给Leader服务器一个ACKEPOCH数据包,Leader会根据发来的数据包解析出来Learner机器当前currentEpochlastZxid,接着Leader服务器会从Zookeeper内存数据库中提取出事务对应的提议缓存队列:proposals,同时完成对以下三个ZXID的初始化,分别是peerLastZxid(Learner服务器最后处理的ZXID),minCommittedLog(提议缓存队列CommittedLog中的最小的ZXID),maxCommittedLog(提议缓存队列CommittedLog中的最大的ZXID)。而在Zookeeper中,数据同步有四类,分别是DIFF差异化同步回滚后差异化同步仅回滚同步以及全量SNAP同步

全量同步

全量SNAP同步发生在以下两个场景,一个是peerLastZxid的值小于minCommittedLog,另外一个是Leader服务器上不存在提议缓存队列的情况下,此时都无法根据提议缓存队列进行同步,只能选择全量同步。

仅回滚同步/回滚后差异化同步

最后

这里附上上述的技术体系图相关的几十套腾讯、头条、阿里、美团等公司2021年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

相信它会给大家带来很多收获:

img

当程序员容易,当一个优秀的程序员是需要不断学习的,从初级程序员到高级程序员,从初级架构师到资深架构师,或者走向管理,从技术经理到技术总监,每个阶段都需要掌握不同的能力。早早确定自己的职业方向,才能在工作和能力提升中甩开同龄人。

  • 无论你现在水平怎么样一定要 持续学习 没有鸡汤,别人看起来的毫不费力,其实费了很大力,这四个字就是我的建议!!!
  • 我希望每一个努力生活的IT工程师,都会得到自己想要的,因为我们很辛苦,我们应得的。

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

当我们在抱怨环境,抱怨怀才不遇的时候,没有别的原因,一定是你做的还不够好!
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 28
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值