Proj. THUIoTFuzz: IoTDB

Apache IoTDB是一个由清华大学软件学院开始研发的物联网数据库,面向管理海量的columnar storage时间序列数据,它的接口类似于SQL,每个节点每秒可以支持写入数百万个数据点,数秒内可以获得数万亿个数据点的查询结果,能够在设备上或者在云上部署。架构很轻,能够和Apache Hadoop,Spark和Flink一起部署。在工厂等环境下,IoTDB可以部署在工厂的用于收集设备信息的本地控制器服务器(local controller)上,然后通过TsFiles Sync这个工具将local controller传输到数据中心。在车联网等需要高速的环境下,每个sensor都是一个client,sensors可以通过IoTDB的窄带物联网将数据发送出去。在高端制造业,则可以让设备存储TsFile,然后用TsFile-Sync来将数据传给数据中心。IoTDB的设计目标:高并发读写,有效且节约的文件夹结构,丰富的查询语法,在硬件上的消耗很低,部署容易。

架构

 对于写入到IoTDB的数据以及本地的TsFile文件,可以通过同步工具TsFileSync将数据文件同步到HDFS上,进而实现在Hadoop或Spark的数据处理平台上的诸如异常检测、机器学习等数据处理任务。

对于写入到HDFS或者本地的TsFile文件,可以利用TsFile-Hadoop或TsFile-Spark连接器允许Hadoop或Spark进行数据处理。

数据模型

IoTDB使用时间序列组织数据,所有时间序列必须以root开始,以传感器作为结尾。例如如下的数据属性层级设置,我们可以命名最右的路径为ROOT.ln.wf01.wt01.status。其他层级都不能出现root,否则会无法解析进而报错。

前缀路径,也即时间序列的前缀路径,允许使用通配符*,*如果出现在结尾,那就递归地代表所有子路径。*不能放在开头,因为所有路径都以root开头。

用户可以将任意前缀路径设置为存储组,比如ROOT.ln,那么这个存储组底下所有的设备会存储在同个文件夹下。用户需要根据自己的数据规模和使用场景来平衡存储文件的存储组设置,以达到更好的性能。存储组一旦设置好就不能再更改,同时,其对应前缀路径的所有父层级和子层级也都不能够再设置新的存储组(这是不是说明所有存储组的深度都是一样的)

数据类型

IoTDB支持Boolean, Int32, int64, float, double, text这六种数据类型。其中FLOAT与DOUBLE类型的序列,如果编码方式采用RLETS_2DIFF可以指定MAX_POINT_NUMBER,该项为浮点数的小数点后位数

时间戳可以分为相对时间戳(now()-1d2h),绝对时间戳,绝对时间戳有long和datetime两种类型。

IoTDB支持以下编码方式:

为了提高数据的存储效率,需要在数据写入的过程中对数据进行编码,从而减少磁盘空间的使用量。在写数据以及读数据的过程中都能够减少I/O操作的数据量从而提高性能。IoTDB支持四种针对不同类型的数据的编码方法:

  • PLAIN编码(PLAIN)

PLAIN编码,默认的编码方式,即不编码,支持多种数据类型,压缩和解压缩的时间效率较高,但空间存储效率较低。

  • 二阶差分编码(TS_2DIFF)

二阶差分编码,比较适合编码单调递增或者递减的序列数据,不适合编码波动较大的数据。

  • 游程编码(RLE)

游程编码,比较适合存储某些整数值连续出现的序列,不适合编码大部分情况下前后值不一样的序列数据。

游程编码也可用于对浮点数进行编码,但在创建时间序列的时候需指定保留小数位数(MAX_POINT_NUMBER,具体指定方式参见本文本文第5.4节)。比较适合存储某些浮点数值连续出现的序列数据,不适合存储对小数点后精度要求较高以及前后波动较大的序列数据。

  • GORILLA编码(GORILLA)

