时序数据库 Apache-IoTDB 源码解析之文件数据块(四)

上一章聊到行式存储、列式存储的基本概念,并介绍了 TsFile 是如何存储数据以及基本概念。详情请见:

时序数据库 Apache-IoTDB 源码解析之文件格式简介(三)

打一波广告,欢迎大家访问IoTDB 仓库,求一波 Star 。欢迎关注头条号:列炮缓开局,欢迎关注OSCHINA博客

这一章主要想聊一聊:

  1. TsFile的文件概览
  2. TsFile的数据块

TsFile文件概览

一个完整的 TsFile 是由图中的几大块组成,图中的数据块与索引块之间使用 1 个字节的分隔符 2 来进行分隔,这个分隔符的意义是当 TsFile 损坏的时候,顺序扫描 TsFile 时,依然可以判断下一个是 MetaData 是什么东西。

1. 识别符(Magic)

现在各种软件五花八门,很多软件都拥有自己的文件格式用来存储数据内容,但当硬盘上文件非常多的时候如何有效的识别是否为自己的文件,确认可以打开呢?经常用 windows 系统的朋友可能会想到用扩展名,但假如文件名丢失了,那我们如何知道这个文件是不是能被程序正确访问呢?

这时候通常会使用一个独有的字符填充在文件开头和结尾,这样程序只要访问 1 个固定长度的字符就知道这个文件是不是自己能正常访问的文件了,当然,TsFile 作为一个数据库文件,肯定需要在这个识别符上精心打造一番,它看起来是这样:

   (decimal)  84 115 70 105 108 101
   (hex)      54 73  46 69  6c  65
   (ASCII)    T  s   F  i   l   e

非常 cool 。

2.文件版本(Version)

再精妙的设计也难免产生一些问题,那么就需要升级,那么文件内容也一样,有时候当你的改动特别大了,就会出现完全不兼容的两个版本,这个很好理解不过多解释。TsFile 中采用了 6 个字节来保存文件版本信息,当前 0.9.x 版本看起来就是这样:

   (decimal)  48  48  48  48  48  50
   (hex)      30  30  30  30  30  32
   (ASCII)    0   0   0   0   0   2

3.数据块

3.1 ChunkGroup

文件的数据块中包含了多个 ChunkGroup ,其中 ChunkGroup 的概念已经在上一章聊过,它代表了设备(逻辑概念上的一个集合)一段时间内的数据,在 IoTDB 中称为 Device。

在实际的文件中,ChunkGroup是由多个 Chunk 和一个 ChunkGroupFooter 组成。其中最后一个 Chunk 的结尾和 ChunkGroupFooter 之间使用 1 个字节的分隔符 0 来做区分,ChunkGroupFooter 没有什么具体作用,不做详细解释。

3.2 Chunk

一个 ChunkGroup 中包含了多个 Chunk,它代表了测点数据(逻辑概念上的某一类数据的集合,如体温数据),在 IoTDB 中称为 Measurement。

在实际文件中 Chunk 是由 ChunkHeader 和多个 Page 组成,并被 1 个字节的分隔符 1 包裹。ChunkHeader中主要保存了当前 Chunk 的数据类型、压缩方式、编码方式、包含的 Pages 占用的字节数等信息。

3.3 Page

一个 Chunk 中包含多个 Page,它是一个数据组织方式,数据大小被限制在 64K 左右。

在实际文件中由 PageHeader 和 PageData 组成。其中 PageHeader 里主要保存了,当前 page 里的一些预聚合信息,包含了最大值、最小值、开始时间、结束时间等。他的存在是非常有意义的,因为当某些特定场景的读时候,不必要解开 page 的数据就能够得到结果,比如说 selece 体温 from 王五 where time > 1580950800 , 当读到 PageHeader 的时候,找到 startTime 和 endTime 就能判断是否可以使用当前 page。 这个聚合信息的结构同样出现在索引块中,下一章再具体聊这个聚合结构。

3.4 PageData

一个 Page 中包含了一个 PageData,里面有两个数组:时间数组和值数组,且这两个数组的下标是对齐的,也就是时间数组中的第一个对应值数组中的第一个。举个例子:

timeArray: [1,2,3,4]
valueArray: [‘a‘, ‘b‘, ‘c‘, ‘d‘]

在page中就是这样保存的数据,其中 1 代表了时间 1970-01-01 08:00:00 后的 1 毫秒,对应的值就是 ‘a‘。

数据块展示

我们继续使用上一章聊到的示例数据来展示真正的TsFile中是如何保存的。

时间戳 人名 体温 心率
1580950800 王五 36.7 100
1580950911 王五 36.6 90

