docs: refactor doc

This commit is contained in:
gccgdb1234 2024-07-25 15:41:18 +08:00
parent d72bf63612
commit 321a3f0f0b
226 changed files with 1416 additions and 3444 deletions

View File

@ -73,3 +73,77 @@ TDengine 经过特别优化,以适应时间序列数据的独特需求,引
3. 简化系统架构带来的成本降低作为一个极简的时序数据平台TDengine 集成了消息队列、缓存、流计算等必要功能,避免了额外集成众多其他组件的需要。这
种简化的系统架构显著降低了系统的复杂度,从而减少了研发和运营成本,提高了整体运营效率。
## 技术生态
在整个时序大数据平台中TDengine 扮演的角色如下:
<figure>
![TDengine Database 技术生态图](eco_system.webp)
<center><figcaption>图 1. TDengine 技术生态图</figcaption></center>
</figure>
上图中,左侧是各种数据采集或消息队列,包括 OPC-UA、MQTT、Telegraf、也包括 Kafka他们的数据将被源源不断的写入到 TDengine。右侧则是可视化、BI 工具、组态软件、应用程序。下侧则是 TDengine 自身提供的命令行程序CLI以及可视化管理工具。
## 典型适用场景
作为一个高性能、分布式、支持 SQL 的时序数据库Time-series DatabaseTDengine 的典型适用场景包括但不限于 IoT、工业互联网、车联网、IT 运维、能源、金融证券等领域。需要指出的是TDengine 是针对时序数据场景设计的专用数据库和专用大数据处理工具因其充分利用了时序大数据的特点它无法用来处理网络爬虫、微博、微信、电商、ERP、CRM 等通用型数据。下面本文将对适用场景做更多详细的分析。
### 数据源特点和需求
从数据源角度,设计人员可以从下面几个角度分析 TDengine 在目标应用系统里面的适用性。
| 数据源特点和需求 | 不适用 | 可能适用 | 非常适用 | 简单说明 |
| ---------------------------- | ------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| 总体数据量巨大 | | | √ | TDengine 在容量方面提供出色的水平扩展功能,并且具备匹配高压缩的存储结构,达到业界最优的存储效率。 |
| 数据输入速度偶尔或者持续巨大 | | | √ | TDengine 的性能大大超过同类产品,可以在同样的硬件环境下持续处理大量的输入数据,并且提供很容易在用户环境里面运行的性能评估工具。 |
| 数据源数目巨大 | | | √ | TDengine 设计中包含专门针对大量数据源的优化,包括数据的写入和查询,尤其适合高效处理海量(千万或者更多量级)的数据源。 |
### 系统架构要求
| 系统架构要求 | 不适用 | 可能适用 | 非常适用 | 简单说明 |
| ---------------------- | ------ | -------- | -------- | ----------------------------------------------------------------------------------------------------- |
| 要求简单可靠的系统架构 | | | √ | TDengine 的系统架构非常简单可靠,自带消息队列,缓存,流式计算,监控等功能,无需集成额外的第三方产品。 |
| 要求容错和高可靠 | | | √ | TDengine 的集群功能,自动提供容错灾备等高可靠功能。 |
| 标准化规范 | | | √ | TDengine 使用标准的 SQL 语言提供主要功能,遵守标准化规范。 |
### 系统功能需求
| 系统功能需求 | 不适用 | 可能适用 | 非常适用 | 简单说明 |
| -------------------------- | ------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------- |
| 要求完整的内置数据处理算法 | | √ | | TDengine 实现了通用的数据处理算法,但是还没有做到妥善处理各行各业的所有需求,因此特殊类型的处理需求还需要在应用层面解决。 |
| 需要大量的交叉查询处理 | | √ | | 这种类型的处理更多应该用关系型数据库处理,或者应该考虑 TDengine 和关系型数据库配合实现系统功能。 |
### 系统性能需求
| 系统性能需求 | 不适用 | 可能适用 | 非常适用 | 简单说明 |
| ---------------------- | ------ | -------- | -------- | -------------------------------------------------------------------------------------------------- |
| 要求较大的总体处理能力 | | | √ | TDengine 的集群功能可以轻松地让多服务器配合达成处理能力的提升。 |
| 要求高速处理数据 | | | √ | TDengine 专门为 IoT 优化的存储和数据处理设计,一般可以让系统得到超出同类产品多倍数的处理速度提升。 |
| 要求快速处理小粒度数据 | | | √ | 这方面 TDengine 性能可以完全对标关系型和 NoSQL 型数据处理系统。 |
### 系统维护需求
| 系统维护需求 | 不适用 | 可能适用 | 非常适用 | 简单说明 |
| ---------------------- | ------ | -------- | -------- | --------------------------------------------------------------------------------------------------------------------- |
| 要求系统可靠运行 | | | √ | TDengine 的系统架构非常稳定可靠,日常维护也简单便捷,对维护人员的要求简洁明了,最大程度上杜绝人为错误和事故。 |
| 要求运维学习成本可控 | | | √ | 同上。 |
| 要求市场有大量人才储备 | √ | | | TDengine 作为新一代产品,目前人才市场里面有经验的人员还有限。但是学习成本低,我们作为厂家也提供运维的培训和辅助服务。 |
## 与其他数据库的对比测试
- [用 InfluxDB 开源的性能测试工具对比 InfluxDB 和 TDengine](https://www.taosdata.com/blog/2020/01/13/1105.html)
- [TDengine 与 OpenTSDB 对比测试](https://www.taosdata.com/blog/2019/08/21/621.html)
- [TDengine 与 Cassandra 对比测试](https://www.taosdata.com/blog/2019/08/14/573.html)
- [TDengine VS InfluxDB ,写入性能大 PK ](https://www.taosdata.com/2021/11/05/3248.html)
- [TDengine 和 InfluxDB 查询性能对比测试报告](https://www.taosdata.com/2022/02/22/5969.html)
- [TDengine 与 InfluxDB、OpenTSDB、Cassandra、MySQL、ClickHouse 等数据库的对比测试报告](https://www.taosdata.com/downloads/TDengine_Testing_Report_cn.pdf)
## 主要产品
TDengine 有两个主要产品TDengine Enterprise (即 TDengine 企业版)和 TDengine Cloud关于它们的具体定义请参考
- [TDengine 企业版](https://www.taosdata.com/tdengine-pro)
- [TDengine 云服务](https://cloud.taosdata.com/?utm_source=menu&utm_medium=webcn)

View File

@ -17,6 +17,8 @@ TDengine 的安装包含括服务端taosd、应用驱动taosc、用
## Linux 系统
## 安装步骤
访问 TDengine 的官方版本发布页面https://docs.taosdata.com/releases/tdengine/ ,下载 TDengine 安装包TDengine-server-3.3.0.0-Linux-x64.tar.gz 。其他类型安装包的安装方法请参考相关文档TDengine 遵循各种安装包的标准。
1. 进入到安装包所在目录,使用 tar 解压安装包
@ -64,6 +66,34 @@ Active: inactive (dead)
- 如果操作系统不支持 systemctl可以通过手动运行 /usr/local/taos/bin/taosd 命令来启动 TDengine 服务。
### 目录结构
安装 TDengine 后,默认会在操作系统中生成下列目录或文件:
| 目录/文件 | 说明 |
| ------------------------- | -------------------------------------------------------------------- |
| /usr/local/taos/bin | TDengine 可执行文件目录。其中的执行文件都会软链接到/usr/bin 目录下。 |
| /usr/local/taos/driver | TDengine 动态链接库目录。会软链接到/usr/lib 目录下。 |
| /usr/local/taos/examples | TDengine 各种语言应用示例目录。 |
| /usr/local/taos/include | TDengine 对外提供的 C 语言接口的头文件。 |
| /etc/taos/taos.cfg | TDengine 默认[配置文件] |
| /var/lib/taos | TDengine 默认数据文件目录。可通过[配置文件]修改位置。 |
| /var/log/taos | TDengine 默认日志文件目录。可通过[配置文件]修改位置。 |
### 可执行程序
TDengine 的所有可执行文件默认存放在 _/usr/local/taos/bin_ 目录下。其中包括:
- _taosd_TDengine 服务端可执行文件
- _taos_TDengine Shell 可执行文件
- _taosdump_:数据导入导出工具
- _taosBenchmark_TDengine 测试工具
- _remove.sh_:卸载 TDengine 的脚本,请谨慎执行,链接到/usr/bin 目录下的**rmtaos**命令。会删除 TDengine 的安装目录/usr/local/taos但会保留/etc/taos、/var/lib/taos、/var/log/taos
- _taosadapter_: 提供 RESTful 服务和接受其他多种软件写入请求的服务端可执行文件
- _TDinsight.sh_:用于下载 TDinsight 并安装的脚本
- _set_core.sh_用于方便调试设置系统生成 core dump 文件的脚本
- _taosd-dump-cfg.gdb_:用于方便调试 taosd 的 gdb 执行脚本。
## Docker
1. 测试机器如果已经安装了 Docker首先拉取最新的 TDengine 容器镜像:

View File

@ -132,4 +132,4 @@ INSERT INTO d1001 (ts, current) VALUES ("2018-10-03 14:38:05", 22);
```sql
delete from meters where ts < '2021-10-01 10:40:00.100' ;
```
```

View File

@ -1,6 +1,7 @@
---
title: 建立连接
description: 使用连接器建立与 TDengine 的连接,以及连接器的安装和连接
sidebar_label: 建立连接
toc_max_heading_level: 4
---
import Tabs from "@theme/Tabs";
@ -14,16 +15,16 @@ import ConnCSNative from "./_connect_cs.mdx";
import ConnC from "./_connect_c.mdx";
import ConnR from "./_connect_r.mdx";
import ConnPHP from "./_connect_php.mdx";
import InstallOnLinux from "../../08-connector/_linux_install.mdx";
import InstallOnWindows from "../../08-connector/_windows_install.mdx";
import InstallOnMacOS from "../../08-connector/_macos_install.mdx";
import VerifyLinux from "../../08-connector/_verify_linux.mdx";
import VerifyMacOS from "../../08-connector/_verify_macos.mdx";
import VerifyWindows from "../../08-connector/_verify_windows.mdx";
import InstallOnLinux from "../../14-reference/05-connector/_linux_install.mdx";
import InstallOnWindows from "../../14-reference/05-connector/_windows_install.mdx";
import InstallOnMacOS from "../../14-reference/05-connector/_macos_install.mdx";
import VerifyLinux from "../../14-reference/05-connector/_verify_linux.mdx";
import VerifyMacOS from "../../14-reference/05-connector/_verify_macos.mdx";
import VerifyWindows from "../../14-reference/05-connector/_verify_windows.mdx";
TDengine 提供了丰富的应用程序开发接口为了便于用户快速开发自己的应用TDengine 支持了多种编程语言的连接器,其中官方连接器包括支持 C/C++、Java、Python、Go、Node.js、C#、Rust、Lua社区贡献和 PHP 社区贡献的连接器。这些连接器支持使用原生接口taosc和 REST 接口(部分语言暂不支持)连接 TDengine 集群。社区开发者也贡献了多个非官方连接器,例如 ADO.NET 连接器、Lua 连接器和 PHP 连接器。
## 连接器建立连接的方式
## 连接方式
连接器建立连接的方式TDengine 提供三种:

View File

@ -1,2 +0,0 @@
label: 数据建模

View File

@ -1,78 +0,0 @@
---
sidebar_label: 数据建模
title: TDengine 数据建模
description: TDengine 中如何建立数据模型
---
TDengine 采用类关系型数据模型,需要建库、建表。因此对于一个具体的应用场景,需要考虑库、超级表和普通表的设计。本节不讨论细致的语法规则,只介绍概念。
关于数据建模请参考[视频教程](https://www.taosdata.com/blog/2020/11/11/1945.html)。
## 创建库
不同类型的数据采集点往往具有不同的数据特征,包括数据采集频率的高低,数据保留时间的长短,副本的数目,数据块的大小,是否允许更新数据等等。为了在各种场景下 TDengine 都能以最大效率工作TDengine 建议将不同数据特征的表创建在不同的库里,因为每个库可以配置不同的存储策略。创建一个库时,除 SQL 标准的选项外,还可以指定保留时长、副本数、缓存大小、时间精度、文件块里最大最小记录条数、是否压缩、一个数据文件覆盖的天数等多种参数。比如:
```sql
CREATE DATABASE power KEEP 365 DURATION 10 BUFFER 16 WAL_LEVEL 1;
```
上述语句将创建一个名为 power 的库,这个库的数据将保留 365 天(超过 365 天将被自动删除),每 10 天一个数据文件,每个 VNode 的写入内存池的大小为 16 MB对该数据库入会写 WAL 但不执行 FSYNC。详细的语法及参数请见 [数据库管理](../../taos-sql/database) 章节。
创建库之后,需要使用 SQL 命令 `USE` 将当前库切换过来,例如:
```sql
USE power;
```
将当前连接里操作的库换为 power否则对具体表操作前需要使用“库名.表名”来指定库的名字。
:::note
- 任何一张表或超级表必须属于某个库,在创建表之前,必须先创建库。
- 创建并插入记录、查询历史记录的时候,均需要指定时间戳。
:::
## 创建超级表
一个物联网系统,往往存在多种类型的设备,比如对于电网,存在智能电表、变压器、母线、开关等等。为便于多表之间的聚合,使用 TDengine, 需要对每个类型的数据采集点创建一个超级表。以 [表 1](../../concept) 中的智能电表为例,可以使用如下的 SQL 命令创建超级表:
```sql
CREATE STABLE meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);
```
与创建普通表一样,创建超级表时,需要提供表名(示例中为 meters表结构 Schema即数据列的定义。第一列必须为时间戳示例中为 ts其他列为采集的物理量示例中为 current, voltage, phase数据类型可以为整型、浮点型、字符串等。除此之外还需要提供标签的 Schema (示例中为 location, groupId标签的数据类型可以为整型、浮点型、字符串等。采集点的静态属性往往可以作为标签比如采集点的地理位置、设备型号、设备组 ID、管理员 ID 等等。标签的 Schema 可以事后增加、删除、修改。具体定义以及细节请见 [TDengine SQL 的超级表管理](../../taos-sql/stable) 章节。
每一种类型的数据采集点需要建立一个超级表,因此一个物联网系统,往往会有多个超级表。对于电网,我们就需要对智能电表、变压器、母线、开关等都建立一个超级表。在物联网中,一个设备就可能有多个数据采集点(比如一台风力发电的风机,有的采集点采集电流、电压等电参数,有的采集点采集温度、湿度、风向等环境参数),这个时候,对这一类型的设备,需要建立多张超级表。
一张超级表最多容许 4096 列,如果一个采集点采集的物理量个数超过 4096需要建多张超级表来处理。一个系统可以有多个 Database一个 Database 里可以有一到多个超级表。
## 创建表
TDengine 对每个数据采集点需要独立建表。与标准的关系型数据库一样一张表有表名Schema但除此之外还可以带有一到多个标签。创建时需要使用超级表做模板同时指定标签的具体值。以 [表 1](../../concept) 中的智能电表为例,可以使用如下的 SQL 命令建表:
```sql
CREATE TABLE d1001 USING meters TAGS ("California.SanFrancisco", 2);
```
其中 d1001 是表名meters 是超级表的表名,后面紧跟标签 Location 的具体标签值为 "California.SanFrancisco",标签 groupId 的具体标签值为 2。虽然在创建表时需要指定标签值但可以事后修改。详细细则请见 [TDengine SQL 的表管理](../../taos-sql/table) 章节。
TDengine 建议将数据采集点的全局唯一 ID 作为表名(比如设备序列号)。但对于有的场景,并没有唯一的 ID可以将多个 ID 组合成一个唯一的 ID。不建议将具有唯一性的 ID 作为标签值。
### 自动建表
在某些特殊场景中,用户在写数据时并不确定某个数据采集点的表是否存在,此时可在写入数据时使用自动建表语法来创建不存在的表,若该表已存在则不会建立新表且后面的 USING 语句被忽略。比如:
```sql
INSERT INTO d1001 USING meters TAGS ("California.SanFrancisco", 2) VALUES (NOW, 10.2, 219, 0.32);
```
上述 SQL 语句将记录`(NOW, 10.2, 219, 0.32)`插入表 d1001。如果表 d1001 还未创建,则使用超级表 meters 做模板自动创建,同时打上标签值 `"California.SanFrancisco", 2`。
关于自动建表的详细语法请参见 [插入记录时自动建表](../../taos-sql/insert#插入记录时自动建表) 章节。
## 多列模型 vs 单列模型
TDengine 支持多列模型,只要物理量是一个数据采集点同时采集的(时间戳一致),这些量就可以作为不同列放在一张超级表里。但还有一种极限的设计,单列模型,每个采集的物理量都单独建表,因此每种类型的物理量都单独建立一超级表。比如电流、电压、相位,就建三张超级表。
TDengine 建议尽可能采用多列模型,因为插入效率以及存储效率更高。但对于有些场景,一个采集点的采集量的种类经常变化,这个时候,如果采用多列模型,就需要频繁修改超级表的结构定义,让应用变的复杂,这个时候,采用单列模型会显得更简单。

View File

@ -0,0 +1,106 @@
---
title: 执行 SQL
sidebar_label: 执行 SQL
toc_max_heading_level: 4
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
上一节我们介绍了如何建立连接,本节以 WebSocket 连接为例,使用各种语言连接器执行 SQL 完成写入。
## 建库和表
<Tabs defaultValue="java" groupId="create">
<TabItem value="java" label="Java">
```java
// create statement
Statement stmt = conn.createStatement();
// create database
stmt.executeUpdate("CREATE DATABASE IF NOT EXISTS power");
// use database
stmt.executeUpdate("USE power");
// create table
stmt.executeUpdate("CREATE STABLE IF NOT EXISTS meters (ts TIMESTAMP, current FLOAT, voltage INT, phase FLOAT) TAGS (groupId INT, location BINARY(24))");
```
</TabItem>
</Tabs>
## 插入数据
<Tabs defaultValue="java" groupId="insert">
<TabItem value="java" label="Java">
```java
// insert data
String insertQuery = "INSERT INTO " +
"power.d1001 USING power.meters TAGS(2,'California.SanFrancisco') " +
"VALUES " +
"(NOW + 1a, 10.30000, 219, 0.31000) " +
"(NOW + 2a, 12.60000, 218, 0.33000) " +
"(NOW + 3a, 12.30000, 221, 0.31000) " +
"power.d1002 USING power.meters TAGS(3, 'California.SanFrancisco') " +
"VALUES " +
"(NOW + 1a, 10.30000, 218, 0.25000) ";
int affectedRows = stmt.executeUpdate(insertQuery);
System.out.println("insert " + affectedRows + " rows.");
```
</TabItem>
</Tabs>
**Note**
NOW 为系统内部函数,默认为客户端所在计算机当前时间。 NOW + 1s 代表客户端当前时间往后加 1 秒数字后面代表时间单位a毫秒smh小时dwny
## 查询数据
<Tabs defaultValue="java" groupId="query">
<TabItem value="java" label="Java">
```java
// query data
ResultSet resultSet = stmt.executeQuery("SELECT * FROM meters");
Timestamp ts;
float current;
String location;
while(resultSet.next()) {
ts = resultSet.getTimestamp(1);
current = resultSet.getFloat(2);
location = resultSet.getString("location");
System.out.printf("%s, %f, %s\n", ts, current, location);
}
```
</TabItem>
</Tabs>
**Note** 查询和操作关系型数据库一致,使用下标获取返回字段内容时从 1 开始,建议使用字段名称获取。
## 执行带有 reqId 的 SQL
reqId 可用于请求链路追踪reqId 就像分布式系统中的 traceId 作用一样。一个请求可能需要经过多个服务或者模块才能完成。reqId 用于标识和关联这个请求的所有相关操作,以便于我们可以追踪和分析请求的完整路径。
使用 reqId 有下面好处:
- 请求追踪:通过将同一个 reqId 关联到一个请求的所有相关操作,可以追踪请求在系统中的完整路径
- 性能分析:通过分析一个请求的 reqId可以了解请求在各个服务和模块中的处理时间从而找出性能瓶颈
- 故障诊断:当一个请求失败时,可以通过查看与该请求关联的 reqId 来找出问题发生的位置
如果用户不设置 reqId连接器会在内部随机生成一个但建议由显式用户设置以以更好地跟用户请求关联起来。
<Tabs defaultValue="java" groupId="query">
<TabItem value="java" label="Java">
```java
AbstractStatement aStmt = (AbstractStatement) connection.createStatement();
aStmt.execute("CREATE DATABASE IF NOT EXISTS power", 1L);
aStmt.executeUpdate("USE power", 2L);
try (ResultSet rs = aStmt.executeQuery("SELECT * FROM meters limit 1", 3L)) {
while(rs.next()){
Timestamp timestamp = rs.getTimestamp(1);
System.out.println("timestamp = " + timestamp);
}
}
aStmt.close();
```
</TabItem>
</Tabs>

View File

@ -1,143 +0,0 @@
---
title: SQL 写入
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import JavaSQL from "./_java_sql.mdx";
import JavaStmt from "./_java_stmt.mdx";
import PySQL from "./_py_sql.mdx";
import PyStmt from "./_py_stmt.mdx";
import GoSQL from "./_go_sql.mdx";
import GoStmt from "./_go_stmt.mdx";
import RustSQL from "./_rust_sql.mdx";
import RustStmt from "./_rust_stmt.mdx";
import NodeSQL from "./_js_sql.mdx";
import NodeStmt from "./_js_stmt.mdx";
import CsSQL from "./_cs_sql.mdx";
import CsStmt from "./_cs_stmt.mdx";
import CSQL from "./_c_sql.mdx";
import CStmt from "./_c_stmt.mdx";
import PhpSQL from "./_php_sql.mdx";
import PhpStmt from "./_php_stmt.mdx";
## SQL 写入简介
应用通过连接器执行 INSERT 语句来插入数据,用户还可以通过 TDengine CLI手动输入 INSERT 语句插入数据。
### 一次写入一条
下面这条 INSERT 就将一条记录写入到表 d1001 中:
```sql
INSERT INTO d1001 VALUES (ts1, 10.3, 219, 0.31);
```
这里的`ts1`为Unix时间戳(Unix timestamp),允许插入的最老记录的时间戳,是相对于当前服务器时间,减去配置的 KEEP 值。时间戳详情规则参考 [TDengine SQL数据写入 关于时间戳一节](../../../taos-sql/insert)
### 一次写入多条
TDengine 支持一次写入多条记录,比如下面这条命令就将两条记录写入到表 d1001 中:
```sql
INSERT INTO d1001 VALUES (ts1, 10.2, 220, 0.23) (ts2, 10.3, 218, 0.25);
```
这里的`ts1`和`ts2`为Unix时间戳(Unix timestamp),允许插入的最老记录的时间戳,是相对于当前服务器时间,减去配置的 KEEP 值。时间戳详情规则参考 [TDengine SQL数据写入 关于时间戳一节](../../../taos-sql/insert)
### 一次写入多表
TDengine 也支持一次向多个表写入数据,比如下面这条命令就向 d1001 写入两条记录,向 d1002 写入一条记录:
```sql
INSERT INTO d1001 VALUES (ts1, 10.3, 219, 0.31) (ts2, 12.6, 218, 0.33) d1002 VALUES (ts3, 12.3, 221, 0.31);
```
这里的`ts1`、`ts2`和`ts3`为Unix时间戳(Unix timestamp),允许插入的最老记录的时间戳,是相对于当前服务器时间,减去配置的 KEEP 值。时间戳详情规则参考 [TDengine SQL数据写入 关于时间戳一节](../../../taos-sql/insert)
详细的 SQL INSERT 语法规则参考 [TDengine SQL 的数据写入](../../../taos-sql/insert)。
:::info
- 要提高写入效率,需要批量写入。一般来说一批写入的记录条数越多,插入效率就越高。但一条记录不能超过 48KB一条 SQL 语句总长度不能超过 1MB。
- TDengine 支持多线程同时写入,要进一步提高写入速度,一个客户端需要打开多个同时写。但线程数达到一定数量后,无法再提高,甚至还会下降,因为线程频繁切换,会带来额外开销,合适的线程数量与服务端的处理能力,服务端的具体配置,数据库的参数,数据定义的 Schema写入数据的 Batch Size 等很多因素相关。一般来说,服务端和客户端处理能力越强,所能支持的并发写入的线程可以越多;数据库配置时的 vgroups 参数值越多(但仍然要在服务端的处理能力以内)则所能支持的并发写入越多;数据定义的 Schema 越简单,所能支持的并发写入越多。
:::
:::warning
- 对同一张表,如果新插入记录的时间戳已经存在,则指定了新值的列会用新值覆盖旧值,而没有指定新值的列则不受影响。
- 写入的数据的时间戳必须大于当前时间减去数据库配置参数 KEEP 的时间。如果 KEEP 配置为 3650 天,那么无法写入比 3650 天还早的数据。写入数据的时间戳也不能大于当前时间加配置参数 DURATION。如果 DURATION 为 2那么无法写入比当前时间还晚 2 天的数据。
:::
## 示例程序
### 普通 SQL 写入
<Tabs defaultValue="java" groupId="lang">
<TabItem label="Java" value="java">
<JavaSQL />
</TabItem>
<TabItem label="Python" value="python">
<PySQL />
</TabItem>
<TabItem label="Go" value="go">
<GoSQL />
</TabItem>
<TabItem label="Rust" value="rust">
<RustSQL />
</TabItem>
<TabItem label="Node.js" value="nodejs">
<NodeSQL />
</TabItem>
<TabItem label="C#" value="csharp">
<CsSQL />
</TabItem>
<TabItem label="C" value="c">
<CSQL />
</TabItem>
<TabItem label="PHP" value="php">
<PhpSQL />
</TabItem>
</Tabs>
:::note
1. 无论 RESTful 方式建立连接还是本地驱动方式建立连接,以上示例代码都能正常工作。
2. 唯一需要注意的是:由于 RESTful 接口无状态, 不能使用 `USE db;` 语句来切换数据库, 所以在上面示例中使用了`dbName.tbName`指定表名。
:::
### 参数绑定写入
TDengine 也提供了支持参数绑定的 Prepare API与 MySQL 类似,这些 API 目前也仅支持用问号 `?` 来代表待绑定的参数。在通过参数绑定接口写入数据时,就避免了 SQL 语法解析的资源消耗,从而在绝大多数情况下显著提升写入性能。
需要注意的是,只有使用原生连接的连接器,才能使用参数绑定功能。
<Tabs defaultValue="java" groupId="lang">
<TabItem label="Java" value="java">
<JavaStmt />
</TabItem>
<TabItem label="Python" value="python">
<PyStmt />
</TabItem>
<TabItem label="Go" value="go">
<GoStmt />
</TabItem>
<TabItem label="Rust" value="rust">
<RustStmt />
</TabItem>
<TabItem label="Node.js" value="nodejs">
<NodeStmt />
</TabItem>
<TabItem label="C#" value="csharp">
<CsStmt />
</TabItem>
<TabItem label="C" value="c">
<CStmt />
</TabItem>
<TabItem label="PHP" value="php">
<PhpStmt />
</TabItem>
</Tabs>

View File

@ -1,47 +0,0 @@
---
title: 从 Kafka 写入
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import PyKafka from "./_py_kafka.mdx";
## Kafka 介绍
Apache Kafka 是开源的分布式消息分发平台被广泛应用于高性能数据管道、流式数据分析、数据集成和事件驱动类型的应用程序。Kafka 包含 Producer、Consumer 和 Topic其中 Producer 是向 Kafka 发送消息的进程Consumer 是从 Kafka 消费消息的进程。Kafka 相关概念可以参考[官方文档](https://kafka.apache.org/documentation/#gettingStarted)。
### kafka topic
Kafka 的消息按 topic 组织,每个 topic 会有一到多个 partition。可以通过 kafka 的 `kafka-topics` 管理 topic。
创建名为 `kafka-events` 的topic:
```
bin/kafka-topics.sh --create --topic kafka-events --bootstrap-server localhost:9092
```
修改 `kafka-events` 的 partition 数量为 3:
```
bin/kafka-topics.sh --alter --topic kafka-events --partitions 3 --bootstrap-server=localhost:9092
```
展示所有的 topic 和 partition:
```
bin/kafka-topics.sh --bootstrap-server=localhost:9092 --describe
```
## 写入 TDengine
TDengine 支持 Sql 方式和 Schemaless 方式的数据写入Sql 方式数据写入可以参考 [TDengine SQL 写入](../sql-writing/) 和 [TDengine 高效写入](../high-volume/)。Schemaless 方式数据写入可以参考 [TDengine Schemaless 写入](../../../reference/schemaless/) 文档。
## 示例代码
<Tabs defaultValue="Python" groupId="lang">
<TabItem label="Python" value="Python">
<PyKafka />
</TabItem>
</Tabs>

View File

@ -1,81 +0,0 @@
---
sidebar_label: InfluxDB 行协议
title: InfluxDB 行协议
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import JavaLine from "./_java_line.mdx";
import PyLine from "./_py_line.mdx";
import GoLine from "./_go_line.mdx";
import RustLine from "./_rust_line.mdx";
import NodeLine from "./_js_line.mdx";
import CsLine from "./_cs_line.mdx";
import CLine from "./_c_line.mdx";
## 协议介绍
InfluxDB Line 协议采用一行字符串来表示一行数据。分为四部分:
```
measurement,tag_set field_set timestamp
```
- measurement 将作为超级表名。它与 tag_set 之间使用一个英文逗号来分隔。
- tag_set 将作为标签数据,其格式形如 `<tag_key>=<tag_value>,<tag_key>=<tag_value>`,也即可以使用英文逗号来分隔多个标签数据。它与 field_set 之间使用一个半角空格来分隔。
- field_set 将作为普通列数据,其格式形如 `<field_key>=<field_value>,<field_key>=<field_value>`,同样是使用英文逗号来分隔多个普通列的数据。它与 timestamp 之间使用一个半角空格来分隔。
- timestamp 即本行数据对应的主键时间戳。
例如:
```
meters,location=California.LosAngeles,groupid=2 current=13.4,voltage=223,phase=0.29 1648432611249500
```
:::note
- tag_set 中的所有的数据自动转化为 NCHAR 数据类型
- field_set 中的每个数据项都需要对自身的数据类型进行描述, 比如 1.2f32 代表 FLOAT 类型的数值 1.2, 如果不带类型后缀会被当作 DOUBLE 处理
- timestamp 支持多种时间精度。写入数据的时候需要用参数指定时间精度,支持从小时到纳秒的 6 种时间精度
- 为了提高写入的效率,默认假设同一个超级表中 field_set 的顺序是一样的(第一条数据包含所有的 field后面的数据按照这个顺序如果顺序不一样需要配置参数 smlDataFormat 为 false否则数据写入按照相同顺序写入库中数据会异常。3.0.1.3 之后的版本 smlDataFormat 默认为 false从3.0.3.0开始,该配置废弃) [TDengine 无模式写入参考指南](../../../reference/schemaless/#无模式写入行协议)
- 子表名生成规则
- 默认产生的子表名是根据规则生成的唯一 ID 值。
- 用户也可以通过在client端的 taos.cfg 里配置 smlAutoChildTableNameDelimiter 参数来指定连接标签之间的分隔符,连接起来后作为子表名。举例如下:配置 smlAutoChildTableNameDelimiter=-, 插入数据为 st,t0=cpu1,t1=4 c1=3 1626006833639000000 则创建的子表名为 cpu1-4。
- 用户也可以通过在client端的 taos.cfg 里配置 smlChildTableName 参数来指定某个标签值作为子表名。该标签值应该具有全局唯一性。举例如下假设有个标签名为tname, 配置 smlChildTableName=tname, 插入数据为 st,tname=cpu1,t1=4 c1=3 1626006833639000000 则创建的子表名为 cpu1。注意如果多行数据 tname 相同,但是后面的 tag_set 不同,则使用第一行自动建表时指定的 tag_set其他的行会忽略。[TDengine 无模式写入参考指南](../../../reference/schemaless/#无模式写入行协议)
:::
要了解更多可参考:[InfluxDB Line 协议官方文档](https://docs.influxdata.com/influxdb/v2.0/reference/syntax/line-protocol/) 和 [TDengine 无模式写入参考指南](../../../reference/schemaless/#无模式写入行协议)
## 示例代码
<Tabs defaultValue="java" groupId="lang">
<TabItem label="Java" value="java">
<JavaLine />
</TabItem>
<TabItem label="Python" value="Python">
<PyLine />
</TabItem>
<TabItem label="Go" value="go">
<GoLine />
</TabItem>
<TabItem label="Node.js" value="nodejs">
<NodeLine />
</TabItem>
<TabItem label="C#" value="csharp">
<CsLine />
</TabItem>
<TabItem label="C" value="c">
<CLine />
</TabItem>
</Tabs>
## SQL 查询示例
`meters` 是插入数据的超级表名。
可以通过超级表的 TAG 来过滤数据,比如查询 `location=California.LosAngeles,groupid=2` 可以通过如下 SQL
```sql
SELECT * FROM meters WHERE location = "California.LosAngeles" AND groupid = 2;
```

View File

@ -1,95 +0,0 @@
---
sidebar_label: OpenTSDB 行协议
title: OpenTSDB 行协议
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import JavaTelnet from "./_java_opts_telnet.mdx";
import PyTelnet from "./_py_opts_telnet.mdx";
import GoTelnet from "./_go_opts_telnet.mdx";
import RustTelnet from "./_rust_opts_telnet.mdx";
import NodeTelnet from "./_js_opts_telnet.mdx";
import CsTelnet from "./_cs_opts_telnet.mdx";
import CTelnet from "./_c_opts_telnet.mdx";
## 协议介绍
OpenTSDB 行协议同样采用一行字符串来表示一行数据。OpenTSDB 采用的是单列模型,因此一行只能包含一个普通数据列。标签列依然可以有多个。分为四部分,具体格式约定如下:
```txt
<metric> <timestamp> <value> <tagk_1>=<tagv_1>[ <tagk_n>=<tagv_n>]
```
- metric 将作为超级表名;
- timestamp 本行数据对应的时间戳。根据时间戳的长度自动识别时间精度。支持秒和毫秒两种时间精度;
- value 度量值,必须为一个数值。对应的列名是 “\_value”
- 最后一部分是标签集, 用空格分隔不同标签, 所有标签自动转化为 NCHAR 数据类型。
例如:
```txt
meters.current 1648432611250 11.3 location=California.LosAngeles groupid=3
```
- 子表名生成规则
- 默认产生的子表名是根据规则生成的唯一 ID 值。
- 用户也可以通过在client端的 taos.cfg 里配置 smlAutoChildTableNameDelimiter 参数来指定连接标签之间的分隔符,连接起来后作为子表名。举例如下:配置 smlAutoChildTableNameDelimiter=-, 插入数据为 st,t0=cpu1,t1=4 c1=3 1626006833639000000 则创建的子表名为 cpu1-4。
- 用户也可以通过在client端的 taos.cfg 里配置 smlChildTableName 参数来指定某个标签值作为子表名。该标签值应该具有全局唯一性。举例如下假设有个标签名为tname, 配置 smlChildTableName=tname, 插入数据为 st,tname=cpu1,t1=4 c1=3 1626006833639000000 则创建的子表名为 cpu1。注意如果多行数据 tname 相同,但是后面的 tag_set 不同,则使用第一行自动建表时指定的 tag_set其他的行会忽略。[TDengine 无模式写入参考指南](../../../reference/schemaless/#无模式写入行协议)
参考 [OpenTSDB Telnet API 文档](http://opentsdb.net/docs/build/html/api_telnet/put.html)。
## 示例代码
<Tabs defaultValue="java" groupId="lang">
<TabItem label="Java" value="java">
<JavaTelnet />
</TabItem>
<TabItem label="Python" value="Python">
<PyTelnet />
</TabItem>
<TabItem label="Go" value="go">
<GoTelnet />
</TabItem>
<TabItem label="Node.js" value="nodejs">
<NodeTelnet />
</TabItem>
<TabItem label="C#" value="csharp">
<CsTelnet />
</TabItem>
<TabItem label="C" value="c">
<CTelnet />
</TabItem>
</Tabs>
以上示例代码会自动创建 2 个超级表, 每个超级表有 4 条数据。
```cmd
taos> USE test;
Database changed.
taos> SHOW STABLES;
name |
=================================
meters_current |
meters_voltage |
Query OK, 2 row(s) in set (0.002544s)
taos> SELECT TBNAME, * FROM `meters_current`;
tbname | _ts | _value | groupid | location |
==================================================================================================================================
t_0e7bcfa21a02331c06764f275... | 2022-03-28 09:56:51.249 | 10.800000000 | 3 | California.LosAngeles |
t_0e7bcfa21a02331c06764f275... | 2022-03-28 09:56:51.250 | 11.300000000 | 3 | California.LosAngeles |
t_7e7b26dd860280242c6492a16... | 2022-03-28 09:56:51.249 | 10.300000000 | 2 | California.SanFrancisco |
t_7e7b26dd860280242c6492a16... | 2022-03-28 09:56:51.250 | 12.600000000 | 2 | California.SanFrancisco |
Query OK, 4 row(s) in set (0.005399s)
```
## SQL 查询示例
`meters_current` 是插入数据的超级表名。
可以通过超级表的 TAG 来过滤数据,比如查询 `location=California.LosAngeles groupid=3` 可以通过如下 SQL
```sql
SELECT * FROM `meters_current` WHERE location = "California.LosAngeles" AND groupid = 3;
```

View File

@ -1,109 +0,0 @@
---
sidebar_label: OpenTSDB JSON 格式协议
title: OpenTSDB JSON 格式协议
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import JavaJson from "./_java_opts_json.mdx";
import PyJson from "./_py_opts_json.mdx";
import GoJson from "./_go_opts_json.mdx";
import RustJson from "./_rust_opts_json.mdx";
import NodeJson from "./_js_opts_json.mdx";
import CsJson from "./_cs_opts_json.mdx";
import CJson from "./_c_opts_json.mdx";
## 协议介绍
OpenTSDB JSON 格式协议采用一个 JSON 字符串表示一行或多行数据。例如:
```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"
}
}
]
```
与 OpenTSDB 行协议类似, metric 将作为超级表名, timestamp 表示时间戳value 表示度量值, tags 表示标签集。
参考[OpenTSDB HTTP API 文档](http://opentsdb.net/docs/build/html/api_http/put.html)。
:::note
- 对于 JSON 格式协议TDengine 并不会自动把所有标签转成 NCHAR 类型, 字符串将将转为 NCHAR 类型, 数值将同样转换为 DOUBLE 类型。
- 子表名生成规则
- 默认产生的子表名是根据规则生成的唯一 ID 值。
- 用户也可以通过在client端的 taos.cfg 里配置 smlAutoChildTableNameDelimiter 参数来指定连接标签之间的分隔符,连接起来后作为子表名。举例如下:配置 smlAutoChildTableNameDelimiter=-, 插入数据为 st,t0=cpu1,t1=4 c1=3 1626006833639000000 则创建的子表名为 cpu1-4。
- 用户也可以通过在client端的 taos.cfg 里配置 smlChildTableName 参数来指定某个标签值作为子表名。该标签值应该具有全局唯一性。举例如下假设有个标签名为tname, 配置 smlChildTableName=tname, 插入数据为 st,tname=cpu1,t1=4 c1=3 1626006833639000000 则创建的子表名为 cpu1。注意如果多行数据 tname 相同,但是后面的 tag_set 不同,则使用第一行自动建表时指定的 tag_set其他的行会忽略。[TDengine 无模式写入参考指南](../../../reference/schemaless/#无模式写入行协议)
:::
## 示例代码
<Tabs defaultValue="java" groupId="lang">
<TabItem label="Java" value="java">
<JavaJson />
</TabItem>
<TabItem label="Python" value="Python">
<PyJson />
</TabItem>
<TabItem label="Go" value="go">
<GoJson />
</TabItem>
<TabItem label="Node.js" value="nodejs">
<NodeJson />
</TabItem>
<TabItem label="C#" value="csharp">
<CsJson />
</TabItem>
<TabItem label="C" value="c">
<CJson />
</TabItem>
</Tabs>
以上示例代码会自动创建 2 个超级表, 每个超级表有 2 条数据。
```cmd
taos> USE test;
Database changed.
taos> SHOW STABLES;
name |
=================================
meters_current |
meters_voltage |
Query OK, 2 row(s) in set (0.001954s)
taos> SELECT * FROM `meters.current`;
_ts | _value | groupid | location |
===================================================================================================================
2022-03-28 09:56:51.249 | 10.300000000 | 2.000000000 | California.SanFrancisco |
2022-03-28 09:56:51.250 | 12.600000000 | 2.000000000 | California.SanFrancisco |
Query OK, 2 row(s) in set (0.004076s)
```
## SQL 查询示例
`meters_voltage` 是插入数据的超级表名。
可以通过超级表的 TAG 来过滤数据,比如查询 `location=California.LosAngeles groupid=1` 可以通过如下 SQL
```sql
SELECT * FROM `meters_current` WHERE location = "California.LosAngeles" AND groupid = 3;
```

View File

@ -1,3 +0,0 @@
```c
{{#include docs/examples/c/line_example.c:main}}
```

View File

@ -1,3 +0,0 @@
```c
{{#include docs/examples/c/json_protocol_example.c:main}}
```

View File

@ -1,3 +0,0 @@
```c
{{#include docs/examples/c/telnet_line_example.c:main}}
```

View File

@ -1,3 +0,0 @@
```c
{{#include docs/examples/c/insert_example.c}}
```

View File

@ -1,6 +0,0 @@
```c title=一次绑定一行
{{#include docs/examples/c/stmt_example.c}}
```
```c title=一次绑定多行 72:117
{{#include docs/examples/c/multi_bind_example.c}}
```

View File

@ -1 +0,0 @@
label: 写入数据

View File

@ -1,3 +0,0 @@
```csharp
{{#include docs/examples/csharp/influxdbLine/Program.cs}}
```

View File

@ -1,3 +0,0 @@
```csharp
{{#include docs/examples/csharp/optsJSON/Program.cs}}
```

View File

@ -1,3 +0,0 @@
```csharp
{{#include docs/examples/csharp/optsTelnet/Program.cs}}
```

View File

@ -1,3 +0,0 @@
```csharp
{{#include docs/examples/csharp/sqlInsert/Program.cs}}
```

View File

@ -1,3 +0,0 @@
```csharp
{{#include docs/examples/csharp/stmtInsert/Program.cs}}
```

View File

@ -1,3 +0,0 @@
```go
{{#include docs/examples/go/insert/line/main.go}}
```

View File

@ -1,3 +0,0 @@
```go
{{#include docs/examples/go/insert/json/main.go}}
```

View File

@ -1,3 +0,0 @@
```go
{{#include docs/examples/go/insert/telnet/main.go}}
```

View File

@ -1,3 +0,0 @@
```go
{{#include docs/examples/go/insert/sql/main.go}}
```

View File

@ -1,8 +0,0 @@
```go
{{#include docs/examples/go/insert/stmt/main.go}}
```
:::tip
driver-go 的模块 `github.com/taosdata/driver-go/v3/wrapper` 是 C 接口的底层封装。使用这个模块也可以实现参数绑定写入。
:::

View File

@ -1,3 +0,0 @@
```java
{{#include docs/examples/java/src/main/java/com/taos/example/LineProtocolExample.java}}
```

View File

@ -1,3 +0,0 @@
```java
{{#include docs/examples/java/src/main/java/com/taos/example/JSONProtocolExample.java}}
```

View File

@ -1,3 +0,0 @@
```java
{{#include docs/examples/java/src/main/java/com/taos/example/TelnetLineProtocolExample.java}}
```

View File

@ -1,3 +0,0 @@
```java
{{#include docs/examples/java/src/main/java/com/taos/example/RestInsertExample.java:insert}}
```

View File

@ -1,3 +0,0 @@
```java
{{#include docs/examples/java/src/main/java/com/taos/example/StmtInsertExample.java}}
```

View File

@ -1,3 +0,0 @@
```js
{{#include docs/examples/node/nativeexample/influxdb_line_example.js}}
```

View File

@ -1,3 +0,0 @@
```js
{{#include docs/examples/node/nativeexample/opentsdb_json_example.js}}
```

View File

@ -1,3 +0,0 @@
```js
{{#include docs/examples/node/nativeexample/opentsdb_telnet_example.js}}
```

View File

@ -1,3 +0,0 @@
```js
{{#include docs/examples/node/nativeexample/insert_example.js}}
```

View File

@ -1,12 +0,0 @@
```js title=一次绑定一行
{{#include docs/examples/node/nativeexample/param_bind_example.js}}
```
```js title=一次绑定多行
{{#include docs/examples/node/nativeexample/multi_bind_example.js:insertData}}
```
:::info
一次绑定一行效率不如一次绑定多行,但支持非 INSERT 语句。一次绑定多行效率更高,但仅支持 INSERT 语句。
:::

View File

@ -1,3 +0,0 @@
```php
{{#include docs/examples/php/insert.php}}
```

View File

@ -1,3 +0,0 @@
```php
{{#include docs/examples/php/insert_stmt.php}}
```

View File

@ -1,124 +0,0 @@
### python Kafka 客户端
Kafka 的 python 客户端可以参考文档 [kafka client](https://cwiki.apache.org/confluence/display/KAFKA/Clients#Clients-Python)。推荐使用 [confluent-kafka-python](https://github.com/confluentinc/confluent-kafka-python) 和 [kafka-python](http://github.com/dpkp/kafka-python)。以下示例以 [kafka-python](http://github.com/dpkp/kafka-python) 为例。
### 从 Kafka 消费数据
Kafka 客户端采用 pull 的方式从 Kafka 消费数据,可以采用单条消费的方式或批量消费的方式读取数据。使用 [kafka-python](http://github.com/dpkp/kafka-python) 客户端单条消费数据的示例如下:
```
from kafka import KafkaConsumer
consumer = KafkaConsumer('my_favorite_topic')
for msg in consumer:
print (msg)
```
单条消费的方式在数据流量大的情况下往往存在性能瓶颈,导致 Kafka 消息积压,更推荐使用批量消费的方式消费数据。使用 [kafka-python](http://github.com/dpkp/kafka-python) 客户端批量消费数据的示例如下:
```
from kafka import KafkaConsumer
consumer = KafkaConsumer('my_favorite_topic')
while True:
msgs = consumer.poll(timeout_ms=500, max_records=1000)
if msgs:
print (msgs)
```
### Python 多线程
为了提高数据写入效率,通常采用多线程的方式写入数据,可以使用 python 线程池 ThreadPoolExecutor 实现多线程。示例代码如下:
```
from concurrent.futures import ThreadPoolExecutor, Future
pool = ThreadPoolExecutor(max_workers=10)
pool.submit(...)
```
### Python 多进程
单个python进程不能充分发挥多核 CPU 的性能有时候我们会选择多进程的方式。在多进程的情况下需要注意Kafka Consumer 的数量应该小于等于 Kafka Topic Partition 数量。Python 多进程示例代码如下:
```
from multiprocessing import Process
ps = []
for i in range(5):
p = Process(target=Consumer().consume())
p.start()
ps.append(p)
for p in ps:
p.join()
```
除了 Python 内置的多线程和多进程方式,还可以通过第三方库 gunicorn 实现并发。
### 完整示例
<details>
<summary>kafka_example_perform</summary>
`kafka_example_perform` 是示例程序的入口
```py
{{#include docs/examples/python/kafka_example_perform.py}}
```
</details>
<details>
<summary>kafka_example_common</summary>
`kafka_example_common` 是示例程序的公共代码
```py
{{#include docs/examples/python/kafka_example_common.py}}
```
</details>
<details>
<summary>kafka_example_producer</summary>
`kafka_example_producer` 是示例程序的 producer 代码,负责生成并发送测试数据到 kafka
```py
{{#include docs/examples/python/kafka_example_producer.py}}
```
</details>
<details>
<summary>kafka_example_consumer</summary>
`kafka_example_consumer` 是示例程序的 consumer 代码,负责从 kafka 消费数据,并写入到 TDengine
```py
{{#include docs/examples/python/kafka_example_consumer.py}}
```
</details>
### 执行步骤
<details>
<summary>执行 Python 示例程序</summary>
1. 安装并启动 kafka
2. python 环境准备
- 安装 python3
- 安装 taospy
- 安装 kafka-python
3. 执行示例程序
程序的执行入口是 `kafka_example_perform.py`,获取程序完整的执行参数,请执行 help 命令。
```
python3 kafka_example_perform.py --help
```
以下为创建 100 个子表,每个子表 20000 条数据kafka max poll 为 100一个进程每个进程一个处理线程的程序执行命令
```
python3 kafka_example_perform.py -table-count=100 -table-items=20000 -max-poll=100 -threads=1 -processes=1
```
</details>

View File

@ -1,3 +0,0 @@
```py
{{#include docs/examples/python/line_protocol_example.py}}
```

View File

@ -1,3 +0,0 @@
```py
{{#include docs/examples/python/json_protocol_example.py}}
```

View File

@ -1,3 +0,0 @@
```py
{{#include docs/examples/python/telnet_line_protocol_example.py}}
```

View File

@ -1,3 +0,0 @@
```py
{{#include docs/examples/python/native_insert_example.py}}
```

View File

@ -1,12 +0,0 @@
```py title=一次绑定一行
{{#include docs/examples/python/bind_param_example.py}}
```
```py title=一次绑定多行
{{#include docs/examples/python/multi_bind_example.py:bind_batch}}
```
:::info
一次绑定一行效率不如一次绑定多行,但支持非 INSERT 语句。一次绑定多行效率更高,但仅支持 INSERT 语句。
:::

View File

@ -1,2 +0,0 @@
```rust
```

View File

@ -1,2 +0,0 @@
```rust
```

View File

@ -1,2 +0,0 @@
```rust
```

View File

@ -1,3 +0,0 @@
```rust
{{#include docs/examples/rust/nativeexample/examples/schemaless_insert_line.rs}}
```

View File

@ -1,3 +0,0 @@
```rust
{{#include docs/examples/rust/restexample/examples/insert_example.rs}}
```

View File

@ -1,3 +0,0 @@
```rust
{{#include docs/examples/rust/nativeexample/examples/stmt_example.rs}}
```

View File

@ -1,14 +0,0 @@
---
sidebar_label: 写入数据
title: 写入数据
description: TDengine 的各种写入方式
---
TDengine 支持多种写入协议,包括 SQLInfluxDB Line 协议, OpenTSDB Telnet 协议OpenTSDB JSON 格式协议。数据可以单条插入也可以批量插入可以插入一个数据采集点的数据也可以同时插入多个数据采集点的数据。同时TDengine 支持多线程插入支持时间乱序数据插入也支持历史数据插入。InfluxDB Line 协议、OpenTSDB Telnet 协议和 OpenTSDB JSON 格式协议是 TDengine 支持的三种无模式写入协议。使用无模式方式写入无需提前创建超级表和子表,并且引擎能自适用数据对表结构做调整。
```mdx-code-block
import DocCardList from '@theme/DocCardList';
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
<DocCardList items={useCurrentSidebarCategory().items}/>
```

View File

@ -1,3 +0,0 @@
```c
{{#include docs/examples/c/query_example.c}}
```

View File

@ -1,3 +0,0 @@
```c
{{#include docs/examples/c/async_query_example.c:demo}}
```

View File

@ -1 +0,0 @@
label: 查询数据

View File

@ -1,3 +0,0 @@
```csharp
{{#include docs/examples/csharp/query/Program.cs}}
```

View File

@ -1,3 +0,0 @@
```go
{{#include docs/examples/go/query/sync/main.go}}
```

View File

@ -1,3 +0,0 @@
```go
{{#include docs/examples/go/query/async/main.go}}
```

View File

@ -1,3 +0,0 @@
```java
{{#include docs/examples/java/src/main/java/com/taos/example/RestQueryExample.java}}
```

View File

@ -1,3 +0,0 @@
```js
{{#include docs/examples/node/nativeexample/query_example.js}}
```

View File

@ -1,3 +0,0 @@
```js
{{#include docs/examples/node/nativeexample/async_query_example.js}}
```

View File

@ -1,3 +0,0 @@
```go
{{#include docs/examples/php/query.php}}
```

View File

@ -1,11 +0,0 @@
通过迭代逐行获取查询结果。
```py
{{#include docs/examples/python/query_example.py:iter}}
```
一次获取所有查询结果,并把每一行转化为一个字典返回。
```py
{{#include docs/examples/python/query_example.py:fetch_all}}
```

View File

@ -1,8 +0,0 @@
```py
{{#include docs/examples/python/async_query_example.py}}
```
:::note
这个示例程序,目前在 Windows 系统上还无法运行
:::

View File

@ -1,3 +0,0 @@
```rust
{{#include docs/examples/rust/restexample/examples/query_example.rs}}
```

View File

@ -1,180 +0,0 @@
---
sidebar_label: 查询数据
title: 查询数据
description: "主要查询功能,通过连接器执行同步查询和异步查询"
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import JavaQuery from "./_java.mdx";
import PyQuery from "./_py.mdx";
import GoQuery from "./_go.mdx";
import RustQuery from "./_rust.mdx";
import NodeQuery from "./_js.mdx";
import CsQuery from "./_cs.mdx";
import CQuery from "./_c.mdx";
import PhpQuery from "./_php.mdx";
import PyAsync from "./_py_async.mdx";
import NodeAsync from "./_js_async.mdx";
import CAsync from "./_c_async.mdx";
## 主要查询功能
TDengine 采用 SQL 作为查询语言。应用程序可以通过 REST API 或连接器发送 SQL 语句,用户还可以通过 TDengine 命令行工具 taos 手动执行 SQL 即席查询Ad-Hoc Query。TDengine 支持如下查询功能:
- 单列、多列数据查询
- 标签和数值的多种过滤条件:>, \<, =, \<>, like 等
- 聚合结果的分组Group by、排序Order by、约束输出Limit/Offset
- 时间窗口Interval、会话窗口Session和状态窗口State_window等窗口切分聚合查询
- 数值列及聚合结果的四则运算
- 时间戳对齐的连接查询Join Query: 隐式连接)操作
- 多种聚合/计算函数: count, max, min, avg, sum, twa, stddev, leastsquares, top, bottom, first, last, percentile, apercentile, last_row, spread, diff 等
例如:在命令行工具 taos 中,从表 d1001 中查询出 voltage > 215 的记录,按时间降序排列,仅仅输出 2 条。
```sql
taos> select * from d1001 where voltage > 215 order by ts desc limit 2;
ts | current | voltage | phase |
======================================================================================
2018-10-03 14:38:16.800 | 12.30000 | 221 | 0.31000 |
2018-10-03 14:38:15.000 | 12.60000 | 218 | 0.33000 |
Query OK, 2 row(s) in set (0.001100s)
```
为满足物联网场景的需求TDengine 支持几个特殊的函数,比如 twa(时间加权平均)spread (最大值与最小值的差)last_row(最后一条记录)等,更多与物联网场景相关的函数将添加进来。
具体的查询语法请看 [TDengine SQL 的数据查询](../../taos-sql/select) 章节。
## 多表聚合查询
物联网场景中往往同一个类型的数据采集点有多个。TDengine 采用超级表(STable)的概念来描述某一个类型的数据采集点,一张普通的表来描述一个具体的数据采集点。同时 TDengine 使用标签来描述数据采集点的静态属性一个具体的数据采集点有具体的标签值。通过指定标签的过滤条件TDengine 提供了一高效的方法将超级表(某一类型的数据采集点)所属的子表进行聚合查询。对普通表的聚合函数以及绝大部分操作都适用于超级表,语法完全一样。
### 示例一
在 TDengine CLI查找加利福尼亚州所有智能电表采集的电压平均值并按照 location 分组。
```
taos> SELECT AVG(voltage), location FROM meters GROUP BY location;
avg(voltage) | location |
===============================================================================================
219.200000000 | California.SanFrancisco |
221.666666667 | California.LosAngeles |
Query OK, 2 rows in database (0.005995s)
```
### 示例二
在 TDengine CLI, 查找 groupId 为 2 的所有智能电表的记录条数,电流的最大值。
```
taos> SELECT count(*), max(current) FROM meters where groupId = 2;
cunt(*) | max(current) |
==================================
5 | 13.4 |
Query OK, 1 row(s) in set (0.002136s)
```
在 [TDengine SQL 的数据查询](../../taos-sql/select) 一章,查询类操作都会注明是否支持超级表。
## 降采样查询、插值
物联网场景里经常需要通过降采样down sampling将采集的数据按时间段进行聚合。TDengine 提供了一个简便的关键词 interval 让按照时间窗口的查询操作变得极为简单。比如,将智能电表 d1001 采集的电流值每 10 秒钟求和
```
taos> SELECT _wstart, sum(current) FROM d1001 INTERVAL(10s);
_wstart | sum(current) |
======================================================
2018-10-03 14:38:00.000 | 10.300000191 |
2018-10-03 14:38:10.000 | 24.900000572 |
Query OK, 2 rows in database (0.003139s)
```
降采样操作也适用于超级表,比如:将加利福尼亚州所有智能电表采集的电流值每秒钟求和
```
taos> SELECT _wstart, SUM(current) FROM meters where location like "California%" INTERVAL(1s);
_wstart | sum(current) |
======================================================
2018-10-03 14:38:04.000 | 10.199999809 |
2018-10-03 14:38:05.000 | 23.699999809 |
2018-10-03 14:38:06.000 | 11.500000000 |
2018-10-03 14:38:15.000 | 12.600000381 |
2018-10-03 14:38:16.000 | 34.400000572 |
Query OK, 5 rows in database (0.007413s)
```
降采样操作也支持时间偏移,比如:将所有智能电表采集的电流值每秒钟求和,但要求每个时间窗口从 500 毫秒开始
```
taos> SELECT _wstart, SUM(current) FROM meters INTERVAL(1s, 500a);
_wstart | sum(current) |
======================================================
2018-10-03 14:38:03.500 | 10.199999809 |
2018-10-03 14:38:04.500 | 10.300000191 |
2018-10-03 14:38:05.500 | 13.399999619 |
2018-10-03 14:38:06.500 | 11.500000000 |
2018-10-03 14:38:14.500 | 12.600000381 |
2018-10-03 14:38:16.500 | 34.400000572 |
Query OK, 6 rows in database (0.005515s)
```
物联网场景里,每个数据采集点采集数据的时间是难同步的,但很多分析算法(比如 FFT)需要把采集的数据严格按照时间等间隔的对齐,在很多系统里,需要应用自己写程序来处理,但使用 TDengine 的降采样操作就轻松解决。
如果一个时间间隔里没有采集的数据TDengine 还提供插值计算的功能。
语法规则细节请见 [TDengine SQL 的按时间窗口切分聚合](../../taos-sql/distinguished) 章节。
## 示例代码
### 查询数据
在 [SQL 写入](../insert-data/sql-writing) 一章,我们创建了 power 数据库,并向 meters 表写入了一些数据,以下示例代码展示如何查询这个表的数据。
<Tabs defaultValue="java" groupId="lang">
<TabItem label="Java" value="java">
<JavaQuery />
</TabItem>
<TabItem label="Python" value="python">
<PyQuery />
</TabItem>
<TabItem label="Go" value="go">
<GoQuery />
</TabItem>
<TabItem label="Rust" value="rust">
<RustQuery />
</TabItem>
<TabItem label="Node.js" value="nodejs">
<NodeQuery />
</TabItem>
<TabItem label="C#" value="csharp">
<CsQuery />
</TabItem>
<TabItem label="C" value="c">
<CQuery />
</TabItem>
<TabItem label="PHP" value="php">
<PhpQuery />
</TabItem>
</Tabs>
:::note
1. 无论是使用 REST 连接还是原生连接的连接器,以上示例代码都能正常工作。
2. 唯一需要注意的是:由于 REST 接口无状态, 不能使用 `use db` 语句来切换数据库。除了在 REST 参数中指定数据库以外也可以在 SQL 语句中使用 \<db_name>.\<table_name> 来指定数据库。
:::
### 异步查询
除同步查询 API 之外TDengine 还提供性能更高的异步调用 API 处理数据插入、查询操作。在软硬件环境相同的情况下,异步 API 处理数据插入的速度比同步 API 快 2-4 倍。异步 API 采用非阻塞式的调用方式,在系统真正完成某个具体数据库操作前,立即返回。调用的线程可以去处理其他工作,从而可以提升整个应用的性能。异步 API 在网络延迟严重的情况下,优点尤为突出。
需要注意的是,只有使用原生连接的连接器,才能使用异步查询功能。
<Tabs defaultValue="python" groupId="lang">
<TabItem label="Python" value="python">
<PyAsync />
</TabItem>
<TabItem label="C" value="c">
<CAsync />
</TabItem>
</Tabs>

View File

@ -0,0 +1,218 @@
---
title: 无模式写入
sidebar_label: 无模式写入
toc_max_heading_level: 4
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
在物联网应用中为了实现自动化管理、业务分析和设备监控等多种功能通常需要采集大量的数据项。然而由于应用逻辑的版本升级和设备自身的硬件调整等原因数据采集项可能会频繁发生变化。为了应对这种挑战TDengine 提供了无模式schemaless写入方式旨在简化数据记录过程。
采用无模式写入方式,用户无须预先创建超级表或子表,因为 TDengine 会根据实际写入的数据自动创建相应的存储结构。此外,在必要时,无模式写入方式还能自动添加必要的数据列或标签列,确保用户写入的数据能够被正确存储。
值得注意的是,通过无模式写入方式创建的超级表及其对应的子表与通过 SQL 直接创建的超级表和子表在功能上没有区别,用户仍然可以使用 SQL 直接向其中写入数据。然而,由于无模式写入方式生成的表名是基于标签值按照固定的映射规则生成的,因此这些表名可能缺乏可读性,不易于理解。
**采用无模式写入方式时会自动创建表,无须手动创建表。**
## 无模式写入行协议
TDengine 的无模式写入行协议兼容 InfluxDB 的行协议、OpenTSDB 的 telnet 行协议和 OpenTSDB 的 JSON 格式协议。InfluxDB、OpenTSDB 的标准写入协议请参考各自的官方文档。
下面首先以 InfluxDB 的行协议为基础,介绍 TDengine 扩展的协议内容。该协议允许用户采用更加精细的方式控制(超级表)模式。采用一个字符串来表达一个数据行,可以向写入 API 中一次传入多行字符串来实现多个数据行的批量写入,其格式约定如下。
```text
measurement,tag_set field_set timestamp
```
各参数说明如下。
- measurement 为数据表名,与 tag_set 之间使用一个英文逗号来分隔。
- tag_set 格式形如 `<tag_key>=<tag_value>, <tag_key>=<tag_value>`,表示标签列数据,使用英文逗号分隔,与 field_set 之间使用一个半角空格分隔。
- field_set 格式形如 `<field_key>=<field_value>, <field_key>=<field_value>`,表示普通列,同样使用英文逗号来分隔,与 timestamp 之间使用一个半角空格分隔。
- timestamp 为本行数据对应的主键时间戳。
tag_set 中的所有的数据自动转化为 nchar 数据类型,并不需要使用双引号。
在无模式写入数据行协议中field_set 中的每个数据项都需要对自身的数据类型进行描述,具体要求如下。
- 如果两边有英文双引号,表示 varchar 类型,例如 "abc"。
- 如果两边有英文双引号而且带有 L 或 l 前缀,表示 nchar 类型,例如 L" 报错信息 "。
- 如果两边有英文双引号而且带有 G 或 g 前缀, 表 示 geometry 类型, 例 如G"Point(4.343 89.342)"。
- 如果两边有英文双引号而且带有 B 或 b 前缀,表示 varbinary 类型,双引号内可以为 \x 开头的十六进制或者字符串,例如 B"\x98f46e" 和 B"hello"。
- 对于空格、等号(=)、逗号(,)、双引号(")、反斜杠(\),前面需要使用反斜杠(\)进行转义(均为英文半角符号)。无模式写入协议的域转义规则如下表所示。
| **序号** | **域** | **需转义字符** |
| -------- | ----------- | ----------------------------- |
| 1 | 超级表名 | 逗号,空格 |
| 2 | 标签名 | 逗号,等号,空格 |
| 3 | 标签值 | 逗号,等号,空格 |
| 4 | 列名 | 逗号,等号,空格 |
| 5 | 列值 | 双引号,反斜杠 |
如果使用两个连续的反斜杠则第1个反斜杠作为转义符当只有一个反斜杠时则无须转义。无模式写入协议的反斜杠转义规则如下表所示。
| **序号** | **反斜杠** | **转义为** |
| -------- | ----------- | ----------------------------- |
| 1 | \ | \ |
| 2 | \\\\ | \ |
| 3 | \\\\\\ | \\\\ |
| 4 | \\\\\\\\ | \\\\ |
| 5 | \\\\\\\\\\ | \\\\\\ |
| 6 | \\\\\\\\\\\\ | \\\\\\ |
数值类型将通过后缀来区分数据类型。无模式写入协议的数值类型转义规则如下表所示。
| **序号** | **后缀** | **映射类型** | **大小(字节)** |
| -------- | ----------- | ----------------------------- | -------------- |
| 1 | 无或 f64 | double | 8 |
| 2 | f32 | float | 4 |
| 3 | i8/u8 | TinyInt/UTinyInt | 1 |
| 4 | i16/u16 | SmallInt/USmallInt | 2 |
| 5 | i32/u32 | Int/UInt | 4 |
| 6 | i64/i/u64/u | BigInt/BigInt/UBigInt/UBigInt | 8 |
- t, T, true, True, TRUE, f, F, false, False 将直接作为 BOOL 型来处理。
例如如下数据行表示:向名为 st 的超级表下的 t1 标签为 "3"NCHAR、t2 标签为 "4"NCHAR、t3
标签为 "t3"NCHAR的数据子表写入 c1 列为 3BIGINT、c2 列为 falseBOOL、c3
列为 "passit"BINARY、c4 列为 4DOUBLE、主键时间戳为 1626006833639000000 的一行数据。
```json
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4f64 1626006833639000000
```
需要注意的是,如果描述数据类型后缀时出现大小写错误,或者为数据指定的数据类型有误,均可能引发报错提示而导致数据写入失败。
TDengine提供数据写入的幂等性保证即用户可以反复调用API进行出错数据的写入操作。无模式写入TDengine的主要处理逻辑请参考TDengine的官方网站此处不赘述。
## 时间分辨率识别
无模式写入支持3个指定的模式如下表所示
| **序号** | **值** | **说明** |
| -------- | ------------------- | ------------------------------- |
| 1 | SML_LINE_PROTOCOL | InfluxDB 行协议Line Protocol) |
| 2 | SML_TELNET_PROTOCOL | OpenTSDB 文本行协议 |
| 3 | SML_JSON_PROTOCOL | JSON 协议格式 |
在 SML_LINE_PROTOCOL 解析模式下,需要用户指定输入的时间戳的时间分辨率。可用的时间分辨率如下表所示:
| **序号** | **时间分辨率定义** | **含义** |
| -------- | --------------------------------- | -------------- |
| 1 | TSDB_SML_TIMESTAMP_NOT_CONFIGURED | 未定义(无效) |
| 2 | TSDB_SML_TIMESTAMP_HOURS | 小时 |
| 3 | TSDB_SML_TIMESTAMP_MINUTES | 分钟 |
| 4 | TSDB_SML_TIMESTAMP_SECONDS | 秒 |
| 5 | TSDB_SML_TIMESTAMP_MILLI_SECONDS | 毫秒 |
| 6 | TSDB_SML_TIMESTAMP_MICRO_SECONDS | 微秒 |
| 7 | TSDB_SML_TIMESTAMP_NANO_SECONDS | 纳秒 |
在 SML_TELNET_PROTOCOL 和 SML_JSON_PROTOCOL 模式下,根据时间戳的长度来确定时间精度(与 OpenTSDB 标准操作方式相同),此时会忽略用户指定的时间分辨率。
## 数据模式映射规则
InfluxDB行协议的数据将被映射成具有模式的数据其中measurement映射为超级表名称tag_set中的标签名称映射为数据模式中的标签名field_set中的名称映射为列名称。例如下面的数据。
```json
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4f64 1626006833639000000
```
该行数据映射生成一个超级表: st 其包含了 3 个类型为 nchar 的标签分别是t1, t2, t3。五个数据列分别是 tstimestampc1 (bigintc3(binary)c2 (bool), c4 (bigint。映射成为如下 SQL 语句:
```json
create stable st (_ts timestamp, c1 bigint, c2 bool, c3 binary(6), c4 bigint) tags(t1 nchar(1), t2 nchar(1), t3 nchar(2))
```
## 数据模式变更处理
本节将说明不同行数据写入情况下,对于数据模式的影响。
在使用行协议写入一个明确的标识的字段类型的时候,后续更改该字段的类型定义,会出现明确的数据模式错误,即会触发写入 API 报告错误。如下所示,
```json
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4 1626006833639000000
st,t1=3,t2=4,t3=t3 c1=3i64,c3="passit",c2=false,c4=4i 1626006833640000000
```
第一行的数据类型映射将 c4 列定义为 Double 但是第二行的数据又通过数值后缀方式声明该列为 BigInt 由此会触发无模式写入的解析错误。
如果列前面的行协议将数据列声明为了 binary 后续的要求长度更长的 binary 长度,此时会触发超级表模式的变更。
```json
st,t1=3,t2=4,t3=t3 c1=3i64,c5="pass" 1626006833639000000
st,t1=3,t2=4,t3=t3 c1=3i64,c5="passit" 1626006833640000000
```
第一行中行协议解析会声明 c5 列是一个 binary(4)的字段,第二次行数据写入会提取列 c5 仍然是 binary 列,但是其宽度为 6此时需要将 binary 的宽度增加到能够容纳 新字符串的宽度。
```json
st,t1=3,t2=4,t3=t3 c1=3i64 1626006833639000000
st,t1=3,t2=4,t3=t3 c1=3i64,c6="passit" 1626006833640000000
```
第二行数据相对于第一行来说增加了一个列 c6类型为 binary(6)。那么此时会自动增加一个列 c6 类型为 binary(6)。
## 无模式写入示例
<Tabs defaultValue="java" groupId="schemaless">
<TabItem value="java" label="Java">
```java
public class SchemalessWsTest {
private static final String host = "127.0.0.1";
private static final String lineDemo = "meters,groupid=2,location=California.SanFrancisco current=10.3000002f64,voltage=219i32,phase=0.31f64 1626006833639000000";
private static final String telnetDemo = "stb0_0 1707095283260 4 host=host0 interface=eth0";
private static final String jsonDemo = "{\"metric\": \"meter_current\",\"timestamp\": 1626846400,\"value\": 10.3, \"tags\": {\"groupid\": 2, \"location\": \"California.SanFrancisco\", \"id\": \"d1001\"}}";
public static void main(String[] args) throws SQLException {
final String url = "jdbc:TAOS-RS://" + host + ":6041/?user=root&password=taosdata&batchfetch=true";
try(Connection connection = DriverManager.getConnection(url)){
init(connection);
try(SchemalessWriter writer = new SchemalessWriter(connection, "power")){
writer.write(lineDemo, SchemalessProtocolType.LINE, SchemalessTimestampType.NANO_SECONDS);
writer.write(telnetDemo, SchemalessProtocolType.TELNET, SchemalessTimestampType.MILLI_SECONDS);
writer.write(jsonDemo, SchemalessProtocolType.JSON, SchemalessTimestampType.SECONDS);
}
}
}
private static void init(Connection connection) throws SQLException {
try (Statement stmt = connection.createStatement()) {
stmt.execute("CREATE DATABASE IF NOT EXISTS power");
stmt.execute("USE power");
}
}
}
```
执行带有 reqId 的无模式写入,此 reqId 可用于请求链路追踪。
```java
writer.write(lineDemo, SchemalessProtocolType.LINE, SchemalessTimestampType.NANO_SECONDS, 1L);
```
</TabItem>
</Tabs>
## 查询写入的数据
运行上节的样例代码,会在 power 数据库中自动建表,我们可以通过 taos shell 或者应用程序来查询数据。下面给出用 taos shell 查询超级表和 meters 表数据的样例。
```shell
taos> show power.stables;
stable_name |
=================================
meter_current |
stb0_0 |
meters |
Query OK, 3 row(s) in set (0.002527s)
taos> select * from power.meters limit 1 \G;
*************************** 1.row ***************************
_ts: 2021-07-11 20:33:53.639
current: 10.300000199999999
voltage: 219
phase: 0.310000000000000
groupid: 2
location: California.SanFrancisco
Query OK, 1 row(s) in set (0.004501s)
```

View File

@ -0,0 +1,68 @@
---
title: 参数绑定写入
sidebar_label: 参数绑定
toc_max_heading_level: 4
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
通过参数绑定方式写入数据时能避免SQL语法解析的资源消耗从而显著提升写入性能。示例代码如下。
<Tabs defaultValue="java" groupId="stmt">
<TabItem value="java" label="Java">
```java
public class WSParameterBindingBasicDemo {
// modify host to your own
private static final String host = "127.0.0.1";
private static final Random random = new Random(System.currentTimeMillis());
private static final int numOfSubTable = 10, numOfRow = 10;
public static void main(String[] args) throws SQLException {
String jdbcUrl = "jdbc:TAOS-RS://" + host + ":6041/?batchfetch=true";
Connection conn = DriverManager.getConnection(jdbcUrl, "root", "taosdata");
init(conn);
String sql = "INSERT INTO ? USING meters TAGS(?,?) VALUES (?,?,?,?)";
try (TSWSPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSWSPreparedStatement.class)) {
for (int i = 1; i <= numOfSubTable; i++) {
// set table name
pstmt.setTableName("d_bind_" + i);
// set tags
pstmt.setTagInt(0, i);
pstmt.setTagString(1, "location_" + i);
// set columns
long current = System.currentTimeMillis();
for (int j = 0; j < numOfRow; j++) {
pstmt.setTimestamp(1, new Timestamp(current + j));
pstmt.setFloat(2, random.nextFloat() * 30);
pstmt.setInt(3, random.nextInt(300));
pstmt.setFloat(4, random.nextFloat());
pstmt.addBatch();
}
pstmt.executeBatch();
}
}
conn.close();
}
private static void init(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
stmt.execute("CREATE DATABASE IF NOT EXISTS power");
stmt.execute("USE power");
stmt.execute("CREATE STABLE IF NOT EXISTS meters (ts TIMESTAMP, current FLOAT, voltage INT, phase FLOAT) TAGS (groupId INT, location BINARY(24))");
}
}
}
```
</TabItem>
</Tabs>

View File

@ -1,3 +1,9 @@
---
title: 高效写入
sidebar_label: 高效写入
toc_max_heading_level: 4
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";

View File

@ -1,113 +0,0 @@
---
sidebar_label: 流式计算
description: "TDengine 流式计算将数据的写入、预处理、复杂分析、实时计算、报警触发等功能融为一体,是一个能够降低用户部署成本、存储成本和运维成本的计算引擎。"
title: 流式计算
---
在时序数据的处理中,经常要对原始数据进行清洗、预处理,再使用时序数据库进行长久的储存。在传统的时序数据解决方案中,常常需要部署 Kafka、Flink 等流处理系统。而流处理系统的复杂性,带来了高昂的开发与运维成本。
TDengine 3.0 的流式计算引擎提供了实时处理写入的数据流的能力,使用 SQL 定义实时流变换,当数据被写入流的源表后,数据会被以定义的方式自动处理,并根据定义的触发模式向目的表推送结果。它提供了替代复杂流处理系统的轻量级解决方案,并能够在高吞吐的数据写入的情况下,提供毫秒级的计算结果延迟。
流式计算可以包含数据过滤标量函数计算含UDF以及窗口聚合支持滑动窗口、会话窗口、状态窗口、事件窗口与计数窗口可以以超级表、子表、普通表为源表写入到目的超级表。在创建流时目的超级表将被自动创建随后新插入的数据会被流定义的方式处理并写入其中通过 partition by 子句,可以以表名或标签划分 partition不同的 partition 将写入到目的超级表的不同子表。
TDengine 的流式计算能够支持分布在多个 vnode 中的超级表聚合;还能够处理乱序数据的写入:它提供了 watermark 机制以度量容忍数据乱序的程度,并提供了 ignore expired 配置项以决定乱序数据的处理策略——丢弃或者重新计算。
详见 [流式计算](../../taos-sql/stream)
## 流式计算的创建
```sql
CREATE STREAM [IF NOT EXISTS] stream_name [stream_options] INTO stb_name AS subquery
stream_options: {
TRIGGER [AT_ONCE | WINDOW_CLOSE | MAX_DELAY time]
WATERMARK time
IGNORE EXPIRED [0 | 1]
}
```
详细的语法规则参考 [流式计算](../../taos-sql/stream)
## 示例一
企业电表的数据经常都是成百上千亿条的,那么想要将这些分散、凌乱的数据清洗或转换都需要比较长的时间,很难做到高效性和实时性,以下例子中,通过流计算可以将电表电压大于 220V 的数据清洗掉,然后以 5 秒为窗口整合并计算出每个窗口中电流的最大值,最后将结果输出到指定的数据表中。
### 创建 DB 和原始数据表
首先准备数据,完成建库、建一张超级表和多张子表操作
```sql
DROP DATABASE IF EXISTS power;
CREATE DATABASE power;
USE power;
CREATE STABLE meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int);
CREATE TABLE d1001 USING meters TAGS ("California.SanFrancisco", 2);
CREATE TABLE d1002 USING meters TAGS ("California.SanFrancisco", 3);
CREATE TABLE d1003 USING meters TAGS ("California.LosAngeles", 2);
CREATE TABLE d1004 USING meters TAGS ("California.LosAngeles", 3);
```
### 创建流
```sql
create stream current_stream trigger at_once into current_stream_output_stb as select _wstart as wstart, _wend as wend, max(current) as max_current from meters where voltage <= 220 interval (5s);
```
### 写入数据
```sql
insert into d1001 values("2018-10-03 14:38:05.000", 10.30000, 219, 0.31000);
insert into d1001 values("2018-10-03 14:38:15.000", 12.60000, 218, 0.33000);
insert into d1001 values("2018-10-03 14:38:16.800", 12.30000, 221, 0.31000);
insert into d1002 values("2018-10-03 14:38:16.650", 10.30000, 218, 0.25000);
insert into d1003 values("2018-10-03 14:38:05.500", 11.80000, 221, 0.28000);
insert into d1003 values("2018-10-03 14:38:16.600", 13.40000, 223, 0.29000);
insert into d1004 values("2018-10-03 14:38:05.000", 10.80000, 223, 0.29000);
insert into d1004 values("2018-10-03 14:38:06.500", 11.50000, 221, 0.35000);
```
### 查询以观察结果
```sql
taos> select wstart, wend, max_current from current_stream_output_stb;
wstart | wend | max_current |
===========================================================================
2018-10-03 14:38:05.000 | 2018-10-03 14:38:10.000 | 10.30000 |
2018-10-03 14:38:15.000 | 2018-10-03 14:38:20.000 | 12.60000 |
Query OK, 2 rows in database (0.018762s)
```
## 示例二
依然以示例一中的数据为基础,我们已经采集到了每个智能电表的电流和电压数据,现在需要求出有功功率和无功功率,并将地域和电表名以符号 "." 拼接,然后以电表名称分组输出到新的数据表中。
### 创建 DB 和原始数据表
参考示例一 [创建 DB 和原始数据表](#创建-db-和原始数据表)
### 创建流
```sql
create stream power_stream trigger at_once into power_stream_output_stb as select ts, concat_ws(".", location, tbname) as meter_location, current*voltage*cos(phase) as active_power, current*voltage*sin(phase) as reactive_power from meters partition by tbname;
```
### 写入数据
参考示例一 [写入数据](#写入数据)
### 查询以观察结果
```sql
taos> select ts, meter_location, active_power, reactive_power from power_stream_output_stb;
ts | meter_location | active_power | reactive_power |
===================================================================================================================
2018-10-03 14:38:05.000 | California.LosAngeles.d1004 | 2307.834596289 | 688.687331847 |
2018-10-03 14:38:06.500 | California.LosAngeles.d1004 | 2387.415754896 | 871.474763418 |
2018-10-03 14:38:05.500 | California.LosAngeles.d1003 | 2506.240411679 | 720.680274962 |
2018-10-03 14:38:16.600 | California.LosAngeles.d1003 | 2863.424274422 | 854.482390839 |
2018-10-03 14:38:05.000 | California.SanFrancisco.d1001 | 2148.178871730 | 688.120784090 |
2018-10-03 14:38:15.000 | California.SanFrancisco.d1001 | 2598.589176205 | 890.081451418 |
2018-10-03 14:38:16.800 | California.SanFrancisco.d1001 | 2588.728381186 | 829.240910475 |
2018-10-03 14:38:16.650 | California.SanFrancisco.d1002 | 2175.595991997 | 555.520860397 |
Query OK, 8 rows in database (0.014753s)
```

View File

@ -0,0 +1,140 @@
---
title: 数据订阅
sidebar_label: 数据订阅
toc_max_heading_level: 4
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
TDengine提供了类似Kafka的数据订阅功能。本章以 WebSocket 连接方式为例介绍数据订阅的相关API以及使用方法。
## 创建主题
创建主题的示例代码如下。
<Tabs defaultValue="java" groupId="createTopic">
<TabItem value="java" label="Java">
```java
Connection connection = DriverManager.getConnection(url, properties);
Statement statement = connection.createStatement();
statement.executeUpdate("CREATE TOPIC IF NOT EXISTS topic_meters AS SELECT ts, current, voltage, phase, groupid, location FROM meters");
```
</TabItem>
</Tabs>
上述代码将使用SQL“select ts, current, voltage, phase, groupId, location from meters”创建一个名为topic_meters的订阅。使用该订阅所获取的消息中的每条记录都由该查询语句所选择的列组成。
**注意**
在TDengine中对于订阅查询有以下限制。
- 查询语句限制:订阅查询只能使用 select 语句不支持其他类型的SQL如 insert、update或delete等。
- 始数据查询:订阅查询只能查询原始数据,而不能查询聚合或计算结果。
- 时间顺序限制:订阅查询只能按照时间正序查询数据。
## 创建消费者
<Tabs defaultValue="java" groupId="createConsumer">
<TabItem value="java" label="Java">
```java
Properties config = new Properties();
config.setProperty("td.connect.type", "ws");
config.setProperty("bootstrap.servers", "localhost:6041");
config.setProperty("auto.offset.reset", "latest");
config.setProperty("msg.with.table.name", "true");
config.setProperty("enable.auto.commit", "true");
config.setProperty("auto.commit.interval.ms", "1000");
config.setProperty("group.id", "group1");
config.setProperty("client.id", "1");
config.setProperty("value.deserializer", "com.taosdata.example.AbsConsumerLoop$ResultDeserializer");
config.setProperty("value.deserializer.encoding", "UTF-8");
this.consumer = new TaosConsumer<(config);
```
</TabItem>
</Tabs>
相关参数说明如下:
1. td.connect.type 连接方式。jni表示使用动态库连接的方式ws/WebSocket表示使用 WebSocket 进行数据通信。默认为 jni 方式。
2. bootstrap.servers TDengine 服务端所在的ip:port如果使用 WebSocket 连接,则为 taosAdapter 所在的ip:port。
3. auto.offset.reset消费组订阅的初始位置earliest 从头开始订阅; latest 仅从最新数据开始订阅。
4. enable.auto.commit 是否允许自动提交。
5. group.id consumer: 所在的 group。
6. value.deserializer 结果集反序列化方法,可以继承 com.taosdata.jdbc.tmq.ReferenceDeserializer并指定结果集 bean实现反序列化。也可以继承 com.taosdata.jdbc.tmq.Deserializer根据 SQL 的 resultSet 自定义反序列化方式。
## 订阅消费数据
订阅消费数据的示例代码如下
<Tabs defaultValue="java" groupId="poll">
<TabItem value="java" label="Java">
```java
while (!shutdown.get()) {
ConsumerRecords<ResultBean records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<ResultBean record : records) {
ResultBean bean = record.value();
process(bean);
}
}
```
</TabItem>
</Tabs>
poll 每次调用获取一个消息,一个消息中可能有多个记录,需要循环处理。
## 指定订阅的 Offset
<Tabs defaultValue="java" groupId="seek">
<TabItem value="java" label="Java">
```java
// 获取订阅的 topicPartition
Set<TopicPartition assignment() throws SQLException;
// 获取 offset
long position(TopicPartition partition) throws SQLException;
Map<TopicPartition, Long position(String topic) throws SQLException;
Map<TopicPartition, Long beginningOffsets(String topic) throws SQLException;
Map<TopicPartition, Long endOffsets(String topic) throws SQLException;
Map<TopicPartition, OffsetAndMetadata committed(Set<TopicPartition partitions) throws SQLException;
// 指定下一次 poll 中使用的 offset
void seek(TopicPartition partition, long offset) throws SQLException;
void seekToBeginning(Collection<TopicPartition partitions) throws SQLException;
void seekToEnd(Collection<TopicPartition partitions) throws SQLException;
```
</TabItem>
</Tabs>
## 提交 Offset
`enable.auto.commit` 为 false 时,可以手动提交 offset。
<Tabs defaultValue="java" groupId="commit">
<TabItem value="java" label="Java">
```java
void commitSync() throws SQLException;
void commitSync(Map<TopicPartition, OffsetAndMetadata offsets) throws SQLException;
// 异步提交仅在 native 连接下有效
void commitAsync(OffsetCommitCallback<V callback) throws SQLException;
void commitAsync(Map<TopicPartition, OffsetAndMetadata offsets, OffsetCommitCallback<V callback) throws SQLException;
```
</TabItem>
</Tabs>
## 取消订阅和关闭消费
<Tabs defaultValue="java" groupId="close">
<TabItem value="java" label="Java">
```java
// 取消订阅
consumer.unsubscribe();
// 关闭消费
consumer.close()
```
</TabItem>
</Tabs>

View File

@ -1,860 +0,0 @@
---
sidebar_label: 数据订阅
description: "数据订阅与推送服务。写入到 TDengine 中的时序数据能够被自动推送到订阅客户端。"
title: 数据订阅
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
import Java from "./_sub_java.mdx";
import JavaWS from "./_sub_java_ws.mdx";
import Python from "./_sub_python.mdx";
import Go from "./_sub_go.mdx";
import Rust from "./_sub_rust.mdx";
import Node from "./_sub_node.mdx";
import CSharp from "./_sub_cs.mdx";
import CDemo from "./_sub_c.mdx";
为了帮助应用实时获取写入 TDengine 的数据或者以事件到达顺序处理数据TDengine 提供了类似 kafka 的数据订阅功能。这样在很多场景下,采用 TDengine 的时序数据处理系统不再需要集成消息队列产品,比如 kafka, 从而简化系统设计的复杂度,降低运营维护成本。
## 数据订阅介绍
### 主题
与 kafka 一样,你需要定义 topic, TDengine 的 topic 有三种,可以是数据库,超级表,或者一个 `SELECT` 语句,具体的语法参见 [CREATE TOPIC](../../taos-sql/tmq)。与其他消息队列软件相比,这是 TDengine 数据订阅功能的最大的优势,它提供了更大的灵活性,数据的颗粒度可以由应用随时调整,而且数据的过滤与预处理交给 TDengine而不是应用完成有效的减少传输的数据量与应用的复杂度。
如下图,每个 topic 涉及到的数据表可能分布在多个 vnode相当于 kafka 里的 partition 上,每个 vnode 上的数据保存在 WAL(Write-Ahead-Log) 文件中WAL 文件里的数据是顺序写入的(由于 WAL 文件中存储的不只有数据,还有元数据,写入消息等,所以数据的版本号不是连续的)。
![img_5.png](img_5.png)
TDengine 会为 WAL 文件自动创建索引以支持快速随机访问,并提供了灵活可配置的文件切换与保留机制,用户可以按需指定 WAL 文件保留的时间以及大小(详见 [CREATE DATABASE](../../taos-sql/database) 语句,由于消费是通过 WAL 实现的,所以应该根据写入消费速度来确定 WAL 的保存时长)。通过以上方式将 WAL 改造成了一个保留事件到达顺序的、可持久化的存储引擎。
对于 `SELECT` 语句形式的 topic在消费时TDengine 根据当前消费进度从 WAL 直接读取数据,并使用统一的查询引擎实现过滤、变换等操作,将数据推送给消费者。
### 生产者
写入 topic 相关联的数据表中数据的都是生产者,生产者实际生产的数据写入到了子表或普通表中,即表所在 vnode 的 WAL 里。
### 消费者
#### 消费者组
消费者订阅 topic 后,可以消费 topic 里的所有数据(这些数据所在的表可能分布在多个 vnode 上,即 db 所在的所有 vnode。订阅 topic 时,需要指定一个消费者组 (consumer group),如果这个消费者组里只有一个消费者,那么这个消费者会顺序的消费这些 vnode 上的数据。
为了提高消费速度,便于多线程、分布式地消费数据,可以在一个消费组里添加多个消费者,这些消费者将均分数据所在的 vnode 进行消费(比如数据分布在 4 个 vnode 上,有 2 个消费者的话,那么每个消费者消费 2 个 vnode有 3 个消费者的话2 个消费者各消费 1 个 vnode1 个消费者消费 2 个 vnode有 5 个消费者的话4 个各分配 1 个 vnode 消费,另外 1 个不消费),如下图:
![img_6.png](img_6.png)
在一个消费组里添加一个消费者后,在 Mnode 上通过 rebalance 的机制实现消费者的重新分配,该操作对用户是透明的。
一个消费者可以订阅多个 topic。TDengine 的数据订阅在宕机、重启等复杂环境下确保 at least once 消费。
#### 消费进度
在 topic 的一个消费组的一个 vnode 上有消费进度。消费者消费的同时,可以提交消费进度,消费进度即 vnode 上 WAL 的版本号(对于 kafka 里的 offset消费进度可以手动提交也可以通过参数auto.commit.interval.ms设置为周期性自动提交。
首次消费数据时通过订阅参数auto.offset.reset来确定消费位置为最新数据latest还是最旧数据earliest
消费进度在一个 vnode 上对于同一个 topic 和 消费者组是唯一的。所以如果同一个 topic 和 消费者组在一个 vnode 上的消费者退出了,并且提交了消费进度。然后同一个 topic 和 消费者组里重新建了一个新的消费者消费这个 vnode那么这个新消费者将继承之前的消费进度继续消费。
如果之前的消费者没有提交消费进度那个新的消费者将根据订阅参数auto.offset.reset设置的值来确定起始消费位置。
不同消费者组中的消费者即使消费同一个 topic, 并不共享消费进度。
![img_7.png](img_7.png)
作为一个数据库产品, WAL 文件中存储的不全是数据,也包括其他写入消息,元数据等,所以消费进度不是连续的。
### 说明
从3.2.0.0版本开始数据订阅支持vnode迁移和分裂。
由于数据订阅依赖wal文件而在vnode迁移和分裂的过程中wal并不会同步过去所以迁移或分裂后之前没消费完的wal数据后消费不到。所以请保证迁移和分裂之前把数据全部消费完后再进行vnode迁移或分裂否则消费会丢失数据。
## 数据订阅语法说明
具体的语法参见 [数据订阅](../../taos-sql/tmq)
## 数据订阅相关参数
消费参数主要用于消费者创建时指定,基础配置项如下表所示:
| 参数名称 | 类型 | 参数说明 | 备注 |
| :----------------------------: | :-----: | -------------------------------------------------------- | ------------------------------------------- |
| `td.connect.ip` | string | 服务端的 IP 地址 | |
| `td.connect.user` | string | 用户名 | |
| `td.connect.pass` | string | 密码 | |
| `td.connect.port` | integer | 服务端的端口号 | |
| `group.id` | string | 消费组 ID同一消费组共享消费进度 | <br />**必填项**。最大长度192。<br />每个topic最多可建立100个 consumer group |
| `client.id` | string | 客户端 ID | 最大长度192。 |
| `auto.offset.reset` | enum | 消费组订阅的初始位置 | <br />`earliest`: default(version < 3.2.0.0);从头开始订阅; <br/>`latest`: default(version >= 3.2.0.0);仅从最新数据开始订阅; <br/>`none`: 没有提交的 offset 无法订阅 |
| `enable.auto.commit` | boolean | 是否启用消费位点自动提交true: 自动提交客户端应用无需commitfalse客户端应用需要自行commit | 默认值为 true |
| `auto.commit.interval.ms` | integer | 消费记录自动提交消费位点时间间隔,单位为毫秒 | 默认值为 5000 |
| `msg.with.table.name` | boolean | 是否允许从消息中解析表名, 不适用于列订阅(列订阅时可将 tbname 作为列写入 subquery 语句从3.2.0.0版本该参数废弃恒为true |默认关闭 |
| `enable.replay` | boolean | 是否开启数据回放功能 |默认关闭 |
## 数据订阅主要 API 接口
不同语言下, TMQ 订阅相关的 API 及数据结构如下详细的接口说明可以参考连接器章节数据订阅部分注意consumer结构不是线程安全的在一个线程使用consumer时不要在另一个线程close这个consumer
<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">
```c
typedef struct tmq_t tmq_t;
typedef struct tmq_conf_t tmq_conf_t;
typedef struct tmq_list_t tmq_list_t;
typedef void(tmq_commit_cb(tmq_t *tmq, int32_t code, void *param));
typedef enum tmq_conf_res_t {
TMQ_CONF_UNKNOWN = -2,
TMQ_CONF_INVALID = -1,
TMQ_CONF_OK = 0,
} tmq_conf_res_t;
typedef struct tmq_topic_assignment {
int32_t vgId;
int64_t currentOffset;
int64_t begin;
int64_t end;
} tmq_topic_assignment;
DLL_EXPORT tmq_conf_t *tmq_conf_new();
DLL_EXPORT tmq_conf_res_t tmq_conf_set(tmq_conf_t *conf, const char *key, const char *value);
DLL_EXPORT void tmq_conf_destroy(tmq_conf_t *conf);
DLL_EXPORT void tmq_conf_set_auto_commit_cb(tmq_conf_t *conf, tmq_commit_cb *cb, void *param);
DLL_EXPORT tmq_list_t *tmq_list_new();
DLL_EXPORT int32_t tmq_list_append(tmq_list_t *, const char *);
DLL_EXPORT void tmq_list_destroy(tmq_list_t *);
DLL_EXPORT int32_t tmq_list_get_size(const tmq_list_t *);
DLL_EXPORT char **tmq_list_to_c_array(const tmq_list_t *);
DLL_EXPORT tmq_t *tmq_consumer_new(tmq_conf_t *conf, char *errstr, int32_t errstrLen);
DLL_EXPORT int32_t tmq_subscribe(tmq_t *tmq, const tmq_list_t *topic_list);
DLL_EXPORT int32_t tmq_unsubscribe(tmq_t *tmq);
DLL_EXPORT int32_t tmq_subscription(tmq_t *tmq, tmq_list_t **topics);
DLL_EXPORT TAOS_RES *tmq_consumer_poll(tmq_t *tmq, int64_t timeout);
DLL_EXPORT int32_t tmq_consumer_close(tmq_t *tmq);
DLL_EXPORT int32_t tmq_commit_sync(tmq_t *tmq, const TAOS_RES *msg);
DLL_EXPORT void tmq_commit_async(tmq_t *tmq, const TAOS_RES *msg, tmq_commit_cb *cb, void *param);
DLL_EXPORT int32_t tmq_commit_offset_sync(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset);
DLL_EXPORT void tmq_commit_offset_async(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset, tmq_commit_cb *cb, void *param);
DLL_EXPORT int32_t tmq_get_topic_assignment(tmq_t *tmq, const char *pTopicName, tmq_topic_assignment **assignment,int32_t *numOfAssignment);
DLL_EXPORT void tmq_free_assignment(tmq_topic_assignment* pAssignment);
DLL_EXPORT int32_t tmq_offset_seek(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset);
DLL_EXPORT int64_t tmq_position(tmq_t *tmq, const char *pTopicName, int32_t vgId);
DLL_EXPORT int64_t tmq_committed(tmq_t *tmq, const char *pTopicName, int32_t vgId);
DLL_EXPORT const char *tmq_get_topic_name(TAOS_RES *res);
DLL_EXPORT const char *tmq_get_db_name(TAOS_RES *res);
DLL_EXPORT int32_t tmq_get_vgroup_id(TAOS_RES *res);
DLL_EXPORT int64_t tmq_get_vgroup_offset(TAOS_RES* res);
DLL_EXPORT const char *tmq_err2str(int32_t code);
```
下面介绍一下它们的具体用法(超级表和子表结构请参考“数据建模”一节),完整的示例代码请见下面 C 语言的示例代码。
</TabItem>
<TabItem value="java" label="Java">
```java
void subscribe(Collection<String> topics) throws SQLException;
void unsubscribe() throws SQLException;
Set<String> subscription() throws SQLException;
ConsumerRecords<V> poll(Duration timeout) throws SQLException;
Set<TopicPartition> assignment() throws SQLException;
long position(TopicPartition partition) throws SQLException;
Map<TopicPartition, Long> position(String topic) throws SQLException;
Map<TopicPartition, Long> beginningOffsets(String topic) throws SQLException;
Map<TopicPartition, Long> endOffsets(String topic) throws SQLException;
Map<TopicPartition, OffsetAndMetadata> committed(Set<TopicPartition> partitions) throws SQLException;
void seek(TopicPartition partition, long offset) throws SQLException;
void seekToBeginning(Collection<TopicPartition> partitions) throws SQLException;
void seekToEnd(Collection<TopicPartition> partitions) throws SQLException;
void commitSync() throws SQLException;
void commitSync(Map<TopicPartition, OffsetAndMetadata> offsets) throws SQLException;
void close() throws SQLException;
```
</TabItem>
<TabItem value="Python" label="Python">
```python
class Consumer:
def subscribe(self, topics):
pass
def unsubscribe(self):
pass
def poll(self, timeout: float = 1.0):
pass
def assignment(self):
pass
def seek(self, partition):
pass
def close(self):
pass
def commit(self, message):
pass
```
</TabItem>
<TabItem label="Go" value="Go">
```go
func NewConsumer(conf *tmq.ConfigMap) (*Consumer, error)
// 出于兼容目的保留 rebalanceCb 参数,当前未使用
func (c *Consumer) Subscribe(topic string, rebalanceCb RebalanceCb) error
// 出于兼容目的保留 rebalanceCb 参数,当前未使用
func (c *Consumer) SubscribeTopics(topics []string, rebalanceCb RebalanceCb) error
func (c *Consumer) Poll(timeoutMs int) tmq.Event
// 出于兼容目的保留 tmq.TopicPartition 参数,当前未使用
func (c *Consumer) Commit() ([]tmq.TopicPartition, error)
func (c *Consumer) Unsubscribe() error
func (c *Consumer) Close() error
```
</TabItem>
<TabItem label="Rust" value="Rust">
```rust
impl TBuilder for TmqBuilder
fn from_dsn<D: IntoDsn>(dsn: D) -> Result<Self, Self::Error>
fn build(&self) -> Result<Self::Target, Self::Error>
impl AsAsyncConsumer for Consumer
async fn subscribe<T: Into<String>, I: IntoIterator<Item = T> + Send>(
&mut self,
topics: I,
) -> Result<(), Self::Error>;
fn stream(
&self,
) -> Pin<
Box<
dyn '_
+ Send
+ futures::Stream<
Item = Result<(Self::Offset, MessageSet<Self::Meta, Self::Data>), Self::Error>,
>,
>,
>;
async fn commit(&self, offset: Self::Offset) -> Result<(), Self::Error>;
async fn unsubscribe(self);
```
可在 \<https://docs.rs/taos> 上查看详细 API 说明。
</TabItem>
<TabItem label="Node.JS" value="Node.JS">
```js
function TMQConsumer(config)
function subscribe(topic)
function consume(timeout)
function subscription()
function unsubscribe()
function commit(msg)
function close()
```
</TabItem>
<TabItem value="C#" label="C#">
```csharp
class ConsumerBuilder<TValue>
ConsumerBuilder(IEnumerable<KeyValuePair<string, string>> config)
public IConsumer<TValue> Build()
void Subscribe(IEnumerable<string> topics)
void Subscribe(string topic)
ConsumeResult<TValue> Consume(int millisecondsTimeout)
List<string> Subscription()
void Unsubscribe()
List<TopicPartitionOffset> Commit()
void Close()
```
</TabItem>
</Tabs>
## 数据订阅示例
### 写入数据
首先完成建库、建一张超级表和多张子表操作,然后就可以写入数据了,比如:
```sql
DROP DATABASE IF EXISTS tmqdb;
CREATE DATABASE tmqdb WAL_RETENTION_PERIOD 3600;
CREATE TABLE tmqdb.stb (ts TIMESTAMP, c1 INT, c2 FLOAT, c3 VARCHAR(16)) TAGS(t1 INT, t3 VARCHAR(16));
CREATE TABLE tmqdb.ctb0 USING tmqdb.stb TAGS(0, "subtable0");
CREATE TABLE tmqdb.ctb1 USING tmqdb.stb TAGS(1, "subtable1");
INSERT INTO tmqdb.ctb0 VALUES(now, 0, 0, 'a0')(now+1s, 0, 0, 'a00');
INSERT INTO tmqdb.ctb1 VALUES(now, 1, 1, 'a1')(now+1s, 11, 11, 'a11');
```
### 创建 topic
使用 SQL 创建一个 topic
```sql
CREATE TOPIC topic_name AS SELECT ts, c1, c2, c3 FROM tmqdb.stb WHERE c1 > 1;
```
### 创建消费者 consumer
对于不同编程语言,其设置方式如下:
<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">
```c
/* 根据需要,设置消费组 (group.id)、自动提交 (enable.auto.commit)、
自动提交时间间隔 (auto.commit.interval.ms)、用户名 (td.connect.user)、密码 (td.connect.pass) 等参数 */
tmq_conf_t* conf = tmq_conf_new();
tmq_conf_set(conf, "enable.auto.commit", "true");
tmq_conf_set(conf, "auto.commit.interval.ms", "1000");
tmq_conf_set(conf, "group.id", "cgrpName");
tmq_conf_set(conf, "td.connect.user", "root");
tmq_conf_set(conf, "td.connect.pass", "taosdata");
tmq_conf_set(conf, "auto.offset.reset", "latest");
tmq_conf_set(conf, "msg.with.table.name", "true");
tmq_conf_set_auto_commit_cb(conf, tmq_commit_cb_print, NULL);
tmq_t* tmq = tmq_consumer_new(conf, NULL, 0);
tmq_conf_destroy(conf);
```
</TabItem>
<TabItem value="java" label="Java">
对于 Java 程序,还可以使用如下配置项:
| 参数名称 | 类型 | 参数说明 |
| ----------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `td.connect.type` | string | 连接类型,"jni" 指原生连接,"ws" 指 websocket 连接,默认值为 "jni" |
| `bootstrap.servers` | string | 连接地址,如 `localhost:6030` |
| `value.deserializer` | string | 值解析方法,使用此方法应实现 `com.taosdata.jdbc.tmq.Deserializer` 接口或继承 `com.taosdata.jdbc.tmq.ReferenceDeserializer` 类 |
| `value.deserializer.encoding` | string | 指定字符串解析的字符集 | |
需要注意:此处使用 `bootstrap.servers` 替代 `td.connect.ip` 和 `td.connect.port`,以提供与 Kafka 一致的接口。
```java
Properties properties = new Properties();
properties.setProperty("enable.auto.commit", "true");
properties.setProperty("auto.commit.interval.ms", "1000");
properties.setProperty("group.id", "cgrpName");
properties.setProperty("bootstrap.servers", "127.0.0.1:6030");
properties.setProperty("td.connect.user", "root");
properties.setProperty("td.connect.pass", "taosdata");
properties.setProperty("auto.offset.reset", "latest");
properties.setProperty("msg.with.table.name", "true");
properties.setProperty("value.deserializer", "com.taos.example.MetersDeserializer");
TaosConsumer<Meters> consumer = new TaosConsumer<>(properties);
/* value deserializer definition. */
import com.taosdata.jdbc.tmq.ReferenceDeserializer;
public class MetersDeserializer extends ReferenceDeserializer<Meters> {
}
```
</TabItem>
<TabItem label="Go" value="Go">
```go
conf := &tmq.ConfigMap{
"group.id": "test",
"auto.offset.reset": "latest",
"td.connect.ip": "127.0.0.1",
"td.connect.user": "root",
"td.connect.pass": "taosdata",
"td.connect.port": "6030",
"client.id": "test_tmq_c",
"enable.auto.commit": "false",
"msg.with.table.name": "true",
}
consumer, err := NewConsumer(conf)
```
</TabItem>
<TabItem label="Rust" value="Rust">
```rust
let mut dsn: Dsn = "taos://".parse()?;
dsn.set("group.id", "group1");
dsn.set("client.id", "test");
dsn.set("auto.offset.reset", "latest");
let tmq = TmqBuilder::from_dsn(dsn)?;
let mut consumer = tmq.build()?;
```
</TabItem>
<TabItem value="Python" label="Python">
Python 语言下引入 `taos` 库的 `Consumer` 类,创建一个 Consumer 示例:
```python
from taos.tmq import Consumer
# Syntax: `consumer = Consumer(configs)`
#
# Example:
consumer = Consumer(
{
"group.id": "local",
"client.id": "1",
"enable.auto.commit": "true",
"auto.commit.interval.ms": "1000",
"td.connect.ip": "127.0.0.1",
"td.connect.user": "root",
"td.connect.pass": "taosdata",
"auto.offset.reset": "latest",
"msg.with.table.name": "true",
}
)
```
</TabItem>
<TabItem label="Node.JS" value="Node.JS">
```js
// 根据需要,设置消费组 (group.id)、自动提交 (enable.auto.commit)、
// 自动提交时间间隔 (auto.commit.interval.ms)、用户名 (td.connect.user)、密码 (td.connect.pass) 等参数
let consumer = taos.consumer({
'enable.auto.commit': 'true',
'auto.commit.interval.ms','1000',
'group.id': 'tg2',
'td.connect.user': 'root',
'td.connect.pass': 'taosdata',
'auto.offset.reset','latest',
'msg.with.table.name': 'true',
'td.connect.ip','127.0.0.1',
'td.connect.port','6030'
});
```
</TabItem>
<TabItem value="C#" label="C#">
```csharp
var cfg = new Dictionary<string, string>()
{
{ "group.id", "group1" },
{ "auto.offset.reset", "latest" },
{ "td.connect.ip", "127.0.0.1" },
{ "td.connect.user", "root" },
{ "td.connect.pass", "taosdata" },
{ "td.connect.port", "6030" },
{ "client.id", "tmq_example" },
{ "enable.auto.commit", "true" },
{ "msg.with.table.name", "false" },
};
var consumer = new ConsumerBuilder<Dictionary<string, object>>(cfg).Build();
```
</TabItem>
</Tabs>
上述配置中包括 consumer group ID如果多个 consumer 指定的 consumer group ID 一样,则自动形成一个 consumer group共享消费进度。
### 订阅 topics
一个 consumer 支持同时订阅多个 topic。
<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">
```c
// 创建订阅 topics 列表
tmq_list_t* topicList = tmq_list_new();
tmq_list_append(topicList, "topicName");
// 启动订阅
tmq_subscribe(tmq, topicList);
tmq_list_destroy(topicList);
```
</TabItem>
<TabItem value="java" label="Java">
```java
List<String> topics = new ArrayList<>();
topics.add("tmq_topic");
consumer.subscribe(topics);
```
</TabItem>
<TabItem value="Go" label="Go">
```go
err = consumer.Subscribe("example_tmq_topic", nil)
if err != nil {
panic(err)
}
```
</TabItem>
<TabItem value="Rust" label="Rust">
```rust
consumer.subscribe(["tmq_meters"]).await?;
```
</TabItem>
<TabItem value="Python" label="Python">
```python
consumer.subscribe(['topic1', 'topic2'])
```
</TabItem>
<TabItem label="Node.JS" value="Node.JS">
```js
// 创建订阅 topics 列表
let topics = ['topic_test']
// 启动订阅
consumer.subscribe(topics);
```
</TabItem>
<TabItem value="C#" label="C#">
```csharp
// 创建订阅 topics 列表
List<String> topics = new List<string>();
topics.add("tmq_topic");
// 启动订阅
consumer.Subscribe(topics);
```
</TabItem>
</Tabs>
### 消费
以下代码展示了不同语言下如何对 TMQ 消息进行消费。
<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">
```c
// 消费数据
while (running) {
TAOS_RES* msg = tmq_consumer_poll(tmq, timeOut);
msg_process(msg);
}
```
这里是一个 **while** 循环,每调用一次 tmq_consumer_poll(),获取一个消息,该消息与普通查询返回的结果集完全相同,可以使用相同的解析 API 完成消息内容的解析。
</TabItem>
<TabItem value="java" label="Java">
```java
while(running){
ConsumerRecords<Meters> meters = consumer.poll(Duration.ofMillis(100));
for (Meters meter : meters) {
processMsg(meter);
}
}
```
</TabItem>
<TabItem value="Go" label="Go">
```go
for {
ev := consumer.Poll(0)
if ev != nil {
switch e := ev.(type) {
case *tmqcommon.DataMessage:
fmt.Println(e.Value())
case tmqcommon.Error:
fmt.Fprintf(os.Stderr, "%% Error: %v: %v\n", e.Code(), e)
panic(e)
}
consumer.Commit()
}
}
```
</TabItem>
<TabItem value="Rust" label="Rust">
```rust
{
let mut stream = consumer.stream();
while let Some((offset, message)) = stream.try_next().await? {
// get information from offset
// the topic
let topic = offset.topic();
// the vgroup id, like partition id in kafka.
let vgroup_id = offset.vgroup_id();
println!("* in vgroup id {vgroup_id} of topic {topic}\n");
if let Some(data) = message.into_data() {
while let Some(block) = data.fetch_raw_block().await? {
// one block for one table, get table name if needed
let name = block.table_name();
let records: Vec<Record> = block.deserialize().try_collect()?;
println!(
"** table: {}, got {} records: {:#?}\n",
name.unwrap(),
records.len(),
records
);
}
}
consumer.commit(offset).await?;
}
}
```
</TabItem>
<TabItem value="Python" label="Python">
```python
while True:
res = consumer.poll(100)
if not res:
continue
err = res.error()
if err is not None:
raise err
val = res.value()
for block in val:
print(block.fetchall())
```
</TabItem>
<TabItem label="Node.JS" value="Node.JS">
```js
while(true){
msg = consumer.consume(200);
// process message(consumeResult)
console.log(msg.topicPartition);
console.log(msg.block);
console.log(msg.fields)
}
```
</TabItem>
<TabItem value="C#" label="C#">
```csharp
// 消费数据
while (true)
{
using (var result = consumer.Consume(500))
{
if (result == null) continue;
ProcessMsg(result);
consumer.Commit();
}
}
```
</TabItem>
</Tabs>
### 结束消费
消费结束后,应当取消订阅。
<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">
```c
/* 取消订阅 */
tmq_unsubscribe(tmq);
/* 关闭消费者对象 */
tmq_consumer_close(tmq);
```
</TabItem>
<TabItem value="java" label="Java">
```java
/* 取消订阅 */
consumer.unsubscribe();
/* 关闭消费 */
consumer.close();
```
</TabItem>
<TabItem value="Go" label="Go">
```go
/* Unsubscribe */
_ = consumer.Unsubscribe()
/* Close consumer */
_ = consumer.Close()
```
</TabItem>
<TabItem value="Rust" label="Rust">
```rust
consumer.unsubscribe().await;
```
</TabItem>
<TabItem value="Python" label="Python">
```py
# 取消订阅
consumer.unsubscribe()
# 关闭消费
consumer.close()
```
</TabItem>
<TabItem label="Node.JS" value="Node.JS">
```js
consumer.unsubscribe();
consumer.close();
```
</TabItem>
<TabItem value="C#" label="C#">
```csharp
// 取消订阅
consumer.Unsubscribe();
// 关闭消费
consumer.Close();
```
</TabItem>
</Tabs>
### 完整示例代码
以下是各语言的完整示例代码。
<Tabs defaultValue="java" groupId="lang">
<TabItem label="C" value="c">
<CDemo />
</TabItem>
<TabItem label="Java" value="java">
<Tabs defaultValue="native">
<TabItem value="native" label="本地连接">
<Java />
</TabItem>
<TabItem value="ws" label="WebSocket 连接">
<JavaWS />
</TabItem>
</Tabs>
</TabItem>
<TabItem label="Go" value="Go">
<Go/>
</TabItem>
<TabItem label="Rust" value="Rust">
<Rust />
</TabItem>
<TabItem label="Python" value="Python">
<Python />
</TabItem>
<TabItem label="Node.JS" value="Node.JS">
<Node/>
</TabItem>
<TabItem label="C#" value="C#">
<CSharp/>
</TabItem>
</Tabs>
## 数据订阅高级功能
### 数据回放
- 订阅支持 replay 功能,按照数据写入的时间回放。
比如,如下时间写入三条数据
```sql
2023/09/22 00:00:00.000
2023/09/22 00:00:05.000
2023/09/22 00:00:08.000
```
则订阅出第一条数据 5s 后返回第二条数据,获取第二条数据 3s 后返回第三条数据。
- 仅查询订阅支持数据回放
- 回放需要保证独立时间线
- 如果是子表订阅或者普通表订阅只有一个vnode上有数据保证是一个时间线
- 如果超级表订阅,则需保证该 DB 只有一个vnode否则报错因为多个vnode上订阅出的数据不在一个时间线上
- 超级表和库订阅不支持回放
- enable.replay 参数true表示开启订阅回放功能false表示不开启订阅回放功能默认不开启。
- 回放不支持进度保存,所以回放参数 enable.replay = true 时auto commit 自动关闭
- 因为数据回放本身需要处理时间所以回放的精度存在几十ms的误差

View File

@ -1,49 +0,0 @@
---
sidebar_label: 缓存
title: 缓存
description: "TDengine 内部的缓存设计"
---
为了实现高效的写入和查询TDengine 充分利用了各种缓存技术,本节将对 TDengine 中对缓存的使用做详细的说明。
## 写缓存
TDengine 采用时间驱动缓存管理策略First-In-First-OutFIFO又称为写驱动的缓存管理机制。这种策略有别于读驱动的数据缓存模式Least-Recent-UsedLRU直接将最近写入的数据保存在系统的缓存中。当缓存达到临界值的时候将最早的数据批量写入磁盘。一般意义上来说对于物联网数据的使用用户最为关心最近产生的数据即当前状态。TDengine 充分利用了这一特性,将最近到达的(当前状态)数据保存在缓存中。
每个 vnode 的写入缓存大小在创建数据库时决定,创建数据库时的两个关键参数 vgroups 和 buffer 分别决定了该数据库中的数据由多少个 vgroup 处理,以及向其中的每个 vnode 分配多少写入缓存。buffer 的单位是MB。
```sql
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。
```sql
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 的数据被立即同步到磁盘上,可靠性更高
## 客户端缓存
为了进一步提升整个系统的处理效率,除了以上提到的服务端缓存技术之外,在 TDengine 的所有客户端都要调用的核心库 libtaos.so (也称为 taosc )中也充分利用了缓存技术。在 taosc 中会缓存所访问过的各个数据库、超级表以及子表的元数据,集群的拓扑结构等关键元数据。
当有多个客户端同时访问 TDengine 集群,且其中一个客户端对某些元数据进行了修改的情况下,有可能会出现其它客户端所缓存的元数据不同步或失效的情况,此时需要在客户端执行 "reset query cache" 以让整个缓存失效从而强制重新拉取最新的元数据重新建立缓存。

View File

@ -1,16 +1,16 @@
---
sidebar_label: 用户定义函数
title: UDF用户定义函数
description: "支持用户编码的聚合函数和标量函数,在查询中嵌入并使用用户定义函数,拓展查询的能力和功能。"
sidebar_label: UDF
title: 用户定义函数
toc_max_heading_level: 4
---
在有些应用场景中,应用逻辑需要的查询无法直接使用系统内置的函数来表示。利用 UDF(User Defined Function) 功能TDengine 可以插入用户编写的处理代码并在查询中使用它们,就能够很方便地解决特殊应用场景中的使用需求。 UDF 通常以数据表中的一列数据做为输入,同时支持以嵌套子查询的结果作为输入。
## UDF 简介
用户可以通过 UDF 实现两类函数:标量函数和聚合函数。标量函数对每行数据输出一个值,如求绝对值 abs正弦函数 sin字符串拼接函数 concat 等。聚合函数对多行数据进行输出一个值,如求平均数 avg最大值 max 等。
在某些应用场景中应用逻辑需要的查询功能无法直接使用TDengine内置的函数来实现。TDengine允许编写用户自定义函数UDF以便解决特殊应用场景中的使用需求。UDF在集群中注册成功后可以像系统内置函数一样在SQL中调用就使用角度而言没有任何区别。UDF分为标量函数和聚合函数。标量函数对每行数据输出一个值如求绝对值abs、正弦函数sin、字符串拼接函数concat等。聚合函数对多行数据输出一个值如求平均数avg、取最大值max等。
TDengine 支持通过 C/Python 语言进行 UDF 定义。接下来结合示例讲解 UDF 的使用方法
TDengine支持用C和Python两种编程语言编写UDF。C语言编写的UDF与内置函数的性能几乎相同Python语言编写的UDF可以利用丰富的Python运算库。为了避免UDF执行中发生异常影响数据库服务TDengine使用了进程分离技术把UDF的执行放到另一个进程中完成即使用户编写的UDF崩溃也不会影响TDengine的正常运行
## 用 C 语言实现 UDF
## 用 C 语言开发 UDF
使用 C 语言实现 UDF 时,需要实现规定的接口函数
- 标量函数需要实现标量接口函数 scalarfn 。
@ -19,331 +19,171 @@ TDengine 支持通过 C/Python 语言进行 UDF 定义。接下来结合示例
接口函数的名称是 UDF 名称,或者是 UDF 名称和特定后缀(`_start`, `_finish`, `_init`, `_destroy`)的连接。列表中的scalarfnaggfn, udf需要替换成udf函数名。
### 用 C 语言实现标量函数
标量函数实现模板如下
```c
#include "taos.h"
#include "taoserror.h"
#include "taosudf.h"
### 接口定义
// initialization function. if no initialization, we can skip definition of it. The initialization function shall be concatenation of the udf name and _init suffix
// @return error number defined in taoserror.h
int32_t scalarfn_init() {
// initialization.
return TSDB_CODE_SUCCESS;
}
// scalar function main computation function
// @param inputDataBlock, input data block composed of multiple columns with each column defined by SUdfColumn
// @param resultColumn, output column
// @return error number defined in taoserror.h
int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn* resultColumn) {
// read data from inputDataBlock and process, then output to resultColumn.
return TSDB_CODE_SUCCESS;
}
// cleanup function. if no cleanup related processing, we can skip definition of it. The destroy function shall be concatenation of the udf name and _destroy suffix.
// @return error number defined in taoserror.h
int32_t scalarfn_destroy() {
// clean up
return TSDB_CODE_SUCCESS;
}
```
scalarfn 为函数名的占位符需要替换成函数名如bit_and。
### 用 C 语言实现聚合函数
聚合函数的实现模板如下
```c
#include "taos.h"
#include "taoserror.h"
#include "taosudf.h"
// Initialization function. if no initialization, we can skip definition of it. The initialization function shall be concatenation of the udf name and _init suffix
// @return error number defined in taoserror.h
int32_t aggfn_init() {
// initialization.
return TSDB_CODE_SUCCESS;
}
// aggregate start function. The intermediate value or the state(@interBuf) is initialized in this function. The function name shall be concatenation of udf name and _start suffix
// @param interbuf intermediate value to initialize
// @return error number defined in taoserror.h
int32_t aggfn_start(SUdfInterBuf* interBuf) {
// initialize intermediate value in interBuf
return TSDB_CODE_SUCCESS;
}
// aggregate reduce function. This function aggregate old state(@interbuf) and one data bock(inputBlock) and output a new state(@newInterBuf).
// @param inputBlock input data block
// @param interBuf old state
// @param newInterBuf new state
// @return error number defined in taoserror.h
int32_t aggfn(SUdfDataBlock* inputBlock, SUdfInterBuf *interBuf, SUdfInterBuf *newInterBuf) {
// read from inputBlock and interBuf and output to newInterBuf
return TSDB_CODE_SUCCESS;
}
// aggregate function finish function. This function transforms the intermediate value(@interBuf) into the final output(@result). The function name must be concatenation of aggfn and _finish suffix.
// @interBuf : intermediate value
// @result: final result
// @return error number defined in taoserror.h
int32_t int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result) {
// read data from inputDataBlock and process, then output to result
return TSDB_CODE_SUCCESS;
}
// cleanup function. if no cleanup related processing, we can skip definition of it. The destroy function shall be concatenation of the udf name and _destroy suffix.
// @return error number defined in taoserror.h
int32_t aggfn_destroy() {
// clean up
return TSDB_CODE_SUCCESS;
}
```
aggfn为函数名的占位符需要修改为自己的函数名如l2norm。
### C 语言 UDF 接口函数定义
接口函数的名称是 udf 名称,或者是 udf 名称和特定后缀_start, _finish, _init, _destroy)的连接。以下描述中函数名称中的 scalarfnaggfn, udf 需要替换成udf函数名。
接口函数返回值表示是否成功。如果返回值是 TSDB_CODE_SUCCESS表示操作成功否则返回的是错误代码。错误代码定义在 taoserror.h和 taos.h 中的API共享错误码的定义。例如 TSDB_CODE_UDF_INVALID_INPUT 表示输入无效输入。TSDB_CODE_OUT_OF_MEMORY 表示内存不足。
接口函数参数类型见数据结构定义。
在TDengine中UDF的接口函数名称可以是UDF名称也可以是UDF名称和特定后缀如_start、_finish、_init、_destroy的连接。后面内容中描述的函数名称例如scalarfn、aggfn需要替换成UDF名称。。
#### 标量函数接口
`int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn *resultColumn)`
其中 scalarFn 是函数名的占位符。这个函数对数据块进行标量计算通过设置resultColumn结构体中的变量设置值
标量函数是一种将输入数据转换为输出数据的函数,通常用于对单个数据值进行计算和转换。标量函数的接口函数原型如下。
参数的具体含义是:
- inputDataBlock: 输入的数据块
- resultColumn: 输出列
```c
int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn *resultColumn)
```
主要参数说明如下。
- inputDataBlock输入的数据块。
- resultColumn输出列。
#### 聚合函数接口
`int32_t aggfn_start(SUdfInterBuf *interBuf)`
聚合函数是一种特殊的函数,用于对数据进行分组和计算,从而生成汇总信息。聚合函数的工作原理如下。
- 初始化结果缓冲区首先调用aggfn_start函数生成一个结果缓冲区result buffer用于存储中间结果。
- 分组数据相关数据会被分为多个行数据块row data block每个行数据块包含一组具有相同分组键grouping key的数据。
- 更新中间结果对于每个数据块调用aggfn函数更新中间结果。aggfn函数会根据聚合函数的类型如sum、avg、count等对数据进行相应的计算并将计算结
果存储在结果缓冲区中。
- 生成最终结果在所有数据块的中间结果更新完成后调用aggfn_finish函数从结果缓冲区中提取最终结果。最终结果通常只包含0条或1条数据具体取决于聚
合函数的类型和输入数据。
`int32_t aggfn(SUdfDataBlock* inputBlock, SUdfInterBuf *interBuf, SUdfInterBuf *newInterBuf)`
聚合函数的接口函数原型如下。
```c
int32_t aggfn_start(SUdfInterBuf *interBuf)
int32_t aggfn(SUdfDataBlock* inputBlock, SUdfInterBuf *interBuf, SUdfInterBuf *newInterBuf)
int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result)
```
`int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result)`
其中 aggfn 是函数名的占位符。首先调用aggfn_start生成结果buffer然后相关的数据会被分为多个行数据块对每个数据块调用 aggfn 用数据块更新中间结果,最后再调用 aggfn_finish 从中间结果产生最终结果,最终结果只能含 0 或 1 条结果数据。
参数的具体含义是:
- interBuf中间结果 buffer
- inputBlock输入的数据块。
- newInterBuf新的中间结果buffer
- result最终结果。
主要参数说明如下。
- interBuf中间结果缓存区。
- inputBlock输入的数据块。
- newInterBuf新的中间结果缓冲区。
- result最终结果。
#### 初始化和销毁接口
`int32_t udf_init()`
`int32_t udf_destroy()`
其中 udf 是函数名的占位符。udf_init 完成初始化工作。 udf_destroy 完成清理工作。如果没有初始化工作无需定义udf_init函数。如果没有清理工作无需定义udf_destroy函数。
### C 语言 UDF 数据结构
```c
typedef struct SUdfColumnMeta {
int16_t type;
int32_t bytes;
uint8_t precision;
uint8_t scale;
} SUdfColumnMeta;
typedef struct SUdfColumnData {
int32_t numOfRows;
int32_t rowsAlloc;
union {
struct {
int32_t nullBitmapLen;
char *nullBitmap;
int32_t dataLen;
char *data;
} fixLenCol;
struct {
int32_t varOffsetsLen;
int32_t *varOffsets;
int32_t payloadLen;
char *payload;
int32_t payloadAllocLen;
} varLenCol;
};
} SUdfColumnData;
typedef struct SUdfColumn {
SUdfColumnMeta colMeta;
bool hasNull;
SUdfColumnData colData;
} SUdfColumn;
typedef struct SUdfDataBlock {
int32_t numOfRows;
int32_t numOfCols;
SUdfColumn **udfCols;
} SUdfDataBlock;
typedef struct SUdfInterBuf {
int32_t bufLen;
char* buf;
int8_t numOfResult; //zero or one
} SUdfInterBuf;
```
数据结构说明如下:
- SUdfDataBlock 数据块包含行数 numOfRows 和列数 numCols。udfCols[i] (0 \<= i \<= numCols-1)表示每一列数据类型为SUdfColumn*。
- SUdfColumn 包含列的数据类型定义 colMeta 和列的数据 colData。
- SUdfColumnMeta 成员定义同 taos.h 数据类型定义。
- SUdfColumnData 数据可以变长varLenCol 定义变长数据fixLenCol 定义定长数据。
- SUdfInterBuf 定义中间结构 buffer以及 buffer 中结果个数 numOfResult
为了更好的操作以上数据结构,提供了一些便利函数,定义在 taosudf.h。
### 编译 C UDF
用户定义函数的 C 语言源代码无法直接被 TDengine 系统使用,而是需要先编译为 动态链接库,之后才能载入 TDengine 系统。
例如,按照上一章节描述的规则准备好了用户定义函数的源代码 bit_and.c以 Linux 为例可以执行如下指令编译得到动态链接库文件:
```bash
gcc -g -O0 -fPIC -shared bit_and.c -o libbitand.so
```
这样就准备好了动态链接库 libbitand.so 文件,可以供后文创建 UDF 时使用了。为了保证可靠的系统运行,编译器 GCC 推荐使用 7.5 及以上版本。
### C UDF 示例代码
#### 标量函数示例 [bit_and](https://github.com/taosdata/TDengine/blob/3.0/tests/script/sh/bit_and.c)
bit_add 实现多列的按位与功能。如果只有一列返回这一列。bit_add 忽略空值。
<details>
<summary>bit_and.c</summary>
初始化和销毁接口是标量函数和聚合函数共同使用的接口相关API如下。
```c
{{#include tests/script/sh/bit_and.c}}
int32_t udf_init()
int32_t udf_destroy()
```
</details>
其中udf_init函数完成初始化工作udf_destroy函数完成清理工作。如果没有初始化工作无须定义udf_init函数如果没有清理工作无须定义udf_destroy函数。
#### 聚合函数示例1 返回值为数值类型 [l2norm](https://github.com/taosdata/TDengine/blob/3.0/tests/script/sh/l2norm.c)
l2norm 实现了输入列的所有数据的二阶范数,即对每个数据先平方,再累加求和,最后开方。
<details>
<summary>l2norm.c</summary>
### 标量函数模板
用C语言开发标量函数的模板如下。
```c
{{#include tests/script/sh/l2norm.c}}
int32_t scalarfn_init() {
return TSDB_CODE_SUCCESS;
}
int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn* resultColumn) {
return TSDB_CODE_SUCCESS;
}
int32_t scalarfn_destroy() {
return TSDB_CODE_SUCCESS;
}
```
### 聚合函数模板
</details>
#### 聚合函数示例2 返回值为字符串类型 [max_vol](https://github.com/taosdata/TDengine/blob/3.0/tests/script/sh/max_vol.c)
max_vol 实现了从多个输入的电压列中找到最大电压返回由设备ID + 最大电压所在(行,列)+ 最大电压值 组成的组合字符串值
创建表:
```bash
create table battery(ts timestamp, vol1 float, vol2 float, vol3 float, deviceId varchar(16));
```
创建自定义函数:
```bash
create aggregate function max_vol as '/root/udf/libmaxvol.so' outputtype binary(64) bufsize 10240 language 'C';
```
使用自定义函数:
```bash
select max_vol(vol1,vol2,vol3,deviceid) from battery;
```
<details>
<summary>max_vol.c</summary>
用C语言开发聚合函数的模板如下。
```c
{{#include tests/script/sh/max_vol.c}}
int32_t aggfn_init() {
return TSDB_CODE_SUCCESS;
}
int32_t aggfn_start(SUdfInterBuf* interBuf) {
return TSDB_CODE_SUCCESS;
}
int32_t aggfn(SUdfDataBlock* inputBlock, SUdfInterBuf *interBuf, SUdfInterBuf *newInterBuf) {
return TSDB_CODE_SUCCESS;
}
int32_t int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result) {
return TSDB_CODE_SUCCESS;
}
int32_t aggfn_destroy() {
return TSDB_CODE_SUCCESS;
}
```
</details>
### 编译
## 用 Python 语言实现 UDF
在TDengine中为了实现UDF需要编写C语言源代码并按照TDengine的规范编译为动态链接库文件。
按照前面描述的规则准备UDF的源代码bit_and.c。以Linux操作系统为例执行如下指令编译得到动态链接库文件。
```shell
gcc-g-O0-fPIC-sharedbit_and.c-olibbitand.so
```
为了保证可靠运行推荐使用7.5及以上版本的GCC。
## 用 Python 语言开发 UDF
### 准备环境
1. 准备好 Python 运行环境
2. 安装 Python 包 `taospyudf`
```shell
pip3 install taospyudf
```
安装过程中会编译 C++ 源码,因此系统上要有 cmake 和 gcc。编译生成的 libtaospyudf.so 文件自动会被复制到 /usr/local/lib/ 目录,因此如果是非 root 用户,安装时需加 sudo。安装完可以检查这个目录是否有了这个文件:
```shell
root@slave11 ~/udf $ ls -l /usr/local/lib/libtaos*
-rw-r--r-- 1 root root 671344 May 24 22:54 /usr/local/lib/libtaospyudf.so
```
然后执行命令
```shell
ldconfig
```
3. 如果 Python UDF 程序执行时,通过 PYTHONPATH 引用其它的包,可以设置 taos.cfg 的 UdfdLdLibPath 变量为PYTHONPATH的内容
4. 启动 `taosd` 服务
细节请参考 [立即开始](../../get-started)
准备环境的具体步骤如下:
- 第1步准备好Python运行环境。
- 第2步安装Python包taospyudf。命令如下。
```shell
pip3 install taospyudf
```
- 第3步执行命令ldconfig。
- 第4步启动taosd服务。
### 接口定义
#### 接口概述
使用 Python 语言实现 UDF 时,需要实现规定的接口函数
- 标量函数需要实现标量接口函数 process 。
- 聚合函数需要实现聚合接口函数 start reduce finish。
- 如果需要初始化,实现 init如果需要清理工作实现 destroy。
当使用Python语言开发UDF时需要实现规定的接口函数。具体要求如下。
- 标量函数需要实现标量接口函数process。
- 聚合函数需要实现聚合接口函数start、reduce、finish。
- 如果需要初始化则应实现函数init。
- 如果需要清理工作则实现函数destroy。
#### 标量函数接口
标量函数的接口如下。
```Python
def process(input: datablock) -> tuple[output_type]:
```
说明:
- input:datablock 类似二维矩阵,通过成员方法 data(row,col)返回位于 row 行col 列的 python 对象
- 返回值是一个 Python 对象元组,每个元素类型为输出类型。
主要参数说明如下:
- input:datablock 类似二维矩阵,通过成员方法 data(row,col)返回位于 row 行col 列的 python 对象
- 返回值是一个 Python 对象元组,每个元素类型为输出类型。
#### 聚合函数接口
聚合函数的接口如下。
```Python
def start() -> bytes:
def reduce(inputs: datablock, buf: bytes) -> bytes
def finish(buf: bytes) -> output_type:
```
说明:
- 首先调用 start 生成最初结果 buffer
- 然后输入数据会被分为多个行数据块,对每个数据块 inputs 和当前中间结果 buf 调用 reduce得到新的中间结果
- 最后再调用 finish 从中间结果 buf 产生最终输出,最终输出只能含 0 或 1 条数据。
上述代码定义了3个函数分别用于实现一个自定义的聚合函数。具体过程如下。
首先调用start函数生成最初的结果缓冲区。这个结果缓冲区用于存储聚合函数的内部状态随着输入数据的处理而不断更新。
然后输入数据会被分为多个行数据块。对于每个行数据块调用reduce函数并将当前行数据块inputs和当前的中间结果buf作为参数传递。reduce函数会根据输入数据和当前状态来更新聚合函数的内部状态并返回新的中间结果
最后当所有行数据块都处理完毕后调用finish函数。这个函数接收最终的中间结果buf作为参数并从中生成最终的输出。由于聚合函数的特性最终输出只能包含0条或1条数据。这个输出结果将作为聚合函数的计算结果返回给调用者。
#### 初始化和销毁接口
初始化和销毁的接口如下。
```Python
def init()
def destroy()
```
说明:
- init 完成初始化工作
- destroy 完成清理工作
参数说明:
- init 完成初始化工作
- destroy 完成清理工作
### Python UDF 函数模板
**注意** 用Python开发UDF时必须定义init函数和destroy函数
#### 标量函数实现模板
标量函数实现模版如下
### 标量函数模板
用Python语言开发标量函数的模板如下。
```Python
def init():
# initialization
@ -351,12 +191,9 @@ def destroy():
# destroy
def process(input: datablock) -> tuple[output_type]:
```
### 聚合函数模板
注意:定义标题函数最重要是要实现 process 函数,同时必须定义 init 和 destroy 函数即使什么都不做
#### 聚合函数实现模板
聚合函数实现模版如下
用Python语言开发聚合函数的模板如下。
```Python
def init():
#initialization
@ -374,11 +211,9 @@ def finish(buf: bytes) -> output_type:
#return obj of type outputtype
```
注意:定义聚合函数最重要是要实现 start, reduce 和 finish且必须定义 init 和 destroy 函数。start 生成最初结果 buffer然后输入数据会被分为多个行数据块对每个数据块 inputs 和当前中间结果 buf 调用 reduce得到新的中间结果最后再调用 finish 从中间结果 buf 产生最终输出。
### 数据类型映射
下表描述了TDengine SQL数据类型和Python数据类型的映射。任何类型的NULL值都映射成Python的None值。
下表描述了TDengine SQL 数据类型和 Python 数据类型的映射。任何类型的 NULL 值都映射成 Python None 值。
| **TDengine SQL数据类型** | **Python数据类型** |
| :-----------------------: | ------------ |
@ -390,19 +225,13 @@ def finish(buf: bytes) -> output_type:
|TIMESTAMP | int |
|JSON and other types | 不支持 |
### 开发指南
### 开发示例
本文内容由浅入深包括 4 个示例程序:
1. 定义一个只接收一个整数的标量函数: 输入 n 输出 ln(n^2 + 1)。
2. 定义一个接收 n 个整数的标量函数, 输入 x1, x2, ..., xn, 输出每个值和它们的序号的乘积的和: x1 + 2 * x2 + ... + n * xn。
3. 定义一个标量函数,输入一个时间戳,输出距离这个时间最近的下一个周日。完成这个函数要用到第三方库 moment。我们在这个示例中讲解使用第三方库的注意事项。
4. 定义一个聚合函数,计算某一列最大值和最小值的差, 也就是实现 TDengine 内置的 spread 函数。
同时也包含大量实用的 debug 技巧。
本文假设你用的是 Linux 系统,且已安装好了 TDengine 3.0.4.0+ 和 Python 3.7+。
本文内容由浅入深包括 5 个示例程序,同时也包含大量实用的 debug 技巧。
注意:**UDF 内无法通过 print 函数输出日志,需要自己写文件或用 python 内置的 logging 库写文件**。
#### 最简单的 UDF
#### 示例一
编写一个只接收一个整数的 UDF 函数: 输入 n 输出 ln(n^2 + 1)。
首先编写一个 Python 文件,存在系统某个目录,比如 /root/udf/myfun.py 内容如下
@ -494,7 +323,7 @@ taos> select myfun(v1) from t;
至此,我们完成了第一个 UDF 😊,并学会了简单的 debug 方法。
#### 示例二:异常处理
#### 示例二
上面的 myfun 虽然测试测试通过了,但是有两个缺点:
@ -547,7 +376,7 @@ At:
至此,我们学会了如何更新 UDF并查看 UDF 输出的错误日志。
(注:如果 UDF 更新后未生效,在 TDengine 3.0.5.0 以前(不含)的版本中需要重启 taosd在 3.0.5.0 及之后的版本中不需要重启 taosd 即可生效。)
#### 示例三 接收 n 个参数的 UDF
#### 示例三
编写一个 UDF输入x1, x2, ..., xn, 输出每个值和它们的序号的乘积的和: 1 * x1 + 2 * x2 + ... + n * xn。如果 x1 至 xn 中包含 null则结果为 null。
这个示例与示例一的区别是,可以接受任意多列作为输入,且要处理每一列的值。编写 UDF 文件 /root/udf/nsum.py
@ -598,7 +427,7 @@ taos> select ts, v1, v2, v3, nsum(v1, v2, v3) from t;
Query OK, 4 row(s) in set (0.010653s)
```
#### 示例四:使用第三方库
#### 示例四
编写一个 UDF输入一个时间戳输出距离这个时间最近的下一个周日。比如今天是 2023-05-25 则下一个周日是 2023-05-28。
完成这个函数要用到第三方库 momen。先安装这个库
@ -690,7 +519,7 @@ taos> select ts, nextsunday(ts) from t;
Query OK, 4 row(s) in set (1.011474s)
```
#### 示例五:聚合函数
#### 示例五
编写一个聚合函数,计算某一列最大值和最小值的差。
聚合函数与标量函数的区别是:标量函数是多行输入对应多个输出,聚合函数是多行输入对应一个输出。聚合函数的执行过程有点像经典的 map-reduce 框架的执行过程,框架把数据分成若干块,每个 mapper 处理一个块reducer 再把 mapper 的结果做聚合。不一样的地方在于,对于 TDengine Python UDF 中的 reduce 函数既有 map 的功能又有 reduce 的功能。reduce 函数接受两个参数:一个是自己要处理的数据,一个是别的任务执行 reduce 函数的处理结果。如下面的示例 /root/udf/myspread.py:
@ -798,90 +627,54 @@ close log file: spread.log
通过这个示例,我们学会了如何定义聚合函数,并打印自定义的日志信息。
### SQL 命令
## 管理 UDF
1. 创建标量函数的语法
在集群中管理UDF的过程涉及创建、使用和维护这些函数。用户可以通过SQL在集群中创建和管理UDF一旦创建成功集群的所有用户都可以在SQL中使用这些函数。由于UDF存储在集群的mnode上因此即使重启集群已经创建的UDF也仍然可用。
在创建UDF时需要区分标量函数和聚合函数。标量函数接受零个或多个输入参数并返回一个单一的值。聚合函数接受一组输入值并通过对这些值进行某种计算如求和、计数等来返回一个单一的值。如果创建时声明了错误的函数类别则通过SQL调用函数时会报错。
此外用户需要确保输入数据类型与UDF程序匹配UDF输出的数据类型与outputtype匹配。这意味着在创建UDF时需要为输入参数和输出值指定正确的数据类型。这有助于确保在调用UDF时输入数据能够正确地传递给UDF并且UDF的输出值与预期的数据类型相匹配。
### 创建标量函数
创建标量函数的SQL语法如下。
```sql
CREATE FUNCTION function_name AS library_path OUTPUTTYPE output_type LANGUAGE 'Python';
```
各参数说明如下。
- or replace如果函数已经存在则会修改已有的函数属性。
- function_name标量函数在SQL中被调用时的函数名。
- language支持C语言和Python语言3.7及以上版本默认为C。
- library_path如果编程语言是C则路径是包含UDF实现的动态链接库的库文件绝对路径通常指向一个so文件。如果编程语言是Python则路径是包含UDF
实现的Python文件路径。路径需要用英文单引号或英文双引号括起来。
- output_type函数计算结果的数据类型名称。
2. 创建聚合函数的语法
### 创建聚合函数
创建聚合函数的SQL语法如下。
```sql
CREATE AGGREGATE FUNCTION function_name library_path OUTPUTTYPE output_type LANGUAGE 'Python';
```
3. 更新标量函数
其中buffer_size 表示中间计算结果的缓冲区大小,单位是字节。其他参数的含义与标量函数相同。
如下SQL创建一个名为 l2norm 的UDF。
```sql
CREATE OR REPLACE FUNCTION function_name AS OUTPUTTYPE int LANGUAGE 'Python';
CREATE AGGREGATE FUNCTION l2norm AS "/home/taos/udf_example/libl2norm.so" OUTPUTTYPE DOUBLE bufsize 8;
```
4. 更新聚合函数
### 删除 UDF
删除指定名称的 UDF 的 SQL 语法如下:
```sql
CREATE OR REPLACE AGGREGATE FUNCTION function_name AS OUTPUTTYPE BUFSIZE buf_size int LANGUAGE 'Python';
```
注意:如果加了 “AGGREGATE” 关键字,更新之后函数将被当作聚合函数,无论之前是什么类型的函数。相反,如果没有加 “AGGREGATE” 关键字,更新之后的函数将被当作标量函数,无论之前是什么类型的函数。
5. 查看函数信息
同名的 UDF 每更新一次,版本号会增加 1。
```sql
select * from ins_functions \G;
```
6. 查看和删除已有的 UDF
```sql
SHOW functions;
DROP FUNCTION function_name;
```
### 查看 UDF
上面的命令可以查看 UDF 的完整信息
### 更多 Python UDF 示例代码
#### 标量函数示例 [pybitand](https://github.com/taosdata/TDengine/blob/3.0/tests/script/sh/pybitand.py)
pybitand 实现多列的按位与功能。如果只有一列返回这一列。pybitand 忽略空值。
<details>
<summary>pybitand.py</summary>
```Python
{{#include tests/script/sh/pybitand.py}}
显示集群中当前可用的所有UDF的SQL如下。
```sql
show functions;
```
</details>
#### 聚合函数示例 [pyl2norm](https://github.com/taosdata/TDengine/blob/3.0/tests/script/sh/pyl2norm.py)
pyl2norm 实现了输入列的所有数据的二阶范数,即对每个数据先平方,再累加求和,最后开方。
<details>
<summary>pyl2norm.py</summary>
```c
{{#include tests/script/sh/pyl2norm.py}}
```
</details>
#### 聚合函数示例 [pycumsum](https://github.com/taosdata/TDengine/blob/3.0/tests/script/sh/pycumsum.py)
pycumsum 使用 numpy 计算输入列所有数据的累积和。
<details>
<summary>pycumsum.py</summary>
```c
{{#include tests/script/sh/pycumsum.py}}
```
</details>
## 管理和使用 UDF
在使用 UDF 之前需要先将其加入到 TDengine 系统中。关于如何管理和使用 UDF请参考[管理和使用 UDF](../../taos-sql/udf)

View File

@ -0,0 +1,228 @@
---
title: 与第三方集成
sidebar_label: 与第三方集成
toc_max_heading_level: 4
---
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
TDengine是一个高性能、可扩展的时序数据库它支持SQL查询语言兼容标准的JDBC和ODBC接口同时还支持RESTful接口。这使得TDengine能够轻松地与各种商业智能软件和工具集成从而构建一个开放的技术生态系统。本章将简要介绍TDengine 如何与一些流行的第三方工具进行集成。
## Grafana
Grafana是一个广受欢迎的开源可视化和分析工具旨在帮助用户轻松地查询、可视化和监控存储在不同位置的指标、日志和跟踪信息。通过Grafana用户可以将时序数据库中的数据以直观的图表形式展示出来实现数据的可视化分析。
为了方便用户在Grafana中集成TDengine并展示时序数据TDengine团队提供了一个名为TDengine Datasource的Grafana插件。通过安装此插件用户无须编写任何代码即可实现TDengine与Grafana的快速集成。这使得用户能够轻松地将存储在TDengine 中的时序数据以可视化的方式呈现在Grafana的仪表盘中从而更好地监控和分析数据库性能。
### 前置条件
要让 Grafana 能正常添加 TDengine 数据源,需要以下几方面的准备工作。
1. TDengine 集群已经部署并正常运行;
2. taosAdapter 已经安装并正常运行;
3. Grafana 7.5.0 及以上的版本已经安装并正常运行。
记录以下信息:
1. taosAdapter 的 REST API 地址例如http://www.example.com:6041
2. TDengine 集群的认证信息包括用户名和密码默认为root/taosdata
### 安装 TDengine Datasource 插件
TDengine Datasource插件安装方式如下。
第1步在Web浏览器打开Grafana页面点击页面左上角的3个横条图标然后点击Connections按钮。
第2步在弹出页面的搜索栏内搜索TDengine选择TDengine Datasource。
第3步点击Install按钮以安装TDengine插件。
安装完成后进入插件的详情页面点击右上角的Add new data source按钮即可进入数据源添加页面。
进入数据源添加页面后输入RESTful接口地址和TDengine集群的认证信息后点击Save & test按钮即可完成TDengine数据源的添加如下图所示
![添加 TDengine 数据源](./addsource.png)
### 创建 Dashboard
添加TDengine数据源后直接点击界面右上角的Build a dashboard按钮即可创建 Dashboard。在Input Sql文本栏中输入TDengine的查询语句便可以轻松实现监控指标的可视化如下图所示
![Dashboard](./dashboard.png)
在该示例中查询的是由taosBenchmark --start-timestamp=1704802806146 --database= power --time-step=1000 -y写入的数据数据库的名称为power查询的是超级表meters 中记录的voltage具体的SQL如下。
```sql
select _wstart, last(voltage) from power.meters where ts>=$from and ts<=$to interval(1m)
```
其中from、to为内置变量表示从TDengine Datasource插件Dashboard获取的查询范围和时间间隔。Grafana还提供了其他内置全局变量如 `interval、org、user、timeFilter`具体可以参阅Grafana的官方文档的Variables部分。可以通过group by或partition by列名来实现分组展示数据。假设要按照不同的 `groupId` 来分组展示voltage为了避免线条太多只查询3个分组SQL如下。
```sql
select _wstart, groupid, last(voltage) from power.meters where groupid < 4 and ts>=$from and ts<=$to partition by groupid interval(1m) fill(null);
```
然后在上图所示的 Group By Column 文本框中配置要分组的列,此处填 `groupId`。将Group By Fromat设置为 `groupId-{{groupId}}`展示的legend名字为格式化的列名。配置好后可如下图所示看到多条曲线。
![按照不同的groupId来分组展示voltage](./dashboard2.png)
在编写和调试SQL时可以使用Grafana提供的Query Inspector通过它可以看到SQL的执行结果十分方便。
为了简化通过Grafana对TDengine实例的运行进行检测了解它的健康状态TDengine还提供了一个Grafana Dashboard: TDinsight for 3.x可在Grafana的官网直接按照此名称搜索并获取。通过Grafana导入后即可直接使用省去了用户自己创建Dashboard的麻烦。
## Looker Studio
Looker Studio作为Google旗下的一个功能强大的报表和商业智能工具前身名为Google Data Studio。在2022年的Google Cloud Next大会上Google将其更名为Looker Studio。这个工具凭借其丰富的数据可视化选项和多样化的数据连接能力为用户提供了便捷的数据报表生成体验。用户可以根据预设的模板轻松创建数据报表满足各种数据分析需求。
由于其简单易用的操作界面和庞大的生态系统支持Looker Studio在数据分析领域受到众多数据科学家和专业人士的青睐。无论是初学者还是资深分析师都可以利用Looker Studio快速构建美观且实用的数据报表从而更好地洞察业务趋势、优化决策过程并提升整体运营效率。
### 获取
目前TDengine连接器作为Looker Studio的合作伙伴连接器partner connector已在Looker Studio官网上线。用户访问Looker Studio的Data Source列表时只须输入 “TDengine”进行搜索便可轻松找到并立即使用TDengine连接器。
TDengine连接器兼容TDengine Cloud和TDengine Server两种类型的数据源。TDengine Cloud是涛思数据推出的全托管物联网和工业互联网大数据云服务平台为用户提供一站式数据存储、处理和分析解决方案而TDengine Server则是用户自行部署的本地版本支持通过公网访问。以下内容将以TDengine Cloud为例进行介绍。
### 使用
在Looker Studio中使用TDengine连接器的步骤如下。
第1步进入TDengine连接器的详情页面后在Data Source下拉列表中选择TDengine Cloud然后点击Next按钮即可进入数据源配置页面。在该页面中填写以下信息然后点击Connect按钮。
- URL和TDengine Cloud Token可以从TDengine Cloud的实例列表中获取。
- 数据库名称和超级表名称。
- 查询数据的开始时间和结束时间。
第2步Looker Studio会根据配置自动加载所配置的TDengine数据库下的超级表的字段和标签。
第3步点击页面右上角的Explore按钮即查看从TDengine数据库中加载的数据。
第4步根据需求利用Looker Studio提供的图表进行数据可视化的配置。
**注意** 在第一次使用时请根据页面提示对Looker Studio的TDengine连接器进行访问授权。
## PowerBI
Power BI是由Microsoft提供的一种商业分析工具。通过配置使用ODBC连接器Power BI可以快速访问TDengine的数据。用户可以将标签数据、原始时序数据或按时间聚合后的时序数据从TDengine导入到Power BI制作报表或仪表盘整个过程不需要任何代码编写过程。
### 前置条件
安装完成Power BI Desktop软件并运行如未安装请从其官方地址下载最新的Windows操作系统X64版本
### 安装 ODBC 驱动
从TDengine官网下载最新的Windows操作系统X64客户端驱动程序并安装在运行Power BI的机器上。安装成功后可在“ODBC数据源64位”管理工具中看到 TAOS_ODBC_DSN”驱动程序。
### 配置ODBC数据源
配置ODBC数据源的操作步骤如下。
第1步在Windows操作系统的开始菜单中搜索并打开“ODBC数据源64位”管理工具。
第2步点击“用户DSN”选项卡→“添加”按钮进入“创建新数据源”对话框。
第3步选择想要添加的数据源后选择“TDengine”点击“完成”按钮进入TDengine ODBC数据源配置页面。填写如下必要信息。
- DSN数据源名称必填比如“MyTDengine”。
- 连接类型勾选“WebSocket”复选框。
- 服务地址输入“taos://127.0.0.1:6041”。
- 数据库表示需要连接的数据库可选比如“test”。
- 用户名输入用户名如果不填默认为“root”。
- 密码输入用户密码如果不填默认为“taosdata”。
第4步点击“测试连接”按钮测试连接情况如果成功连接则会提示“成功连接到taos://root:taosdata@127.0.0.1:6041”。
第5步点击“确定”按钮即可保存配置并退出。
### 导入TDengine数据到Power BI
将TDengine数据导入Power BI的操作步骤如下。
第1步打开Power BI并登录后点击“主页”→“获取数据”→“其他”→“ODBC”→“连接”添加数据源。
第2步选择刚才创建的数据源名称比如“MyTDengine”点击“确定”按钮。在弹出的“ODBC驱动程序”对话框中在左侧导航栏中点击“默认或自定义”→“连接”按钮即可连接到配置好的数据源。进入“导航器”后可以浏览对应数据库的数据表并加载。
第3步如果需要输入SQL则可以点击“高级选项”选项卡在展开的对话框中输入并加载数据。
为了充分发挥Power BI在分析TDengine中数据方面的优势用户需要先理解维度、度量、窗口切分查询、数据切分查询、时序和相关性等核心概念之后通过自定义的SQL导入数据。
- 维度通常是分类文本数据描述设备、测点、型号等类别信息。在TDengine的超级表中使用标签列存储数据的维度信息可以通过形如“select distinct tbname, tag1, tag2 from supertable”的SQL语法快速获得维度信息。
- 度量可以用于进行计算的定量数值字段常见计算有求和、取平均值和最小值等。如果测点的采集周期为1s那么一年就有3000多万条记录把这些数据全部导入Power BI会严重影响其执行效率。在TDengine中用户可以使用数据切分查询、窗口切分查询等语法结合与窗口相关的伪列把降采样后的数据导入Power BI中具体语法请参阅TDengine官方文档的特色查询功能部分。
- 窗口切分查询比如温度传感器每秒采集一次数据但须查询每隔10min的温度平均值在这种场景下可以使用窗口子句来获得需要的降采样查询结果对应的SQL形如`select tbname, _wstart dateavg(temperature) temp from table interval(10m)`其中_wstart是伪列表示时间窗口起始时间10m表示时间窗口的持续时间avg(temperature)表示时间窗口内的聚合值。
- 数据切分查询如果需要同时获取很多温度传感器的聚合数值可对数据进行切分然后在切分出的数据空间内进行一系列的计算对应的SQL形如 `partition by part_list`。数据切分子句最常见的用法是在超级表查询中按标签将子表数据进行切分,将每个子表的数据独立出来,形成一条条独立的时间序列,方便针对各种时序场景的统计分析。
- 时序在绘制曲线或者按照时间聚合数据时通常需要引入日期表。日期表可以从Excel表格中导入也可以在TDengine中执行SQL获取例如 `select _wstart date, count(*) cnt from test.meters where ts between A and B interval(1d) fill(0)`其中fill字句表示数据缺失情况下的填充模式伪列_wstart则为要获取的日期列。
- 相关性告诉数据之间如何关联如度量和维度可以通过tbname列关联在一起日期表和度量则可以通过date列关联配合形成可视化报表。
### 智能电表样例
TDengine采用了一种独特的数据模型以优化时序数据的存储和查询性能。该模型利用超级表作为模板为每台设备创建一张独立的表。每张表在设计时考虑了高度的可扩展性最多可包含4096个数据列和128个标签列。这种设计使得TDengine能够高效地处理大量时序数据同时保持数据的灵活性和易用性。
以智能电表为例假设每块电表每秒产生一条记录那么每天将产生86 400条记录。对于1000块智能电表来说每年产生的记录将占用大约600GB的存储空间。面对如此庞大的数据量Power BI等商业智能工具在数据分析和可视化方面发挥着重要作用。
在Power BI中用户可以将TDengine表中的标签列映射为维度列以便对数据进行分组和筛选。同时数据列的聚合结果可以导入为度量列用于计算关键指标和生成报表。通过这种方式Power BI能够帮助决策者快速获取所需的信息深入了解业务运营情况从而制定更加明智的决策。
根据如下步骤便可以体验通过Power BI生成时序数据报表的功能。
第1步使用TDengine的taosBenchMark快速生成1000块智能电表3天的数据采集频率为1s。
```shell
taosBenchmark-t1000-n259200-S1000-H200-y
```
第2步导入维度数据。在Power BI中导入表的标签列取名为tags通过如下SQL获取超级表下所有智能电表的标签数据。
```sql
selectdistincttbnamedevice,groupId,locationfromtest.meters
```
第3步导入度量数据。在Power BI中按照1小时的时间窗口导入每块智能电表的电流均值、电压均值、相位均值取名为dataSQL如下。
```sql
第3步导入度量数据。在Power BI中按照1小时的时间窗口导入每块智能电表的电流均值、电压均值、相位均值取名为dataSQL如下。
```
第4步导入日期数据。按照1天的时间窗口获得时序数据的时间范围及数据计数SQL如下。需要在Power Query编辑器中将date列的格式从“文本”转化为“日期”。
```sql
select_wstartdate,count(*)fromtest.metersinterval(1d)havingcount(*)>0
```
第5步建立维度和度量的关联关系。打开模型视图建立表tags和data的关联关系将tbname设置为关联数据列。
第6步建立日期和度量的关联关系。打开模型视图建立数据集date和data的关联关系关联的数据列为date和datatime。
第7步制作报告。在柱状图、饼图等控件中使用这些数据。
由于TDengine处理时序数据的超强性能使得用户在数据导入及每日定期刷新数据时都可以得到非常好的体验。更多有关Power BI视觉效果的构建方法请参照Power BI的官方文档。
## 永洪 BI
永洪 BI 是一个专为各种规模企业打造的全业务链大数据分析解决方案,旨在帮助用户轻松发掘大数据价值,获取深入的洞察力。该平台以其灵活性和易用性而广受好评,无论企业规模大小,都能从中受益。
为了实现与 TDengine 的高效集成,永洪 BI 提供了 JDBC 连接器。用户只须按照简单的步骤配置数据源,即可将 TDengine 作为数据源添加到永洪BI中。这一过程不仅快速便捷还能确保数据的准确性和稳定性。
一旦数据源配置完成永洪BI便能直接从TDengine中读取数据并利用其强大的数据处理和分析功能为用户提供丰富的数据展示、分析和预测能力。这意味着用户无须编写复杂的代码或进行烦琐的数据转换工作即可轻松获取所需的业务洞察。
### 安装永洪 BI
确保永洪 BI 已经安装并运行(如果未安装,请到永洪科技官方下载页面下载)。
### 安装JDBC驱动
从 maven.org 下载 TDengine JDBC 连接器文件 “taos-jdbcdriver-3.2.7-dist.jar”并安装在永洪 BI 的机器上。
### 配置JDBC数据源
配置JDBC数据源的步骤如下。
第1步在打开的永洪BI中点击“添加数据源”按钮选择SQL数据源中的“GENERIC”类型。
第2步点击“选择自定义驱动”按钮在“驱动管理”对话框中点击“驱动列表”旁边的“+”输入名称“MyTDengine”。然后点击“上传文件”按钮上传刚刚下载的TDengine JDBC连接器文件“taos-jdbcdriver-3.2.7-dist.jar”并选择“com.taosdata.jdbc.
rs.RestfulDriver”驱动最后点击“确定”按钮完成驱动添加步骤。
第3步复制下面的内容到“URL”字段。
```text
jdbc:TAOS-RS://127.0.0.1:6041?user=root&password=taosdata
```
第4步在“认证方式”中点击“无身份认证”单选按钮。
第5步在数据源的高级设置中修改“Quote 符号”的值为反引号(`)。
第6步点击“测试连接”按钮弹出“测试成功”对话框。点击“保存”按钮输入“MyTDengine”来保存TDengine数据源。
### 创建TDengine数据集
创建TDengine数据集的步骤如下。
第1步在永洪BI中点击“添加数据源”按钮展开刚刚创建的数据源并浏览TDengine中的超级表。
第2步可以将超级表的数据全部加载到永洪BI中也可以通过自定义SQL导入部分数据。
第3步当勾选“数据库内计算”复选框时永洪BI将不再缓存TDengine的时序数据并在处理查询时将SQL请求发送给TDengine直接处理。
当导入数据后永洪BI会自动将数值类型设置为“度量”列将文本类型设置为“维度”列。而在TDengine的超级表中由于将普通列作为数据的度量将标签列作为数据的维度因此用户可能需要在创建数据集时更改部分列的属性。TDengine在支持标准SQL的基础之上还提供了一系列满足时序业务场景需求的特色查询语法例如数据切分查询、窗口切分查询等具体操作步骤请参阅TDengine的官方文档。通过使用这些特色查询当永洪BI将SQL查询发送到TDengine时可以大大提高数据访问速度减少网络传输带宽。
在永洪BI中你可以创建“参数”并在SQL中使用通过手动、定时的方式动态执行这些SQL即可实现可视化报告的刷新效果。如下SQL可以从TDengine实时读取数据。
```sql
select _wstart ws, count(*) cnt from supertable where tbname=?{metric} and ts = ?{from} and ts < ?{to} interval(?{interval})
```
其中:
1. `_wstart`:表示时间窗口起始时间。
2. `count*`:表示时间窗口内的聚合值。
3. `?{interval}`:表示在 SQL 语句中引入名称为 `interval` 的参数,当 BI 工具查询数据时,会给 `interval` 参数赋值,如果取值为 1m则表示按照 1 分钟的时间窗口降采样数据。
4. `?{metric}`:该参数用来指定查询的数据表名称,当在 BI 工具中把某个“下拉参数组件”的 ID 也设置为 metric 时,该“下拉参数组件”的被选择项将会和该参数绑定在一起,实现动态选择的效果。
5. `?{from}``{to}`:这两个参数用来表示查询数据集的时间范围,可以与“文本参数组件”绑定。
您可以在 BI 工具的“编辑参数”对话框中修改“参数”的数据类型、数据范围、默认取值,并在“可视化报告”中动态设置这些参数的值。
### 21.4.5 制作可视化报告
制作可视化报告的步骤如下。
1. 在永洪 BI 工具中点击“制作报告”,创建画布。
2. 拖动可视化组件到画布中,例如“表格组件”。
3. 在“数据集”侧边栏中选择待绑定的数据集,将数据列中的“维度”和“度量”按需绑定到“表格组件”。
4. 点击“保存”后,即可查看报告。
5. 更多有关永洪 BI 工具的信息,请查询永洪科技官方帮助文档。

Binary file not shown.

After

Width:  |  Height:  |  Size: 210 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 302 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

@ -8,10 +8,6 @@ import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import Preparation from "./_preparation.mdx"
import RustInsert from "../08-develop/03-insert-data/_rust_sql.mdx"
import RustBind from "../08-develop/03-insert-data/_rust_stmt.mdx"
import RustSml from "../08-develop/03-insert-data/_rust_schemaless.mdx"
import RustQuery from "../08-develop/04-query-data/_rust.mdx"
import RequestId from "./_request_id.mdx";
[![Crates.io](https://img.shields.io/crates/v/taos)](https://crates.io/crates/taos) ![Crates.io](https://img.shields.io/crates/d/taos) [![docs.rs](https://img.shields.io/docsrs/taos)](https://docs.rs/taos)

View File

@ -7,7 +7,7 @@ title: R Language Connector
import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';
import Rdemo from "../08-develop/01-connect/_connect_r.mdx"
import Rdemo from "../../08-develop/01-connect/_connect_r.mdx"
通过 R 语言中的 RJDBC 库可以使 R 语言程序支持访问 TDengine 数据。以下是安装过程、配置过程以及 R 语言示例代码。

Some files were not shown because too many files have changed in this diff Show More