TDengine使用随记(最后一次更新:2020-10-16 21:54)

==背景==

在搭建工业物联网的数据平台,用来完成设备连接以及数据存储等,

以支持上层的应用及数据分析等。目前使用的是mq→flink→influxdb的基本套路。

虽然说InfluxDB的性能和功能都非常的棒,特别是连续查询对性能的支撑,

不过由于influxdb使用的是社区版,存在单点故障的问题,总之不是长久直接。

于是打算尝试验证一下陶老师的TDEngine。祝自己一切顺利。

==环境==

服务器节点数:3个(VMware虚拟机)

操作系统:Linux Centos8 64位

TDengine版本:2.0.4(2020年10月16日,使用版本换成了2.0.5.1)

JDK:1.8.0_77

==下载==

https://www.taosdata.com/cn/getting-started/

TDengine-server-2.0.4.0-Linux-x64.rpm
TDengine-client-2.0.4.0-Linux-x64.tar.gz
TDengine-alert-2.0.4.0-Linux-x64.tar.gz

  

==集群部署==

参考官网:https://www.taosdata.com/cn/documentation/cluster/

PS:中文文档是真的很爽,比看英文省心多了。

1、安装server

上传到服务器,并复制到各个节点,在各个节点执行rpm安装命令

命令:rpm -ivh TDengine-server-2.0.4.0-Linux-x64.rpm

2、修改配置

配置文件路径:/etc/taos/taos.cfg

配置文件说明:TDengine的运营与维护

按照官网的提示,修改了几个配置:firstEp、secondEp、fqdn、serverPort、logDir、dataDir、replica。

根据官网的说明,我罗列了配置的中文,供参考。没有写的是在这个版本的官网上没有找到的了。

########################################################
#                                                      #
#                  TDengine Configuration              #
#   Any questions, please email support@taosdata.com   #
#                                                      #
########################################################

# first fully qualified domain name (FQDN) for TDengine system
# taosd启动时,主动连接的集群中第一个dnode的end point, 默认值为localhost:6030firstEp               vm1:6030

# second fully qualified domain name (FQDN) for TDengine system, for cluster only
# taosd启动时,如果first连接不上,尝试连接集群中第二个dnode的end point, 默认值为空。
secondEp              vm2:6030

# local fully qualified domain name (FQDN)
# 数据节点的FQDN,缺省为操作系统配置的第一个hostname。如果习惯IP地址访问,可设置为该节点的IP地址。
fqdn                  vm1

# first port number for the connection (12 continuous UDP/TCP port number are used) 
# taosd启动后,对外服务的端口号,默认值为6030。
serverPort            6030

# log file's directory
# 日志文件目录,客户端和服务器的运行日志文件将写入该目录。默认值:/var/log/taos。
logDir                /home/radmin/data/tdengine/log

# data file's directory
# 数据文件目录,所有的数据文件都将写入该目录。默认值:/var/lib/taos。
dataDir               /home/radmin/data/tdengine/data

# the arbitrator's fully qualified domain name (FQDN) for TDengine system, for cluster only   
# 系统中裁决器的end point, 缺省值为空。
# arbitrator            arbitrator_hostname:6042     

# number of threads per CPU core
# numOfThreadsPerCore   1.0

# number of management nodes in the system
# 系统中管理节点个数。默认值:3。
# numOfMnodes           3

# enable/disable backuping vnode directory when removing dnode
# vnodeBak              1

# enable/disable load balancing
# 是否启动负载均衡。0:否,1:是。默认值:1。
# balance               1

# role for dnode. 0 - any, 1 - mnode, 2 - dnode
# dnode的可选角色。0-any; 既可作为mnode,也可分配vnode;1-mgmt;只能作为mnode,不能分配vnode;2-dnode;不能作为mnode,只能分配vnode
# role                  0

# max timer control blocks
# maxTmrCtrl            512

# time interval of system monitor, seconds 
# monitorInterval       30

# number of seconds allowed for a dnode to be offline, for cluster only 
# dnode离线阈值,超过该时间将导致该dnode从集群中删除。单位为秒,默认值:86400*10(即10天)。
# offlineThreshold      8640000

# RPC re-try timer, millisecond
# rpcTimer              300

# RPC maximum time for ack, seconds. 
# rpcMaxTime            600

# time interval of dnode status reporting to mnode, seconds, for cluster only 
# statusInterval        1

# time interval of heart beat from shell to dnode, seconds
# shellActivityTimer    3

# time of keeping table meta data in cache, seconds
# tableMetaKeepTimer    7200

# minimum sliding window time, milli-second
# minSlidingTime        10

# minimum time window, milli-second
# minIntervalTime       10

# maximum delay before launching a stream compution, milli-second
# maxStreamCompDelay    20000

# maximum delay before launching a stream computation for the first time, milli-second
# maxFirstStreamCompDelay   10000

# retry delay when a stream computation fails, milli-second
# retryStreamCompDelay      10

# the delayed time for launching a stream computation, from 0.1(default, 10% of whole computing time window) to 0.9
# streamCompDelayRatio      0.1

# max number of vgroups per db, 0 means configured automatically
# 每个数据库中能够使用的最大vnode个数。
# maxVgroupsPerDb           0

# max number of tables per vnode
# 每个vnode中能够创建的最大表个数。默认值:1000000。
# maxTablesPerVnode         1000000

# step size of increasing table number in a vnode
# tableIncStepPerVnode      1000

# cache block size (Mbyte)
# cache                     16

# number of cache blocks per vnode
# blocks                    6

# number of days per DB file
# 一个数据文件存储数据的时间跨度,单位为天,默认值:10。
# days                  10

# number of days to keep DB file
# 数据库中数据保留的天数,单位为天,默认值:3650。
# keep                  3650

# minimum rows of records in file block
# 文件块中记录的最小条数,单位为条,默认值:100。
# minRows               100

