清华自研时间序列数据库Apache IoTDB原理解析
时序数据库 Apache-IoTDB 源码解析之前言(一)
时序数据库 Apache-IoTDB 源码解析之系统架构(二)
时序数据库 Apache-IoTDB 源码解析之文件格式简介(三)
时序数据库 Apache-IoTDB 源码解析之文件数据块(四)
时序数据库 Apache-IoTDB 源码解析之文件索引块(五)
时序数据库 Apache-IoTDB 源码解析之元数据索引块(六)
IOTDB介绍

1 背景

1.1 智能运维领域的数据特点

指标数据作为运维场景中的重要观测项,是服务可用性监控、系统健康度度量等场景的主要数据来源。从下面架构示意图中们可以看出,采集器采集服务器上各种指标数据,发往消息队列,通过实时流处理和离线计算最终存入到数据库。
在这里插入图片描述
在这个上述场景中,我们往往会遇到以下几种数据挑战:
(1)我们日常需要监控的指标数量超百万,峰值时甚至会达到千万级,每天沉淀下来的指标数据量达到GB级别,甚至TB级别。
(2)针对指标数据的日常分析行为通常涉及到近1小时、近1天、近7天、近30天、近1年等多种时间跨度。对范围查询的性能有一定要求。
(3)在数据传输过程中,由于受到网络、设备资源等原因造成短时间内出现乱序到达、缺丢点、峰谷潮、重复数据等问题。
(4)由于服务器或设备本身原因,采集的指标数据时间往往不够精准,导致数据粒度不齐整的问题。例如对于秒级别的指标,上一个采集的数据点的时间戳是2021-01-01 10:00:00:000下一个数据点的有可能是2021-01-01 10:00:01:015。而不同的指标相同时刻采集的数据点时间戳分别是2021-01-01 10:00:00:000和2021-01-01 10:00:00:015。

至此整体的需求基本已经明确,在做数据库选型时需要满足以下需求:
1)支持数据长时间存储;
2)支持大时间跨度的快速检索;
3)高速的数据吞吐能力;
4)高效的数据压缩比;
5)能够有效的解决数据的乱序、缺失值、粒度不齐整以及重复数据等数据质量问题。

1.2 智能运维领域的时序数据该如何存储

对于上述的需求我们该如何选型?是传统的关系型数据库,还是通用的NoSQL数据库,亦或是专用的时序数据库?他们能否满足上述数据库选型的需求?

数据如何存储还要结合数据本身的特点。

这里以一个真实场景中的案例,某运营商有约3000万的监控指标,并且采集的过程中存在空值数据、数据缺失、数据重复等情况,甚至会出现新的指标。如果一分钟采集一次指标在允许一定数据延迟的情况下,写入速率要超过(3000/60=50w/s),一天需要存储432亿条的数据,这对关系数据库来说无论是从写入速率还是在查询时效都很难满足需求。

再看看通用的NoSQL数据库,首先先简单梳理一下这些指标数据的特点,我们发现这些指标数据除了有时间戳和指标值外还会有一些tag 来标识数据来自那台机器,通过采集器实际的采集数据的样例如下图所示:
在这里插入图片描述
通用的NoSql 数据库虽然可以满足吞吐量性能以及查询性能,但是为了满足指标的动态变更我们只能按照一个设备一张表或者多个设备共享一张表的建模方式如下图所示。
在这里插入图片描述
无论是一个设备一张表还是多个设备共享一张表的存储方式,为了能够区分数据来哪个指标,我们只能把tags作为一列进行存储,不难发现这种这样建表方式会出现大量的tag数据冗余存储的问题。

并且通用的NoSql数据库往往在解决数据重复的问题上并不友好,更多的是依靠一些排重策略来实现。排重策略通常有两种:一种是依靠外部的排重方式达到存储时数据已经排重,另一种是存储不排除查询时依靠sql来做排重。如果使用第一种数据排重无疑会增加系统的复杂度,如果使用第二种这会导致在处理过程的出现数据冗余存储的情况。

而且通用的NoSql数据库还存在一个问题就是:没有原生操作支持粒度对齐或者线性填充来解决数据质量差的问题。

然而我们上面的遇到的一些数据挑战实际是属于时序数据库要解决的典型问题,市面上也有许多优秀的时序数据库,例如InfluxDB、Apache IoTDB等,它们在高吞吐、低延时查询、数据去重、数据填充、数据降采样、高压缩比等功能方面皆能满足数据存储需求。接下来我们来重点看下完全开源的Apache IoTDB它是如何设计的。

