一、内存结构
类似Unix的文件系统,Zookeeper在内存中维护着一个树形层级结构,只不过没有目录,每个节点被成为znode.znode可以存放数据,并有自己的访问权限列表和关联的统计信息(stat).
然而Zookeeper是用来设计作为协同服务的,而不是用于大容量数据存储。虽然每个znode上的数据上限是1M.即便如此,我们还是不推荐开发人员按照这个上限来使用,因为客户端的写请求在服务端都是串行处理,数据的传输会大大影响性能。
在内存中,DataTree维护着ZNode的树形结构,然而在处理来自于客户端的对目标节点的操作请求时,zk并非从根节点层层找到目标节点,而是通过另外一个hashtable通过全路径直接定位到目标节点。这样做的目的是为了提高查找效率,将复杂度从O(pathNodes)降低到O(1).
1
|
private
final
ConcurrentHashMap<String, DataNode> nodes =
new
ConcurrentHashMap<String, DataNode>();
|
在DataNode上除了其parent,data,acl,children等属性外,还保存了一些统计数据Stat:
1
2
3
4
5
6
7
8
9
|
czxid 创建这个znode的zxid
mzxid 最后一次修改这个znode的zxid
ctime 该znode创建时间
mtime 该znode最后一次修改的时间
version 该znode的version,也就是该znode的修改次数。
cversion 该znode的子节点的更新次数
aversion 该znode的ACL信息更新次数
ephemeralOwner 如果该znode是ephemeral node,此字段就是对应client的sessionId;否则为0。
pzxid create or delete childnode txn id
|
二、磁盘存储
ZK的数据存储在磁盘上分为快照和事务日志两种:
1.对于客户端的写请求zk会将其写入到transaction log中
2.如果事务日志达到一定数量,则产生一个新日志,并启动一个线程进行进行takeSnapshot,把当前的内存DataTree和会话信息写入到snapshot file中,snapshot的文件名以datatree的最后处理的transactionId为结尾
3.ZK在启动的时候首先将snapshot从磁盘加载到内存的DataTree中,然后根据dataTree.lastProcessedZxid+1找到需要处理的transaction log记录,一一附加到datatree上。
Zookeeper高效原因之一就是对请求的处理主要是操作内存结构. 然而对于每个写请求都需要写事务日志,为了提高append日志效率,zk使用了group commit机制,也就是并非每条日志都直接flush到磁盘上,而是等到多个请求产生的日志缓存起来,到达指定条数后,一次性flush到磁盘上。
此外zk为了在运行过程中迅速地向事务日志文件中append记录,为每个txn log文件预先分配了指定大小的磁盘块(通过preAllocSize来设置),默认是64M,接近这个值的时候(少于4k)便会再次分配64M.这样做的原因是如果没有预先分配,那么写完一个块的话就要重新分配,会带来额外的磁盘寻道。
preAllocSize默认是64M,但是我们可以根据snapCount(事务日志达到多少条便产生一个snapshot)的数量来预估事务日志文件的大小。这是因为每当达到snapCount对应的事务日志条数后,便会产生一个新日志(rollLog)。合理的预估有助于避免空间浪费。
我们看一下快照文件的二进制结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
由于图较小,无法将具体信息都放进去,我们看下ACL和DataNode的具体结构:
Map<nodeAcl,List<ACL>>
nodeAcl(long)
ACL:
scheme(string)
identifier(string)
permissions(int)
Tree<DataNode> 从根节点开始开始遍历
path(string)
data(bytes)
acl(long) access control list
czxid(long) createNode txn id
mzxid(long) setData txn id
ctime(long) createNode time
mtime(long) setData time
version(int) increase when setData
cversion(int) increase when delete or create childnode
aversion(int) setAcl txn id
ephemeralOwner(long) It is sessionId if ephemeral node,else 0
pzxid(long) create or delete childnode txn id
|
我们再看一下事务日志文件的二进制结构:
http://www.mthinking.net/blog/tecnology/dailylearning/918bc3a1-e154-4175-82b5-cd539c67c55f