Appearance
TDengine
优点
数据汇聚和转换
- 消除数据孤岛,TD能将来自各种类型数据源(MQTT)的工业数据汇聚到一起,并可以进行数据的清洗、加工、转换工作,以保证入库数据的质量,进行总体数据分析。
- 并且,TDengine是一个零代码平台,只需很少的配置,即可实现工业数据源的ETL(提取、转换和加载)流程。
简单安全的数据共享、分发
- 通过view的定义,支持将一个数据库完全开放,设置读/写权限;
- 也支持通过数据订阅方式,安全、灵活地将库、超级表、一组或一张表、或聚合处理后的数据分享出去,实时发送给另外一个应用。
- 用户组的精细访问控制来实现对数据的保护,包括可配置到期时间、数据加密和安全访问令牌。
基于sql的查询,并有所增强
- 通过sql和时序数据扩展,提供内建的分析能力,并支持实时流式计算;
- TD3.0对查询引擎做了大量改进和优化;
- TDengine SQL 是用户对TD进行数据写入和查询的主要工具。TDengine SQL提供标准的SQL语法,并针对时序数据和业务的特点优化和新增了许多语法和功能;
- 数据类型(其它的和sql类型和占用字节数差不多)
- BINARY 只用于处理 ASCII 可见字符,VARCHAR是BINARY 类型的别名。VARBINARY可变长的二进制数据(可以通过sql或schemaless方式写入二进制数据);
- NCHAR 用于处理中文等多字节字需使用,每个 NCHAR 字符占用 4 字节的存储空间, NCHAR(10) 的列表示此列的字符串最多存储 10 个 NCHAR 字符;
- BOOL 布尔型,{true, false}
- JSON 数据类型,只有 Tag 可以是 JSON 格式;
- GEOMETRY 几何类型
- 表的每行长度不能超过64KB,注意:每个 BINARY/NCHAR/GEOMETRY/VARBINARY 类型的列还会额外占用 2 个字节的存储位置;
- 创建数据库(可指定的参数)
- CREATE DATABASE [IF NOT EXISTS] db_name [database_options]
- database_option: {key:val}
- BUFFER: 一个 VNODE 写入内存池大小,单位为 MB,默认为 256
- CACHEMODEL:表示是否在内存中缓存子表的最近数据。默认为 none。
- COMP:表示数据库文件压缩标志位,缺省值为 2,取值范围为 [0, 2],2:表示两阶段压缩。
- DURATION:数据文件存储数据的时间跨度。不加时间单位时默认单位为天,如 DURATION 50 表示 50 天。
- WAL_FSYNC_PERIOD:当 WAL_LEVEL 参数设置为 2 时,用于设置落盘的周期。默认为 3000,单位毫秒;
- MAXROWS:文件块中记录的最大条数,默认为 4096 条。
- MINROWS:文件块中记录的最小条数,默认为 100 条。
- KEEP:表示数据文件保存的天数,缺省值为 3650,取值范围 [1, 365000],且必须大于或等于3倍的 DURATION 参数值,默认单位为天。
- PAGES:一个 VNODE 中元数据存储引擎的缓存页个数,默认为 256,
- PAGESIZE:一个 VNODE 中元数据存储引擎的页大小,单位为 KB,默认为 4 KB。即 1 KB 到 16 MB。
- PRECISION:数据库的时间戳精度。ms 表示毫秒,us 表示微秒,ns 表示纳秒,默认 ms 毫秒。
- REPLICA:表示数据库副本数,取值为 1 或 3,默认为 1。
- create database if not exists db vgroups 10 buffer 10; 创建了一个有10个vgroup名为db的数据库,其中每个vnode分配10MB的写入缓存;
- 修改数据库参数 ALTER DATABASE db_name [alter_database_options];
- 查看数据库参数 SELECT * FROM INFORMATION_SCHEMA.INS_DATABASES WHERE NAME='db_name' \G;
- 创建表
- CREATE TABLE 用于创建普通表、和以超级表为模板创建子表。
- 创建表有三类语句(超级表也类似)
- create_subtable_clause
- create_definition: col_name -> column_type;
- table_options:table_option: {TTL value}
- 创建子表并指定标签的值;
- CREATE TABLE [IF NOT EXISTS] tb_name USING stb_name TAGS (tag_value1, ...);
- CREATE TABLE [IF NOT EXISTS] tb_name USING stb_name (tag_name1, ...) TAGS (tag_value1, ...);
- TTL:Time to Live,是用户用来指定表的生命周期的参数。如果创建表时指定了这个参数,当该表的存在时间超过 TTL 指定的时间后,TDengine 自动删除该表。
- 修改普通表:用alter ADD/DROP/MODIFY/RENAME COLUMN来指定;
- 创建超级表
- CREATE STABLE [IF NOT EXISTS] stb_name () TAGS () [table_options]
- TAGS语法指定超级表的标签列,TAGS 列名不能与其他列名相同,TAGS 最多允许 128 个,至少 1 个;
- SHOW CREATE STABLE stb_name; 显示一个超级表的创建语句;
- DESCRIBE [db_name.]stb_name; 获取超级表的结构信息;
- SHOW TABLE TAGS FROM [db_name.]table_name; 获取超级表中所有子表的标签信息
- SHOW TAGS FROM st1s1; 获取某个子表的标签信息;
- 修改超级表的结构会对其下的所有子表生效。无法针对某个特定子表修改表结构。TDengine 会自动作用于此超级表的所有子表。
- 超级表查询
- 使用 SELECT 语句可以完成在超级表上的投影及聚合两类查询,
- 在 WHERE 语句中可以对标签及列进行筛选及过滤。
- 在超级表查询语句中不加 ORDER BY, 返回顺序是先返回一个子表的所有数据,然后再返回下个子表的所有数据;
- 如果增加了 ORDER BY 语句,会严格按 ORDER BY 语句指定的顺序返回的。
- 数据写入语法
- 数据查询语法
- 标签索引
- 函数
- 数学函数
- 字符串函数
- 转换函数
- 时间和日期函数
- 聚合函数
- APERCENTILE
- ELAPSED:
- elapsed函数表达了统计周期内连续的时间长度,如果没有INTERVAL子句,则返回整个给定时间范围内的有数据覆盖的时间范围;
- 适用于: 表,超级表,嵌套查询的外层查询;
- SPREAD
- 统计表中某列的最大值和最小值之差。
- 适用于:表和超级表。
- HYPERLOGLOG
- 采用 hyperloglog 算法,返回某列的基数。该算法在数据量很大的情况下,可以明显降低内存的占用,求出来的基数是个估算值;
- 在数据量较少的时候该算法不是很准确,可以使用 select count(data) from (select unique(col) as data from table) 的方法。
- HISTOGRAM
- 统计数据按照用户指定区间的分布。
- 适用于: 表和超级表。
- 选择函数
- BOTTOM:统计表/超级表中某列的值最小 k 个非 NULL 值。k值取值范围 1≤k≤100。系统同时返回该记录关联的时间戳列;
- FIRST:统计表/超级表中某列的值最先写入的非 NULL 值。
- INTERP:返回指定时间截面指定列的记录值或插值。ignore_null_values 参数的值可以是 0 或 1,为 1 时表示忽略 NULL 值, 缺省值为0。
- LAST:统计表/超级表中某列的值最后写入的非 NULL 值。
- LAST_ROW:返回表/超级表的最后一条记录。
- 时序数据特有函数
- CSUM:累加和(Cumulative sum),输出行与输入行数相同。
- DERIVATIVE:统计表中某列数值的单位变化率。
- DIFF:统计表中某列的值与前一行对应值的差。 ignore_negative 取值为 0|1 , 可以不填,默认值为 0. 不忽略负值
- IRATE:计算瞬时增长率。
- MAVG:计算连续 k 个值的移动平均数(moving average);
- STATECOUNT:返回满足某个条件的连续记录的个数,结果作为新的一列追加在每行后面;
- STATEDURATION:返回满足某个条件的连续记录的时间长度,结果作为新的一列追加在每行后面。
- TWA:时间加权平均函数。统计表中某列在一段时间内的时间加权平均。
- 系统信息函数
- SELECT DATABASE();
- SELECT CURRENT_USER();
- Geometry 函数
- 特色查询
- 数据切分查询
- 数据切分子句最常见的用法就是在超级表查询中,按标签将子表数据进行切分,然后分别进行计算。
- 数据切分子句位于 WHERE 子句之后。
- PARTITION BY part_list; 按一定的维度对数据进行切分,然后在切分出的数据空间内再进行一系列的计算;
- 每个切分的分片进行指定的计算。计算由之后的子句定义(窗口子句、GROUP BY 子句或 SELECT 子句)
- select location, avg(voltage) from meters partition by location; 将数据按标签 location 进行分组,取每个分组内的电压平均值:
- PARTITION BY TBNAME 用法,极大的方便了各种时序场景的统计分析。例如,统计每个电表每 10 分钟内的电压平均值:
- select _wstart, tbname, avg(voltage) from meters partition by tbname interval(10m);
- 时间窗口切分查询
- 按时间窗口切分方式进行聚合结果查询,比如温度传感器每秒采集一次数据,但需查询每隔10分钟的温度平均值。这种场景可以使用窗口子句来获得需要的查询结果。
- 窗口子句用于针对查询的数据集合按照窗口切分成为查询子集并进行聚合,窗口包含四种窗口
- 时间窗口(time window),时间窗口又可划分为滑动时间窗口和翻转时间窗口;
- 状态窗口(status window)、
- 会话窗口(session window)、
- 事件窗口(event window)。
- 窗口子句语法如下:
- window_clause: {}
- SESSION()
- STATE_WINDOW()
- INTERVAL()
- EVENT_WINDOW START WITH start_trigger_condition END WITH end_trigger_condition;
- 窗口子句的规则
- 窗口子句位于数据切分子句之后,不可以和 GROUP BY 子句一起使用。
- 窗口子句将数据按窗口进行切分,对每个窗口进行 SELECT 列表中的表达式的计算,SELECT 列表中的表达式只能包含:
- 常量。
- 聚集函数(包括选择函数和可以由参数确定输出行数的时序特有函数)。
- 且至少包含一个聚集函数。
- WHERE 语句可以指定查询的起止时间和其他过滤条件。
- FILL 子句
- 时间窗口
- 状态窗口
- 会话窗口
- 事件窗口
- 时间戳伪列
- 数据切分查询
- 数据订阅
- 流式计算
- JSON 类型
- 索引,支持 SMA 索引和 tag 索引
概述
- 概述
- TDengine 是一款开源的时序数据库(Time Series Database, TSDB),其核心模块是极简的时序数据库 TDengine OSS;
- 它还带有内建的缓存、流式计算、数据订阅等系统功能,能大幅减少系统设计的复杂度,降低研发和运营成本;
- 充分利用了时序数据的特点,提出了“一个数据采集点一张表”与“超级表”的概念,设计了创新的存储引擎,让数据的写入、查询和存储效率都得到极大的提升。
- 开发指南
- 该部分有建模、插入数据、查询、流式计算、缓存、数据订阅、用户自定义函数等功能;
- TDengine 采用 SQL 作为查询语言,同时针对时序数据场景,又做了一些扩展,以支持插值、降采样、时间加权平均等操作;
- 可以安全高效地将大量设备、数据采集器每天产生的高达 TB 甚至 PB 级的数据进行汇聚、存储、分析和分发;
- TDengine OSS 的主要功能如下:
- 写入数据
- SQL 写入
- 无模式(Schemaless)写入,支持多种标准写入协议
- OpenTSDB JSON 协议
- InfluxDB Line 协议
- 与多种第三方工具的无缝集成,仅通过配置而无需任何代码即可将数据写入 TDengine
- EMQX
- Prometheus
- 查询数据
- 标准 SQL,含嵌套查询
- 时序数据特色函数
- 时序数据特色查询,例如降采样、插值、累加和、时间加权平均、状态窗口、会话窗口等
- 用户自定义函数(UDF)
- 缓存,将每张表的最后一条记录缓存起来,这样无需 Redis 就能对时序数据进行高效处理
- 流式计算(Stream Processing),不仅支持连续查询,还支持事件驱动的流式计算,这样在处理时序数据时就无需 Flink 或 Spark 这样流式计算组件;
- 数据订阅,应用程序可以订阅一张表或一组表的数据,提供与 Kafka 相同的 API,而且可以指定过滤条件;
- 可视化,支持与 Grafana 的无缝集成;
- 管理
- 多种数据导入方式
- 多种数据导出方式
- 提供交互式命令行程序(CLI),便于管理集群,检查系统状态,做即席查询
- 提供各种语言的连接器,也支持REST 接口;
- 写入数据
- 优势
- TDengine 充分利用了时序数据特点,比如结构化、无需事务、很少删除或更新、写多读少等等;
- 是唯一一个解决了时序数据存储的高基数难题的时序数据库,支持上亿数据采集点,并在数据插入、查询和数据压缩上远胜其它时序数据库。
- 分析能力:通过超级表、存储计算分离、分区分片、预计算和其它技术,TDengine 能够高效地浏览、格式化和访问数据。
- 生态:MQTT、Kafka等等将他们的数据源源不断的写入到 TDengine。TDengine 自身提供的命令行程序(CLI)以及可视化管理工具。
- 适用场景:
- 是针对时序数据场景设计的专用数据库和专用大数据处理工具,
- 因其充分利用了时序大数据的特点,它无法用来处理网络爬虫、微博、微信、电商、ERP、CRM 等通用型数据;
数据模型和基本概念
数据模型
- 理解采集量、标签、超级与子表的关系。TDengine 采用超级表(STable)的概念来描述某一个类型的数据采集点,一张普通的表来描述一个具体的数据采集点。
- 具体的应用场景,需要考虑库、超级表和普通表的设计;
- 创建一个库:
- 除SQL标准的选项外,还可以指定保留时长、副本数、缓存大小、时间精度、文件块里最大最小记录条数、是否压缩、一个数据文件覆盖的天数等多种参数。
- CREATE DATABASE power KEEP 365 DURATION 10 BUFFER 16 WAL_LEVEL 1;
- 这个库的数据将保留365天(超过365天将被自动删除),每10天一个数据文件,每个VNode的写入内存池的大小为16MB,对该数据库入会写WAL但不执行 FSYNC。
- 创建库之后,需要使用 SQL 命令 USE 将当前库切换过来,例如:USE power;
- 创建并插入记录、查询历史记录的时候,均需要指定时间戳。
- 创建超级表
- 对每个类型的数据采集点创建一个超级表
- CREATE STABLE meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);
- 与创建普通表一样,创建超级表时,需要提供表名(示例中为 meters),“表结构Schema”,即数据列的定义。第一列必须为时间戳;
- 还需要提供“标签的 Schema”(示例中为 location, groupId)标签的数据类型可以为整型、浮点型、字符串等。采集点的静态属性往往可以作为标签;
- 每一种类型的数据采集点需要建立一个超级表,因此一个物联网系统,往往会有多个超级表。例如电网中的,智能电表、变压器、母线、开关等都建立一个超级表;
- 一张超级表最多容许 4096 列,一个系统可以有多个 Database,一个 Database 里可以有一到多个超级表。
- 创建表
- 对每个数据采集点需要独立建表。与标准的关系型数据库一样,一张表有表名,Schema,但除此之外,还可以带有一到多个标签。
- 创建时,需要使用超级表做模板,同时指定标签的具体值。
- CREATE TABLE d1001 USING meters TAGS ("California.SanFrancisco", 2);
- d1001 是表名,meters 是超级表的表名,后面紧跟标签 Location 的具体标签值为 "California.SanFrancisco",标签 groupId 的具体标签值为 2。
- 建议将数据采集点的全局唯一 ID 作为表名(比如设备序列号)。但对于有的场景,并没有唯一的 ID,可以将多个 ID 组合成一个唯一的 ID。
- 不建议将具有唯一性的 ID 作为标签值。
- 多列模型 vs 单列模型
- 支持多列模型,只要物理量是一个数据采集点同时采集的(时间戳一致),这些量就可以作为不同列放在一张超级表里。
- 但还有一种极限的设计,单列模型,每个采集的物理量都单独建表,因此每种类型的物理量都单独建立一超级表。比如电流、电压、相位,就建三张超级表。
- 建议尽可能采用多列模型,因为插入效率以及存储效率更高。
- 写入数据
- 数据可以单条插入,也可以批量插入,可以插入一个数据采集点的数据,也可以同时插入多个数据采集点的数据。
- 支持时间乱序数据插入,也支持历史数据插入。INSERT INTO d1001 VALUES (ts1, 10.3, 219, 0.31),ts1为Unix时间戳(Unix timestamp);
- 写入的数据的时间戳必须大于当前时间减去数据库配置参数 KEEP 的时间。如果 KEEP 配置为 3650 天,那么无法写入比 3650 天还早的数据。
- 写入数据的时间戳也不能大于当前时间加配置参数 DURATION。如果 DURATION 为 2,那么无法写入比当前时间还晚 2 天的数据。
- 对同一张表,如果新插入记录的时间戳已经存在,则指定了新值的列会用新值覆盖旧值,而没有指定新值的列则不受影响。
- 查询数据
- 采用 SQL 作为查询语言,
- sql的查询基本都支持,单列、多列数据查询;
- 独有的,时间窗口(Interval)、会话窗口(Session)和状态窗口(State_window)等窗口切分聚合查询;
- 时间戳对齐的连接查询(Join Query: 隐式连接)操作;
- 多种聚合/计算函数: count, max, min, avg, sum, twa, stddev, leastsquares, top, bottom, first, last, percentile, apercentile, last_row, spread, diff 等;
- 为满足物联网场景的需求,TDengine 支持几个特殊的函数,比如 twa(时间加权平均),spread (最大值与最小值的差),last_row(最后一条记录)等;
- 多表聚合查询
- SELECT AVG(voltage), location FROM meters GROUP BY location;
- 降采样查询、插值interval
- SELECT _wstart, sum(current) FROM d1001 INTERVAL(10s); 将智能电表采集的电流值每10秒钟求和。interval将采集的数据按时间段进行聚合;
- SELECT _wstart, SUM(current) FROM meters where location like "California%" INTERVAL(1s);降采样操作也适用于超级表。
- 降采样操作也支持时间偏移,比如:将所有智能电表采集的电流值每秒钟求和,但要求每个时间窗口从 500 毫秒开始;
- SELECT _wstart, SUM(current) FROM meters INTERVAL(1s, 500a);
- 采用 SQL 作为查询语言,
- 流式计算
- 在时序数据的处理中,经常要对原始数据进行清洗、预处理,再使用时序数据库进行长久的储存。
- 流式计算引擎提供了实时处理写入的数据流的能力,使用 SQL 定义实时流变换,当数据被写入流的源表后,数据会被以定义的方式自动处理,并根据定义的触发模式向目的表推送结果。
- 流式计算可以包含数据过滤,标量函数计算(含UDF),以及窗口聚合(支持滑动窗口、会话窗口与状态窗口),可以以超级表、子表、普通表为源表,写入到目的超级表。
- 在创建流时,目的超级表将被自动创建,随后新插入的数据会被流定义的方式处理并写入其中,通过 partition by 子句,可以以表名或标签划分 partition,不同的 partition 将写入到目的超级表的不同子表。
- 流式计算的创建
- CREATE STREAM [IF NOT EXISTS] stream_name
- 示例:
- 创建 DB 和原始数据表,首先准备数据,完成建库、建一张超级表和多张子表操作;
- 创建流;
- 写入数据
- 数据订阅
- 应用实时获取写入 TDengine 的数据,或者以事件到达顺序处理数据,TDengine 提供了类似消息队列产品的数据订阅、消费接口。
- 发布:
- 这样在很多场景下,采用 TDengine 的时序数据处理系统不再需要集成消息队列产品,比如 kafka, 从而简化系统设计的复杂度,降低运营维护成本。
- 与 kafka 一样,你需要定义 topic, 但 TDengine 的 topic 是基于一个已经存在的超级表、子表或普通表的查询条件,即一个 SELECT 语句。
- 可以使用 SQL 对标签、表名、列、表达式等条件进行过滤,以及对数据进行标量函数与 UDF 计算(不包括数据聚合)。
- 消费者订阅 topic 后,可以实时获得最新的数据。
- 一个消费者可以订阅多个 topic。如果订阅的是超级表,数据可能会分布在多个不同的 vnode 上,也就是多个 shard 上;
- TDengine 的消息队列提供了消息的 ACK 机制,在宕机、重启等复杂环境下确保 at least once 消费。
- 为了实现上述功能,TDengine 会为 WAL (Write-Ahead-Log) 文件自动创建索引以支持快速随机访问,并提供了灵活可配置的文件切换与保留机制:
- 对于以 topic 形式创建的查询,TDengine 将对接 WAL 而不是 TSDB 作为其存储引擎。
- 在消费时,TDengine 根据当前消费进度从 WAL 直接读取数据,并使用统一的查询引擎实现过滤、变换等操作,将数据推送给消费者。
- 数据订阅依赖wal文件,而在vnode迁移和分裂的过程中,wal并不会同步过去,所以迁移或分裂后,之前没消费完的wal数据后消费不到。
- 数据订阅:
- CREATE TOPIC topic_name AS SELECT ts, c1, c2, c3 FROM tmqdb.stb WHERE c1 > 1;
- 使用 SQL 创建一个 topic,topic创建个数有上限,通过参数 tmqMaxTopicNum 控制,默认 20 个;
- 列订阅:CREATE TOPIC topic_name as subquery
- 超级表订阅:CREATE TOPIC topic_name [with meta] AS STABLE stb_name [where_condition]
- 数据库订阅:CREATE TOPIC topic_name [with meta] AS DATABASE db_name;
- DROP TOPIC topic_name; 删除 topic
- SHOW TOPICS; 状态查看
- SHOW CONSUMERS; 查询 consumer 的状态及其订阅的 topic
- SHOW SUBSCRIPTIONS; 查询 consumer 与 vgroup 之间的分配关系
- 创建消费者 consumer
- 消费者需要通过一系列配置选项创建,基础配置项如下表所示:
- td.connect.ip
- td.connect.user
- 缓存
- 为了实现高效的写入和查询,TDengine 充分利用了各种缓存技术;
- 写缓存
- TDengine 采用时间驱动缓存管理策略(First-In-First-Out,FIFO),又称为写驱动的缓存管理机制。
- 这种策略有别于读驱动的数据缓存模式(Least-Recent-Used,LRU),直接将最近写入的数据保存在系统的缓存中。当缓存达到临界值的时候,将最早的数据批量写入磁盘。
- 一般对于物联网数据的使用,用户最为关心最近产生的数据,即当前状态。TDengine 充分利用了这一特性,将最近到达的(当前状态)数据保存在缓存中。
- 每个 vnode 的写入缓存大小在创建数据库时决定,创建数据库时的两个关键参数 vgroups 和 buffer 分别决定了该数据库中的数据由多少个 vgroup 处理;
- create database db0 vgroups 100 buffer 16;
- 理论上缓存越大越好,但超过一定阈值后再增加缓存对写入性能提升并无帮助,一般情况下使用默认值即可。
- 读缓存
- 在创建数据库时可以选择是否缓存该数据库中每个子表的最新数据。由参数 cachemodel 设置,分为四种情况:
- none: 不缓存
- last_row: 缓存子表最近一行数据,这将显著改善 last_row 函数的性能
- last_value: 缓存子表每一列最近的非 NULL 值,这将显著改善无特殊影响(比如 WHERE, ORDER BY, GROUP BY, INTERVAL)时的 last 函数的性能
- both: 同时缓存最近的行和列,即等同于上述 cachemodel 值为 last_row 和 last_value 的行为同时生效;
- 元数据缓存
- 为了更高效地处理查询和写入,每个vnode都会缓存自己曾经获取到的元数据。元数据缓存由创建数据库时的两个参数pages和pagesize决定。pagesize的单位是kb。
- create database db0 pages 128 pagesize 16;
- 上述语句会为数据库 db0 的每个 vnode 创建 128 个 page,每个 page 16kb 的元数据缓存。
- 文件系统缓存
- TDengine 利用 WAL 技术来提供基本的数据可靠性。写入 WAL 本质上是以顺序追加的方式写入磁盘文件。
- 此时文件系统缓存在写入性能中也会扮演关键角色。在创建数据库时可以利用 wal 参数来选择性能优先或者可靠性优先。
- 1: 写 WAL 但不执行 fsync ,新写入 WAL 的数据保存在文件系统缓存中但并未写入磁盘,这种方式性能优先;
- 2: 写 WAL 且执行 fsync,新写入 WAL 的数据被立即同步到磁盘上,可靠性更高;
- 客户端缓存
- 除了以上提到的服务端缓存技术之外,在taosc中会缓存所访问过的各个数据库、超级表以及子表的元数据,集群的拓扑结构等关键元数据。
- UDF(用户定义函数)
- 在有些应用场景中,应用逻辑需要的查询无法直接使用系统内置的函数来表示。利用 UDF(User Defined Function) 功能,
- 就能够很方便地解决特殊应用场景中的使用需求。 UDF 通常以数据表中的一列数据做为输入,同时支持以嵌套子查询的结果作为输入。
- 用户可以通过 UDF 实现两类函数:标量函数和聚合函数。
- 支持通过 C/Python 语言进行 UDF 定义。
基本概念
- 采集量(Metric)
- 采集量是指传感器、设备或其他类型采集点采集的物理量;
- 是随时间变化的,数据类型可以是整型、浮点型、布尔型,也可是字符串;
- 随着时间的推移,存储的采集量的数据量越来越大。
- 标签(Label/Tag)
- 标签是指传感器、设备或其他类型采集点的静态属性,不是随时间变化的,比如设备型号、颜色、设备的所在地等,数据类型可以是任何类型;
- 然是静态的,但 TDengine 容许用户修改、删除或增加标签值。
- 数据采集点(Data Collection Point)
- 数据采集点是指按照预设时间周期或受事件触发采集物理量的硬件或软件。
- 一个数据采集点可以采集一个或多个采集量,但这些采集量都是同一时刻采集的,具有相同的时间戳。
- 对于复杂的设备,往往有多个数据采集点,比如对于一台汽车,
- 有数据采集点专门采集 GPS 位置,有数据采集点专门采集发动机状态,有数据采集点专门采集车内的环境,这样一台汽车就有三个数据采集点。
- 表(Table)
- 因为采集量一般是结构化数据,TDengine 采用传统的关系型数据库模型管理数据。
- 用户需要先创建库,然后创建表,之后才能插入或查询数据。
- 为充分利用其数据的时序性和其他数据特点,TD采取一个数据采集点一张表的策略,要求对每个数据采集点单独建表,比如有一千万个智能电表,就需创建一千万张表
- 这种设计有几大优点:
- 不同数据采集点产生数据的过程完全独立,每个数据采集点的数据源是唯一的,一张表也就只有一个写入者,这样就可采用无锁方式来写,写入速度就能大幅提升。
- 对于一个数据采集点而言,其产生的数据是按照时间排序的,因此写的操作可用追加的方式实现,进一步大幅提高数据写入速度。
- 一个数据采集点的数据是以块为单位连续存储的。如果读取一个时间段的数据,它能大幅减少随机读取操作,成数量级的提升读取和查询速度。
- 一个数据块内部,采用列式存储,对于不同数据类型,采用不同压缩算法,而且由于一个数据采集点的采集量的变化是缓慢的,压缩率更高。
- 如果采用传统的方式,将多个数据采集点的数据写入一张表,不同数据采集点的数据到达服务器的时序是无法保证的,写入操作是要有锁保护的,而且一个数据采集点的数据是难以保证连续存储在一起的。
- 采用一个数据采集点一张表的方式,能最大程度的保证单个数据采集点的插入和查询的性能是最优的。
- 建表
- 建议用数据采集点的名字(如上表中的 d1001)来做表名。
- 每个数据采集点可能同时采集多个采集量,每个采集量对应一张表中的一列,数据类型可以是整型、浮点型、字符串等。
- 表的第一列必须是时间戳,即数据类型为 Timestamp。
- 对采集量,TDengine 将自动按照时间戳建立索引,但对采集量本身不建任何索引。数据用列式存储方式保存。
- 超级表(STable)
- 由于一个数据采集点一张表,导致表的数量巨增,难以管理,而且应用经常需要做采集点之间的聚合操作,聚合的操作也变得复杂起来。超级表为解决这个问题;
- 超级表是指某一特定类型的数据采集点的集合。同一类型的数据采集点,其表的结构是完全一样的,但每个表(数据采集点)的静态属性(标签)是不一样的。
- 描述一个超级表(某一特定类型的数据采集点的集合),除需要定义采集量的表结构之外,还需要定义其标签的 Schema,标签可以有多个,可以事后调整。
- 如果整个系统有 N 个不同类型的数据采集点,就需要建立 N 个超级表。
- 表用来代表一个具体的数据采集点,超级表用来代表一组相同类型的数据采集点集合;
- 子表(Subtable)
- 当为某个具体数据采集点创建表时,用户可以使用超级表的定义做模板,同时指定该具体采集点(表)的具体标签值来创建该表。通过超级表创建的表称之为子表。
- 正常的表与子表的差异在于:
- 子表就是表,因此所有正常表的 SQL 操作都可以在子表上执行。
- 子表在正常表的基础上有扩展,它是带有静态标签的,而且这些标签可以事后增加、删除、修改,而正常的表没有。
- 子表一定属于一张超级表,但普通表不属于任何超级表
- 普通表无法转为子表,子表也无法转为普通表。
- 超级表与基于超级表建立的子表之间的关系表现在:
- 一张超级表包含有多张子表,这些子表具有相同的采集量 Schema,但带有不同的标签值。
- 不能通过子表调整数据或标签的模式,对于超级表的数据模式修改立即对所有的子表生效。
- 超级表只定义一个模板,自身不存储任何数据或标签信息。因此,不能向一个超级表写入数据,只能将数据写入子表中。
- 查询既可以在表上进行,也可以在超级表上进行。
- 针对超级表的查询,TDengine 将把所有子表中的数据视为一个整体数据集进行处理,会先把满足标签过滤条件的表从超级表中找出来,
- 然后再扫描这些表的时序数据,进行聚合操作,这样需要扫描的数据集会大幅减少,从而显著提高查询的性能。
- 本质上,TDengine 通过对超级表查询的支持,实现了多个同类数据采集点的高效聚合。
- 建议给一个数据采集点建表,需要通过超级表建表,而不是建普通表。例如通过超级表 meters 创建子表 d1001、d1002、d1003、d1004 等。
- 库(Database)
- 一个库里,可以有一到多个超级表,但一个超级表只属于一个库。一个超级表所拥有的子表全部存在一个库里。
- 库是指一组表的集合。TDengine 容许一个运行实例有多个库,而且每个库可以配置不同的存储策略。
- 不同类型的数据采集点往往具有不同的数据特征,包括数据采集频率的高低,数据保留时间的长短,副本的数目,数据块的大小,是否允许更新数据等等。
- 为了在各种场景下 TDengine 都能最大效率的工作,建议将不同数据特征的超级表创建在不同的库里。
- FQDN & Endpoint
- FQDN(Fully Qualified Domain Name,完全限定域名)是 Internet 上特定计算机或主机的完整域名。
- FQDN 由两部分组成:主机名和域名。
- TDengine 集群的每个节点是由 Endpoint 来唯一标识的,Endpoint 是由 FQDN 外加 Port 组成,比如 h1.tdengine.com:6030。
- 这样当 IP 发生变化的时候,我们依然可以使用 FQDN 来动态找到节点,不需要更改集群的任何配置。
- 采集量(Metric)
TDengine 完整的软件包包括
- 服务端(taosd)、
- 应用驱动(taosc)、
- 用于与第三方系统对接并提供 RESTful 接口的 taosAdapter、
- 命令行程序(CLI,taos)、
- 一些工具软件taosdump、taosTools
TDengine和mysql的区别
- 在时序应用场景下,存储空间、写入和读取速度更快;
- 超级表和N个子表对应mysql的两个关联表;
MQTT协议和WEBSOCKET协议的区别
- 相同点
- MQTT和WebSocket都是面向报文的二进制传输协议。WebSocket更简单,更灵活;MQTT相对复杂,但功能强大。
- 区别
- 使用场景不同,MQTT 主要应用在物联网等场景,WS因为有配套的浏览器API,主要应用在 Web 开发领域。但两者均为通用的应用层协议,可以在任何相关的场景使用。
- MQTT是为了物联网场景设计的基于TCP的Pub/Sub协议,有许多为物联网优化的特性,比如适应不同网络的QoS、层级主题;
- MQTT是一种面向主题(topic)的消息广播协议。客户端可以创建、加入和订阅任意主题,并向主题发布消息或者接收广播消息。
- MQTT 则是一种比较复杂的“消息协议”。MQTT 不仅规定了具体的协议编码,还规定了客户端和服务器的通信模型。
- WebSocket 是一种简单的“报文协议”,着重解决浏览器和服务端不能进行双向通信的问题。
- WebSocket是为了HTML5应用方便与服务器双向通讯而设计的协议,HTTP握手然后转TCP协议,用于取代之前的Server Push、Comet、长轮询等老旧实现。
- 报文结构不同,虽然两都均使用二进行编码,但 WebSocket 的报文要远比 MQTT 简单。两者的应用场景不一样:
- 可以用于MQTT设备跟Web端通信,支持有序双向连接的网络协议都可以支持MQTT,MQTT over WebSocket即使用WebSocket来支持MQTT Client和Broker的连接
- 相同点
MQTT协议
- MQTT协议,Message Queuing Telemetry Transport消息队列遥测传输协议,MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议,构建于TCP/IP上。
- 是一种消息队列传输协议,有非常短的消息头,采用发布/订阅的消息模式,订阅者只接收自己已经订阅的数据;
- 数据的传输以消息为单位,每个消息包含主题,消息服务质量和有效数据。消息的发送和接收都需要依赖主题;
- 主题是一个字符串,由服务端维护。客户端通过在服务端订阅主题,客户端可向该主题发送消息,所有订阅该主题的客户端都会受到该消息;
- MQTT协议中,数据包由三部分构成:
- 固定报头(Fixed header)
- 每个MQTT控制包都包含一个固定报头,表示数据包类型及数据包的分组类标识
- 所有控制报文都包含;
- 表示控制报文的类型,报文的一些标志位(包括消息服务质量),以及报文剩余的字节长度。
- 消息类型
- CONNECT:客户端请求连接服务器;
- PUBLISH:发布消息,两个方向都允许;
- PUBREC:发布收到,两个方向都允许;
- PUBACK:QoS1消息发布收到确认,两个方向都允许;
- SUBSCRIBE:客户端订阅请求,两个方向都允许;
- PINGREQ:心跳请求,客户端到服务端;
- PINGRESP:心跳响应,服务端到客户端;
- 消息类型all
- 可变报头(Variable header)
- 部分控制报文都包含;
- 存在于部分MQTT数据包中,数据包类型决定了可变头是否存在及其具体内容。
- 可变报头的内容跟报文类型有关。不同类型的报文,其不同点在于可变报头及有效载荷。
- 包含报文标识符,一个16bit的数据,用于唯一的标记此次通信的报文。当客户端处理完当前报文后,标识符可释放重用。
- QoS0的消息不需要标识符,因为不需要服务端的应答。
- 消息体(payload),有效荷载
- 部分控制报文都包含;
- 存在于部分MQTT数据包中,表示客户端收到的具体内容。
- 前面都是协议规定必须的,有效载荷是真正的用户数据。不同类型的报文,有效载荷里的数据不同。
- 连接报文
- 可变报头:包含 “使用传输层协议名”(TCP),MQTT协议等级(3.1.1),连接标志,保持连接。
- 保持连接:MQTT客户端需要在一个时间内给服务端发送心跳报文,服务端也要在规定时间内应答。由此判断通信双方是否在线。
- 有效载荷:由连接标志指示其内容,出现的顺序为:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码。
- 客户端标志:字符串,用于唯一标志一个客户端。
- 固定报头字段:
- DUP*:重发标志,控制报文的重复分发标志,qos0为不重发;
- QoS2:PUBLISH报文的服务质量等级;
- RETAIN:保留标志位。PUBLISH报文的保留标志,若为1,服务端需要在内存中保留该消息。
- 有效载荷:应用数据,一般是json字符串
- 固定报头(Fixed header)
- 最小的数据包只有2字节,且无应用消息头。消息传输不需要知道负载内容,使用 TCP/IP 提供网络连接。
- 有三种消息发布的服务质量:
- QoS(Quality of Service):MQTT 提供不同的 QoS 级别,以确保消息的可靠传递;
- 至多一次(0会丢失)、至少一次(1会重复)、只有一次(2,解决前两个的问题,有ack);
- MQTT提供will和Retain的消息选项,在客户端断线情况下,代理会通知所有订阅的客户端;保留消息使得新订阅的客户端也能收到消息通知。
- MQTT不需要设备有强大的计算能力,更适用于移动设备,对电池消耗更低,在吞吐量、响应时间、带宽使用率方面都更适合物联网应用。
- 核心在于由MQTT Broker负责所有消息路由和分发的工作,用很小的传输消耗和协议数据交换,最大限度减少网络流量。
- 主题
- 主题支持分级,如/Home/BathRoom/Mirror,/Home/LivingRoom/Tv,是同一等级下的主题。
- 主题也支持通配符#,发送消息到主题 /Home/# 则BathRoom和LivingRoom主题下的客户端都会收到消息。
- 主题也支持单层通配符+,/Home/+则BathRoom和LivingRoom主题下的客户端会受到消息,但/Home/BathRoom/Mirror和/Home/LivingRoom/Tv不会收到消息,
举例(就这些就可以用了)
有一个小区的电表设备需要联网。
- 那么电表就会存在张三家的电表,李四家的电表,张三家电表的电流和电压,李四家的电流和电压,以及王五等等家的设备信息。
- 电表就可以设计成超级表super_table,电流和电压(动态)就是超级表中定义表字段属性,而电表所属的业主名、小区地址、门派号(静态)可以存放在TAG。
- 每种类型的采集设备可以定义一个 STable。而标签信息属于 Meta Data,用户在创建表(数据采集点)时除可以指定 STable(采集类型)外,还可以指定标签的值。
- 超级表是同一类型数据采集点的集合,标签名和数据类型由 STable 定义,标签值记录着每个子表的静态信息,用于对子表进行分组过滤;
- 总结就是:
- 一个采集点一张表,同一类型的采集点用一个超级表来描述,也就是一个表结构Schema和静态标签Schema;
- 利用超级表作为模板,生成子表 – 对应各采集点,有了超级表,极大地方便了同类采集点的数据检索、查询、聚合。
- 这种设计有几大优点(提高读写速度):
- 能保证一个采集点的数据在存储介质上是以块为单位连续存储的。如果读取一个时间段的数据,它能大幅减少随机读取操作,成数量级的提升读取和查询速度。
- 由于不同采集设备产生数据的过程完全独立,每个设备的数据源是唯一的,一张表也就只有一个写入者,这样就可采用无锁方式来写,写入速度就能大幅提升。
- 对于一个数据采集点而言,其产生的数据是时序的,因此写的操作可用追加的方式实现,进一步大幅提高数据写入速度。
- TDengine操作
(1)创建电表超级表:super_dianbiao
- create database mydb;
- use mydb;
- create stable super_dianbiao (ts timestamp,dianya float,dianliu float) tags (yezhu_name nchar(15),xiaoqu_location nchar(50),menpai_num nchar(10));
(2)创建子表dianbiao
- create table dianbiao1001 using super_dianbiao tags('张三','东城小区','1-1101');
- create table dianbiao1002 using super_dianbiao tags('李四','东城小区','1-1102');
(3)往子表中插入数据
- insert into dianbiao1001 values(now,1.7,3.2);
- insert into dianbiao1002 values(now,1.6,3.1);
select * from super_dianbiao; 会查出两个子表的数据,包括3个tags;
select * from dianbiao1001; 只会查出ts/dianya/dianliu这三个动态数据;