1.3 场景介绍

IoTDB 是一款时序数据库,相关竞品有 Kairosdb,InfluxDB,TimescaleDB等,主要使用场景是在物联网相关行业,如:车联网、风力发电、地铁、飞机监控等等。
在这里插入图片描述
当一台设备、一辆车连接到协议网关后,便开始了真正的收发数据。一般通信的方式都是基于 tcp,搞一段二进制协议,所以协议网关基本要做的工作就是完成对连接的管理、完成对数据的收发及编解码。

当数据完成编解码之后一般会发往消息队列当中,一般都是 Kafka 之中。用来解耦生产和消费两端,提供一层缓冲,无论消费服务是死是活还是速度慢,包治百病,甚至还能治未病。

数据发往消息队列的过程中,或之后花活儿就多起来了。但主要的我认为无非还是三种处理方式:
(1)需要将原始数据保存入库,这里的原始数据包含二进制数据和解码后的二进制数据。
(2)流处理或批处理数据,在数据落到硬盘之前将能够提前计算的数据全部预先计算出来,这样做的好处是将来查询的时候如果和预计算的模型匹配,那就能非常快的得到结果。
(3)离线处理,这里的应用就太广泛了,一般来讲都是将耗时比较大的放置离线计算来做。但是这里要声明一点,离线计算依然是越快越好,不能因为他叫离线计算所以在设计或开发阶段就不关注时效。

实际工作中,往往质量会出现各种意想不到的数据,下面是工程中影响数据质量的几个比较大的问题:
(1)数据丢失,不管是在采集,上报,数据流转环节,都可能会带来一定的数据丢失比例。
(2)数据乱序,数据在打包、上报、流转等环节均可能出现乱序,尤其是在补传数据中。
(3)数据重复,数据重复发送,尤其是在网络不好时。
(4)数据本身不准确,这个最突出的地方就是在 GPS 数据中,经常出现飘点、噪点等等。

1.4 行式与列式存储的区别

假如我们的逻辑上的数据表格式及数据为:
在这里插入图片描述
那么它出现在硬盘格式就是:
在这里插入图片描述
一、行式数据
行式数据是把逻辑相关的数据在硬盘上放到一起,比如上面的例子,我们可以称之为体温表,所以在逻辑上:时间、人、体温,就成为了逻辑上紧密相关的数据。

所以把相关的数据的硬盘上的组织方式也变成连续的,假如我需要取 张三 的数据,那么当你读出 R1 文件块的时候,就是读出了所有 张三 相关的数据。

二、列式数据
列式数据是将物理相关的数据放到一起,比如时间是一类(long 类型)、名字是一类(string 类型)、体温是一类(float 类型)。当然这种硬盘的组织方式,相比起行式数据库,在取拼回体温表的结构的时候,速度就慢了很多,因为你要分别取 C1、C2、C3 文件块,然后还要写个容器往里 Set()。

那么列式数据存储方式相比于行式存储优势在哪里呢?
1、取数据方式
有一种叫法是只读投影列,避免查询无关列的读取。列式存储的优势在于查询的列数远小于总属性数量,就能少读很多数据。
举个例子:比如我需要查体温大于 36 度的体温值,sql : select 体温 FROM table WHERE 体温 > 36 。这时候如果是列式存储只需要读出 C3 数据块就可以一次性查到所有数据。而行式数据库中,则需要读出 R1、 R2、 R3。在第二章中介绍到物联网中的时序数据的特点:存量数据非常大,如果遍历几百亿数据,时间差距明显就拉开了。

2、数据编码和压缩
因为物理相关的数据他们类型相同,可以使用多种多样的编码方式,比如 IoTDB 中就提供了 8 种编码方式。

我们继续拿时间列举例子,我们可以把时间列改造为差值存储: 比如 C1 文件块中先存储基础值 1580950800 那么他后面的数据值只需要存储 0 就可以,存储的数字小了,那么占用的存储空间肯定也就小了,当数字特别大且差值比较小的时候,这用编码方式就非常有意义。

1.5 TsFile 文件格式

