Zookeeper内存结构

Zookeeper是怎么存储数据的,什么机制保证集群中数据是一致性,在网络异常,当机以及停电等异常情况下恢复数据的,我们知道数据库给我们提供了这些功能,其实zookeeper也实现了类似数据库的功能。

1.      Zookeeper内存结构

Zookeeper数据在内存中的结构类似于linux的目录结构

DataTree代表这个目录结构, DataNode代表一个节点
DataTree:
默认初始化三目录
)""
) "/zookeeper"
) "/zookeeper/quota"
DataNode
表示一个节点
) 存储了父节点的引用
) 节点的权限信息
) 子节点路径集合

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 = ;
serializeList(longKeyMap, oa);
serializeNode(oa, newStringBuilder(""));
if (root != null) {
oa.writeString("/","path");
}
} 2.1)序列化longKeyMap是存储在datatree中的acl权限集合
readInt("map") //acl的映射个数?
while (map > ) {
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") //读取下一个路径
}
) 文件尾部校验数据
2F snapshot文件结尾5位数据用来校验snapshot文件是否有效
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反序列化

 5)TxnLog事务日志

事务日志文件用来记录事物操作,每一个事务操作如添加,删除节点等等,都会在事务日志中记录一条记录,用来在zookeeper异常情况下,通过txnlog和snapshot文件来恢复数据,下面我们来看下txnLog事务日志文件的格式

打开一个事务日志文件看看


一个日志文件LogFile: FileHeader TxnList ZeroPad三部分组成

1)   日志文件头FileHeader: {

magic   4bytes (ZKLG) //常量代表

version 4bytes        //常量2

dbid     8bytes        //这个没啥用,就是默认值0

}

头文件是固定长度 16  = 4 + 4 + 8数据,它的值也固定

2)   TxnList代表记录记录集合,txn代表一条记录

Txn:checksum Txnlen TxnHeaderRecord 0x42由顺序的五部分组成

//序列化TxnHeader Record记录到byte[]

byte[] buf = Util.marshallTxnEntry(hdr, txn);

Checksum crc = makeChecksumAlgorithm();

//根据指定数组更新校验值

crc.update(buf, 0, buf.length);

//将校验吗写入输出流

oa.writeLong(crc.getValue(), "txnEntryCRC");

//将TxnHeader Record数据写入到输出流

//1.先计算buf数据长度写入

//2.写入buf数组数据

//3.记录尾部以’B’字符结尾,写入0x42

Util.writeTxnBytes(oa, buf);

2.1) checksum校验位计算,是由Adler32校验算法计算TxnHeader Record序列化后的字节码(跟文档说明有出入,文档说是由Txnlen TxnHeaderRecord 0x42计算出来的, 可是看代码不是,难道我理解错了????????)

2.2) TxnLen:记录数据长度包括记录头TxnHeader和记录Record

2.3)TxnHeader: {

sessionid 8bytes

cxid 4bytes  // 与客户端交互的xid

zxid 8bytes  // 服务器端生成的事务id

time 8bytes  // 时间

type 4bytes  // 事务操作的类型

}

2.4)Record:事务记录的内容,由jute规范定义了序列化反序列化流程,各个事务操作都实现了Record接口,下面看下创建的事务操作记录

public class CreateTxn implements Record {

privateString path;  //创建路径

privatebyte[] data;  //节点数据内容

privatejava.util.List<org.apache.zookeeper.data.ACL> acl; //节点权限

privateboolean ephemeral; //是否临时节点

privateint parentCVersion; //父节点的版本号

//下面过程就是序列化过程,反序列化类似

publicvoid serialize(OutputArchive a_, String tag) throws Java.io.IOException {

a_.startRecord(this,tag);

a_.writeString(path,"path");

a_.writeBuffer(data,"data");

{

a_.startVector(acl,"acl");

if(acl!= null) {          int len1 =acl.size();

for(int vidx1 = 0; vidx1<len1;vidx1++) {

org.apache.zookeeper.data.ACL e1 = (org.apache.zookeeper.data.ACL)acl.get(vidx1);

a_.writeRecord(e1,"e1");

}

}

a_.endVector(acl,"acl");

}

a_.writeBool(ephemeral,"ephemeral");

a_.writeInt(parentCVersion,"parentCVersion");

a_.endRecord(this,tag);

}

2.5)0x42:每条事务记录尾部以’B’字符结尾就是0x42

