公司的主营业务是给企业客户提供短信服务,内部有一个强大的运营监控平台。每日短信业务量巨大,监控平台须要使用时序数据库来对发送的海量短信分地理区域、运营商、接收状态等类别进行统计和监控。之前一直在用InfluxDB,对于时间跨度稍大些的查询(好比一个月的数据)就显得很是慢了。从TDengine开源后,便知道了这个很牛的家伙,因而尝试着使用TDengine。git
在搞懂了基本的功能后,便上线了TDengine版的监控系统,可是发现,在grafana中竟然不能“group by”,只能经过写where多条语句将多个短信状态数据放到一个仪表盘里,如图:github
若是where条件有更多,这样的方法就太笨,而且灵活性太差。sql
因而,开始仔细研究官方文档,搞懂了“超级表”、“连续查询“等,在这个过程当中遇到过很多问题,在这里作一下记录(测试环境,数据是模拟产生的)。shell
测试环境:数据库
cat /etc/redhat-release CentOS Linux release 7.7.1908 (Core)
安装很简单:浏览器
rpm -ivh tdengine-1.6.3.1-3.x86_64.rpm
配置都用默认的(未改配置文件)服务器
启动TDengine:数据结构
systemctl start taosd
在命令行输入“taos”架构
tas>
如下建库、建表操做都在此提示符下进行性能
create database jk keep 365 precision 'us';
说明:
2. 建立超级表
进入数据库“jk”
taos> use jk; Database changed.
create table jiankong (ts timestamp, gatewayid binary(6), companyid binary(20), provinceid binary(10), cityid binary(10), value int, timestr binary(30)) tags(type binary(10), subtype binary(10));
短信系统中有3种type,几百种subtype,因此将这些静态信息(或者简单地理解为须要将进行group by的字段设置为tag)
解释一下超级表:
STable是同一类型数据采集点的抽象,是同类型采集实例的集合,包含多张数据结构同样的子表。
每一个STable为其子表定义了表结构和一组标签:表结构即表中记录的数据列及其数据类型;标签名和数据类型由STable定义,标签值记录着每一个子表的静态信息,用以对子表进行分组过滤。
子表本质上就是普通的表,由一个时间戳主键和若干个数据列组成,每行记录着具体的数据,数据查询操做与普通表彻底相同;但子表与普通表的区别在于每一个子表从属于一张超级表,并带有一组由STable定义的标签值。
针对全部的经过STable建立的子表进行多表聚合查询,支持按照所有的TAG值进行条件过滤(where),并可将结果按照TAGS中的值进行聚合(group by),暂不支持针对binary类型的模糊匹配过滤。
标签数据(或者叫标签值)直接关联到每一个子表,相同的标签值(一个或者多个,最多6个)定位到一个子表(用“写数据时自动建表”的方式,能够将“相同的标签值”对应到多个子表)。
tag值支持中文,须要将tag类型设置为NCHAR(在其余测试中已经验证过)。
3. 建立子表(子表就是普通表,表结构彻底由超级表定义):
create table jiankong_sub_send using jiankong tags ('send', 'send'); create table jiankong_sub_delivrd using jiankong tags ('delivrd', 'delivrd'); create table jiankong_sub_undeliv_db_0108 using jiankong tags ('undeliv', 'DB:0108'); create table jiankong_sub_undeliv_db_0107 using jiankong tags ('undeliv', 'DB:0107'); create table jiankong_sub_undeliv_balance using jiankong tags ('undeliv', 'BALANCE'); create table jiankong_sub_undeliv_id_0076 using jiankong tags ('undeliv', 'ID:0076'); create table jiankong_sub_undeliv_ib_0008 using jiankong tags ('undeliv', 'IB:0008');
相同类型的type和subtype的组合建立为一个子表(只是进行测试,因此并无将全部的几百个subtype都进行建表)
4. 插入数据
INSERT INTO jiankong_sub_send VALUES (now, 3034, '1564', '109', '1272', '2', '201909231530') INSERT INTO jiankong_sub_delivrd VALUES (now, 3034, '1564', '109', '1272', '2', '201909231530') INSERT INTO jiankong_sub_undeliv_balance VALUES (now, 1179, '152', '106', '1000', '1', '201910071113') INSERT INTO jiankong_sub_undeliv_id_0076 VALUES (now, 1165, '1785', '111', '1226', '1', '201910071415') INSERT INTO jiankong_sub_undeliv_ib_0008 VALUES (now, 1165, '1785', '127', '1000', '2', '201910061727') INSERT INTO jiankong_sub_undeliv_db_0108 VALUES (now, 90, '548', '123', '1237', '1', '201910061127') INSERT INTO jiankong_sub_undeliv_db_0107 VALUES (now, 2261, '808', '116', '1314', '2', '201910032106')
以上是对上述建立的7个子表分别插入模拟数据,因为模拟大量数据,因此须要写shell脚本(也能够用其余方式)进行数据灌入。
写入数据时不能直接对STable操做,而是要对每张子表进行操做。
5. 数据库、表结构等查询
查询数据库信息:
taos> show databases; name | created time | ntables | vgroups |replica| days | keep1,keep2,keep(D) | tables | rows | cache(b) | ablocks |tblocks| ctime(s) | clog | comp |time precision| status | ============================================================================================================================================================================================================================================== log | 19-11-18 16:37:14.025| 4| 1| 1| 10|30,30,30 | 32| 1024| 2048| 2.00000| 32| 3600| 1| 2|us |ready | jk | 19-11-18 16:48:19.867| 10| 1| 1| 10|365,365,365 | 1024| 4096| 16384| 4.00000| 100| 3600| 1| 2|us |ready | Query OK, 1 row(s) in set (0.002487s)
查询超级表:
taos> show stables; name | created_time |columns| tags | tables | ==================================================================================================================== jiankong | 19-11-18 16:48:41.540| 7| 2| 7| Query OK, 1 row(s) in set (0.002140s)
查询超级表的表结构:
taos> describe jiankong; Field | Type | Length | Note | ======================================================================================================= ts |TIMESTAMP | 8| | gatewayid |BINARY | 6| | companyid |BINARY | 20| | provinceid |BINARY | 10| | cityid |BINARY | 10| | value |INT | 4| | timestr |BINARY | 30| | type |BINARY | 10|tag | subtype |BINARY | 10|tag | Query OK, 9 row(s) in set (0.001301s)
能够在Note列看到“tag”,表示是此列是标签
查询子表:
taos> show tables; table_name | created_time |columns| stable | ================================================================================================================================================================= jiankong_sub_delivrd | 19-11-18 16:49:17.009| 7|jiankong | jiankong_sub_undeliv_ib_0008 | 19-11-18 16:49:17.025| 7|jiankong | jiankong_sub_undeliv_db_0108 | 19-11-18 16:49:17.016| 7|jiankong | jiankong_sub_undeliv_db_0107 | 19-11-18 16:49:17.018| 7|jiankong | jiankong_sub_undeliv_id_0076 | 19-11-18 16:49:17.023| 7|jiankong | jiankong_sub_send | 19-11-18 16:49:17.003| 7|jiankong | jiankong_sub_undeliv_balance | 19-11-18 16:49:17.021| 7|jiankong | Query OK, 10 row(s) in set (0.007001s)
查询具体的子表的表结构:
taos> describe jiankong_sub_undeliv_db_0108; Field | Type | Length | Note | ========================================================================================================= ts |TIMESTAMP | 8| | gatewayid |BINARY | 6| | companyid |BINARY | 20| | provinceid |BINARY | 10| | cityid |BINARY | 10| | value |INT | 4| | timestr |BINARY | 30| | type |BINARY | 10|undeliv | subtype |BINARY | 10|DB:0108 | Query OK, 9 row(s) in set (0.001195s)
能够在Note列看到“undeliv”(超级表中的type字段)和“DB:0108"(超级表中的subtype字段),这2个静态标签值肯定了这个子表
6. 数据查询
对type进行分组聚合查询:
taos> select sum(value) from jk.jiankong group by type; sum(value) | type | ================================= 11827688|delivrd | 55566578|send | 46687487|undeliv | Query OK, 3 row(s) in set (0.018251s)
对subtype进行分组聚合查询:
taos> taos> select sum(value) from jk.jiankong group by subtype; sum(value) | subtype | ================================= 9317|BALANCE | 65219|DB:0107 | 2077691|DB:0108 | 2804417|IB:0008 | 41730843|ID:0076 | 11827688|delivrd | 55566578|send | Query OK, 7 row(s) in set (0.013978s)
对type和subtype进行分组聚合查询:
taos> select sum(value) from jk.jiankong group by type, subtype; sum(value) | type | subtype | ============================================ 11827688|delivrd |delivrd | 55566578|send |send | 9317|undeliv |BALANCE | 65219|undeliv |DB:0107 | 2077691|undeliv |DB:0108 | 2804417|undeliv |IB:0008 | 41730843|undeliv |ID:0076 | Query OK, 7 row(s) in set (0.732830s)
按天对type和subtype进行分组聚合查询:
taos> select sum(value) from jk.jiankong interval(1d) group by type, subtype; ts | sum(value) | type | subtype | ====================================================================== 19-11-18 00:00:00.000000| 1760800|delivrd |delivrd | 19-11-19 00:00:00.000000| 14768|delivrd |delivrd | 19-11-20 00:00:00.000000| 3290720|delivrd |delivrd | 19-11-21 00:00:00.000000| 4973640|delivrd |delivrd | 19-11-22 00:00:00.000000| 1787760|delivrd |delivrd | 19-11-18 00:00:00.000000| 36976790|send |send | 19-11-19 00:00:00.000000| 310128|send |send | 19-11-20 00:00:00.000000| 9482760|send |send | 19-11-21 00:00:00.000000| 6470940|send |send | 19-11-22 00:00:00.000000| 2325960|send |send | 19-11-18 00:00:00.000000| 6200|undeliv |BALANCE | 19-11-19 00:00:00.000000| 52|undeliv |BALANCE | 19-11-20 00:00:00.000000| 1590|undeliv |BALANCE | 19-11-21 00:00:00.000000| 1085|undeliv |BALANCE | 19-11-22 00:00:00.000000| 390|undeliv |BALANCE | 19-11-18 00:00:00.000000| 43400|undeliv |DB:0107 | 19-11-19 00:00:00.000000| 364|undeliv |DB:0107 | 19-11-20 00:00:00.000000| 11130|undeliv |DB:0107 | 19-11-21 00:00:00.000000| 7595|undeliv |DB:0107 | 19-11-22 00:00:00.000000| 2730|undeliv |DB:0107 | 19-11-18 00:00:00.000000| 1382600|undeliv |DB:0108 | 19-11-19 00:00:00.000000| 11596|undeliv |DB:0108 | 19-11-20 00:00:00.000000| 354570|undeliv |DB:0108 | 19-11-21 00:00:00.000000| 241955|undeliv |DB:0108 | 19-11-22 00:00:00.000000| 86970|undeliv |DB:0108 | 19-11-18 00:00:00.000000| 1866200|undeliv |IB:0008 | 19-11-19 00:00:00.000000| 15652|undeliv |IB:0008 | 19-11-20 00:00:00.000000| 478590|undeliv |IB:0008 | 19-11-21 00:00:00.000000| 326585|undeliv |IB:0008 | 19-11-22 00:00:00.000000| 117390|undeliv |IB:0008 | 19-11-18 00:00:00.000000| 27769800|undeliv |ID:0076 | 19-11-19 00:00:00.000000| 232908|undeliv |ID:0076 | 19-11-20 00:00:00.000000| 7121610|undeliv |ID:0076 | 19-11-21 00:00:00.000000| 4859715|undeliv |ID:0076 | 19-11-22 00:00:00.000000| 1746810|undeliv |ID:0076 | Query OK, 35 row(s) in set (0.023865s)
此处interval是聚合时间段的长度, 最短期间隔10毫秒(10a)
未建超级表时,对普通表进行分组聚合查询,会报错:
taos> select sum(value) from jk.jiankong group by type; TSDB error: invalid SQL: group by only available for STable query
7. 写数据时自动建子表
咱们有另一个需求,因为要监控的静态数据多达几百个,并且具备不肯定性,因此没法所有在建库、建表的时候建立全部子表,这个功能彻底解决了咱们的这个问题。
如下是官网的文档摘录:
在某些特殊场景中,用户在写数据时并不肯定某个设备的表是否存在,此时可以使用自动建表语法来实现写入数据时用超级表定义的表结构自动建立不存在的子表,若该表已存在则不会创建新表。
注意:自动建表语句只能自动创建子表而不能创建超级表,这就要求超级表已经被事先定义好。自动建表语法跟insert/import语法很是类似,惟一区别是语句中增长了超级表和标签信息。具体语法以下:
INSERT INTO <tb_name> USING <stb_name> TAGS (<tag1_value>, ...) VALUES (field_value, ...) (field_value, ...) ...;
对比,用create建立子表:
create table jiankong_sub_send using jiankong tags ('send', 'send');
在官网https://grafana.com/grafana/download下载grafana的rpm安装包后,进行安装:
rpm -ivh grafana-6.4.4-1.x86_64.rpm
2. copy TDengine的Grafana插件到Grafana的插件目录
TDengine的Grafana插件在安装包的/usr/local/taos/connector/grafana目录下
cp -r /usr/local/taos/connector/grafana/tdengine/ /var/lib/grafana/plugins
3. 启动Grafana
systemctl start grafana-server
4. 在浏览器中经过host:3000登陆Grafana服务器
默认用户名和密码都是admin
5. 添加TDengine数据源
在最下方找到“TDengine”
Name:“TDengine”(能够是其余名字)
Host:测试服务器地址“http://192.168.8.66:6020”
User: 默认为“root”
Password:默认为“taosdata”
测试一下:
6. 添加Folder
将相同类型须要监控的仪表盘(dashboard)放到一个Folder中
7. 添加Dashboard
进入刚才建立的Folder后,建立Dashboard
INPUT处的sql语句,要注意fill的位置,须要在group by前面,不然报错
配置图形显示
能够根据需求进行曲线图,表格,仪表盘等的选择
在这里配置“曲线图”,图示下方是具体的图形显示细则,如:标线、是否填充,对显示的字段进行曲线颜色的自定义等
给此仪表盘起个一看就懂的名字:
8. 配置了6个仪表盘
9. 无group by和有group by的实例
无group by,须要写多条sql,经过where条件去区分,若是分类多就很麻烦且不灵活
有group by,一条sql就解决问题了:
下图是有无group by的曲线趋势对比,能够看到是如出一辙的
TDengine的“预计算”的概念我以为很是的棒,官网文档摘录以下:
为有效地提高查询处理的性能,针对物联网数据的不可更改的特色,TDengine采用在每一个保存的数据块上,都记录下该数据块中数据的最大值、最小值、和等统计数据。若是查询处理涉及整个数据块的所有数据,则直接使用预计算结果,再也不读取数据块的内容。因为预计算模块的大小远小于磁盘上存储的具体数据的大小,对于磁盘IO为瓶颈的查询处理,使用预计算结果能够极大地减少读取IO,并加速查询处理的流程。
连续查询的官网文档摘录以下:
基于滑动窗口的流式计算(Stream)
连续查询是TDengine按期自动执行的查询,采用滑动窗口的方式进行计算,是一种简化的时间驱动的流式计算。针对库中的表或超级表,TDengine可提供按期自动执行的连续查询,用户可以让TDengine推送查询的结果,也能够将结果再写回到TDengine中。每次执行的查询是一个时间窗口,时间窗口随着时间流动向前滑动。在定义连续查询的时候须要指定时间窗口(time window, 参数interval )大小和每次前向增量时间(forward sliding times, 参数sliding)。
其中,将结果再写回到TDengine中的方式其实就是一种用户级别的预计算,这样由TDengine按照用户定义的时间窗口和时间增量进行后台的计算,在用户查询数据的时候,直接从回写的表中读取数据,速度就会很是快。
建立连续查询:
taos> create table test_stream_sum as select sum(value) from jiankong interval(20s) sliding(10s) group by type, subtype; Query OK, 1 row(s) affected (0.000983s)
上述连续查询,sql的select部分实际的输出结果为:
taos> select sum(value) from jiankong interval(20s) group by type, subtype; ts | sum(value) | type | subtype | ====================================================================== 19-11-18 16:50:40.000000| 9088|delivrd |delivrd | 19-11-18 16:51:00.000000| 31808|delivrd |delivrd | 19-11-18 16:51:20.000000| 15904|delivrd |delivrd | 19-11-18 16:52:20.000000| 12212|delivrd |delivrd | 19-11-18 16:52:40.000000| 31524|delivrd |delivrd | 19-11-18 16:53:00.000000| 31524|delivrd |delivrd | 19-11-18 16:53:20.000000| 31808|delivrd |delivrd | 19-11-18 16:53:40.000000| 31240|delivrd |delivrd | 19-11-18 16:54:00.000000| 31524|delivrd |delivrd | 19-11-18 16:54:20.000000| 31524|delivrd |delivrd | 19-11-18 16:54:40.000000| 31240|delivrd |delivrd | 19-11-18 16:55:00.000000| 31524|delivrd |delivrd | 19-11-18 16:55:20.000000| 28400|delivrd |delivrd | 19-11-18 16:55:40.000000| 31808|delivrd |delivrd | 19-11-18 16:56:00.000000| 31524|delivrd |delivrd | 19-11-18 16:56:20.000000| 31240|delivrd |delivrd | 19-11-18 16:56:40.000000| 31524|delivrd |delivrd | 19-11-18 16:57:00.000000| 32092|delivrd |delivrd | 19-11-18 16:57:20.000000| 31240|delivrd |delivrd | 19-11-18 16:57:40.000000| 32092|delivrd |delivrd | 19-11-18 16:58:00.000000| 31240|delivrd |delivrd | 19-11-18 16:58:20.000000| 22720|delivrd |delivrd | 19-11-18 16:50:40.000000| 190848|send |send |
自动建立的连续查询的表中实际的数据为:
taos> select * from test_stream_sum; ts | sum_value_ | ================================================ 19-11-18 17:17:30.000000| 2556| 19-11-18 17:17:40.000000| 18460| 19-11-18 17:17:50.000000| 15904| 19-11-18 17:18:00.000000| 15620| Query OK, 4 row(s) in set (0.000431s)
上述结果并非期待的结果,没有按照我定义的group by字段进行聚合查询显示。
因而github Issues, taos的攻城狮回复“连续查询目前还不能很好的支持 group by,这个问题已经在咱们的计划列表里,后续会完善这方面的功能”,缘由是“由于这样回写后,新表的时间戳主键可能出现相同(不一样的group by字段会有相同的时间),就会冲突了,因此暂时不支持”。
虽然目前这个功能未达到个人预期,攻城狮的回复仍是聊以安慰,期待这个功能的完善。
TDengine的设计初衷是服务于物联网,有结构化和时序的特性,建议是和时序密切的数据能够用,其余的数据不建议使用。
从TDengine的架构设计、存储等等,以为不局限于百分之百的时序特性,有些明细查询也是能够尝试在TDengine中进行存储的,后续会尝试进行一下测试。