GORILLA编码,比较适合编码前后值比较接近的浮点数序列,不适合编码前后波动较大的数据。

  • 定频数据编码 (REGULAR)

定频数据编码,仅适用于整形(INT32)和长整型(INT64)的定频数据,且允许数据中有一些点缺失,使用此方法编码定频数据优于二阶差分编码(TS_2DIFF)。

定频数据编码无法用于非定频数据,建议使用二阶差分编码(TS_2DIFF)进行处理。

  • 数据类型与编码的对应关系

前文介绍的四种编码适用于不同的数据类型,若对应关系错误,则无法正确创建时间序列。数据类型与支持其编码的编码方式对应关系总结如表格2-3。

**表格2-3 数据类型与支持其编码的对应关系**
数据类型支持的编码
BOOLEAN PLAIN, RLE
INT32 PLAIN, RLE, TS_2DIFF, REGULAR
INT64 PLAIN, RLE, TS_2DIFF, REGULAR
FLOAT PLAIN, RLE, TS_2DIFF, GORILLA
DOUBLE PLAIN, RLE, TS_2DIFF, GORILLA
TEXT PLAIN

IoTDB允许在创建一个时间序列的时候指定该列的压缩方式。现阶段IoTDB现在支持的压缩方式有两种:

  • UNCOMPRESSED(不压缩)
  • SNAPPY压缩

编码与压缩方式的区别:

虽然编码和压缩都旨在提升存储效率,但编码技术通常只适合特定的数据类型(如二阶差分编码只适合与INT32或者INT64编码,存储浮点数需要先将他们乘以10m以转换为整数),然后将它们转换为二进制流。压缩方式(SNAPPY)针对二进制流进行压缩,因此压缩方式的使用不再受数据类型的限制。

数据定义语言

创建存储组

IoTDB > set storage group to root.ln
IoTDB > set storage group to root.sgcc

 

查看存储组

IoTDB> show storage group

  

创建时间序列

IoTDB > create timeseries root.ln.wf01.wt01.status with datatype=BOOLEAN,encoding=PLAIN
IoTDB > create timeseries root.ln.wf01.wt01.temperature with datatype=FLOAT,encoding=RLE
IoTDB > create timeseries root.ln.wf02.wt02.hardware with datatype=TEXT,encoding=PLAIN
IoTDB > create timeseries root.ln.wf02.wt02.status with datatype=BOOLEAN,encoding=PLAIN
IoTDB > create timeseries root.sgcc.wf03.wt01.status with datatype=BOOLEAN,encoding=PLAIN
IoTDB > create timeseries root.sgcc.wf03.wt01.temperature with datatype=FLOAT,encoding=RLE

当创建时间序列时指定的编码方式与数据类型不相符时,系统会给出错误提示。

IoTDB> create timeseries root.ln.wf02.wt02.status WITH DATATYPE=BOOLEAN, ENCODING=TS_2DIFF
error: encoding TS_2DIFF does not support BOOLEAN

  

标签

我们可以在创建时间序列的时候,为它添加别名和额外的标签和属性信息。

标签能够更好地辅助存储组合理分配。

create timeseries root.turbine.d1.s1(temprature) with datatype=FLOAT, encoding=RLE, compression=SNAPPY tags(tag1=v1, tag2=v2) attributes(attr1=v1, attr2=v2)

  

括号里的tempratures1这个传感器的别名。 我们可以在任何用到s1的地方,将其用temprature代替,这两者是等价的。(注意:额外的标签和属性信息总的大小不能超过tag_attribute_total_size.)

标签和属性的唯一差别在于,我们为标签信息在内存中维护了一个倒排索引,所以可以在show timeseries的条件语句中使用标签作为查询条件。

创建时间序列后,我们也可以对其原有的标签点属性进行更新,主要有以下五种更新方式:

重命名标签或属性
ALTER timeseries root.turbine.d1.s1 RENAME tag1 TO newTag1

