Zookeeper的数据存储与恢复
Posted 哈哈哈张大侠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Zookeeper的数据存储与恢复相关的知识,希望对你有一定的参考价值。
文章转载于我的个人博客:https://0522-isniceday.top/archives/zookeeper%E6%95%B4%E7%90%86
事务日志
类型
- 日志文件
命名
- log.XX(十六进制的数字-高32位代表epoch、低32位代表操作序号)
内容
01:07:41 session 0x144699552020000 cxid 0x0 zxid 0x300000002 createSession 30000
01:08:40 session 0x144699552020000 cxid 0x2 zxid 0x300000003 create
/test_log,#7631,vs31 ,s/w orld,'anyone,F,2
FileTxnLog
-
复制维护日志的相关操作:事务日志的相关写入、读取以及数据恢复
-
写入事务日志流程
-
1.判断当前类是否关联一个日志文件,如果没有关联就以当前事务id为文件后缀创建一个日志文件,同时创建日志头,然后将文件流存入集合StreamsToFlush
-
2.每次写入都触发磁盘空间大小的检查,如果不足4kb则提前触发宽容(申请)64mb,扩容内容在没使用的情况下用0占满
- zookeeper.preAllocSize可调节扩容大小
-
3.将待写入的信息进行序列化,计算checkNum
-
4.将序列化后的事务头、事务体、checkNum写入文件流
- 注意:再故障leader选举后,可能存在之前的leader节点的zxid大于选举后的leader节点,这个时候新的leader节点会发送TRUNC命令给这个follower节点。强制对这部分日志进行截断,会从文件中将该部分的事务日志删除
-
Snapshot数据快照
概念
- 记录某一时刻zookeeper上的全量内容
命名
- 使用zxid的十六进制作为文件后缀
FileSnap类
-
复制处理快照的写入、读取
-
根据事务记录的个数snapCount来触发快照文件的创建
-
流程
-
1.每次事务日志的写入都会去判断是否写入快照文件
- 比较算法:logCount > (snapCount /2 + randRoll),randRoll为snapCount /2~snapCount /2-1大小的一个值
-
2.事务日志在写入snapCount个事务后,会重新创建一个事务日志文件,并会单独创建一个线程去执行快照的dump操作
-
3.根据已提交的最大的zxid来命名快照文件,并将序列化过后的文件头信息、会话信息、DataTree分别序列化写入文件
-
数据的初始化
FileTxnSnapLog类
-
流程
- 1.事物日志操作类–FileTxnSnapLog和快照管理类–FileSnap的初始化
- 2.初始化一些节点,例如/zookeeper、/zookeeper/quota.,创建PlayBackListener监听器,用于数据修正时候的回调
- 3.先读取100个快照文件,按照zxid的顺序从大开始读取,校验checkNum校验完整性,如果校验失败则放弃这个快照文件继续读取下一个。如果100个都检验失败了则直接启动失败
- 4.当全量数据恢复完成后,这个时候有DataTree实例和sessionsWithTimeOuts集合了,能够从快照中得到当前的zxid,再找到比该zxid大的事务日志(也就是还未存入快照的事务)。每一条事务日志恢复后同通过PlayBackListener回调生成proposal提议同步到follower
- 5.事务日志恢复完成后,得到最大的zxid并解析出epoch,同时在磁盘的currentEpoch和acceptedEpoch文件读取上次记录的epoch进行校验,至此数据初始化流程完成
数据同步
开始阶段
- 当zookeeper初始化完成后,集群选举后,Learner服务器会向Leader完成注册以后,就会触发数据同步环节
概念
- leader将没有在learner服务器上同步过的数据同步给learner服务器
流程
-
获得learn状态
- 1.learner服务器初始化完成后,learner服务器发送ACKEPOCH数据包给leader服务器,leader服务器从中获得learner服务器的currentEpoch和lastZxid
-
数据同步初始化
-
开始数据同步之前,leader服务器先数据同步初始化,会从zookeeper内存数据库TreeData中提取出“提议缓存队列”:proposals,也就是说选举出了leader,leader就立马先从快照、日志给自己先初始化数据
接着Leader服务器会从Zookeeper内存数据库中提取出事务对应的提议缓存队列:proposals
-
完成下面三个zxid的初始化(这几个参数都是从内存中读到的并非日志等文件)
- peerLastZxid:该Learner服务器最后处理的ZXID。
- minCommittedLog: Leader服务器提议缓存队列committedLog中的最小ZXID。
- maxCommittedLog:Leader服务器提议缓存队列committedLog中的最大ZXID
-
-
-
数据化同步
-
流程
- 首先leader节点会优先使用全量同步的方式进行数据同步,然后根据leader与learner之间的差异决定同步方式
-
同步方式
-
直接差异化同步(DIFF 同步)
-
场景(触发条件)
- peerlastZxid介于minCommittedLog和maxCommittedLog之间
- leader再第一阶段提交的proposal还没有commit给follower,然后该leader就挂了,然后某个接收到这个proposal的节点选举为master,然后重新数据初始化,再进行数据同步时,其他节点的lastZxid则会触发DIFF同步,因为他们只是将上个leader的proposal写入了日志文件但是没有接受到commit指令同步到内存
-
同步流程
-
leader首先发送一个DIFF指令,通知learner进入数据差异化同步阶段,即将推送proposal给learner
- 实际推送是推送两个包,proposal内容数据包和commit指令数据包
-
数据包推送完成后,leader会将learner放入forwardingFollowers或observingLearners队列,并发送一个newleader指令通知learner数据已同步
-
learner接收到newleader指令之后发送ack消息表示接收到了同步的数据包
-
leader接收到ack包之后进入“过半”策略的等待,即接收到过半的leader发送的ack包,此时再发送uptodata数据包给所有已完成数据同步的learner
-
learner接收到uptodata指令之后会结束数据同步流程并再次发送ack包给leader
-
-
-
先回滚再差异化同步(TRUNC+DIFF 同步)
-
场景
- 是Leader服务器在已经将事务记录到了本地事务日志中,但是没有成功发起Proposal流程的时候就挂了
-
同步流程
-
当leader服务器发现learner包含了一条自己没有的事务记录,那么就会让learner进行事务回滚到leader服务器存在且最接近于peerLastZxid的事务
-
解决了故障恢复阶段第二个问题
- leader服务器再第一阶段提交的消息,但是并没有发送给learner服务器,需要保证该事务消息被丢弃
-
-
再进行DIFF差异同步
-
-
-
仅回滚同步(TRUNC 同步)
-
场景
- peerLastZxid>maxCommittedLog
-
同步流程
- leader服务器要求learner同步回滚到maxCommitteLog
-
-
全量同步(SNAP同步)
-
场景
-
peerLastZxid 小于minCommittedLog。
-
Leader 服务器上没有提议缓存队列,peerLastZxid 不等于lastProcessedZxid(Leader服务器数据恢复后得到的最大ZXID)
- 也就是说当前leader从事务日志中找不到比快照最大的zxid还要大的事务id,并且leader最大的zxid又和learner出现了不一致,这个时候就只能全量同步数据
-
-
同步流程
- leader先向learner发送SNAP全量同步指令
- Leader会从内存数据库中获取到全量的数据节点和会话超时时间记录器,将它们序列化后传输给Learner
-
-
-
zookeeper原理解析-数据存储
Zookeeper内存结构
Zookeeper是怎么存储数据的,什么机制保证集群中数据是一致性,在网络异常,当机以及停电等异常情况下恢复数据的,我们知道数据库给我们提供了这些功能,其实zookeeper也实现了类似数据库的功能。
1. Zookeeper内存结构
Zookeeper数据在内存中的结构类似于linux的目录结构
DataTree代表这个目录结构, DataNode代表一个节点 DataTree: 默认初始化三目录 1)"" 2) "/zookeeper" 3) "/zookeeper/quota" DataNode 表示一个节点 1) 存储了父节点的引用 2) 节点的权限信息 3) 子节点路径集合
Snapshot
Snapshot是datatree在内存中某一时刻的影像,zookeeper有一定的机制会定时生成datatree的snapshot。FileSnap实现了SnapShot接口负责将数据写入文件中,下面我们来看看snap相关内容。
2.1 snapshot文件格式
Snapshot是以二进制形式存在在文件的,我们用ue打开一个新的snapshot文件
Snapshot文件的中数据大体可以分为两部分header和body。
Header数据格式:
FileHeader{ int magic //魔数 常量ZKSN 代表zookeeper snapshot文件 int version //版本 常量 2 long dbid //dbid 常量 -1 }
这里很奇怪 version和dbid都是常量,那还有什么意思,也许是保留字段为后续版本使用。
由头部字段可以计算出头部信息占用 4 + 4 + 8 =16bit的固定长度
5A 4B 53 4E 就是魔术ZKSN
00 00 00 02 就是dbid号2
FF FF FF FF FF FF FF FF就是十六进制的-1
public voidserialize(OutputArchive oa, String tag) throws IOException { scount = 0; serializeList(longKeyMap, oa); serializeNode(oa, newStringBuilder("")); if (root != null) { oa.writeString("/","path"); } } 2.1)序列化longKeyMap是存储在datatree中的acl权限集合 readInt("map") //acl的映射个数? while (map > 0) { readLong("long") //这个long值longKeyMap的key,作用?是这一组acl的key readInt("acls") while (acls) { readInt("perms") readString("scheme") readString("id") } } 2.2)存储datatree中数据节点 readString("path")//第一个datanode "" while(!path.equals("/")) { // "/"代表路径结束 readRecord(node, "node")包括: readBuffer("data") readLong("acl") deserialize(archive,"statpersisted") 状态存储包括: readLong("czxid") //createNode时事务号 readLong("mzxid") //createNode时与Czxid同,setData时的事务号 readLong("ctime") // 创建节点时间 readLong("mtime") //createNode时与ctime相同,setData时间 readInt("version") //createNode版本为0,setData的数据版本号 readInt("cversion") //createNode版本为0,增加/删除子节点时父节点+1 readInt("aversion") //createNode版本为0,setACL时节点的版本号 readLong("ephemeralOwner") // 临时节点表示sessionid,非临时节点这个值为0 readLong("pzxid") //createNode时与Czxid同,增加/删除子节点时为子节点事务号 readString("path") //读取下一个路径 } 3) 文件尾部校验数据 00 00 00 01 2F snapshot文件结尾5位数据用来校验snapshot文件是否有效 00 00 00 01一个int的数值就是数字1,代表后面1一个字符数据 2F 就是snapshot的结束符/
Body数据格式:
Snapshot文件中头部信息之后,紧接着就是body部分的信息,body数据大小是动态不是固定。
1) Map<Long, Integer> sessionWithTimeoutbody信息前面部分存储的是内存中活着的session以及session的超时时间
oa.writeInt(sessSnap.size(),"count");
for (Entry<Long, Integer> entry :sessSnap.entrySet()) {
oa.writeLong(entry.getKey().longValue(), "id");
oa.writeInt(entry.getValue().intValue(),"timeout");
}
由上面序列到文件代码可以看出先写入一个int类型字段用来存储sessionWithTimeout的个数,然后在遍历集合以一个long一个int的形式写入
2) 紧接着就是对datatree序列化到文件了
我们看下datatree的序列化方法
4)Snapshot序列化
5)Snapshot反序列化
以上是关于Zookeeper的数据存储与恢复的主要内容,如果未能解决你的问题,请参考以下文章
ZooKeeper运维——数据备份与恢复(事务日志+快照日志,万字总结,你想要的都有)
ZooKeeper运维——数据备份与恢复(事务日志+快照日志,万字总结,你想要的都有)