在这里插入图片描述
这是一个数据被刷入磁盘后的缩减版 TsFile 格式,我们还拿上面的数据举例,用来直观的解释 TsFile 中出现的一些名词,假如我的数据为:
在这里插入图片描述
上面的数据刷新到磁盘上后会对应关系如下:
在这里插入图片描述
看到这里应该能理解每个英文名词的意思:
(1)ChunkGroup 代表了设备(逻辑概念上的一个集合),在 IoTDB 中称为 Device。
(2)Chunk 代表了测点数据(逻辑概念上的某一类数据的集合,如体温数据),在 IoTDB 中称为 Measurement。
(3)Page 中存储的是具体数据,包含一个时间序列、一个值序列。
(4)PageStatistics 是保存的是Page当中数据的预聚合信息。
(5)ChunkStatistics 是保存的是Chunk当中数据的预聚合信息。
(6)ChunkGroup 中包含多个 Chunk,Chunk 中包含多个 Page ,Page 中 包含多个 时间点和数据项

回想上面提到的 SQL : select 体温 FROM 王五 WHERE 体温 > 36 , 在 TsFile 中,只要在文件中找到 王五 的 ChunkGroup ,并在 ChunkGroup 中找到 体温 的 Chunk,然后从第一个 Page 开始遍历就完成了。

1.6 TsFile文件概览

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

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

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

1.6.3 数据块

在这里插入图片描述

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

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

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

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

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

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

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

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

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

1.6.4 索引块

在这里插入图片描述
索引块由两大部分组成,其写入的方式是从左到右写入,也就是从文件头向文件尾写入。但读出的方式是先读出TsFileMetaData 再读出 TsDeviceMetaDataList 中的具体一部分。我们按照读取数据的顺序介绍:
一、TsFileMetaData
TsFileMetaData属于文件的 1 级索引,用来索引 Device 是否存在、在哪里等信息,其中主要保存了:

1、DeviceMetaDataIndexMap:Map结构,Key 是设备名,Value 是 TsDeviceMetaDataIndex ,保存了包含哪些 Device(逻辑概念上的一个集合一段时间内的数据,例如前几章我们讲到的:张三、李四、王五)以及他们的开始时间及结束时间、在左侧 TsDeviceMetaDataList 文件块中的偏移量等。
2、MeasurementSchemaMap:Map结构,Key 是测点的一个全路径,Value 是 measurementSchema ,保存了包含的测点数据(逻辑概念上的某一类数据的集合,如体温数据)的原信息,如:压缩方式,数据类型,编码方式等。
3、最后是一个布隆过滤器,快速检测某一个 时间序列 是不是存在于文件内(这里等聊到 server 模块写文件的策略时候再聊)。我们知道这个过滤器的特点就是:没有的一定没有,但有的不一定有。为了保证准确性和过滤器序列化后的大小均衡,这里提供了一个 1% - 10% 错误率的可配置,当为 1% 错误率时,保存 1 万个测点信息,大概是 11.7 K。

我们再回想 SQL :SELECT 体温 FROM 王五 WHERE time = 1 。读文件的过程就应该是:
(1)先用布隆过滤器判断文件内是否有王五的体温列,如果没有,查找下一个文件。
(2)从 DeviceMetaDataIndexMap 中找到王五的 TsDeviceMetaDataIndex ,从而得到了王五的 TsDeviceMetadata 的 offset,接下来就寻道至这个 offset 把王五的 TsDeviceMetadata 读出来。
(3)MeasurementSchemaMap 不用关注,主要是给 Spark 使用的,ChunkHeader 中也保存了这些信息。

二、TsDeviceMetaDataList
TsDeviceMetaDataList 属于文件的 2 级索引,用来索引具体的测点数据是不是存在、在哪里等信息。其中主要保存了:

1、ChunkGroupMetaData:ChunkGroup 的索引信息,主要包含了每个 ChunkGroup 数据块的起止位置以及包含的所有的测点元信息(ChunkMetaData)。
2、ChunkMetaData :Chunk 的索引信息,主要包含了每个设备的测点在文件中的起止位置、开始结束时间、数据类型和预聚合信息。
上面的例子中,从 TsFileMetadata 已经拿到了王五的 TsDeviceMetadataIndex,这里就可以直接读出王五的 TsDeviceMetadata,并且遍历里边的 ChunkGroupMetadata 中的 ChunkMetadata,找到体温对应的所有的 ChunkMetadata。通过预聚合信息对时间过滤,判断能否使用当前的 Chunk 或者能否直接使用预聚合信息直接返回数据(等介绍到 server 的查询引擎时候细聊)。