# maximum rows of records in file block
# 文件块中记录的最大条数,单位为条,默认值:4096。
# maxRows               4096

# enable/disable compression
# 文件压缩标志位,0:关闭,1:一阶段压缩,2:两阶段压缩。默认值:2。
# comp                  2

# write ahead log (WAL) level, 0: no wal; 1: write wal, but no fysnc; 2: write wal, and call fsync
# WAL级别。1:写wal, 但不执行fsync; 2:写wal, 而且执行fsync。默认值:1。
# walLevel              1

# if walLevel is set to 2, the cycle of fsync being executed, if set to 0, fsync is called right away
# 当wal设置为2时,执行fsync的周期。设置为0,表示每次写入,立即执行fsync。单位为毫秒,默认值:3000。
# fsync                 3000

# number of replications, for cluster only 
# 副本个数,取值范围:1-3。单位为个,默认值:1
replica               2

# mqtt hostname  
# mqttHostName          test.mosquitto.org

# mqtt port  
# mqttPort              1883

# mqtt topic
# mqttTopic             /test

# the compressed rpc message, option:
#  -1 (no compression)
#   0 (all message compressed),
# > 0 (rpc message body which larger than this value will be compressed)
# compressMsgSize       -1

# max length of an SQL
# 单条SQL语句允许最长限制。默认值:65380字节。
# maxSQLLength          65480

# the maximum number of records allowed for super table time sorting
# maxNumOfOrderedRes    100000

# system time zone
# 默认值:从系统中动态获取当前的时区设置
# timezone              Asia/Shanghai (CST, +0800)

# system locale
# 默认值:系统中动态获取,如果自动获取失败,需要用户在配置文件设置或通过API设置
# locale                en_US.UTF-8

# default system charset
# 默认值:系统中动态获取,如果自动获取失败,需要用户在配置文件设置或通过API设置
# charset               UTF-8

# max number of connections allowed in dnode
# maxShellConns         5000

# max numerber of connections allowed in client 
# maxConnections        5000

# stop writing logs when the disk size of the log folder is less than this value
# minimalLogDirGB       0.1

# stop writing temporary files when the disk size of the log folder is less than this value
# minimalTmpDirGB       0.1

# stop writing data when the disk size of the log folder is less than this value
# minimalDataDirGB      0.1

# enbale/disable http service
# http                  1

# enable/disable muqq service
# mqtt                  0

# enable/disable system monitor 
# monitor               1

# enable/disable recording the SQL statements via restful interface
# httpEnableRecordSql   0

# number of threads used to process http requests
# httpMaxThreads        2

# maximum number of rows returned by the restful interface
# restfulRowLimit       10240

# The following parameter is used to limit the maximum number of lines in log files.
# max number of rows per log filters
# 单个日志文件允许的最大行数。默认值:10,000,000行。
# numOfLogLines         10000000

# time of keeping log files, days
# 日志文件的最长保存时间。大于0时,日志文件会被重命名为taosdlog.xxx,其中xxx为日志文件最后修改的时间戳,单位为秒。默认值:0天。
# logKeepDays           0

# enable/disable async log
# asyncLog              1

# The following parameters are used for debug purpose only.
# debugFlag 8 bits mask: FILE-SCREEN-UNUSED-HeartBeat-DUMP-TRACE_WARN-ERROR
# 131: output warning and error, 135: output debug, warning and error, 143 : output trace, debug, warning and error to log.
# 199: output debug, warning and error to both screen and file
# 207: output trace, debug, warning and error to both screen and file

# debug flag for all log type, take effect when non-zero value
# debugFlag             0

# debug flag for meta management messages
# mDebugFlag            135

# debug flag for dnode messages
# dDebugFlag            135

# debug flag for sync module
# sDebugFlag            135

# debug flag for WAL
# wDebugFlag            135

# debug flag for SDB
# sdbDebugFlag          135

# debug flag for RPC 
# rpcDebugFlag          131

# debug flag for TAOS TIMER
# tmrDebugFlag          131

# debug flag for TDengine client 
# cDebugFlag            131

# debug flag for JNI
# jniDebugflag          131

# debug flag for ODBC
# odbcDebugflag         131

# debug flag for storage
# uDebugflag            131

# debug flag for http server
# httpDebugFlag         131

# debug flag for mqtt
# mqttDebugFlag         131

# debug flag for monitor
# monitorDebugFlag      131

# debug flag for query
# qDebugflag            131

# debug flag for vnode
# vDebugflag            131

# debug flag for http server
# tsdbDebugFlag         131

# enable/disable recording the SQL in taos client
# tscEnableRecordSql    0

# generate core file when service crash
# enableCoreFile        1

# maximum display width of binary and nchar fields in the shell. The parts exceeding this limit will be hidden
# maxBinaryDisplayWidth 30

PS:到此的感受:

总体感觉TDengine的安装非常容易,配置文件也简单易懂,到此体验良好。

==启动==

1、启动第1个节点

命令:systemctl start taosd

2、验证第1个节点是否启动成功

命令:taos

命令:show dnodes;

3、启动后续数据节点

先将后两个节点的服务启动起来。

在每个节点执行:systemctl start taosd

确认服务状态:systemctl status taosd

如下图,第2个节点的服务正常:

第3个节点的服务正常:

【小插曲】

在启动第2个节点的时候报错,

[root@vm2 ~]# systemctl status taosd
● taosd.service - TDengine server service
   Loaded: loaded (/etc/systemd/system/taosd.service; enabled; vendor preset: disabled)
   Active: failed (Result: start-limit) since Tue 2020-09-29 20:38:20 UTC; 2s ago
  Process: 5095 ExecStart=/usr/bin/taosd (code=exited, status=1/FAILURE)
 Main PID: 5095 (code=exited, status=1/FAILURE)

