Table of Contents |
---|
1. 模块概述
MetaFile模块向CachedMTreeStore提供可靠的MTree文件存储功能,提供MNode在文件中的读写接口。
...
- 以存储组子树为粒度,组织整个元数据树的持久化存储
- 单个文件内部为树形文件格式
- 用户可配置文件文件记录大小
- 设置文件整理线程,定期优化文件内的数据组织,将其整理为查询友好的格式
- 重启恢复时,文件可以恢复到一个没有脏数据的完整可用的状态
2. 文件组织设计
元数据树的持久化文件以存储组为单位进行组织,每个存储组代表的子树维护一个MTreeFile。
...
- 该MTreeFile内部以全局MTree的root节点为根节点
- 该MTreeFile存储StorageGroupMNode父节点以上的MNode
- StorageGroupMNode父节点在该文件中指向StorageGroupMNode的地址指针值为-1,标志子节点需至具体的存储组文件进行查询
3. 树形文件格式设计
3.1. 文件整体设计
文件整体采用定长记录的组织方式,即一个定长的Header+定长Node序列。Header中存储文件的描述性信息,Node中存储MNode的序列化信息。
Header中目前的字段信息如下表所示。
字段名 | 类型 | 大小 | 描述 |
---|---|---|---|
header length | byte | 1 byte | 文件头的长度,1byte 可保证 256 个字节的存储,已足够当前 header 的信息存储,当前的header使用64 byte |
node length | short | 2 byte | 一个节点记录的长度,2 byte长度可保证65536个字节的存储; 目前代码中多使用512B或1024B,实验表明类似sg1、sg2、d1、d2这种的简单命名,1KB的Node大小可支撑100个子节点 |
root position | long | 8 byte | 根节点的地址 |
reserved | 保留字段 |
3.2. 文件记录Node设计
MNode对象的序列化信息在MTree文件中,以定长Node记录链表的形式进行存储。Node记录中的信息包含两个部分,Node Header与 data body, 其中Node Header包含bitmap、pre position、extension position三个字段。Node记录有如下几种类型:
Root Node,根节点。
Internal Node,中间节点记录形式,主要存储name、parent、children等结构信息。
StorageGroup Node,在中间节点的基础上额外存储一个dataTTL字段。
Entity Node,实体节点,在中间节点基础上额外存储aliasChildren、template、LastCacheMap等信息。
StorageGroupEntity Node,实体节点与存储组节点的结合形式,为二者信息取并集。
Measurement Node,在中间节点的基础上额外存储alias、tagOffset、schema以及lastCache信息。
Extension Node,每个Node记录内依次存储字段信息,如果一个Node记录空间不够,则开额外的Extension Node进行使用,即MNode对象的序列化信息被组织成链表进行存储。
Internal Node的格式如下:
字段 | 描述 | ||||
长度 | 内容 | ||||
Header | bitmap | 1 byte | 1-3位用于标识类别 | ||
Pre position | 8 byte | 若Node不是Extension Node,则表示父节点地址,Extension Node的此位表示链表前置Node的地址 | |||
Next position | 8 byte | 链表中下一个Extension Node的地址 | |||
Body | Name | var | MNode节点名,String的存储一律采用length+content的形式,其中length采用varInt类型,content采用byte[] | ||
templateName | var | 模板名 | |||
isUsingTemplate | 1 byte | 是否使用模板 | |||
children | size | var int | child数量 | ||
name | position | var | 子节点map,存储采用键值对序列的形式,其中name采用string格式,表示子节点名,position采用long,表示子节点地址,最后一个end tag 即1 byte的 00000000 表示map结束 | ||
...... | |||||
name | position |
StorageGroup Node的格式如下:
字段 | 描述 | ||||
长度 | 内容 | ||||
Header | bitmap | 1 byte | 1-3位用于标识类别 | ||
Pre position | 8 byte | 若Node不是Extension Node,则表示父节点地址,Extension Node的此位表示链表前置Node的地址 | |||
Next position | 8 byte | 链表中下一个Extension Node的地址 | |||
Body | Name | var | MNode节点名,String的存储一律采用length+content的形式,其中length采用varInt类型,content采用byte[] | ||
dataTTL | 8 byte | 存储组中的数据生存时间 | |||
templateName | var | 模板名 | |||
isUsingTemplate | 1 byte | 是否使用模板 | |||
children | size | var int | child数量 | ||
name | position | var | 子节点map,存储采用键值对序列的形式,其中name采用string格式,表示子节点名,position采用long,表示子节点地址,最后一个end tag 即1 byte的 00000000 表示map结束 | ||
...... | |||||
name | position |
EntityMNode
字段 | 描述 | ||||
长度 | 内容 | ||||
Header | bitmap | 1 byte | 1-3位用于标识类别 | ||
Pre position | 8 byte | 若Node不是Extension Node,则表示父节点地址,Extension Node的此位表示链表前置Node的地址 | |||
Next position | 8 byte | 链表中下一个Extension Node的地址 | |||
Body | Name | var | MNode节点名,String的存储一律采用length+content的形式,其中length采用varInt类型,content采用byte[] | ||
templateName | var | 模板名 | |||
isUsingTemplate | 1 byte | 是否使用模板 | |||
children | size | var int | child数量 | ||
name | position | var | 子节点map,存储采用键值对序列的形式,其中name采用string格式,表示子节点名,position采用long,表示子节点地址,最后一个end tag 即1 byte的 00000000 表示map结束 | ||
...... | |||||
name | position | ||||
aliasChildren | size | var int | child数量 | ||
name | position | var | 子节点别名map,存储方式与children相同 | ||
...... | |||||
name | position |
StorageGroupEntityMNode
字段 | 描述 | ||||
长度 | 内容 | ||||
Header | bitmap | 1 byte | 1-3位用于标识类别 | ||
Pre position | 8 byte | 若Node不是Extension Node,则表示父节点地址,Extension Node的此位表示链表前置Node的地址 | |||
Next position | 8 byte | 链表中下一个Extension Node的地址 | |||
Body | Name | var | MNode节点名,String的存储一律采用length+content的形式,其中length采用varInt类型,content采用byte[] | ||
dataTTL | 8 byte | 存储组中的数据生存时间 | |||
templateName | var | 模板名 | |||
isUsingTemplate | 1 byte | 是否使用模板 | |||
children | size | var int | child数量 | ||
name | position | var | 子节点map,存储采用键值对序列的形式,其中name采用string格式,表示子节点名,position采用long,表示子节点地址,最后一个end tag 即1 byte的 00000000 表示map结束 | ||
...... | |||||
name | position | ||||
aliasChildren | size | var int | child数量 | ||
name | position | var | 子节点别名map,存储方式与children相同 | ||
...... | |||||
name | position |
Measurement Node的格式如下:
字段 | 描述 | |||||
长度 | 内容 | |||||
Header | bitmap | 1 byte | 1-3位用于标识类别 | |||
Pre position | 8 byte | 若Node不是Extension Node,则表示父节点地址,Extension Node的此位表示链表前置Node的地址 | ||||
Next position | 8 byte | 链表中下一个Extension Node的地址 | ||||
Body | Name | var | MNode节点名,String的存储一律采用length+content的形式,其中length采用varInt类型,content采用byte[] | |||
alias | var | Measurement MNode节点别名 | ||||
tagOffset | 8 byte | 标签/属性信息在TagFile中的位置 | ||||
schema | type | 1 byte | 数据类型 | |||
encoding | 1 byte | 编码方式 | ||||
compressor | 1 byte | 压缩方式 | ||||
props | key | value | var | 压缩参数 | ||
key | value | |||||
...... | ||||||
End tag |
4. 文件读写功能设计
目前的文件读写功能的实现主要分三个层次
MetaFile 层:对外接口为 MetaFileAccess,提供 MNode 文件读写功能;主要负责交互逻辑的处理,存储组文件的管理;与外部交互所使用的数据类为PersistenceInfo。目前的一个具体实现为 MetaFile 类。
MTreeFile 层:在单个文件内,实现基于地址的 MNode 文件读写功能,包括MNode序列化和反序列化、基于地址的节点读写与节点删除等功能。文件二进制数据的读取和写入通过调用 SlottedFile 层进行实现。
SlottedFile 层:对外接口为 SlottedFileAccess,提供基于地址和长度的二进制数据读取功能。实现文件头加定长记录序列的这种格式的文件数据读写,以块为数据传输单位进行文件读写。目前的一个实现为 SlottedFile 类。
4.1. MNode读取
目前的MNode读取以寻址读取为主。在获取到MNode的存储地址后,直接对文件对应位置的序列化数据进行读取,主要分为以下几个步骤:
- 获取二进制数据:首先读取第一个Node的bitmap中的节点类型,将文件中的Node链表中的每个Node的data body读取为完整的ByteBuffer。
- 反序列化:基于节点类型创建对应的对象,依次读取ByteBuffer中的字段信息。
4.2. MNode写入
MNode对象的创建与修改,都以写入的方式在文件中实现。MNode的写入分以下两个步骤:
- 序列化:按照MNode在文件中的字段存储顺序,将所有属性信息序列化为一个ByteBuffer。
- 二进制数据分割与写入:将1中得到的ByteBuffer,根据文件Node的data body长度进行分割,分割过程中给每一个Extension Node分配空闲空间,数据分割与地址分配完成后,进行数据写入
4.3. Node跨页读写
针对链表型的二进制数据,设计合理的数据结构,数据从磁盘读进内存时直接读入该数据结构并自动实现切割。
该数据结构需支持跨块/跨Node字段的二进制数据组合读取和分割写入: 每个Node的body结尾设置end tag,表示该页已结束,需要使用下一页。
该数据结构可避免序列化数据的切割和二次copy。
4.4. MNode删除与文件整理
上层执行节点删除后,其父节点中指向该节点的指针被清除,意味着该子树的存储空间不会再被访问,成为文件内碎片。
...
- 如果仅为了处理文件碎片,可以根据delete的操作次数或delete的子树节点数量/size进行统计和触发
- 后期为了优化查询,可以设计定时整理
4.5. 两阶段提交
MTree文件为随机读写文件,为了保证文件的可用性和完整性,避免宕机时脏数据破坏文件,参照Mysql设计两阶段写功能。
创建tmp文件,作为磁盘buffer,记录目标地址和写入数据。
4.5.1. 磁盘buffer的格式设计
逻辑上一个磁盘buffer应具备如下三个状态:
- 未被使用:此时的重启恢复无需处理磁盘buffer,也证明文件处于完整可用的状态
- 正在准备数据:此时重启恢复需要清空磁盘buffer,新数据仍未写入文件,抛弃该次写入操作
- 完成数据准备,正在执行数据迁移:此时重启恢复需要重做数据迁移,直接覆盖目标地址空间
...
- 标记位,是否已正在被使用
- 标记位,是否已完成buffer数据的准备
- 目标地址
- 待刷盘数据,以链表为粒度一次性准备好
4.5.2. 写入流程
- 将目标地址刷入磁盘buffer,并将其设置为正在使用且未完成数据准备状态
- 将所有已修改的MNode的数据写入磁盘buffer
- 将磁盘buffer更新为正在使用且数据准备完成状态
- 将磁盘buffer的数据写入MNode在MTreeFile的指定位置(覆盖原有数据)
- 将磁盘buffer更新为未使用状态
4.5.3. 重启恢复
单个MTreeFile重启时检查磁盘buffer状态
...