文章目录
- 1. 基本概念
-
- 1.1 表设计的优点
- 2. 开发指南
-
- 2.1 建立连接
-
- 2.1.1 原生连接
- 2.1.2 REST 连接
- 2.2 数据建模
-
- 2.2.1 建库
- 2.2.2 创建超级表
- 2.2.3 创建表
- 2.2.4 多列模型 vs 单列模型
- 2.3. 写入数据
-
- 2.3.1 InfluxDB 行协议
- 2.3.2 OpenTSDB 行协议
- 2.3.3 OpenTSDB JSON 格式协议
- 2.4. 查询数据
-
- 2.4.1 多表聚合查询
- 2.4.2 降采样查询、插值
- 2.5. 连续查询
-
- 2.5.1 使用
- 2.5.2 Note
- 2.5.3 连续管理查询
- 2.6. 数据订阅
-
- 2.6.1 提出问题
- 2.6.2 问题解决
-
- 2.6.2.1 创建订阅
- 2.6.2.2 参数解析
- 2.6.2.3 消费数据
- 2.6.2.4 测试订阅功能
- 2.6.2.5 问题
- 2.7. 缓存
- 2.8. 用户自定义函数(难)
- 3. 集群管理
-
- 3.1 集群部署
-
- 3.1.1 准备步骤:
- 3.1.2 启动集群
- 3.2 管理数据节点
-
- 3.2.1 dnode 、vnode 、mnode三者关系
- 3.2.2 常规操作数据节点
- 3.2.3 数据节点的手动迁移
-
- 3.2.3.1 参数解析
- 3.2.3.2 Note
- 3.2.4 平衡高可用性和负载
-
- 3.2.4.1 高可用
- 3.2.4.2 负载均衡
- 4. SQL手册
-
- 4.1 My
- 4.2 INFO
- 4.3 TIP
- 4.4 NOTE
- 5. 运维指南
-
- 4.3 TIP
- 4.4 NOTE
- 5. 运维指南
数据洞察
基础设施监测:过程资源消耗类似TOP命令
日志监控:检查日志、过滤、分析和查询
应用性能监控:程序运行结点的消耗及API请求,SQL语句
CI可视化、用户访问监控、安全检查
可用性监控:服务结点响应、数据性能、网络延迟和错误
智能监控:配置报警检测项目
合作,使用场景,SRE套件
可编程:可二次开发、自定义场景、报警开发
超级表的列分为动态和静态两部分
TDengine使用的是Posix所以标准UTC-八、表示东八区,而UTC 8表示的是西八区
子查询注意事项
1. 基本概念
- :集成数据库、消息队列、缓存、流式计算等功能,应用程序不需要集成 Kafka/Redis/HBase/Spark 等软件,大大降低了应用开发和维护成本。
- :不需要一行代码就可以匹配 Telegraf、Grafana、Prometheus、EMQX、HiveMQ、StatsD、collectd、icinga、TCollector、Matlab、R 等第三方工具无缝集成。
1.1 表设计的优点
- 由于不同数据收集点生成数据的过程完全独立,每个数据收集点的数据源是唯一的,一个表只有一个写入者,因此可以使用无锁方式写作时,写高写入速度。
- 对于一个数据采集点来说,它产生的数据是按时间排序的,所以写作操作是可用的追加的方式进一步提高数据写入速度。
- 数据采集点的数据是以块为单位连续存储是的。如果读取一段时间的数据,可以大大降低随机读取操作,提高读取和查询速度。
- 在一个数据块中,列式存储用于不同的数据类型压缩算法不同。
2. 开发指南
- 如果要根据时序数据进行实时统计分析,包括各种监控看板,建议采用TDengine的连续查询没有在线功能Spark, Flink流式计算系统等复杂。
- 如果您的应用程序有一个需要消费插入的模块数据,并且希望在插入新数据时获得通知,建议您使用它TDengine提供的数据订阅无需专门部署功能Kafka或者其他消息队列软件。
- 在许多情况下(如车辆管理),应用程序需要获得每个数据收集点的最新状态,因此建议您使用它TDengine的cache而不是单独部署功能Redis等待缓存软件。
2.1 建立连接
2.1.1 原生连接
public class JNIConnectExample {
public static void main(String[] args) throws SQLException {
String jdbcUrl = "jdbc:TAOS://localhost:6030?user=root&password=taosdata";
Properties connProps = new Properties();
connProps.setProperty(TSDBDriver.PROPERTY_KEY_CHARSET, "UTF-8");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_LOCALE, "en_US.UTF-8");
connProps.setProperty(TSDBDriver.PROPERTY_KEY_TIME_ZONE, "UTC-8");
Connection conn = DriverManager.getConnection(jdbcUrl, connProps);
System.out.println("Connected");
conn.close();
}
}
// if you want to connect a specified database named "dbName"
// String jdbcUrl = "jdbc:TAOS://localhost:6030/dbName?user=root&password=taosdata";
2.1.2 REST 连接
public static void main(String[] args) throws SQLException {
String jdbcUrl = "jdbc:TAOS-RS://localhost:6041?user=root&password=taosdata";
Connection conn = DriverManager.getConnection(jdbcUrl);
conn.close();
}
使用 REST 连接时,如果查询数据量比较大,还可开启批量拉取功能。
public static void main(String[] args) throws SQLException {
String jdbcUrl = "jdbc:TAOS-RS://localhost:6041?user=root&password=taosdata";
Properties connProps = new Properties();
connProps.setProperty(TSDBDriver.PROPERTY_KEY_BATCH_LOAD, "true");
Connection conn = DriverManager.getConnection(jdbcUrl, connProps);
conn.close();
}
2.2 数据建模
2.2.1 建库
CREATE DATABASE power KEEP 365 DAYS 10 BLOCKS 6 UPDATE 1;
上述语句将创建一个名为 power 的库,这个库的数据将保留 365 天(超过 365 天将被自动删除),每 10 天一个数据文件,内存块数为 6,允许更新数据。
2.2.2 创建超级表
CREATE STABLE meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);
Device ID | Time Stamp | Collected Metrics | Tags | |||
---|---|---|---|---|---|---|
Device ID | Time Stamp | current | voltage | phase | location | groupId |
d1001 | 1538548685000 | 10.3 | 219 | 0.31 | California.SanFrancisco | 2 |
d1002 | 1538548684000 | 10.2 | 220 | 0.23 | California.SanFrancisco | 3 |
2.2.3 创建表
CREATE TABLE d1001 USING meters TAGS ("California.SanFrancisco", 2);
其中 d1001 是表名,meters 是超级表的表名,后面紧跟标签 Location 的具体标签值 “California.SanFrancisco”,标签 groupId 的具体标签值 2。虽然在创建表时,需要指定标签值,但可以。
(在写数据时并不确定某个数据采集点的表是否存在)
INSERT INTO d1001 USING meters TAGS ("California.SanFrancisco", 2) VALUES (now, 10.2, 219, 0.32);
2.2.4 多列模型 vs 单列模型
TDengine 支持,只要物理量是一个数据采集点同时采集的(时间戳一致),这些量就可以作为不同列放在一张超级表里。
但还有一种极限的设计,,每个采集的物理量都单独建表,因此每种类型的物理量都单独建立一超级表。比如电流、电压、相位,就建三张超级表。
TDengine 建议尽可能采用多列模型,因为插入效率以及存储效率更高。但对于有些场景,一个采集点的采集量的种类经常变化,这个时候,如果采用多列模型,就需要频繁修改超级表的结构定义,让应用变的复杂,这个时候,采用单列模型会显得更简单。
2.3. 写入数据
2.3.1 InfluxDB 行协议
InfluxDB Line 协议采用一行字符串来表示一行数据。分为四部分:
measurement,tag_set field_set timestamp
分别用英文逗号、半角空格、半角空格来分隔四个部分的数据
measurement 将作为超级表名;
tag_set 将作为标签数据;
field_set 将作为普通列数据;
timestamp 即本行数据对应的主键时间戳。
meters,location=California.LosAngeles,groupid=2 current=13.4,voltage=223,phase=0.29 1648432611249500
- tag_set 中的所有的数据自动转化为 nchar 数据类型;
- field_set 中的每个数据项都需要对自身的数据类型进行描述, 比如 1.2f代表 float 类型的数值 1.2, 如果不带类型后缀会被当作 double 处理;
- timestamp 支持多种时间精度。写入数据的时候需要用参数指定时间精度,支持从小时到纳秒的 6 种时间精度。
2.3.2 OpenTSDB 行协议
OpenTSDB 行协议同样采用一行字符串来表示一行数据。OpenTSDB 采用的是单列模型,因此一行只能包含一个普通数据列。标签列依然可以有多个。分为四部分:
<tagk_1>=<tagv_1>[ <tagk_n>=<tagv_n>]
- metric 将作为超级表名
- timestamp 本行数据对应的时间戳
- value 度量值,必须为一个数值。对应的列名也是 “value”
- 最后一部分是标签集, 用空格分隔不同标签, 所有标签自动转化为 nchar 数据类型
meters.current 1648432611250 11.3 location=California.LosAngeles groupid=3
2.3.3 OpenTSDB JSON 格式协议
[
{
"metric": "sys.cpu.nice",
"timestamp": 1346846400,
"value": 18,
"tags": {
"host": "web01",
"dc": "lga"
}
},
{
"metric": "sys.cpu.nice",
"timestamp": 1346846400,
"value": 9,
"tags": {
"host": "web02",
"dc": "lga"
}
}
]
2.4. 查询数据
和MySQL差不多
2.4.1 多表聚合查询
通过指定标签的过滤条件,TDengine 提供了一高效的方法将超级表(某一类型的数据采集点)所属的子表进行聚合查询。
查找加利福尼亚州所有智能电表采集的电压平均值,并按照 location 分组。
SELECT AVG(voltage) FROM meters GROUP BY location;
avg(voltage) | location |
---|---|
222.000000000 | California.LosAngeles |
219.200000000 | California.SanFrancisco |
查找 groupId 为 2 的所有智能电表过去 24 小时的记录条数,电流的最大值
select count(*) , max(current) from meters where groupId = 2 and ts > now -24h;
count(*) | max(current) |
---|---|
5 | 13.4 |
2.4.2 降采样查询、插值
通过降采样(down sampling)将采集的数据按时间段进行聚合
TDengine 提供了一个简便的关键词 interval 让按照时间窗口的查询操作变得极为简单
将智能电表 d1001 采集的电流值每 10 秒钟求和
SELECT sum(current) FROM d1001 INTERVAL(10s);
降采样也适用于超级表
将加利福尼亚州所有智能电表采集的电流值每秒钟求和
SELECT SUM(current) FROM meters where location like “California%” INTERVAL(1s);
降采样操作也支持时间偏移
将所有智能电表采集的电流值每秒钟求和,但要求每个时间窗口从 500 毫秒开始
SELECT SUM(current) FROM meters INTERVAL(1s, 500a);
2.5. 连续查询
TDengine 可提供定期自动执行的连续查询(Continuous Query)。每次执行的查询是一个时间窗口,时间窗口随着时间流动向前滑动。
2.5.1 使用
指定时间窗口(time window, 参数 interval)大小和每次前向增量时间(forward sliding times, 参数 sliding)。
以一分钟为时间窗口、30 秒为前向增量统计这些电表的平均电压
每隔 30 秒执行一次来增量计算最近一分钟的数据
create table avg_vol as select avg(voltage) from meters interval(1m) sliding(30s);
会自动创建一个名为
avg_vol
的新表,然后每隔 30 秒,TDengine 会增量执行as
后面的 SQL 语句,并将查询结果写入这个表中,用户程序后续只要从avg_vol
中查询数据即可。
2.5.2 Note
-
查询时间窗口的最小值是 10 毫秒,没有时间窗口范围的上限。
-
此外,TDengine 还支持用户指定连续查询的起止时间。
如果不输入开始时间,连续查询将从第一条原始数据所在的时间窗口开始;
如果没有输入结束时间,连续查询将永久运行;如果用户指定了结束时间,连续查询在系统时间达到指定的时间以后停止运行。比如使用下面的 SQL 创建的连续查询将运行一小时,之后会自动停止。
create table avg_vol as select avg(voltage) from meters where ts > now and ts <= now + 1h interval(1m) sliding(30s);
-
为了尽量避免原始数据延迟写入导致的问题,TDengine 中连续查询的计算有一定的延迟。也就是说,一个时间窗口过去后,TDengine 并不会立即计算这个窗口的数据,所以要稍等一会(一般不会超过 1 分钟)才能查到计算结果。
2.5.3 管理连续查询
用户可在控制台中通过 show streams
命令来查看系统中全部运行的连续查询,并可以通过 kill stream
命令杀掉对应的连续查询。
2.6. 数据订阅
TDengine 在内部严格按照数据时间序列单调递增的方式保存数据。本质上来说,TDengine 中每一张表均可视为一个标准的消息队列。
TDengine中与订阅相关的API有以下三个:
taos_subscribe
、``taos_consume、
taos_unsubscribe`
2.6.1 提出问题
我们希望当某个电表的电流超过一定限制(比如 10A)后能得到通知并进行一些处理==(也就是告警)==
有两种方法:,每次查询后记录最后一条数据的时间戳,后续只查询这个时间戳之后的数据:
select * from D1001 where ts > {last_timestamp1} and current > 10;
select * from D1002 where ts > {last_timestamp2} and current > 10;
存在的问题是:随着电表数量的增加,查询数量也会增加,客户端和服务端的性能都会受到影响,当电表数增长到一定的程度,系统就无法承受了。
。这样,无论有多少电表,都只需一次查询:
select * from meters where ts > {last_timestamp} and current > 10;
存在的问题是:如何选择 last_timestamp
,因为数据产生的时间(timeStamp)和数据入库的时间不一致。
如果选用最慢的一台电表的数据的时间戳作为last_timestamp
,就可能会其他电表的数据;
如果选用最快的一台电表的数据的时间戳作为last_timestamp
,就可能造成其他电表的数据的。
2.6.2 问题解决
TDengine的订阅功能解决了此问题
2.6.2.1 创建订阅
首先是使用 taos_subscribe
创建订阅:
TAOS_SUB* tsub = NULL;
if (async) {
// 异步
// create an asynchronized subscription, the callback function will be called every 1s
tsub = taos_subscribe(taos, restart, topic, sql, subscribe_callback, &blockFetch, 1000);
} else {
// 同步
// create an synchronized subscription, need to call 'taos_consume' manually
tsub = taos_subscribe(taos, restart, topic, sql, NULL, NULL, 0);
}
上面的代码会根据从命令行获取的参数 async
的值来决定使用哪种方式。
这里,同步的意思是用户程序要直接调用 taos_consume
来拉取数据,而异步则由 API 在内部的另一个线程中调用 taos_consume
,然后把拉取到的数据交给回调函数 subscribe_callback
去处理。(,subscribe_callback
中不宜做较为耗时的操作,否则有可能导致客户端阻塞等不可控的问题。)
2.6.2.2 参数解析
-
参数
taos
是一个已经建立好的,在同步模式下无特殊要求。但在异步模式下,需要注意它不会被其它线程使用,否则可能导致不可预计的错误,因为回调函数在 API 的内部线程中被调用,而 TDengine 的部分 API 不是线程安全的。 -
参数
sql
是,可以在其中使用 where 子句指定过滤条件。 -
订阅的
topic
实际上是它的,因为订阅功能是在客户端 API 中实现的,所以没必要保证它全局唯一,但需要它在一台客户端机器上唯一。如果名为
topic
的订阅不存在,参数restart
没有意义;但如果用户程序创建这个订阅后退出,当它再次启动并重新使用这个
topic
时,restart
就会被用于决定是。本例中,如果restart
是 true,用户程序肯定会读到所有数据。但如果这个订阅之前就存在了,并且已经读取了一部分数据,且
restart
是 false,用户程序就不会读到之前已经读取的数据了。 -
参数
taos_subscribe
的最后一个参数是以毫秒为单位的。在同步模式下,如果前后两次调用
taos_consume
的时间间隔小于此时间,taos_consume
会阻塞,直到间隔超过此时间。异步模式下,这个时间是两次调用回调函数的最小时间间隔。 -
参数
taos_subscribe
的倒数第二个参数用于用户程序向回调函数传递附加参数,订阅 API 不对其做任何处理,只原样传递给回调函数。此参数在同步模式下无意义。
2.6.2.3 消费数据
if (async) {
getchar();
} else while(1) {
TAOS_RES* res = taos_consume(tsub);
if (res == NULL) {
printf("failed to consume data.");
break;
} else {
print_result(res, blockFetch);
getchar();
}
}
这里是一个 循环,用户每按一次回车键就调用一次 taos_consume
,而 taos_consume
的返回值是查询到的结果集,与 taos_use_result
完全相同
print_result
函数没看懂
2.6.2.4 测试订阅功能
package com.taosdata.example.test;
import com.taosdata.jdbc.TSDBConnection;
import com.taosdata.jdbc.TSDBDriver;
import com.taosdata.jdbc.TSDBResultSet;
import com.taosdata.jdbc.TSDBSubscribe;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
public class SubscribeDemo {
// topic 是一个订阅的名字,可以自定义,但是需要保证在一台机器上不重复
private static final String topic = "topic-meter-current-bg-10";
// sql 是订阅的条件
private static final String sql = "select * from meters where current > 10";
public static void main(String[] args) {
Connection connection = null;
TSDBSubscribe subscribe = null;
/** * 数据订阅debug记录 * 1、首先创建一个TSDBSubscribe对象 * 2、设置订阅100次,每次订阅间隔1秒,防止频繁回调 * 3、消费数据,将符合sql结果的数据存入TSDBResultSet对象中 * 4、设置回调函数,回调函数中输出订阅结果 * 5、每成功订阅一次就count++,订阅不结束,程序不结束 * 6、当订阅数据获取到100次,则结束订阅 */
try {
// 获取连接对象
Class.forName("com.taosdata.jdbc.TSDBDriver");
Properties properties = new Properties();
properties.setProperty(TSDBDriver.PROPERTY_KEY_CHARSET, "UTF-8");
properties.setProperty(TSDBDriver.PROPERTY_KEY_TIME_ZONE, "UTC-8");
String jdbcUrl = "jdbc:TAOS://192.168.116.201:6030/power?user=root&password=taosdata";
connection = DriverManager.getConnection(jdbcUrl, properties);
// 创建订阅对象
// 但如果用户程序创建这个订阅后退出,当它再次启动并重新使用这个 `topic` 时,
// `restart` 就会被用于决定是从头开始读取数据,还是接续上次的位置进行读取。
// 本例中,如果 `restart` 是 true,用户程序肯定会读到所有数据。
// 我们设置为false,只订阅最新的数据。
subscribe = ((TSDBConnection) connection).subscribe(topic, sql, false);
// 设置订阅次数
int count = 0;
while (count < 100) {
// 订阅100次
// wait 1 second to avoid frequent calls to consume
TimeUnit.SECONDS.sleep(1);
// consume 详细的可debug进去查看消费流程
TSDBResultSet resultSet = subscribe.consume();
if (resultSet == null) {
continue;
}
// 获取结果集的元数据做一个简单的输出
ResultSetMetaData metaData = resultSet.getMetaData();
while (resultSet.next()) {
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
System.out.print(metaData.getColumnLabel(i) + ": " + resultSet.getString(i) + "\t");
}
System.out.println();
count++;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != subscribe)
// close subscribe
subscribe.close(true);
if (connection != null)
connection.close();
} catch (SQLException throwable) {
throwable.printStackTrace();
}
}
}
}
2.6.2.5 问题
REST 连接没有订阅功能,使用原生连接可以体验 TDengine 的全部功能。
但我本机REST连接可以连接到TDengine,但是原生连接失败
数据订阅暂时没实现
问题解决
$ systemctl taos adapter
报错:JNI ERROR (2351): failed to create subscription
去本地配置host,然后再下图:
2.7. 缓存
TDengine 采用时间驱动缓存管理策略(First-In-First-Out,FIFO),又称为写驱动的缓存管理机制,直接将最近写入的数据保存在系统的缓存中。当缓存达到临界值的时候,将最早的数据批量写入磁盘。一般意义上来说,对于物联网数据的使用,用户最为关心最近产生的数据,即当前状态。TDengine 充分利用了这一特性,将最近到达的(当前状态)数据保存在缓存中。
需要注意的是,TDengine 重启以后系统的缓存将被清空,之前缓存的数据均会被批量写入磁盘,缓存的数据将不会像专门的 key-value 缓存系统再将之前缓存的数据重新加载到缓存中。
TDengine 分配固定大小的内存空间作为缓存空间,缓存空间可根据应用的需求和硬件资源配置。通过适当的设置缓存空间,TDengine 可以提供极高性能的写入和查询的支持。
可以通过函数 last_row() 快速获取一张表或一张超级表的最后一条记录,这样很便于在大屏显示各设备的实时状态或采集值。例如:
select last_row(voltage) from meters where location=‘California.SanFrancisco’;
2.8. 用户自定义函数(难)
先C语言定义UDF,(有规定的参数模板)
然后编译,(gcc -g -O0 -fPIC -shared add_one.c -o add_one.so)
再在TDengine中使用:
-
创建:CREATE FUNCTION add_one AS “/home/taos/udf_example/add_one.so” OUTPUTTYPE INT;
-
管理:删除(DROP FUNCTION ids(X);)、查看(SHOW FUNCTIONS;)
-
调用:SELECT X© FROM table/stable;
3. 集群管理
TDengine 支持集群,提供水平扩展的能力。如果需要获得更高的处理能力,只需要多增加节点即可。
TDengine 采用虚拟节点技术,将一个节点虚拟化为多个虚拟节点,以实现负载均衡。同时,TDengine可以将多个节点上的虚拟节点组成虚拟节点组,通过多副本机制,以保证供系统的高可用。
进行了一些实践操作,但卡壳了,后续再学习
3.1 集群部署
3.1.1 准备步骤:
1.配置FQDN,在每个物理节点的hosts中都要进行修改;
2.打开6030-6042的TCP/UDP协议端口;(systemctl stop firewalld)
3.安装TDengine(版本一致),先不启动taosd,第一个物理节点直接回车创建新的,后续的输入任何一个在线的物理节点的FQDN:端口号;
4.检查集群里的各个节点是否能互通;
5.修改taso.cfg,每个节点的配置文件都需要修改,
// firstEp 是每个数据节点首次启动后连接的第一个数据节点 firstEp h1.taosdata.com:6030
// 必须配置为本数据节点的 FQDN,如果本机只有一个 hostname,可注释掉本项 fqdn h1.taosdata.com
// 配置本数据节点的端口号,缺省是 6030 serverPort 6030
// 副本数为偶数的时候,需要配置,请参考《Arbitrator 的使用》的部分 arbitrator ha.taosdata.com:6042
在每个数据节点,firstEp 需全部配置成一样,但 fqdn 一定要配置成其所在数据节点的值
3.1.2 启动集群
在第一个节点里面使用:CREATE DNODE “h2.taos.com:6030”;
3.2 数据节点管理
3.2.1 dnode 、vnode 、mnode三者关系
dnode
:数据节点,是 TDengine 服务器侧执行代码 taosd 在物理节点上的一个,一个工作的系统必须有至少一个数据节点。在系统中的唯一标识由实例的End Point (EP )决定。
vnode
:虚拟节点,,而且查询、计算都在这些节点上进行。为便于负载均衡、数据恢复、支持异构环境,TDengine 将一个物理节点根据其计算和存储资源切分为多个 vnode。这些 vnode 的管理是 TDengine 自动完成的,对应用完全透明。
vgroup
:虚拟节点组,不同数据节点上的 vnode 可以组成一个虚拟节点组(vnode group)来保证系统的高可靠。
mnode
:管理节点,dnode会报告状态(包括硬盘空间、内存大小、C