如果不能直接返回,因为 ChunkMetaData 包含了这个 Chunk 对应的文件的偏移量,只需要使用 seek(offSet) 就会跳转到数据块,使用上一章介绍的读取方法进行遍历就完成了整个读取。

2 IoTDB的设计

2.1 IoTDB的架构

日志结构的合并树(LSM-tree)是一种分层的、有序的、基于硬盘的数据结构,它的核心思路其实非常简单,首先写入数据到内存中,不需要每次有数据更新就必须将数据写入到磁盘中,等到积累到一定阈值之后,再使用归并排序的方式将内存中的数据合并追加到磁盘队尾(因为所有待排序的树都是有序的,可以通过合并排序的方式快速合并到一起)。

IoTDB是基于LSM-Tree(Log-Structured Merge Tree)的架构进行设计的列式存储数据库,LSMtree的核心思想就是放弃部分读的能力来换取最大的写入能力。从下图中IoTDB的整体架构图中我们可以看出IoTDB主要有三部分构成:分别是数据库引擎、存储引擎和分析引擎。
在这里插入图片描述
(1)数据库引擎主要是负责sql语句的解析、数据写入、数据查询、数据删除等功能。

(2)存储引擎主要是由TsFile来组成也是IoTDB的最具特色的设计,它不仅可以为IoTDB存储引擎使用,而且还可以直接通过链接器供分析引擎使用,同时还对外开放了TsFile的API,用户也可以自己直接通过API来获取里面的内容。

(3)分析引擎主要是用于与开源的数据处理平台对接等。

2.2 IoTDB的数据读写流程

上面提到了IoTDB是基于LSM-Tree的思想来实现的,从以下数据写入流程图中我们可以看出:
在这里插入图片描述

数据经过time detector时会根据内存中维护的最大时间戳来判断是否数据有序,在内存缓冲区memtable(内存表)中分为有序序列和乱序序列,同时为了保障在断电后数据不丢失,IoTDB也会把数据写入到WAL(Write-Ahead Logging)中,到此客户端的数据写入就已经完成。

随着数据的不断写入,memtable中的数据达到一定的程度后,IoTDB通过submit flush task把memtable变成Immutable最终刷到磁盘变成SStable即TsFile文件,同时当持久化的TsFile文件达到一定程度会触发合并。

上面介绍了IoTDB数据的写入流程后,我们再来看下IoTDB核心的查询流程。
在这里插入图片描述

如上图所示,当客户端发送查询请求时,首先通过Antlr4进行sql解析,然后去内存中的MemTable、ImmuTable和硬盘中TsFile中进行查询。当然,IoTDB这里会通过BloomFilter 和索引来提高数据的查询效率。我们知道BloomFilter的原理是哈希,结果不存在那么一定没有此数据,如果哈希结果存在,IoTDB那么还需要继续借助索引进行进一步的查找。

2.3 TsFile结构

上一节IoTDB的读写流程都离不开TsFile,那我们看看IoTDB最核心TsFile是怎样一个结构。从下面的TsFile 结构示意图中可以看出TsFile整体分为两部分:一部分是数据区,另一部分是索引区。
在这里插入图片描述

(1)数据区主要包括Page 数据页、Chunk数据块和ChunkGroup数据组。
其中Page 由一个 PageHeader 和一段数据(time-value 编码的键值对)组成。
Chunk数据块由多个Page和一个Chunk Header组成。
ChunkGroup 存储了一个实体(Entity) 一段时间的数据,它由若干个 Chunk, 一个字节的分隔符 0x00 和一个ChunkFooter组成。

(2)索引区主要包括TimeseriesIndex、IndexOfTimeseriesIndex和BloomFilter。
其中TimeseriesIndex包含1个头信息和数据块索引(ChunkIndex)列表,头信息记录文件内某条时间序列的数据类型、统计信息(最大最小时间戳等);数据块索引列表记录该序列各Chunk在文件中的 offset,并记录相关统计信息(最大最小时间戳等)。
IndexOfTimeseriesIndex用于索引各TimeseriesIndex在文件中的 offset。BloomFilter针对实体(Entity)的布隆过滤器。

TsFile 是为物联网设备时序数据存储定制的文件格式,整体以树状目录结构组织,一个 TsFile 里可存储多个设备的数据,每个设备包含多个 measurment(指标)。上图中TsFile包括两个实体 d1、d2,每个实体分别包含三个物理量 s1、s2、s3,共 6 个时间序列,每个时间序列包含两个 Chunk。