Sep 29 20:38:20 vm2 systemd[1]: taosd.service: main process exited, code=exited, status=1/FAILURE
Sep 29 20:38:20 vm2 systemd[1]: Unit taosd.service entered failed state.
Sep 29 20:38:20 vm2 systemd[1]: taosd.service failed.
Sep 29 20:38:20 vm2 systemd[1]: taosd.service holdoff time over, scheduling restart.
Sep 29 20:38:20 vm2 systemd[1]: Stopped TDengine server service.
Sep 29 20:38:20 vm2 systemd[1]: start request repeated too quickly for taosd.service
Sep 29 20:38:20 vm2 systemd[1]: Failed to start TDengine server service.
Sep 29 20:38:20 vm2 systemd[1]: Unit taosd.service entered failed state.
Sep 29 20:38:20 vm2 systemd[1]: taosd.service failed.

【原因】

没有创建log和data的文件路径,在第2个和第3个节点上分别创建两个路径

mkdir -p /home/radmin/data/tdengine/log

mkdir -p /home/radmin/data/tdengine/data

4、添加节点

在第一个数据节点,使用CLI程序taos, 登录进TDengine系统, 执行命令:

CREATE DNODE "vm2:6030";
CREATE DNODE "vm3:6030";

查看新的节点是否加入进来。

【小插曲】

执行show dnodes;之后,发现vm2这个节点处于offline状态。按照官网提示的方法:

查看日志中提示如下错误:

09/29 20:59:55.055274 0x7fcb0aae7700 DND ERROR status rsp is received, error:Cluster cfg inconsistent
09/29 20:59:56.060418 0x7fcb0aae7700 DND ERROR status rsp is received, error:Cluster cfg inconsistent
09/29 20:59:57.065157 0x7fcb0aae7700 DND ERROR status rsp is received, error:Cluster cfg inconsistent

【原因】

经过群里的咨询,有朋友建议排查一下时区是否一致,经排查第2个节点的时区确实与另外两个不一致。

修改了一下时区之后,从新启动第2个节点,发现状态恢复正常。修改时区命令:

mv /etc/localtime /etc/localtime.bak
ln -s /usr/share/zoneinfo/Asia/Shanghai /etc/locaktime

【小插曲2】

解决了上面的小插曲,非常的开心,合上笔记本电脑回家。

回家之后,重新连接上服务器,查看状态,结果发现第2个节点的状态又变成offline了,真是开心不过3分钟啊。

【原因】

发现时间貌似不同步。看来是时候加上NTP时间同步了。按照以下步骤尝试了一下

1)停掉taos服务

2)配置ntp同步(参考我的另一篇博客:Linux配置ntp时间服务器

3)重启taos服务

重启之后,第2个节点的状态恢复为ready,开森。

为了以防万一,将系统放置了一整夜,第2天早上起来查看集群状态,依然是正常的,这下才算放心。

【感谢道友】

  

5、数据节点管理命令

添加数据节点:CREATE DNODE "fqdn:port"; 
删除数据节点:DROP DNODE "fqdn:port";
查看数据节点:SHOW DNODES;

PS:到此的感受

TDengine的集群部署也非常的简单,比传统的HBase,MongoDB之类的简单的多,感受不错。 

唯一比较大的遗憾是,目前的社区版不支持多级存储,我们实际项目中磁盘是插满的,

如果要利用磁盘空间,就必须要加上lvm,但是lvm会影响读写速度,对已经存在的hdfs也有一些影响。

6、集群卸载

因为是用rpm的格式安装的,目前用的是yum remove来卸载,不清楚是否为最佳卸载方式。

rpm -qa | grep TD

yum remove TDengine-2.0.5.0-3.x86_64

==TAOS SQL==

官网提供了完整的文档,官网地址:https://www.taosdata.com/cn/documentation20/taos-sql/

我个人习惯了逐个尝试验证一遍,以加深印象,好记性不如烂笔头。

另外,可以通过官方提供的样例数据创建一些表供验证。

命令:taosdemo(注意,需要预留大约2.1GB的存储空间)

1、数据库管理

#创建库:
#COMP参数是指修改数据库文件压缩标志位,取值范围为[0, 2]. 0表示不压缩,1表示一阶段压缩,2表示两阶段压缩。
#REPLICA参数是指修改数据库副本数,取值范围[1, 3]。在集群中使用,副本数必须小于dnode的数目。
#KEEP参数是指修改数据文件保存的天数,缺省值为3650,取值范围[days, 365000],必须大于或等于days参数值。
#QUORUM参数是指数据写入成功所需要的确认数。取值范围[1, 3]。对于异步复制,quorum设为1,具有master角色的虚拟节点自己确认即可。对于同步复制,需要至少大于等于2。原则上,Quorum >=1 并且 Quorum <= replica(副本数),这个参数在启动一个同步模块实例时需要提供。
#BLOCKS参数是每个VNODE (TSDB) 中有多少cache大小的内存块,因此一个VNODE的用的内存大小粗略为(cache * blocks)。取值范围[3, 1000]。
#DAYS一个数据文件存储数据的时间跨度,单位为天,默认值:10。
create database mydb keep
365 days 10 blocks 4; #创建库(如果不存在): create database if not exists mydb keep 365 days 10 blocks 4; #使用库: use mydb; #删除库: drop database mydb; #删除库(如果存在): drop database if exists mydb; #显示所有数据库: show databases; #修改数据库文件压缩标志位: alter database mydb comp 2; #修改数据库副本数: alter database mydb replica 2; #修改数据文件保存的天数: alter database mydb keep 365; #修改数据写入成功所需要的确认数: alter database mydb quorum 2; #修改每个VNODE (TSDB) 中有多少cache大小的内存块: alter database mydb blocks 100;

2、表管理

#创建表(搞了个包含所有数据类型的表):
create table if not exists mytable(time timestamp, intfield int, bigintfield bigint, floatfield float, doublefield double, binaryfield binary(20), smallintfield smallint, tinyintfield tinyint, boolfield bool, ncharfiel
d nchar(50));

#删除数据表
drop table if exists mytable;