3) 每个文件尾部都用一个字符 0 填充, 工具Util.padLogFile扩充文件的时候在尾部填写上

6)FileTxnLog& FileTxnSnapLog工具

FileTxnLog类用来操作事务记录文件下面我们来看看这个类主要实现方法

4.1)append方法: 用来向文件尾部添加一条记录

4.1.1)判断当前输入流logStream是否已清空(在同步处理器SyncRequestProcessor中据一定算法得出一个count,记录大于count就要rollLog,开启一个新的文件,算法是: 100000/2 + random.nextInt(100000/2), 这个十万是一个默认值可配置),清空开启一个新的文件写入

4.1.2)padFile()判断是否要扩充文件容量预分配buffer,当buffer容量小于等于4k的时候预分配,每次扩充预分配64k容量

4.1.3)数据序列化,写入输入流缓存

4.2)read(zxid): 读取事务日志, 这个方法在服务当机恢复的时候,用来遍历事务日志来恢复数据。Zxid是事物日志号递增生成,在FileTxnLog中会将大于zxid的所有日志文件组合成一个FileTxnIterator用来遍历从zxid开始的所有日志

4.2.1)FileTxnIterator的构造器中调用init方法,init方法中过滤出所有需要读的日志文件,并利用goToNextLog()方法打开第一个日志日志文件的输入流

4.2.2)FileTxnIterator的next方法用来从日志文件中读取一条记录,校验并反序列化出来,读取成功返回true,如果读到了文件末尾调goToNextLog()读下一个文件,以此递归直到最后

4.2.3)FileTxnIterator的getTxn()方法,返回next()方法中读取的记录

4.3)commit()方法,将流中数据刷到硬盘SyncRequestProcessor中任务会定时调用异步刷盘

4.4)truncate(zxid)方法,用来删除日志,主要是在恢复数据的时候,利用leader的最后有效zxid,来删除learner的无效多余的事务记录。类似于数据库中的truncate操作概念,它并不是一条一条删除记录,大于zxid的文件直接将文件删除掉,zxid所在文件直接修改文件的长度,将文件长度设置到zxid所在的位置


4.5)序列化事务记录

4.6)反序列化事务记录

5FileTxnSnapLog

这是一个工具类主要用来操作TxnLog和Snapshot文件,我们主要关注一下restore方法

7)ZKDatabase

ZKDatabase在内存中维护了zookeeper的sessions, datatree和commit logs集合。 当zookeeper server启动的时候会将txnlogs和snapshots从磁盘读取到内存中

6.1)loadDatabase: 跟数据库的启动类似zookeeper服务启动结合txnlogs和snapshot, snapshot是内存数据的某个点一份影像,takeSnapshot操作还是很耗时,为了性能根据某算法(在同步处理器SyncRequestProcessor中据一定算法得出一个count,记录大于count就要takeSnapshot,算法是: 100000/2 + random.nextInt(100000/2),这个十万是一个默认值可配置)计算出一个点来异步做一次takeSnapShot操作,这个跟数据库实现原理上很类似, 但是这样在非正常关机情况下,最新有效的那个snapshot并不是内存中最新的数据,所以需要利用txnLogs来把没有生成snapshot的操作在内存重新执行一边来恢复到非正常关闭服务那一刻内存情况。

下面我们来看一下loadDatabase的流程:

6.1.1) 构建一个PlayBackListener对象

6.1.2) snapshot的反序列,倒叙排目录下的snapshot文件,遍历查找出最新的那个有效snapshot文件进行反序列化到内存(具体流程查看snapshot那部分介绍),snapshot的反序列后我们会知道snapshot最新的zxid叫做lastProcessedZxid, 这个lastProcessedZxid之前的事务操作,都成功执行并序列到snapshot中可恢复到内存,lastProcessedZxid之后的操作只有事务日志,不能直接通过snapshot恢复。

6.1.2) lastProcessedZxid+1从事务日志文件txnLog读取事务操作

FileTxnLog txnLog = newFileTxnLog(dataDir);

TxnIterator itr =txnLog.read(dt.lastProcessedZxid+1);

遍历TxnIterator,执行processTransaction方法,就是把事务操作在内存中在执行一边把丢失的操作补回来

