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运维——数据备份与恢复(事务日志+快照日志,万字总结,你想要的都有)

ZooKeeper运维——数据备份与恢复(事务日志+快照日志,万字总结,你想要的都有)

Zookeeper

分布式Zookeeper数据与存储

ZooKeeper日志与快照文件简单分析