重新设置标签或属性的值
ALTER timeseries root.turbine.d1.s1 SET tag1=newV1, attr1=newV1

删除已经存在的标签或属性
ALTER timeseries root.turbine.d1.s1 DROP tag1, tag2

添加新的标签
ALTER timeseries root.turbine.d1.s1 ADD TAGS tag3=v3, tag4=v4

添加新的属性
ALTER timeseries root.turbine.d1.s1 ADD ATTRIBUTES attr3=v3, attr4=v4

更新插入别名,标签和属性
如果该别名,标签或属性原来不存在,则插入,否则,用新值更新原来的旧值

ALTER timeseries root.turbine.d1.s1 UPSERT ALIAS=newAlias TAGS(tag2=newV2, tag3=v3) ATTRIBUTES(attr3=v3, attr4=v4)

查看时间序列信息

  • SHOW TIMESERIES prefixPath? showWhereClause? limitClause?

    SHOW TIMESERIES 后可以跟三种可选的子句,查询结果为这些时间序列的所有信息

时间序列信息具体包括:时间序列路径名,存储组,Measurement别名,数据类型,编码方式,压缩方式,属性和标签。

示例:

  • SHOW TIMESERIES

    展示系统中所有的时间序列信息

  • SHOW TIMESERIES <Path>

    返回给定路径的下的所有时间序列信息。其中 Path 需要为一个前缀路径、带星路径或时间序列路径。例如,分别查看root路径和root.ln路径下的时间序列,SQL语句如下所示:

IoTDB> show timeseries root
IoTDB> show timeseries root.ln
  • SHOW TIMESERIES (<PrefixPath>)? WhereClause

    返回给定路径的下的所有满足条件的时间序列信息,SQL语句如下所示:

show timeseries root.ln where unit=c
show timeseries root.ln where description contains 'test1'
show timeseries root.ln where tag4 contains 'v'

现在我们只支持一个查询条件,要么是等值条件查询,要么是包含条件查询。当然where子句中涉及的必须是标签值,而不能是属性值。
  • SHOW TIMESERIES LIMIT INT OFFSET INT

    只返回从指定下标开始的结果,最大返回条数被 LIMIT 限制,用于分页查询



查看子路径

show child paths prefixpath

例如show child paths root.ln

show child paths root.*.ln.*

show child paths root.ln.*.wt01

当没有找到对应前缀路径时返回null,否则只返回下一层的路径,且如果前缀路径只对应叶节点,那就只返回空的而不是null。

统计时间序列总数

count timeseries <path>

IoTDB > COUNT TIMESERIES root
IoTDB > COUNT TIMESERIES root.ln
IoTDB > COUNT TIMESERIES root.ln.*.*.status
IoTDB > COUNT TIMESERIES root.ln.wf01.wt01.status

  

还可以通过group by level来根据层级分组统计(ROOT level=0):

COUNT TIMESERIES <Path> GROUP BY LEVEL=<INTEGER>

 这里<path>只是一个用来过滤timeseries的前缀路径,<path>与groupby没有明显关系,<path>可以比group by level中的level更深。

统计节点数

 COUNT NODES <Path> LEVEL=<INTEGER>

IoTDB > COUNT NODES root LEVEL=2
IoTDB > COUNT NODES root.ln LEVEL=2
IoTDB > COUNT NODES root.ln.wf01 LEVEL=3

删除时间序列

DELETE TimeSeries <PrefixPath>

IoTDB> delete timeseries root.ln.wf01.wt01.status
IoTDB> delete timeseries root.ln.wf01.wt01.temperature, root.ln.wf02.wt02.hardware
IoTDB> delete timeseries root.ln.wf02.*

  

查看设备

IoTDB> show devices
IoTDB> show devices root.ln

  

TTL

