Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

文件存储模块,向MetadataDiskManager提供MetaFile接口,目前文件读写的主要实现逻辑在MTreeFile中。提供MNode在文件中的read、write以及remove等功能接口。

Table of Contents

1.

...

模块概述

MetaFile模块向CachedMTreeStore提供可靠的MTree文件存储功能,提供MNode在文件中的读写接口。

本模块有以下两个特征:

  1. 以存储组子树为粒度,组织整个元数据树的持久化存储
  2. 单个文件内部为树形文件格式
  3. 用户可配置文件文件记录大小
  4. 设置文件整理线程,定期优化文件内的数据组织,将其整理为查询友好的格式
  5. 重启恢复时,文件可以恢复到一个没有脏数据的完整可用的状态


2. 文件组织设计

元数据树的持久化文件以存储组为单位进行组织,每个存储组代表的子树维护一个MTreeFile。

  1. 文件名以存储组名(full path)命名
  2. MTreeFile内部以该存储组节点为根节点

2. 文件格式设计

...


对于存储组层往上的前缀树,也使用一个MTreeFile进行管理。

  1. 该MTreeFile内部以全局MTree的root节点为根节点
  2. 该MTreeFile存储StorageGroupMNode父节点以上的MNode
  3. StorageGroupMNode父节点在该文件中指向StorageGroupMNode的地址指针值为-1,标志子节点需至具体的存储组文件进行查询


3. 树形文件格式设计

3.1. 文件整体设计

文件整体采用定长记录的组织方式,即一个定长的Header+定长Node序列。Header中存储文件的描述性信息,Node中存储MNode的序列化信息。

Header中目前的字段信息如下表所示。

字段名类型大小描述
header lengthbyte1 byte文件头的长度,1byte 可保证 256 个字节的存储,已足够当前 header 的信息存储,当前的header使用64 byte
node lengthshort2 byte

一个节点记录的长度,2 byte长度可保证65536个字节的存储;

目前代码中多使用512B或1024B,实验表明类似sg1、sg2、d1、d2这种的简单命名,1KB的Node大小可支撑100个子节点

root positionlong8 byte根节点的地址
reserved

保留字段

...

3.2.

...

文件记录Node设计

MNode对象的序列化信息在MTree文件中,以定长Node记录链表的形式进行存储。Node记录中的信息包含两个部分,Node Header与 data body, 其中Node Header包含bitmap、pre position、extension position三个字段。Node记录有如下几种类型:

  1. Root Node,根节点。

  2. Internal Node,中间节点记录形式,主要存储name、parent、children等结构信息。

  3. StorageGroup Node,在中间节点的基础上额外存储一个dataTTL字段。

  4. Entity Node,实体节点,在中间节点基础上额外存储aliasChildren、template、LastCacheMap等信息。

  5. StorageGroupEntity Node,实体节点与存储组节点的结合形式,为二者信息取并集。

  6. Measurement Node,在中间节点的基础上额外存储alias、tagOffset、schema以及lastCache信息。

  7. 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

模板名

isUsingTemplate1 byte是否使用模板

children




size

var int

child数量

name

position

var


子节点map,存储采用键值对序列的形式,其中name采用string格式,表示子节点名,position采用long,表示子节点地址,最后一个end tag 即1 byte的 00000000 表示map结束

name

position

......

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

模板名

isUsingTemplate1 byte是否使用模板

children




sizevar intchild数量

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

模板名

isUsingTemplate1 byte是否使用模板

children

size

var int

child数量

name

position

var



子节点map,存储采用键值对序列的形式,其中name采用string格式,表示子节点名,position采用long,表示子节点地址,最后一个end tag 即1 byte的 00000000 表示map结束

name

position

......

name

position

aliasChildren

sizevar int
子节点别名map,存储方式与children相同

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

模板名

isUsingTemplate1 byte是否使用模板

children

size

var int

child数量

name

position

var



子节点map,存储采用键值对序列的形式,其中name采用string格式,表示子节点名,position采用long,表示子节点地址,最后一个end tag 即1 byte的 00000000 表示map结束

name

position

......

name

position

aliasChildren

size

var int

子节点别名map,存储方式与children相同

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



Image Added

...


4. 文件读写功能设计

