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

可以看到里面的内容只能隐约的看到一些节点的路径以外,其他的几乎分辨不出来了,而在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,开辟新的空间,导致写入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属性进行配置存储的目录,我们打开对应的存储目录,查看一下快照文件的格式,如下:

  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机器进行数据同步的过程,接下来我们来看看这两个过程是如何进行数据之间的恢复与同步的。

最后总结

搞定算法,面试字节再不怕,有需要文章中分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析

最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化

image

分享的这些二叉树、链表、字符串、栈和队列等等各大面试高频知识点及解析

最后再分享一份终极手撕架构的大礼包(学习笔记):分布式+微服务+开源框架+性能优化

[外链图片转存中…(img-iXp94wmM-1719177380485)]

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值