IoTDB支持对存储组级别设置数据存活时间(TTL),这使得IoTDB可以定期、自动地删除一定时间之前的数据。合理使用TTL 可以帮助您控制IoTDB占用的总磁盘空间以避免出现磁盘写满等异常。

IoTDB> set ttl to root.ln 3600000

  

IoTDB> unset ttl to root.ln

  

FLUSH

将指定存储组的内存缓存持久化到磁盘上。

IoTDB> FLUSH 
IoTDB> FLUSH root.ln
IoTDB> FLUSH root.sg1,root.sg2

MERGE

合并乱序数据。

MERGE仅仅重写重复的Chunk

FULL MERGE将需要合并的顺序和乱序文件的所有数据都重新写一份。

Clear cache

清除chunk, chunk metadata, timeseries metadata的缓存。

数据操作语言

可以向指定地一条或者多条时间序列中插入数据。基本格式为一个timestamp加上一到多个数据采集值。

IoTDB > insert into root.ln.wf02.wt02(timestamp,status) values(1,true)
IoTDB > insert into root.ln.wf02.wt02(timestamp,hardware) values(1, "v1")
IoTDB > insert into root.ln.wf02.wt02(timestamp, status, hardware) VALUES (2, false, 'v2')

  

若用户向一个不存在的时间序列中插入数据,例如执行以下命令:

IoTDB > insert into root.ln.wf02.wt02(timestamp, temperature) values(1,"v1")
 

由于root.ln.wf02.wt02. temperature时间序列不存在,系统将会返回以下ERROR告知该Timeseries路径不存在:

Msg: The resultDataType or encoding or compression of the last node temperature is conflicting in the storage group root.ln
 

若用户插入的数据类型与该Timeseries对应的数据类型不一致,例如执行以下命令:

IoTDB > insert into root.ln.wf02.wt02(timestamp,hardware) values(1,100)
 

系统将会返回以下ERROR告知数据类型有误:

error: The TEXT data type should be covered by " or '

查询语句

IoTDB在Select语句上表现和普通数据库是非常相似的,可以使用一个列作为约束条件来查其他列的结果。

select temperature from root.ln.wf01.wt01 where time < 2017-11-01T00:08:00.000

  

也支持and和or

select status,temperature from root.ln.wf01.wt01 where (time > 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000) or (time >= 2017-11-01T16:35:00.000 and time <= 2017-11-01T16:37:00.000);

  

可以选择一个时间序列,也可以选择多个时间序列。

select wf01.wt01.status,wf02.wt02.hardware from root.ln where (time > 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000) or (time >= 2017-11-01T16:35:00.000 and time <= 2017-11-01T16:37:00.000);

  

IoTDB支持两种结果返回形式: 1. 按照设备时间对齐align by device 2. 不对齐 disable align 

降频聚合查询

主要使用Select语句的group by子句。IoTDB支持根据时间间隔和自定义的滑动步长对结果集合进行划分之后聚合计算,并且将默认结果按照时间进行升序排列。

select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01T00:00:00, 2017-11-07T23:00:00),1d);

由于用户没有指定滑动步长,滑动步长默认设置为和时间间隔参数相同,也就是1d。将1d当作划分间隔,显示窗口参数的起始时间当作分割原点,时间轴即被划分为连续的时间间隔:[0,1d), [1d, 2d), [2d, 3d)等等。

然后系统将会用WHERE子句中的时间和值过滤条件以及GROUP BY语句中的第一个参数作为数据的联合过滤条件,获得满足所有过滤条件的数据,并把这些数据映射到之前分割好的时间轴中。

 如果指定了步长,比如说3h,那么每次统计一个时间间隔之后,只把统计区间开头后移3h。

区间使用的是左开右闭区间。

对空值,IoTDB也提供了两种简单的用前值补空值的方法:

  • PREVIOUS:只要空值前边有值,就会用其填充空值。
  • PREVIOUSUNTILLAST:不会填充此序列最新点后的空值