TsFile 整体是一个多级映射表,
TsFileMetaData ==> TimeSeriesMetadata ==> ChunkMetadata ==> Chunk。

(1)TsFileMetadata 描述整个 TsFile ,包含格式版本信息, MetadataIndexNode 的位置,总的 chunk 数等元数据信息。
(2)MetadataIndexNode 包含多个 TimeSeriesMetadata ,每个 TimeSeriesMetadata 指向一个设备的元数据信息 ChunkMetadata 列表;
(3)ChunkMetadata 指向 ChunkHeader 位置,并对应最终的 Chunk Data。

2.4 TsFile索引构建

在数据结构中,树是一对多的存在,如下图是一颗树。
在这里插入图片描述
结点拥有的子树个数称为结点的度,比如结点①的度为4,结点②的度为0,结点③的度为3。

对于树而言,树的度为树内各结点最大的度,从图中可知,结点①的度是最大的,为4,所以这棵树的度为4。

TsFile中所有的索引节点构成一棵类B+树结构的多叉索引树,这棵树由两部分组成:实体索引部分和物理量索引部分。下面举一个例子来展示索引树的构成:假设我们设置树的度为10,我们有150个设备每个设备有150个测点共计22500条时间序列,这时我们需构建一个深度为6的索引树即可,这时我们查询数据所在位置需做6次磁盘的IO,具体如下图所示。

0-9,10-19,20-29,30-39,40-49,50-59,60-69,70-79,80-89,90-99

100-109,110-119,120-129,130-139,140-149

在这里插入图片描述
上面这种方式看似磁盘IO次数比较多,这是由于我们设置的树的度比较小从而导致整体树的深度比较大。如果我们把树的度增大到300,在实体索引部分一个高度为2的子树即可实现90000个设备存储,同理一个高度为2个物理量索引部分也可存放90000个物理量,最终形成的整个索引树可存放81亿条时间序列。

0-149

这时我们再读取数据只需要做4次磁盘IO即可定位到我们需要的数据位置。

2.5 TsFile查询流程

在了解了TsFile的结构以及索引的构建,那么IoTDB是如何在TsFile内部完成一次查询的,下面用一个具体查询例如select s1 from root.ln.d1 where time>100 and time<200,来演示在TsFile中是如何定位到所需数据。

他的具体步骤以及示意图如下所示:
在这里插入图片描述
1)读取TsFile MetadataSize信息。
2)根据TsFile MetadataSize和offset获取TsFile MetaData的位置。
3)读取Metadata IndexNode中的数据,通过MetadataIndexEntry中的name定位到设备root.ln.d1。
4)读取到设备root.ln.d1中的offset偏移量,根据偏移量找到TimeSeries Metadata 中的信息进而找到s1。
5)通过ChunkMetadata记录的统计信息startTime和endTime与我们查询的区间(100,200)对比获取到root.ln.d1设备下面测点s1的偏移量。
6)根据s1中的偏移量可以直接获取到ChunkGroup。
7)依次通过ChunkGroupHeader、ChunkHeader定位到Chunk数据依次读取Page中的PageHeader,如果时间区间在(100,200)中那么我们就直接读取PageData数据。

3 基本框架

3.1 应用

在这里插入图片描述
其中,灰色部分是IotDB的组件,数据可以通过JDBC/Native API 写入IoTDB,多个IoTDB之间的数据通过TsFile Sync来实现同步。IotDB Egine通过TsFile的API将数据写成TsFile的格式,支持的存储方式有本地存储和HDFS。TsFile支持hadoop、Hive、Spark直接进行大数据分析。可以看到IoTDB的核心部分就是TsFile,承载了IoTDB多个实例间的数据同步和数据分析

上面的图,在逻辑上被分为三部分,其中:
(1)Engine是完整的数据库进程,负责sql语句的解析,数据写入、查询、元数据管理等功能。
(2)Storage是底层存储结构,类似于Mysql的idb文件。
(3)Analyzing Layer是各种连接器。