同时将事务操作通过PlayBackListener添加到commitedLog集合,commitedLog的事务操作在服务恢复的时候会同步到其他leaner server, 因为很有可能其他leaner server也没有及时的takesnapshot

返回最后的事务日志zxid给database,作为ZKDatabase的最新事物id

6.1.3) 在zookeeperServer成功loadDatabase后,会及时主动的做一次takesnapshot操作来得到一份最新的内存影像

8)数据存储小结

Zookeeper数据是以文件形式存储在硬盘上的,以snapshot为主,txnlog为辅。因为当对内存数据进行变更的时候,会保证将事务操作记入log日志,而snapshot只是内存某一个时刻影像,为了性能takeSnapshot生成snapshot并不是实时的,而是由后台线程根据一定规则处理的

来看看snapshot和txnlog在磁盘上的文件

文件名是以log.或者snapshot.加上一串long的16进制数字组成,这个long值就是zxid服务器端事务id

Snapshot文件名生成, FileTxnSnapLog.save方法中

long lastZxid = dataTree.lastProcessedZxid;

FilesnapshotFile = new File(snapDir, Util.makeSnapshotName(lastZxid));

如上代码创建一个新的snapshot文件,工具Util用来用来创建文件名

public static String makeSnapshotName(long zxid) {

return "snapshot." +Long.toHexString(zxid);

}

日志Log文件生成,在FileTxnLog.apend方法中,如果被执行了rollLog方法,那么文件输入流会被清空,这里会创建一个新的文件

if (logStream==null) {

logFileWrite = new File(logDir,("log." + Long.toHexString(hdr.getZxid())));

fos = newFileOutputStream(logFileWrite);

………

}

如上代码可以看出文件名是最新请求的zxid,这里snapshot和log文件都和zxid有关,那么下面我们来看看zxid。

Zxid

当客户端一个事务请求操作是leader的PrepRequestProcessor处理器会对请求进行预处理包括生成zxid设置到请求中去,zxid的生成是通过调用ZookeeperServer.getNextZxid生成

protected long hzxid = 0;

synchronized long getNextZxid() {

return ++hzxid;

}

它是hzxid一个自增的long值,有没有奇怪这个变量取名叫做hzixd多了一个h, h我的理解是high的缩写代表64位long的高32位。Zxid的分为两部分高32位用来存储每次选举的时代epoch,低32位用来存储事务请求的自增序列。所谓选举时代就是一个数值,标记代表一次选举,跟年份一样是自增的。每次服务器启动或者zookeeper异常导致重新选举都会在原来epoch值加一代表一个新的时代,工具类ZxidUtils用来操作前32或者后32位

public class ZxidUtils {

static public long getEpochFromZxid(long zxid) {

return zxid >> 32L;

}

static public long getCounterFromZxid(long zxid) {

return zxid & 0xffffffffL;

}

static public long makeZxid(long epoch,long counter) {

return (epoch << 32L) | (counter & 0xffffffffL);

}

static public String zxidToString(long zxid) {

return Long.toHexString(zxid);

}

}

比如现在epoch=4代表经历了4次选举,如果重新选举后epoch值为5,通过工具类的zxid=hzxid=ZxidUtils.makeZxid(5,0)= 21474836480,此时低32重新开始值为0, 如果这时来了新的请求值为zxid=21474836481=21474836480+ 1 = ZxidUtils.makeZxid(5, 1)

