Tdengine的时序数据库简介、单机部署、操作语句及java应用
本文介绍了Tdengine的功能特点、应用场景、超级表和子表等概念,讲述了Tdengine2.6.0.34的单机部署,并介绍了taos数据库的常见使用方法及特色窗口查询方法,最后介绍了在java中的应用。
一、tdengine简要介绍及应用场景
1、简要介绍
TDengine是涛思数据专为物联网、车联网、工业互联网、IT运维等设计和优化的大数据平台,是一款优秀的时序数据库。
2、应用场景
- 公共安全:上网记录、通话记录、个体追踪、区间筛选
- 电力行业:智能电表、电网、发电设备的集中监测
- 通讯行业:话费详单、用户行为、基站/通讯设备监测
- 金融行业:交易记录、存取记录、ATM、POS 机监测
- 出行工具:火车/汽车/出租/飞机/自行车的实时监测
- 交通行业:实时路况,路口流量监测,卡口数据;
- 石油石化:油井、运输管线、运输车队的实时监测
- 互联网: 服务器/应用监测、用户访问日志、广告点击日志
- 物流行业:车辆、集装箱的追踪监测
- 环境监测:天气、空气、水文、地质环境等监测;
- 物联网: 电梯、锅炉、机械、水表、气表等各种联网设备
- 军工行业:各种军事装备的数据采集、存储
- 制造业: 生产过程管控,流程数据、供应链数据采集与分析
如果采用传统的方式,将多个设备的数据写入一张表,由于网络延时不可控,不同设备的数据到达服务器的时序是无法保证的,写入操作是要有锁保护的,而且一个设备的数据是难以保证连续存储在一起的。采用一个数据采集点一张表的方式,能最大程度的保证单个数据采集点的插入和查询的性能是最优的。
二、tdengine功能特点
1、相比mysql可以查询快10倍以上;
2、提供缓存、数据订阅、流式计算等功能;
3、提供了独有的按时间自动划分窗口并执行查询的能力;
4、自动按照时间戳建立索引,但对采集的物理量不建任何索引。数据用列式存储方式保存。
三、tdengine技术概念(超级表和子表)
1、引入超级表和子表概念,并提供窗口查询。taos数据库常用于具有时序的大量数据存储,能够快速查询并定时清除历史数据。诸如能源系统的电表、水表、气表等,通过mqtt采集后的实时数据,存储在tdengine中,对于每一类的采集设备或者采集数据,有着相似的数据结构,便建立“超级表”定义表结构,设置tags标签用于建立,超级表下的子表,通常每一个上报数据的设备建立一个子表。
超级表有效用于聚合查询等数据分析,子表用于单设备节点的实时数据分析,超级表和子表的查询有连接的地方。
2、 超级表是指某一特定类型的数据采集点的集合。同一类型的数据采集点,其表的结构是完全一样的,但每个表(数据采集点)的静态属性(标签)是不一样的。
能源管理系统中数据量最大的就是各个智能抄表、传感器上报的数据。这些智能抄表的上报数据要进行长期记录,并定期查询、统计生成报表;在系统中,也需要在用户点击查看具体设备时,显示出该设备的实时读数以及历史变化曲线等。
随着公司业务的不断扩大,所有客户的数据我们还需要一个总的平台来监测存储,传统的关系型数据库已经不足以支撑业务量,因此针对抄表数据时序结构化的特点选择合适的数据库变得非常重要。
子表用来代表一个具体的数据采集点,超级表用来代表一组相同类型的数据采集点集合。
四、单机版tdengine部署
1、下载TDengine离线rpm安装包
wget https://www.taosdata.com/assets-download/TDengine-server-2.6.0.34-Linux-x64.rpm
2、安装离线包
sudo yum install TDengine-server-2.6.0.34-Linux-x64.rpm –y
3、启动tdengine
sudo systemctl start taosd
systemctl start taosadapter
taos
alter user root pass 'admin123';
五、tdengine创建
1、创建数据库
#命令行执行,登陆taos数据库
taos
# 创建数据库ems
create database if not exists ems;
# 查看所有的数据库
show databases;
# 切换到数据库ems
use ems;
2、超级表和子表的结构与表名设计
超级表为某一类的数据结构,相当于表结构声明;子表为单节点的数据表,继承超级表的表结构,tag标签用于区分该类设备/数据的单节点。在java应用中,为便于查询,常将部分tag标签用于组建子表名。
如下设计为电表的表结构,electric_meter_ui为超级表,通过定义标签location和sn创建子表,并将tag标签用于子表的命名。
超级表:electric_meter_ui | ||||||
ts | Ua | Ub | Uc | typestatues | ||
子表:electric_meter_ui_${location}${sn} | ||||||
ts | Ua | Ub | Uc | typestatues | location | sn |
(tag) | (tag) |
3、超级表创建和结构修改
(1)超级表创建
# 创建超级表
CREATE STABLE `electric_data` (`ts` TIMESTAMP, `Ua` FLOAT , `Ub` FLOAT , `Uc` FLOAT ) TAGS ( `location` NCHAR(100), `sn` NCHAR(100));
# 查看超级表
show stables;
# 查看超级表结构
DESCRIBE electric_data;
(2)超级表结构修改-列删除
ALTER TABLE electric_data DROP COLUMN typestatues;
(3)超级表结构修改-列增加
ALTER TABLE electric_data ADD COLUMN typestatues INT;
(4)超级表结构修改-列修改
ALTER TABLE electric_data MODIFY COLUMN typestatues INT;
4、子表创建和数据新增
(1)子表创建
# 创建子表
create table electric_data_beijing_SN001 using electric_data tags("beijing",'SN001');
create table electric_data_beijing_SN002 using electric_data tags("beijing",'SN002');
create table electric_data_jinan_SN002 using electric_data tags("jinan",'SN002');
create table electric_data_shenzhen_SN005 using electric_data tags("shenzhen",'SN005');
# 查看子表结构
DESCRIBE electric_data_shenzhen_SN005;
(2)新增数据
taos> INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 00:05:12.272', 10.2, 200, 1);
Query OK, 1 row(s) affected (0.000859s)
taos> INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 01:05:12.272', 20.2, 210, 2);
Query OK, 1 row(s) affected (0.000272s)
taos> INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 02:05:12.272', 10.2, 220, 3);
Query OK, 1 row(s) affected (0.000233s)
taos> INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 03:05:12.272', 20.2, 200, 2);
Query OK, 1 row(s) affected (0.000385s)
taos> INSERT INTO electric_data_jinan_SN002 VALUES ('2024-06-08 12:04:12.272', 10.2, 200, 1);
Query OK, 1 row(s) affected (0.000696s)
taos> INSERT INTO electric_data_jinan_SN002 VALUES ('2024-06-08 13:05:12.272', 20.2, 210, 2);
Query OK, 1 row(s) affected (0.000346s)
taos> INSERT INTO electric_data_jinan_SN002 VALUES ('2024-06-08 14:05:12.272', 10.2, 220, 3);
Query OK, 1 row(s) affected (0.000305s)
taos> INSERT INTO electric_data_jinan_SN002 VALUES ('2024-06-08 15:05:12.272', 20.2, 200, 2);
Query OK, 1 row(s) affected (0.000299s)
taos> INSERT INTO electric_data_shenzhen_SN005 VALUES ('2024-06-08 15:09:12.272', 20.2, 200, 2);
Query OK, 1 row(s) affected (0.000327s)
taos> create table electric_data_beijing_SN002 using electric_data tags("beijing",'SN002');
Query OK, 0 row(s) affected (0.004323s)
taos> INSERT INTO electric_data_shenzhen_SN005 VALUES ('2024-06-08 15:09:12.272', 20.2, 200, 2);
Query OK, 1 row(s) affected (0.000352s)
(3)子表删除
drop table electric_data_beijing_SN001;
六、查询操作
主要示例数据:
INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 00:05:12.272', 10.2, 200, 1,1);
INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 01:05:12.272', 20.2, 210, 2,1);
INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 02:05:12.272', 10.2, 220, 3,1);
INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 03:05:12.272', 20.2, 200, 2,0);
INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 03:31:12.272', 20.2, 210, 2,0);
INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 05:05:12.272', 10.2, 200, 3,1);
INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 05:55:12.272', 10.2, 220, 3,1);
INSERT INTO electric_data_beijing_SN001 VALUES ('2024-06-08 06:55:12.272', 10.2, 210, 3,0);
1、过滤查询
select count(*) from `ems`.`electric_data` where `location` = 'beijing' ;
超级表的筛选条件可以是静态字段或者tag, 当筛选条件为所有tag时,等同于查询对应的子表,如下两个查询语句有相同结果:
select count(*) from `ems`.`electric_data` where `location` = 'beijing' and `sn` = '001';
select COUNT(*) from electric_data_beijing_SN001;
2、聚合查询
直接对超级表聚合查询<==>子表数据聚合查询
SELECT AVG(`Ua`), `location` FROM `ems`.`electric_data` GROUP BY `location`;
3、时间窗口查询(interval)
时间窗口通过对数据按时间顺序划分切割,实现数据的降采样,在时间窗口内的数据可以avg/max/min等聚合运算或时长计算,并提供了滑动窗口sliding以及自动填充方案FILL。
(1)interval等时间间隔窗口
如下是等时间间隔窗口示意图,未声明滑动窗口,则滑动窗口sliding=interval,时间窗口数据无重叠:
(2)sliding 滑动窗口
如下是等时间间隔窗口示意图,声明滑动窗口,则滑动窗口sliding<interval,时间窗口数据有重叠[t0s-t0e]和[t1s-t1e]:
(3)fill填充
FILL 语句指定某一窗口区间数据缺失的情况下的填充模式。填充模式包括以下几种:
不进行填充:NONE(默认填充模式)。
VALUE 填充:固定值填充,此时需要指定填充的数值。例如:FILL(VALUE, 1.23)。
PREV 填充:使用前一个非 NULL 值填充数据。例如:FILL(PREV)。
NULL 填充:使用 NULL 填充数据。例如:FILL(NULL)。
LINEAR 填充:根据前后距离最近的非 NULL 值做线性插值填充。例如:FILL(LINEAR)。
NEXT 填充:使用下一个非 NULL 值填充数据。例如:FILL(NEXT)。
(4)示例1(无滑动窗口)
# 从2024-06-08 00:00:00.000开始,每间隔1h的Ub平均值,如果区间的1h没数据,使用前一个非 NULL 值填充。
#查询结果有24条
SELECT avg(`Ub`) as Ub FROM electric_data_beijing_SN001 where `ts` > '2024-06-08 00:00:00.000' and `ts` < '2024-06-09 00:00:00.000' interval(1h)
(5)示例2(有滑动窗口)
# 从2024-06-08 00:00:00.000开始,每间隔30min, 时长区间未1h的Ub平均值,如果区间的1h没数据,使用前一个非 NULL 值填充。
#查询结果有49条
SELECT avg(`Ub`) as Ub FROM electric_data_beijing_SN001 where `ts` > '2024-06-08 00:00:00.000' and `ts` < '2024-06-09 00:00:00.000' interval(1h) sliding(30m) FILL(PREV);
4、状态窗口查询(state_window)
状态窗口让用户可以按照某个整型/字符串型的采集量(一般是表征状态的值,如设备工作模式等)的值来划分窗口,将该状态值连续不变的记录划入同一个窗口。然后对每个窗口内的采集值进行avg/max/min等统计聚合、或计算状态持续时间等。
(1)示例1 state_window
# typestatues连续不变的总共有4段,划分四个窗口,对窗口内的数据计算记录条数和持续时长。
select first(`ts`) as ts, `typestatues` as status,count(*) as record_count,spread(`ts`)/1000 as "duration(s)" from electric_data_beijing_SN001 where `ts` > '2024-06-08 00:00:00.000' and `ts` <
'2024-06-09 00:00:00.000' state_window(`typestatues`);
5、会话窗口查询(session)
会话窗口让用户可以按照上报记录的时间连续性来划分窗口,即相邻两条记录时间间隔不超过某一阈值,则划归同一窗口;超过该阈值,则老窗口结束,新窗口开始;然后对每个窗口进行诸如持续时间等统计,或对窗口内的原始采集数据进行各种聚合计算,可以是 avg/max/min/count 或其他用户自定义函数。
用于物联网中,上设备连续上报数据时表名在线,如果间隔时间过长,表明不在线,我们对数据基于间隔时间最大阈值进行划分,更有效的分析数据。
(1)示例1 session
# 上报数据间隔时间超过1h的,作为窗口切分的边缘;
# 结果分为划分为两个窗口,对窗口内的数据计算记录条数和持续时长。
select last(`ts`) as session_endtime, spread(ts)/1000 as 'duration(s)',count(*) as record_count from `ems`.electric_data_beijing_SN001 session(ts,1h);
七、java下的tdengine操作
1、通过HttpUtils网址调用
// 拼接子表表名
String sqls = "select last_row(*) from electric_data_".concat(location.toString()).concat("_").concat(sn);
// 查询
JSONObject Json = JSONObject.parseObject(httpUtil.postTDengine(sqls));
Urs = jdbc:TAOS-RS://47.95.215.167:6041/ems
public String postTDengine(String sqls) {
CloseableHttpResponse response = null;
BufferedReader in = null;
String result = "";
try {
HttpPost httpPost = new HttpPost(url);
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(30000).setConnectionRequestTimeout(30000).setSocketTimeout(30000).build();
httpPost.setConfig(requestConfig);
httpPost.setConfig(requestConfig);
httpPost.addHeader("Content-type", "text/plain; charset=utf-8");
httpPost.addHeader("Authorization", "Basic " + authorizationToken);
httpPost.setHeader("Accept", "*/*");
httpPost.setEntity(new StringEntity(sqls, Charset.forName("UTF-8")));
response = httpClient.execute(httpPost);
in = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
StringBuffer sb = new StringBuffer();
String line = "";
String NL = System.getProperty("line.separator");
while ((line = in.readLine()) != null) {
sb.append(line + NL);
}
in.close();
result = sb.toString();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (null != response) {
response.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
2、通过mapper接口层调用
<dependency>
<groupId>com.taosdata.jdbc</groupId>
<artifactId>taos-jdbcdriver</artifactId>
<version>3.0.4</version>
</dependency>
username: root
password: taosdata
url: jdbc:TAOS-RS://47.95.215.167:6041/ems
driver-class-name: com.taosdata.jdbc.rs.RestfulDriver
主要参考网站
按窗口切分聚合 | TDengine 文档 | 涛思数据
https://www.sohu.com/a/602180065_121276745