#显示当前数据库下的所有数据表信息
show tables;

#显示当前数据库下的所有数据表信息
#可在like中使用通配符进行名称的匹配。通配符匹配:1)’%’ (百分号)匹配0到任意个字符;2)’_’下划线匹配一个字符。
show tables like "%my%";

#获取表的结构信息
describe mytable;

#表增加列
alter table mytable add column addfield int;

#表删除列
alter table mytable drop column addfield;

3、超级表管理

#创建超级表
#创建STable, 与创建表的SQL语法相似,但需指定TAGS字段的名称和类型。说明:
#1) TAGS 列的数据类型不能是timestamp类型;
#2) TAGS 列名不能与其他列名相同;
#3) TAGS 列名不能为预留关键字;
#4) TAGS 最多允许128个,可以0个,总长度不超过16k个字符
create table if not exists mysupertable (time timestamp, intfield int, bigintfield bigint, floatfield float, doublefield double, binaryfield binary(20), smallintfield smallint, tinyintfield tinyint, boolfield bool, nch
arfield nchar(50)) TAGS (product nchar(50), device nchar(100));

#删除超级表
drop table if exists mysupertable;

#显示当前数据库下的所有超级表信息
show stables like "%super%";

#获取超级表的结构信息
describe mysupertable;

#超级表增加列
alter table mysupertable add column addfield int;

#超级表删除列
alter table mysupertable drop column addfield;

#添加标签
alter table mysupertable add tag devicetype nchar(60);

#删除标签
alter table mysupertable drop tag devicetype;

#修改标签名
alter table mysupertable change tag product productKey;

#修改子表标签值
#说明:除了更新标签的值的操作是针对子表进行,其他所有的标签操作(添加标签、删除标签等)均只能作用于STable,不能对单个子表操作。对STable添加标签以后,依托于该STable建立的所有表将自动增加了一个标签,所有新增标签的默认值都是NULL。
alter table mysupertable set tag productkey="abc";

【小插曲】

执行了一个下面的创建超级表的语句(通过自己写的程序生成的),结果报错

SQL语句:create table if not exists AI_PICK_UDATA ( time timestamp, data double ) tags ( productKey binary(256),deviceName binary(256),pointId binary(256),name binary(256),dataType binary(256),min binary(256),max binary(256),step binary(256),unit binary(256),description binary(256) );

提示错误:DB error: invalid SQL: invalid tag name

【问题原因】

创建表的语句中有tdengine的保留字段,如min,max。

但是官方文档上并没有对此进行相关介绍,吐槽一下文档。

4、数据插入

#插入一条数据
insert into mytable values(now, 1, 2, 3, 4, 0, 6, 7, 1, "s");

#插入一条记录,数据对应到指定的列
insert into mytable(time, intfield, bigintfield, floatfield, doublefield, binaryfield, smallintfield, tinyintfield, boolfield, ncharfield) values(now, 1, 2, 3, 4, 0, 6, 7, 1, "s");

#插入多条记录
insert into mytable values(now, 1, 2, 3, 4, 0, 6, 7, 1, "s") (now, 2, 3, 4, 5, 6, 7, 8, 0, "t");

#按指定的列插入多条记录
insert into mytable(time, intfield, bigintfield, floatfield, doublefield, binaryfield, smallintfield, tinyintfield, boolfield, ncharfield) values(now, 1, 2, 3, 4, 0, 6, 7, 1, "s") (now, 2, 3, 4, 5, 6, 7, 8, 0, "t");

#向多个表插入多条记录(本人没有验证此语句)
INSERT INTO tb1_name VALUES (field1_value1, ...)(field1_value2, ...)  tb2_name VALUES (field1_value1, ...)(field1_value2, ...);

#同时向多个表按列插入多条记录(本人没有验证此语句)
INSERT INTO tb1_name (tb1_field1_name, ...) VALUES (field1_value1, ...) (field1_value2, ...) tb2_name (tb2_field1_name, ...) VALUES (field1_value1, ...) (field1_value2, ...);

【小插入】

插入多条记录的时候,语句中写的是两条数据,实际上只插入了一条

插入语句:

insert into mytable(time, intfield, bigintfield, floatfield, doublefield, binaryfield, smallintfield, tinyintfield, boolfield, ncharfield) values(now, 1, 2, 3, 4, 0, 6, 7, 1, "s") (now, 2, 3, 4, 5, 6, 7, 8, 0, "t");

【原因】

需要使用不同的时间戳,如果两条语句都使用now,时间戳一样,最终只能插入一条。

【感谢道友】

5、数据查询

查询语法:

SELECT select_expr [, select_expr ...]
    FROM {tb_name_list}
    [WHERE where_condition]
    [INTERVAL (interval_val [, interval_offset])]
    [FILL fill_val]
    [SLIDING fill_val]
    [GROUP BY col_list]
    [ORDER BY col_list { DESC | ASC }]    
    [SLIMIT limit_val [, SOFFSET offset_val]]
    [LIMIT limit_val [, OFFSET offset_val]]
    [>> export_file]

==查询语句==

查询语法:

SELECT select_expr [, select_expr ...]
    FROM {tb_name_list}
    [WHERE where_condition]
    [INTERVAL (interval_val [, interval_offset])]
    [FILL fill_val]
    [SLIDING fill_val]
    [GROUP BY col_list]
    [ORDER BY col_list { DESC | ASC }]    
    [SLIMIT limit_val [, SOFFSET offset_val]]
    [LIMIT limit_val [, OFFSET offset_val]]
    [>> export_file]

常用查询语句样例:

#查询表中的所有字段
select * from t_znsllj001;
#按照时间戳查询表中的所有字段
select * from t_znsllj001 where time > "2020-10-10 22:23:08.728";
#按照时间戳查询超级表中的所有字段
select * from st_znsllj where time > "2020-10-10 22:23:08.728";
#查询超级表中的指定字段
select time, forwardintegratedflow, product from st_znsllj;
#按照标签值查询超级表中的指定字段
select time, forwardintegratedflow, product from st_znsllj where product = "product1";
#查询结果按照时间倒序排序
select time, forwardintegratedflow, product from st_znsllj where product = "product1" order by time desc;
#结果集列名重命名
select time, forwardintegratedflow as ff, product from st_znsllj;
#查询超级表数据并附带表名(TBNAME: 在超级表查询中可视为一个特殊的标签,代表查询涉及的子表名,不区分大小写)
select tbname, * from st_znsllj;
#查询超级表的表名及第一列
 select tbname, _c0 from st_znsllj;

#获取当前所在的数据库
select database();
#获取客户端版本号
select client_version()
#获取服务器版本号
select server_version();
#服务器状态检测语句
select server_status()

#统计超级表下辖子表数量
select count(tbname) from st_znsllj;

==用户管理==

#创建用户,并指定用户名和密码,密码需要用单引号引起来,单引号为英文半角
create user admin pass 'admin123';

#删除用户,限root用户使用
drop user admin;

#修改用户密码, 为避免被转换为小写,密码需要用单引号引用,单引号为英文半角
alter user admin pass 'admin1234';

#修改用户权限为:super/write/read。 为避免被转换为小写,密码需要用单引号引用,单引号为英文半角
#语法:ALTER USER <user_name> PRIVILEGE <super|write|read>;
alter user admin privilege read;

==Java连接==

官网地址:https://www.taosdata.com/cn/documentation/connector-java/

1、maven配置

<dependency>
  <groupId>com.taosdata.jdbc</groupId>
  <artifactId>taos-jdbcdriver</artifactId>
  <version>2.0.4</version>
</dependency>

2、JDBC连接样例

【TdUtils.java】

单例工具类,实现创建连接等通用方法。

package com.rexel.tdengine.utils;

import com.taosdata.jdbc.TSDBDriver;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

/**
 * @ClassName TdUtils
 * @Description TDengine共通类
 * @Author: chunhui.qu
 * @Date: 2020/9/30
 */
public class TdUtils {
    private Connection connection = null;

    /**
     * 构造函数
     */
    private TdUtils() {
        // do nothing
    }

    /**
     * 单例模式
     */
    private static class SingletonInstance {
        private static final TdUtils INSTANCE = new TdUtils();
    }

    /**
     * 获取对象句柄
     */
    public static TdUtils getInstance() {
        return SingletonInstance.INSTANCE;
    }

    public Connection getConnection() {
        if (connection != null) {
            return connection;
        }

        try {
            Class.forName("com.taosdata.jdbc.TSDBDriver");
            String jdbcUrl = "jdbc:TAOS://rexel-ids001:6030/qch_test?user=root&password=taosdata";
            Properties connProps = new Properties();
            connProps.setProperty(TSDBDriver.PROPERTY_KEY_USER, "root");
            connProps.setProperty(TSDBDriver.PROPERTY_KEY_PASSWORD, "taosdata");
            connProps.setProperty(TSDBDriver.PROPERTY_KEY_CONFIG_DIR, "C:\TDengine\cfg");
            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, "Asia/Shanghai");
            connection = DriverManager.getConnection(jdbcUrl, connProps);
        } catch (ClassNotFoundException | SQLException e) {
            e.printStackTrace();
        }

        return connection;
    }
}

【CreateDatabase.java】

创建数据的样例程序。

package com.rexel.tdengine.api;

import com.rexel.tdengine.utils.TdUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;

/**
 * @ClassName CreateDatabase
 * @Description CreateDatabase
 * @Author: chunhui.qu
 * @Date: 2020/9/30
 */
public class CreateDatabase {
    public static void main(String[] args) throws SQLException {
        TdUtils tdUtils = TdUtils.getInstance();
        Connection conn = tdUtils.getConnection();
        if (conn == null) {
            return;
        }
        System.out.println("get connection");

        Statement stmt = conn.createStatement();
        if (stmt == null) {
            return;
        }
        stmt.executeUpdate("create database if not exists javatestdb");
        System.out.println("create database");
        stmt.executeUpdate("use javatestdb");
        System.out.println("use database");
        stmt.executeUpdate("create table if not exists javatesttable (ts timestamp, temperature int, humidity float)");
        System.out.println("create table");
    }
}

【小插曲1】

写完了测试代码,在执行的时候报错:

Exception in thread "main" java.lang.UnsatisfiedLinkError: no taos in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1122)
    at com.taosdata.jdbc.TSDBJNIConnector.<clinit>(TSDBJNIConnector.java:25)
    at com.taosdata.jdbc.TSDBDriver.connect(TSDBDriver.java:133)
    at java.sql.DriverManager.getConnection(DriverManager.java:664)
    at java.sql.DriverManager.getConnection(DriverManager.java:208)
    at com.rexel.tdengine.utils.TdUtils.getConnection(TdUtils.java:54)
    at com.rexel.tdengine.api.CreateDatabase.main(CreateDatabase.java:17)
Disconnected from the target VM, address: '127.0.0.1:54954', transport: 'socket'

【解决过程】

重新看了一下官方文档,怀疑是没有安装Window客户端,尝试安装客户端程序。

安装文件:TDengine-client-2.0.4.0-Windows-x64.exe

重新执行程序之后,报了另一个错误:

java.sql.SQLException: TDengine Error: Invalid timestamp
    at com.taosdata.jdbc.TSDBJNIConnector.connect(TSDBJNIConnector.java:100)
    at com.taosdata.jdbc.TSDBConnection.connect(TSDBConnection.java:64)
    at com.taosdata.jdbc.TSDBConnection.<init>(TSDBConnection.java:56)
    at com.taosdata.jdbc.TSDBDriver.connect(TSDBDriver.java:135)
    at java.sql.DriverManager.getConnection(DriverManager.java:664)
    at java.sql.DriverManager.getConnection(DriverManager.java:208)
    at com.rexel.tdengine.utils.TdUtils.getConnection(TdUtils.java:54)
    at com.rexel.tdengine.api.CreateDatabase.main(CreateDatabase.java:17)