目前的文件读写功能的实现主要分三个层次

  1. MetaFile 层:对外接口为 MetaFileAccess,提供 MNode 文件读写功能;主要负责交互逻辑的处理,存储组文件的管理;与外部交互所使用的数据类为PersistenceInfo。目前的一个具体实现为 MetaFile 类。

  2. MTreeFile 层:在单个文件内,实现基于地址的 MNode 文件读写功能,包括MNode序列化和反序列化、基于地址的节点读写与节点删除等功能。文件二进制数据的读取和写入通过调用 SlottedFile 层进行实现。

  3. SlottedFile 层:对外接口为 SlottedFileAccess,提供基于地址和长度的二进制数据读取功能。实现文件头加定长记录序列的这种格式的文件数据读写,以块为数据传输单位进行文件读写。目前的一个实现为 SlottedFile 类。

...



4.1. MNode读取

目前的MNode读取以寻址读取为主。在获取到MNode的存储地址后,直接对文件对应位置的序列化数据进行读取,主要分为以下几个步骤:

  1. 获取二进制数据:首先读取第一个Node的bitmap中的节点类型,将文件中的Node链表中的每个Node的data body读取为完整的ByteBuffer。
  2. 反序列化:基于节点类型创建对应的对象,依次读取ByteBuffer中的字段信息。

...

4.2. MNode写入

MNode对象的创建与修改,都以写入的方式在文件中实现。MNode的写入分以下两个步骤:

  1. 序列化:按照MNode在文件中的字段存储顺序,将所有属性信息序列化为一个ByteBuffer。
  2. 二进制数据分割与写入:将1中得到的ByteBuffer,根据文件Node的data body长度进行分割,分割过程中给每一个Extension Node分配空闲空间,数据分割与地址分配完成后,进行数据写入

...


4.3. Node跨页读写

针对链表型的二进制数据,设计合理的数据结构,数据从磁盘读进内存时直接读入该数据结构并自动实现切割。

该数据结构需支持跨块/跨Node字段的二进制数据组合读取和分割写入: ? 每个Node结尾设置end 每个Node的body结尾设置end tag,表示该页已结束,需要使用下一页。

该数据结构可避免序列化数据的切割和二次copy。

...


4.4. MNode删除与文件整理

上层执行节点删除后,其父节点中指向该节点的指针被清除,意味着该子树的存储空间不会再被访问,成为文件内碎片。

3.5. 文件地址预分配

文件地址管理包括地址预分配

每一个MNode在MTree上创建时,地址预分配保证了后期刷盘的父节点地址与子节点地址信息完整性。

...

设计一个文件整理线程,定期地将MTreeFile整理为查询友好的格式。

文件整理线程的任务:

  1. 清空文件内部碎片,提高磁盘利用率
  2. 对MNode顺序进行重新排列,支持后续的功能开发,例如:
    1. 一个MNode的子节点按name字典序排列,支持按name过滤的范围查询,如查找device编号在device1-device20之间的device节点
    2. 后期引入统计信息,识别热点序列/MNode,将经常查询的一条路径上的MNode尽量存储在同一个磁盘块或相近的磁盘块

文件整理线程的触发时机:

  1. 如果仅为了处理文件碎片,可以根据delete的操作次数或delete的子树节点数量/size进行统计和触发
  2. 后期为了优化查询,可以设计定时整理


4.5. 两阶段提交

MTree文件为随机读写文件,为了保证文件的可用性和完整性,避免宕机时脏数据破坏文件,参照Mysql设计两阶段写功能。

创建tmp文件,作为磁盘buffer,记录目标地址和写入数据。

...


4.

...

5.1. 磁盘buffer的格式设计

逻辑上一个磁盘buffer应具备如下三个状态:

  1. 未被使用:此时的重启恢复无需处理磁盘buffer,也证明文件处于完整可用的状态
  2. 正在准备数据:此时重启恢复需要清空磁盘buffer,新数据仍未写入文件,抛弃该次写入操作
  3. 完成数据准备,正在执行数据迁移:此时重启恢复需要重做数据迁移,直接覆盖目标地址空间


因此磁盘buffer的格式可有如下的初步设计:

  1. 标记位,是否已正在被使用
  2. 标记位,是否已完成buffer数据的准备
  3. 目标地址
  4. 待刷盘数据,以链表为粒度一次性准备好

...


4.

...

5.2. 写入流程

  1. 将目标地址刷入磁盘buffer,并将其设置为正在使用且未完成数据准备状态
  2. 将所有已修改的MNode的数据写入磁盘buffer
  3. 将磁盘buffer更新为正在使用且数据准备完成状态
  4. 将磁盘buffer的数据写入MNode在MTreeFile的指定位置(覆盖原有数据)
  5. 将磁盘buffer更新为未使用状态

...


4.

...

5.3. 重启恢复

单个MTreeFile重启时检查磁盘buffer状态

  1. 数据未准备好,则清空磁盘buffer,结束恢复
  2. 数据已准备好,重新将数据更新至文件中指定地址,清空buffer,结束恢复