zookeeper原理解析-数据存储的更多相关文章

  1. 1.zookeeper原理解析-数据存储之Zookeeper内存结构

    Zookeeper是怎么存储数据的,什么机制保证集群中数据是一致性,在网络异常,当机以及停电等异常情况下恢复数据的,我们知道数据库给我们提供了这些功能,其实zookeeper也实现了类似数据库的功能. ...

  2. 解析数据存储MySQL

    为了适应不同项目对不同感兴趣属性的解析存储,数据存储结构采用纵向的属性列表方式,即一个url页面多个属性存储多条记录方式,并且按照text,html, data,num几大典型类型分别对应存储. 创建 ...

  3. zookeeper原理解析-序列化

    1)底层通信数据封装与操作           BinaryInputArchive& BinaryOutputArchive底层通信数据封装与操作     BinaryInputArchiv ...

  4. zookeeper原理解析-选举

    1)QuorumPeerMain加载 Zookeeper集群启动的入口类是QuorumPeerMain来加载配置启动QuorumPeer线程.首先我们来看下QuorumPeer, 谷歌翻译quorum ...

  5. Zookeeper原理分析之存储结构Snapshot

    Zookeeper内存结构 Zookeeper数据在内存中的结构类似于linux的目录结构,DataTree代表这个目录结构, DataNode代表一个节点.DataTree默认初始化三个目录:&qu ...

  6. ZooKeeper学习之路 (八)ZooKeeper原理解析

    ZooKeeper中的各种角色 ZooKeeper与客户端 每个Server在工作过程中有三种状态: LOOKING:当前Server不知道leader是谁,正在搜寻 LEADING:当前Server ...

  7. ZooKeeper原理解析

    目录 ZooKeeper中的各种角色 ZooKeeper与客户端 Zookeeper节点数据操作流程 Paxos 算法概述(ZAB 协议) ZooKeeper 的选主机制 选择机制中的概念 选举消息内 ...

  8. zookeeper原理解析-客户端与服务器端交互

    Zookeeper集群中server数量总是确定的,所以集群中的server交互采用比较可靠的bio长连接模型:不同于集群中sever间交互zookeeper客户端其实数量是未知的,为了提高zooke ...

  9. zookeeper原理解析-服务器端处理流程

    1)处理器链 这部分内容我们主要讲解zookeeper请求在zookeeper server端的处理流程,对于不同角色的zookeeper具有不同的处理流程, ZookeepeerServer的sta ...

随机推荐

  1. 深入理解DOM节点类型第二篇——文本节点Text

    × 目录 [1]特征 [2]空白 [3]属性[4]方法[5]性能 前面的话 文本节点顾名思义指向文本的节点,网页上看到的文字内容都属于文本节点.该节点简单直观,本文将详细介绍该部分内容 特征 文本节点 ...

  2. 手把手教你用FineBI做数据可视化

    前些日子公司引进了帆软商业智能FineBI,在接受了简单的培训后,发现这款商业智能软件用作可视分析只用一个词形容的话,那就是“轻盈灵动”!界面简洁.操作流畅,几个步骤就可以创建分析,获得想要的效果.此 ...

  3. android6.0的坑

    虽然现在android已经出了7.0了.但是大部分人用的应该还是5.0和6.0的. 其中对于开发者来说,变化比较大的应该是6.0之前和6.0之后的版本. 因为以6.0为分界线多了一个比较坑的东西:权限 ...

  4. Android中使用java.util.Properties犯的错

    今天尝试使用java.util.Properties来保存应用配置,然而遇到了好几个问题,对于熟悉此内容的来说可能都是猪一样的错误,但难免有像我一样的新手再次遇到,希望此文能有所帮助. 错误1 jav ...

  5. Appfuse:记录操作日志

    appfuse的数据维护操作都发生在***form页面,与之对应的是***FormController,在Controller中处理数据的操作是onSubmit方法,既然所有的操作都通过onSubmi ...

  6. Javascript中,document.getElementsByName获取的就一定是数组了么?

    今天在一张JSP网页中,写一个javascript方法,用于全选. 全部被选checkBox位于一个名为mainForm的Form下,name=pushIds.方法如下: function selec ...

  7. Oracle的SQL基础

    1.了解SQL的种类 (1)DDL 数据定义语言:定义数据库中数据要如何存储的,包括对数据库对象的创建(create)修改(alter)删除(drop)的操作,这些对象主要有数据库,数据表,视图,索引 ...

  8. 使用python的Flask实现一个RESTful API服务器端[翻译]

    最近这些年,REST已经成为web services和APIs的标准架构,很多APP的架构基本上是使用RESTful的形式了. 本文将会使用python的Flask框架轻松实现一个RESTful的服务 ...

  9. shell 脚本之判断语句 if 详解

    使用 Linux 系统这么长时间,对 shell 脚本也算是比较熟悉.其实不管是搞开发,还是搞运维,shell 脚本都是必备的基本技能.这次抽时间好好总结一下 shell 方面的知识,综合的再学习一下 ...

  10. 用普通计算机假设基于liunx系统的NAS部署FineReport决策系统

    何为NAS? 简单说就是连接在网络上,具备资料存储功能的装置因此也称为“网络存储器”.它是一种专用数据存储服务器.他以数据为中心,将存储设备与服务器彻底分离,集中管理数据,从而释放带宽.提高性能.降低 ...