当数据被写入 TsFile 中,大概就是下面一个展示的情况,这里省略了索引部分。

            POSITION|	CONTENT
            -------- 	-------
                   0|	[magic head] TsFile
                   6|	[version number] 000002
                  // 因为 6个字节的magic + 6个字节的 version 所以 chunkGroup 从 12 开始
|||||||||||||||||||||	[Chunk Group] of wangwu begins at pos 12, ends at pos 253, version:0, num of Chunks:2
                   // 这里展示的是 ChunkHeader 中保存的信息
                  12|	[Chunk] of xinlv, numOfPoints:1, time range:[1580950800,1580950800], tsDataType:INT32,
                     	[minValue:100,maxValue:100,firstValue:100,lastValue:100,sumValue:100.0]
                    |		[marker] 1      //  chunk 的真正开始是从这个分隔符 1 开始的
                    |		[ChunkHeader]    // header 的数据在上面展示了
                    |		1 pages         //这里保存的具体数据
                    |		time:1580950800; value:100
                   // 下一个 chunk
                 121|	[Chunk] of tiwen, numOfPoints:1, time range:[1580950800,1580950800], tsDataType:FLOAT,
                     	[minValue:36.7,maxValue:36.7,firstValue:36.7,lastValue:36.7,sumValue:36.70000076293945]
                    |		[marker] 1
                    |		[ChunkHeader]
                    |		1 pages
                    |		time:1580950800; value:36.7
                 230|	[Chunk Group Footer]
                    |		[marker] 0 // chunkFooter 和 chunk 使用 0 作为分隔
                    |		[deviceID] wangwu
                    |		[dataSize] 218
                    |		[num of chunks] 2
|||||||||||||||||||||	[Chunk Group] of wangwu ends

回想我们的查询语句 select 体温 from 王五 , 当经历过索引之后会得到 offset 的值等于 121 ,这时候我们只需要调用reader.seek(121),从这里开始就是所有体温数据的开始点,从这里一直读到 230 的 ChunkGroupFooter 结构的时候,就可以返回给用户数据了。

有兴趣自己实验的朋友可以,引入 TsFile 的包,自行实验,下面给出测试代码:

<dependency>
    <groupId>org.apache.iotdb</groupId>
    <artifactId>tsfile</artifactId>
    <version>0.9.1</version>
</dependency>
public static void main(String[] args) throws IOException, WriteProcessException {
  MeasurementSchema chunk1 = new MeasurementSchema("tiwen", TSDataType.FLOAT, TSEncoding.PLAIN);
  MeasurementSchema chunk2 = new MeasurementSchema("xinlv", TSDataType.INT32, TSEncoding.PLAIN);  

  Schema chunks = new Schema();
  chunks.registerMeasurement(chunk1);
  chunks.registerMeasurement(chunk2);  

  TsFileWriter writer = new TsFileWriter(new File("test"), chunks);  

  RowBatch chunkGroup = chunks.createRowBatch("wangwu");  

  long[] timestamps = chunkGroup.timestamps;
  Object[] values = chunkGroup.values;  

  timestamps[0] = 1580950800;
  float[] tiwen = (float[]) values[0];
  int[] xinlv = (int[]) values[1];  

  // 写入王五的体温
  tiwen[0] = 36.7f;
  //写入王五的心率
  xinlv[0] = 100;
  chunkGroup.batchSize++;  

  timestamps[1] = 1580950800;
  // 写入第二条王五的体温
  tiwen[1] = 36.6f;
  //写入第二条王五的心率
  xinlv[1] = 90;
  chunkGroup.batchSize++;  

  writer.write(chunkGroup);
  writer.close();
}

执行完成之后你可以使用 IoTDB 中的 TsFileSketchTool 来查看文件结构,得到文中示例的展示结果;或者使用 od 等工具查看,祝玩儿的开心。IoTDB 0.9.1 版本下载

这一章聊到了 TsFile 分为了 数据块 和 索引块,并且介绍了数据块的具体组成部分和查询逻辑。那么索引块是什么结构,怎样完成了在大量混杂的数据中搜索到的想要的数据,请持续关注。

原文地址:https://www.cnblogs.com/liutaohua/p/12298486.html

时间: 2024-10-11 16:35:17

时序数据库 Apache-IoTDB 源码解析之文件数据块(四)的相关文章

时序数据库 Apache-IoTDB 源码解析之文件索引块(五)