当然,group by在填补空值的同时,不能够设置滑动步长。

SELECT last_value(temperature) FROM root.ln.wf01.wt01 GROUP BY([8, 39), 5m) FILL (int32[PREVIOUSUNTILLAST])
SELECT last_value(temperature) FROM root.ln.wf01.wt01 GROUP BY([8, 39), 5m) FILL (int32[PREVIOUSUNTILLAST, 3m])

  

select也可以设置填充空值功能。其基本格式为:

select <path> from <prefixPath> where time = <T> fill(<data_type>[previous, <before_range>], …)

  

这里<T>是必填的,指查询<path>的那个时刻。<before_range>指如果<T>时刻对应的<path>为空,那么就在[<T>-<before_range>, T)中找不是空值的值来返回。<before_range>默认为-1,即无穷大。previous在这里就是指previous这种方法名称。如果在前面这个区间也没有非空值,还是会返回null的。这里datatype需要填基本类型中的一种,标识填充返回的类型。

select temperature from root.sgcc.wf03.wt01 where time = 2017-11-01T16:37:50.000 fill(float[previous, 1m])

  

除了previous方法,IoTDB还在单纯的select提供了Linear方法(groupby在2020.11.11还未实现linear)。

select <path> from <prefixPath> where time = <T> fill(<data_type>[linear, <before_range>, <after_range>]…)

如果在[T-before_range,T]或[T, T + after_range]两个范围中任意一个范围内不存在有效填充值,则线性填充返回null值。否则,用线性拟合计算。注意linear不支持boolean和text两种datatype。

查询最新数据

select last <Path> [COMMA <Path>]* from < PrefixPath > [COMMA < PrefixPath >]* <DISABLE ALIGN>
> select last speed from root.ln.wf01.wt01

| Time | Path                    | Value |
| ---  | ----------------------- | ----- |
|  5   | root.ln.wf01.wt01.speed | 100   |

  

> select last speed, status, temperature from root.ln.wf01

| Time | Path                         | Value |
| ---  | ---------------------------- | ----- |
|  5   | root.ln.wf01.wt01.speed      | 100   |
|  7   | root.ln.wf01.wt01.status     | true  |
|  9   | root.ln.wf01.wt01.temperature| 35.7  |

  

Limit, OFFSET

select status,temperature from root.ln.wf01.wt01 where time > 2017-11-01T00:05:00.000 and time< 2017-11-01T00:12:00.000 limit 2 offset 3

  

select count(status), max_value(temperature) from root.ln.wf01.wt01 group by ([2017-11-01T00:00:00, 2017-11-07T23:00:00),1d) limit 5 offset 3

  

值得注意的是,由于当前的FILL子句只能在某个时间点填充时间序列的缺失值,也就是说,FILL子句的执行结果恰好是一行,因此LIMIT和OFFSET不会是 与FILL子句结合使用,否则将提示错误。 例如,执行以下SQL语句:

select temperature from root.sgcc.wf03.wt01 where time = 2017-11-01T16:37:50.000 fill(float[previous, 1m]) limit 10
 

SQL语句将不会执行,并且相应的错误提示如下:

 SLIMIT和SOFFSET则是对应列的。

select * from root.ln.wf01.wt01 where time > 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000 slimit 1 soffset 1

  所选设备为ln组wf01工厂wt01设备; 所选时间序列是该设备下的第二列。

预期SLIMIT子句将与星形路径或前缀路径一起使用,并且当SLIMIT子句与完整路径查询一起使用时,系统将提示错误。 

select status,temperature from root.ln.wf01.wt01 where time > 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000 slimit 1

当LIMIT / SLIMIT的参数N / SN超过结果集的大小时,IoTDB将按预期返回所有结果。 例如,原始SQL语句的查询结果由六行组成,我们通过LIMIT子句选择前100行:

select status,temperature from root.ln.wf01.wt01 where time > 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000 limit 100