Engine 和 Storage 中主要包含:
(1)IoTDB Engine,也就是代码中的Server模块。
(2)Native API,是高效写入的基石,代码中的Session模块。
(3)JDBC,传统的JDBC连接调用方式,代码中的JDBC模块。
(4)TsFile,这是IoTDB整个数据库的一个特色所在,传统的数据库如果使用 Spark 做离线分析,或者 ETL 都需要通过数据库进程对外读取,而 IoTDB 可以直接迁移文件,省去了来回转换类型的开销。TsFile 提供了两种读写模式,一种基于 HDFS,一种基于本地文件。

3.2 架构

在这里插入图片描述
IoTDB使用客户端/服务器的架构,客户端向服务端发起读或写的请求,服务端将请求转发到相应的模块,例如写入请求转发到StorageEgnine, 读请求转发到QueryEngine,其他还有schema manager 和Administration模块。

3.3 模块

从IoTDB的源码,我们可以得知IoTDB项目目前包含30个模块。现对IoTDB所有的模块进行大致分类,可划分为Client,IoTDB Engine(JDBC,Server,TsFile),Grafana,Distribution 以及各种生态的连接器这几类。

3.3.1 Client模块介绍

(1)cli:基Java的客户端接口。
(2)client-cpp:基于C++的客户端接口。
(3)client-py:基于Python的客户端接口。

3.3.2 IoTDB Engine模块介绍

(1)server:对应框架中的IoTDB Engine。
(2)session:它是高效写入的基石,对应框架中的Native API。
(3)jdbc:传统的 JDBC 连接调用方式,对应框架中的JDBC。
(4)tsfile:这是整个数据库的一个特色所在,传统的数据库如果使用 Spark 做离线分析,或者 ETL 都需要通过数据库进程对外读取,而 IoTDB 可以直接迁移文件,省去了来回转换类型的开销。在Storage中TsFile 提供了两种读写模式,一种基于 HDFS,一种基于本地文件。对应框架中的TsFile。

3.3.3 各种生态及其生态连接器模块介绍

(1)flink-iotdb-connector、flink-tsfile-connector:flink连接器。flink流处理框架,其核心是用Java和Scala编写的分布式流数据流引擎。
(2)grafana-connector、grafana-plugin:grafana连接器和grafana插件。grafana是一款用Go语言开发的开源数据可视化工具,可以做数据监控和数据统计,带有告警功能。
(3)hadoop:tsfile的HDFS读写方式。Hadoop实现了一个分布式文件系统( Distributed File System),其中一个组件是HDFS(Hadoop Distributed File System)。
(4)hive-connector:hive连接器。hive是基于Hadoop的一个数据仓库工具,用来进行数据提取、转化、加载,这是一种可以存储、查询和分析存储在Hadoop中的大规模数据的机制。
(5)spark-iotdb-connector、spark-tsfile:spark连接器和spark处理tsfile。Apache Spark是专为大规模数据处理而设计的快速通用的计算引擎。
(6)zeppelin-interpreter:Apache Zeppelin 是一个让交互式数据分析变得可行的基于网页的开源框架。Zeppelin提供了数据分析、数据可视化等功能。
(7)server-rpc:服务端rpc。
(8)compile-tools.thrift:编译工具-thrift,thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。
(9)thrift、thrift-cluster、thrift-influxdb、thrift-sync:Thrift是一种接口描述语言和二进制通讯协议,它被用来定义和创建跨语言的服务。它被当作一个远程过程调用(RPC)框架来使用。它通过一个代码生成引擎联合了一个软件栈,来创建不同程度的、无缝的跨平台高效服务,可以使用C#、C++(基于POSIX兼容系统)、Cappuccino、Cocoa、Delphi、Erlang、Go、Haskell、Java、Node.js、OCaml、Perl、PHP、Python、Ruby和Smalltalk。

3.3.4 其他模块介绍

(1)antlr:使用antlr4 自定义语法。antlr4是一款强大的语法生成器工具,可用于读取、处理、执行和翻译结构化的文本或二进制文件。
(2)cluster:集群管理。
(3)integration:集群集成。
(4)cross-tests:一个测试模块。
(5)distribution:使用assembly插件自定义各个模块的打包方式。
(6)example:各种连接器连接实例代码。
(7)metrics:metrics可以为你的代码的运行提供无与伦比的洞察力。作为一款监控指标的度量类库,它提供了很多模块可以为第三方库或者应用提供辅助统计信息, 比如Jetty, Logback, Log4j, Apache HttpClient, Ehcache, JDBI, Jersey, 它还可以将度量数据发送给Ganglia和Graphite以提供图形化的监控。
(8)openapi:使用openapi3接口规范。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