上一章聊到 TsFile 的文件组成,以及数据块的详细介绍.详情请见: 时序数据库 Apache-IoTDB 源码解析之文件数据块(四) 打一波广告,欢迎大家访问IoTDB 仓库,求一波 Star. 这一章主要想聊聊: TsFile索引块的组成 索引块的查询过程 索引块目前在做的改进项 索引块 索引块由两大部分组成,其写入的方式是从左到右写入,也就是从文件头向文件尾写入.但读出的方式是先读出TsFileMetaData 再读出 TsDeviceMetaDataList 中的具体一部分.我们按照读

jquery源码解析:jQuery数据缓存机制详解1

jQuery中有三种添加数据的方法,$().attr(),$().prop(),$().data().但是前面两种是用来在元素上添加属性值的,只适合少量的数据,比如:title,class,name等.对于json这种数据量大的,就适合用data方法来添加,而data方法就是jQuery缓存机制最重要的方法. jQuery中为什么要用缓存机制系统呢?因为DOM元素与js对象之间互相引用,在大部分浏览器下会引起内存泄漏.为了解决这个问题,jQuery就写了一个缓存机制系统.举个例子: var di

tp3.2源码解析——入口文件

如果有人读这篇文章并跟着做的话,希望你能使用支持函数跳转的编辑器,还要善用var_dump和exit,对着源码去调试着看.跟着入口文件读,执行到哪里你看到哪里,对于那些不能一眼看出来的配置,则要记录下来,可能一个比较简单的功能会写出很长的代码,这个时候难免会看到后面忘了前面. 那么进入正题,从index.php文件可以看到入口文件只定义了几项常量作为配置,紧接着就引入了require './ThinkPHP/ThinkPHP.php'; 1 // 检测PHP环境 2 if(version_com

Apache Flink源码解析之stream-window

window(窗口)是Flink流处理中非常重要的概念,本篇我们来对窗口相关的概念以及关联的实现进行解析.本篇的内容主要集中在package org.apache.flink.streaming.api.windowing下. Window 一个Window代表有限对象的集合.一个窗口有一个最大的时间戳,该时间戳意味着在其代表的某时间点--所有应该进入这个窗口的元素都已经到达. Flink的根窗口对象是一个抽象类,只提供了一个抽象方法: public abstract long maxTimes

Apache Flink源码解析之stream-operator

前面我们谈论了Flink stream中的transformation.你可以将transformation看成编写Flink程序并构建流式处理程序的必要组成部分(静态表现形式):而本篇我们将探讨transformation在Flink运行时对应的动态表现形式--operator.他们之间的映射关系见下图: 具体的探讨可以查看前文:Flink中的一些核心概念 StreamOperator 所有operator的最终基类,operator的分类方式,按照输入流个数不同分为: 无输入:StreamS

Apache Flink源码解析之stream-windowfunction

Window也即窗口,是Flink流处理的特性之一.前一篇文章我们谈到了Winodw的相关概念及其实现.窗口的目的是将无界的流转换为有界的元素集合,但这还不是最终的目的,最终的目的是在这有限的集合上apply(应用)某种函数,这就是我们本篇要谈的主题--WindowFunction(窗口函数). 那么窗口函数会在什么时候被应用呢?还记得上篇文章我们谈到了触发器Trigger,在触发器触发后会返回TriggerResult这个枚举类型的其中一个枚举值.当返回的是FIRE或者FIRE_AND_PUR

数据库中间件Mycat源码解析(三):Mycat的SQL解析和路由

mycat对sql的解析分为两部分,一个是普通sql,另一个是PreparedStatment. 下面以解析普通sql为例分析(另一种方式大同小异),sql从客户端发过来后server接收后会调用FrontendCommandHandler的handle方法,这个方法会调用FrontendConnection的query方法,接着query方法会调用ServerQueryHandler的query方法,接着调用ServerConnection的execute方法.如下图所示: public vo

Apache Flink源码解析之stream-sink

上一篇我们谈论了Flink stream source,它作为流的数据入口是整个DAG(有向无环图)拓扑的起点.那么与此对应的,流的数据出口就是跟source对应的Sink.这是我们本篇解读的内容. SinkFunction 跟SourceFunction对应,Flink针对Sink的根接口被称为SinkFunction.继承自Function这一标记接口.SinkFunction接口只提供了一个方法: void invoke(IN value) throws Exception; 该方法提供基

Apache Flink源码解析之stream-transformation

之前我们聊了Flink程序的source.sink就差transformation了.今天我们就来解读一下Flink的transformation.它们三者的关系如下图: 当然这还是从Flink编程API的角度来看的(编程视角).所谓的transformation,用于转换一个或多个DataStream从而形成一个新的DataStream对象.Flink提供编程接口,允许你组合这些transformation从而形成非常灵活的拓扑结构. StreamTransformation StreamTr