当LIMIT / SLIMIT子句的参数N / SN超过允许的最大值(N / SN的类型为int32)时,系统将提示错误。 例如,执行以下SQL语句:

select status,temperature from root.ln.wf01.wt01 where time > 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000 limit 1234567890123456789

当LIMIT / LIMIT子句的参数N / SN不是正整数时,系统将提示错误。 例如,执行以下SQL语句:

select status,temperature from root.ln.wf01.wt01 where time > 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000 limit 13.1

当LIMIT子句的参数OFFSET超过结果集的大小时,IoTDB将返回空结果集。 例如,执行以下SQL语句:

select status,temperature from root.ln.wf01.wt01 where time > 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000 limit 2 offset 6

当SLIMIT子句的参数SOFFSET不小于可用时间序列数时,系统将提示错误。 例如,执行以下SQL语句:

select * from root.ln.wf01.wt01 where time > 2017-11-01T00:05:00.000 and time < 2017-11-01T00:12:00.000 slimit 1 soffset 2

 

SQL语句将不会执行,

 

GROUP BY DEVICE

group by device将deviceID视为一列并按此分组。

数据删除

delete from root.ln.wf02.wt02.status where time<=2017-11-01T16:26:00;



权限管理

基本概念:用户,权限,角色。这里前两者和大众定义一样。角色则是提供了批量定义权限的方式。目前在IoTDB中并不存在相互冲突的权限,因此一个用户真正具有的权限是用户自身权限与其所有的角色的权限的并集。

初始安装后的IoTDB中有一个默认用户:root,默认密码为root。该用户为管理员用户,固定拥有所有权限,无法被赋予、撤销权限,也无法被删除。

创建用户

CREATE USER ln_write_user 'write_pwd'
CREATE USER sgcc_write_user 'write_pwd'
LIST USER <!--列出用户-->
<!--授予用户权限-->
 
GRANT USER ln_write_user PRIVILEGES 'INSERT_TIMESERIES' on root.ln
GRANT USER sgcc_write_user PRIVILEGES 'INSERT_TIMESERIES' on root.sgcc

 

权限名称说明
SET_STORAGE_GROUP 创建时间序列。包含设置存储组的权限。路径相关
INSERT_TIMESERIES 插入数据。路径相关
UPDATE_TIMESERIES 更新数据。路径相关
READ_TIMESERIES 查询数据。路径相关
DELETE_TIMESERIES 删除数据或时间序列。路径相关
CREATE_USER 创建用户。路径无关
DELETE_USER 删除用户。路径无关
MODIFY_PASSWORD 修改所有用户的密码。路径无关。(没有该权限者仍然能够修改自己的密码。)
LIST_USER 列出所有用户,列出某用户权限,列出某用户具有的角色三种操作的权限。路径无关
GRANT_USER_PRIVILEGE 赋予用户权限。路径无关
REVOKE_USER_PRIVILEGE 撤销用户权限。路径无关
GRANT_USER_ROLE 赋予用户角色。路径无关
REVOKE_USER_ROLE 撤销用户角色。路径无关
CREATE_ROLE 创建角色。路径无关
DELETE_ROLE 删除角色。路径无关
LIST_ROLE 列出所有角色,列出某角色拥有的权限,列出拥有某角色的所有用户三种操作的权限。路径无关
GRANT_ROLE_PRIVILEGE grant role priviledges; path independent
REVOKE_ROLE_PRIVILEGE 撤销角色权限。路径无关

用户名限制

IoTDB规定用户名的字符长度不小于4,其中用户名不能包含空格。

密码限制

IoTDB规定密码的字符长度不小于4,其中密码不能包含空格,密码采用MD5进行加密。

角色名限制

IoTDB规定角色名的字符长度不小于4,其中角色名不能包含空格。

 
原文地址:https://www.cnblogs.com/xuesu/p/13958036.html