网友说可能是因为客户端的时间与服务器时间相差比较多的原因,

于是调整了服务器的时间。命令如下,调整之后,重启了一下服务器

修改系统时间:date --set "09/30/20 14:15"
系统时间向硬件同步:clock --show

重新运行程序,正确执行。感谢网友的帮助。

【感谢道友】

【小插曲2】

本来运行的好好的,debug也都没有问题,但是下午去酒店再debug测试,就提示“Unable to establish connection”

按照官网的FAQ提示,一顿检查,没有发现什么问题,非常迷惑。

后来想到由于服务器用的是阿里云ECS,需要配置网络安全组的IP地址白名单,

我配置的时候只配置了TCP,没有配置UDP,配置上了UCP的权限之后,debug就恢复正常了。

3、插入数据样例

我这里写了一个JSON字符串转换为SQL,并持续插入的样例。

package com.rexel.tdengine.api;

import com.alibaba.fastjson.JSONObject;
import com.rexel.tdengine.utils.TdUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;

/**
 * @ClassName CreateDatabase
 * @Description CreateDatabase
 * @Author: chunhui.qu
 * @Date: 2020/10/12
 */
public class InsertJson {
    public static void main(String[] args) throws SQLException {
        TdUtils tdUtils = TdUtils.getInstance();
        Connection conn = tdUtils.getConnection();
        if (conn == null) {
            return;
        }
        System.out.println("get connection");

        Statement stmt = conn.createStatement();
        if (stmt == null) {
            return;
        }

        List<String> aiList = new ArrayList<String>(){{
            add("AI_PICK_UDATA");
            add("AI_PROD_NPER");
            add("AI_PROD_NPICK");
            add("AI_PRODUCT_EFF");
            add("AI_PRODUCT_QUANTITY");
            add("AI_PRODUCT_TIME_MIN");
            add("AI_PROD_NPRODING");
        }};
        List<String> diList = new ArrayList<String>(){{
            add("DI_BELOWCLOSTS");
            add("DI_BELOWOPNSTS");
            add("DI_CANDY_OUTBEELINEA");
            add("DI_CANDY_OUTBEELINEB");
            add("DI_CANDY_OUTBEELINEC");
            add("DI_CHUCKCLOSTS");
            add("DI_CHUCKOPNSTS");
            add("DI_COVERCLOSTS");
            add("DI_COVEROPNSTS");
            add("DI_CTNCLO_BSTART");
            add("DI_CTNOPN_BSTART");
            add("DI_DENSO_BAUTOST");
            add("DI_DENSO_BERROR");
            add("DI_DENSO_BMOTORONST");
            add("DI_DENSO_BREADY");
            add("DI_DENSO_BRUNNING");
            add("DI_EPSON_BERROR");
            add("DI_EPSON_BESTOPON");
            add("DI_EPSON_BMOTORONST");
            add("DI_EPSON_BREADY");
            add("DI_EPSON_BRUNNING");
            add("DI_LEFTPUSHCLOSTS");
            add("DI_LEFTPUSHOPNSTS");
            add("DI_PRINT_BRUNNING");
            add("DI_RIGHTCLOSTS");
            add("DI_RIGHTOPNSTS");
            add("DI_RIGHTPUSHCLOSTS");
            add("DI_RIGHTPUSHLOPN");
            add("DI_RLCLOSTS");
            add("DI_RLOPNSTS");
            add("DI_ROBOT1_BLINKSTS");
            add("DI_ROBOT1_BSTART");
            add("DI_ROBOT2_BLINKSTS");
            add("DI_ROBOT2TOSTOR");
            add("DI_ROBOT2TOWAI");
            add("DI_SYS_BRUNNING");
            add("DI_TOPCLOSTS");
            add("DI_TOPOPNSTS");
            add("DI_UPDOWNCLOSTS");
            add("DI_UPDOWNOPNSTS");
            add("DI_BBELTSENSOROVT");
            add("DI_BCTNCLOCOOPNOVT");
            add("DI_BCTNCLORCLOOVT");
            add("DI_BCTNCLOROPNOVT");
            add("DI_BCTNCLOTCLOOVT");
            add("DI_BCTNCLOTOPNOVT");
            add("DI_BCTNOVT");
            add("DI_BELOWVALCLOOVT");
            add("DI_BELOWVALOPNOVT");
            add("DI_BROBOT2VALCLOVT");
            add("DI_BROBOT2VALOPOVT");
            add("DI_BRTSUCKEROVT");
            add("DI_BVALCOVERCLOOVT");
            add("DI_CHUCJVALCLOOVT");
            add("DI_CHUCJVALOPOVT");
            add("DI_CHUCKVALOPNOVT");
            add("DI_DENSOBERROR");
            add("DI_EPSONBERROR");
            add("DI_INSENCTNALARM");
            add("DI_LEFTNEWVALCLOVT");
            add("DI_RLNEWOPVALOPOVT");
            add("DI_RLNEWVALCLOOVT");
            add("DI_UPDOWNNVALCLOVT");
            add("DI_UPDOWNNVALOPOVT");
        }};

        int count = 0;
        while(true) {
            Random intRandom = new Random();
            JSONObject demoJson = new JSONObject();
            demoJson.put("table", "device_data_up");
            demoJson.put("time", System.currentTimeMillis());
            JSONObject tagJson = new JSONObject();
            tagJson.put("productKey", "a1B6t6ZG6oR");
            tagJson.put("deviceName", "QCHTestDevice1");
            demoJson.put("tags", tagJson);
            JSONObject dataJson = new JSONObject();
            aiList.forEach(field -> dataJson.put(field, intRandom.nextInt(10000)));
            diList.forEach(field -> dataJson.put(field, intRandom.nextInt(2)));
            demoJson.put("datas", dataJson);

            String sql = jsonToSql(demoJson);

            stmt.executeUpdate(sql);
            count ++;
            System.out.println("executeUpdate. count=" + count + ", sql=" + sql);

            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private static String jsonToSql(JSONObject jsonObject) {
        String sql = "insert into {table} ({fields}) values ({values});";
        sql = sql.replace("{table}", getTable(jsonObject));
        sql = sql.replace("{fields}", getFields(jsonObject));
        return sql.replace("{values}", getValues(jsonObject));
    }

    private static String getTable(JSONObject jsonObject) {
        return "t_" + jsonObject.getString("table");
    }

    private static String getTime(JSONObject jsonObject) {
        if (jsonObject.containsKey("time")) {
            return timeLongToStr(jsonObject.getLong("time"));
        } else {
            return timeLongToStr(System.currentTimeMillis());
        }
    }

    private static String getFields(JSONObject jsonObject) {
        JSONObject dataJson = jsonObject.getJSONObject("datas");
        StringBuilder sb = new StringBuilder();
        sb.append("time").append(",");
        dataJson.forEach((key, value) -> sb.append(key).append(","));
        return sb.substring(0, sb.length() - 1);
    }

    private static String getValues(JSONObject jsonObject) {
        JSONObject dataJson = jsonObject.getJSONObject("datas");
        StringBuilder sb = new StringBuilder();
        sb.append(""").append(getTime(jsonObject)).append(""").append(",");
        dataJson.forEach((key, value) -> sb.append(value).append(","));
        return sb.substring(0, sb.length() - 1);
    }

    private static String timeLongToStr(long time) {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
        return sdf.format(new Date(time));
    }
}

【小插曲】

测试过程中,发现了一个问题,详细请参考issue:https://github.com/taosdata/TDengine/issues/3818

==数据模型==

官网地址:https://www.taosdata.com/cn/documentation/architecture/#%E6%95%B0%E6%8D%AE%E6%A8%A1%E5%9E%8B 

官网上面写了TD的数据模型,不过没有图例进行直观的说明,所以介绍的多少有些抽象。希望文档上可以提升一下逼格。

【物理量】

也叫“测点”、“点位””。就像做心电图时,粘在身上的几个吸盘。一吸盘就是一个测点,也就是一个物理量。

我这里以智能水流量计为例,包括以下测点。

【数据采集点】

每个数据采集点可以采集多个物理量。

我觉得实际场景中的PLC应该算是一个数据采集点了。

【表】

一个数据采集点一张表。

【超级表】

同一类型数据采集点的集合。

我这里做了一写想到哪里就试到哪里的尝试。

1、先创建一个超级表

create table if not exists znsllj (time timestamp, ForwardIntegratedFlow double, BackwardIntegratedFlow double, InstantaneousFlow double, Velocity double, Density double, Temperature double, Pressure double, Error bool) tags(product nchar(256), device nchar(256));

2、基于超级表创建表

create table znsllj001 using znsllj tags("product1", "device1");

3、向表中插入一条数据

insert into znsllj001(time, ForwardIntegratedFlow, BackwardIntegratedFlow, InstantaneousFlow, Velocity, Density, Temperature, Pressure, Error) values(now, 10, 11, 12, 13, 14, 15, 16, 0);

4、如果修改表结构可以吗?

alter table znsllj001 add column AddField double;

发现直接修改表的结构是失败的,提示只能修改超级表

5、尝试修改超级表结构

alter table znsllj add column AddField double;

发现修改成功,并且表的结构也跟着变化了。

6、分别查看一下表和超级表的结构

describe znsllj001;

describe znsllj;

7、删除超级表中一个列(有数据的列)

alter table znsllj drop column ForwardIntegratedFlow;

看到的是可以成功删除,同时表结构也发生了改变。

8、此时如果删除超级表会如何?

drop table if exists znsllj;

看到可以正常删除超级表,而且表也被连带着一起删掉了。

9、重新创建超级表、表及插入一条数据(修改了一下超级表和表的的前缀,便于区分)

create table if not exists st_znsllj (time timestamp, ForwardIntegratedFlow double, BackwardIntegratedFlow double, InstantaneousFlow double, Velocity double, Density double, Temperature double, Pressure double, Error bool) tags (product nchar(256), device nchar(256));

create table t_znsllj001 using st_znsllj tags("product1", "device1");

insert into t_znsllj001(time, ForwardIntegratedFlow, BackwardIntegratedFlow, InstantaneousFlow, Velocity, Density, Temperature, Pressure, Error) values(now, 10, 11, 12, 13, 14, 15, 16, 0);

10、继续创建几个表(同一个超级表)

两个标签值重复:create table t_znsllj002 using st_znsllj tags("product1", "device1");

标签值都不重复:create table t_znsllj003 using st_znsllj tags("product3", "device3");

少一个标签:create table t_znsllj004 using st_znsllj tags("product4");

无标签:create table t_znsllj005 using st_znsllj;

多一个标签:create table t_znsllj006 using st_znsllj tags("product6", "device6", "type6");

一个标签值重复:create table t_znsllj007 using st_znsllj tags("product1", "device7");

结论如下:

表的标签数量必须与超级表一致。

表的标签值可以重复。

11、分别向几个成功的表中插入一条数据

insert into t_znsllj001(time, ForwardIntegratedFlow, BackwardIntegratedFlow, InstantaneousFlow, Velocity, Density, Temperature, Pressure, Error) values(now, 10, 11, 12, 13, 14, 15, 16, 0);

insert into t_znsllj002(time, ForwardIntegratedFlow, BackwardIntegratedFlow, InstantaneousFlow, Velocity, Density, Temperature, Pressure, Error) values(now, 20, 21, 22, 23, 24, 25, 26, 0);

insert into t_znsllj003(time, ForwardIntegratedFlow, BackwardIntegratedFlow, InstantaneousFlow, Velocity, Density, Temperature, Pressure, Error) values(now, 30, 31, 32, 33, 34, 35, 36, 0);

insert into t_znsllj007(time, ForwardIntegratedFlow, BackwardIntegratedFlow, InstantaneousFlow, Velocity, Density, Temperature, Pressure, Error) values(now, 70, 71, 72, 73, 74, 75, 76, 0);

12、查看一下几个表的数据(仅时间戳,正向累计流量)

select time, forwardintegratedflow from t_znsllj001 order by time;

select time, forwardintegratedflow from t_znsllj002 order by time;

select time, forwardintegratedflow from t_znsllj003 order by time;

select time, forwardintegratedflow from t_znsllj007 order by time;

13、检索超级表的所有数据(时间戳,一个物理量(正向累计流量)、两个TAG(产品、设备))

select time, forwardintegratedflow, product, device from st_znsllj order by time;

可以看到,通过超级表的结果会带有标签,普通表是么有标签的。另外超级表只看标签值,结果集中看不到具体是从哪个表中数据。

==聚合语句==

根据官方文档进行了一堆验证,感觉这些函数还不错。但是在大量数据的情况下,结果是否正确没有验证过。仅进行了功能上的验证,基本可以代替InfluxDB。

另外,对于“适用于”这一列,实际上与官方文档有出入。至少在这个版本,我的验证结果如下。

Excel版本的验证结果,我共享到百度网盘了,有需要的自提。

链接:https://pan.baidu.com/s/1eSHLbvfO08SBRXnnXjmfFw 

提取码:971s 

==滚动升级测试==

目前版本的td尚属于更新迭代很快的阶段,所以难免会有一些bug发生,所以在实际使用中,会频繁的进行版本升级。

这一点也会影响td是否能够顺利落地,以及长久使用的一个考量指标。我按照以下的方式进行了一次尝试。

【验证思路】

因为目前集群版release的就这一个版本,所以没有办法测试版本升级,只能测试卸载之后,在重新安装。

【验证结论】

卸载并重新安装之后,集群可以正常使用。

【验证过程】

1、先备份配置文件

分别将3个节点的配置文件下载并备份。

2、停止td集群

在3个节点分别执行命令:systemctl stop taosd

3、卸载tdengine

在3个节点分别执行命令:

#查看tdengine
rpm -qa | grep TDengine
#卸载tdengine
yum remove TDengine-2.0.4.0-3.x86_64

4、查看卸载后的状态

配置文件保留,没有被删除。

数据文件保留,没有被删除。

5、重新安装集群

在3个节点分别执行:rpm -ivh /home/radmin/soft/TDengine-server-2.0.4.0-Linux-x64.rpm

6、观察配置文件

配置文件没有被覆盖,保留了卸载之前的配置,这样挺好的,省着重新配置了。

7、启动集群

在3个节点分别执行命令:systemctl start taosd

8、进入命令行

在任意节点运行taos,确认可以进入命令行。

9、查看集群状态

show dnodes;

查看数据库show databases;

切换数据库,并查看超级表

查询任意表

2020年10月12日 13:38 追记

看到官方刚刚发布了2.0.5.0版本,按照下面的顺序重新尝试了一遍,结果一样,顺利升级成功。

2020年10月12日 14:00 追记

集群使用了一会之后,发现第2个节点宕掉了,

没有办法,由于着急测试,将tdengine卸载,然后删除数据目录,重新安装之后才得以解决。

2020年10月12日 19:37 追记

服务器版本升级到2.0.5.0之后,taos-jdbcdriver的版本需要使用2.0.8,使用2.0.5的时候会报错。感谢网友

==连续查询==

在InfluxDB中有连续查询的功能,可以按照一定的时间频率对原始数据进行聚合,并将聚合结果放入到新的数据库中,

具体可以参考我InfluxDB连续查询的另一篇博客:使用InfluxDB的连续查询解决聚合性能问题

同样,在TDengine中也提供了连续查询的功能,我这里需要验证一下TDengine的连续查询与InfluxDB的区别,

用来评估替换InfluxDB之后,对应用端的代码改动的影响大小。

【语法尝试】

1、创建连续查询

create table avg_velocity as select avg(velocity) from st_znsllj interval(1m) sliding(30s);

在定义连续查询的时候需要指定时间窗口大小(time window, 参数interval)和每次前向增量时间(forward sliding times, 参数sliding)。

2、查看连续查询

show streams;

3、杀掉指定连续查询

kill stream 3:1;

【小插曲】

在超级表的查询语句中加上sliding之后,提示Redirect错误。Issue为:https://github.com/taosdata/TDengine/issues/3801

4、尝试将连续查询的结果放入另一个库

需要在查询的表以及目标表上加上数据的名字即可。

create table qch_cq.device_data_up_sum as select sum(AI_PICK_UDATA) from qch_test.st_device_data_up interval(1m) sliding(30s) order by time desc;

【小插曲】

配置上连续查询,并且程序段在一直的写入数据,发现了几个奇怪的现象

现象1:streamId会一直变化

==性能测试==

我写了一个单列模式的测试样例,

代码放在github上了:https://github.com/quchunhui/demo-macket/tree/master/tdengine

1、单列模式

2、自动建表

3、10万个测点(10个表)

4、1个超级表

如果在本地运行环境中跑,由于服务器在阿里云上,受网络的影响,速度时非常的慢的,

10万点的数据,一共跑了3次,每次都差不多要1个小时才能跑完(一共3个小时)

如果把程序打成jar包,扔到阿里云ECS服务器上跑,就快了太多太多了,

一样的程序,每次10万,一共3次,每次差多也就2分钟就可以插入完成。

(但是,感觉还是有一些慢,等找到一些优化的方法,在继续更新)

==稳定性测试==

==可靠性测试==

==实际场景使用思路==

--END--

原文地址:https://www.cnblogs.com/quchunhui/p/13731825.html