Merge branch '3.0' into docs/sheyj-3.0
|
@ -4,9 +4,9 @@ sidebar_label: 文档首页
|
|||
slug: /
|
||||
---
|
||||
|
||||
TDengine 是一款[开源](https://www.taosdata.com/tdengine/open_source_time-series_database)、[高性能](https://www.taosdata.com/fast)、[云原生](https://www.taosdata.com/tdengine/cloud_native_time-series_database)的<a href="https://www.taosdata.com/" data-internallinksmanager029f6b8e52c="2" title="时序数据库" target="_blank" rel="noopener">时序数据库</a>(<a href="https://www.taosdata.com/time-series-database" data-internallinksmanager029f6b8e52c="9" title="Time Series DataBase" target="_blank" rel="noopener">Time Series Database</a>, <a href="https://www.taosdata.com/tsdb" data-internallinksmanager029f6b8e52c="8" title="TSDB" target="_blank" rel="noopener">TSDB</a>), 它专为物联网、车联网、工业互联网、金融、IT 运维等场景优化设计。同时它还带有内建的缓存、流式计算、数据订阅等系统功能,能大幅减少系统设计的复杂度,降低研发和运营成本,是一款极简的时序数据处理平台。本文档是 TDengine 的用户手册,主要是介绍 TDengine 的基本概念、安装、使用、功能、开发接口、运营维护、TDengine 内核设计等等,它主要是面向架构师、开发工程师与系统管理员的。
|
||||
TDengine 是一款[开源](https://www.taosdata.com/tdengine/open_source_time-series_database)、[高性能](https://www.taosdata.com/fast)、[云原生](https://www.taosdata.com/tdengine/cloud_native_time-series_database)的<a href="https://www.taosdata.com/" data-internallinksmanager029f6b8e52c="2" title="时序数据库" target="_blank" rel="noopener">时序数据库</a>(<a href="https://www.taosdata.com/time-series-database" data-internallinksmanager029f6b8e52c="9" title="Time Series DataBase" target="_blank" rel="noopener">Time Series Database</a>, <a href="https://www.taosdata.com/tsdb" data-internallinksmanager029f6b8e52c="8" title="TSDB" target="_blank" rel="noopener">TSDB</a>), 它专为物联网、车联网、工业互联网、金融、IT 运维等场景优化设计。同时它还带有内建的缓存、流式计算、数据订阅等系统功能,能大幅减少系统设计的复杂度,降低研发和运营成本,是一款极简的时序数据处理平台。本文档是 TDengine 的用户手册,主要是介绍 TDengine 的基本概念、安装、使用、功能、开发接口、运营维护、TDengine 内核设计等等,它主要是面向架构师、开发工程师与系统管理员的。如果你对时序数据的基本概念、价值以及其所能带来的业务价值尚不了解,请参考[时序数据基础](./concept)
|
||||
|
||||
TDengine 充分利用了时序数据的特点,提出了“一个数据采集点一张表”与“超级表”的概念,设计了创新的存储引擎,让数据的写入、查询和存储效率都得到极大的提升。为正确理解并使用 TDengine,无论如何,请您仔细阅读[基本概念](./concept)一章。
|
||||
TDengine 充分利用了时序数据的特点,提出了“一个数据采集点一张表”与“超级表”的概念,设计了创新的存储引擎,让数据的写入、查询和存储效率都得到极大的提升。为正确理解并使用 TDengine,无论如何,请您仔细阅读[快速入门](./basic)一章。
|
||||
|
||||
如果你是开发工程师,请一定仔细阅读[开发指南](./develop)一章,该部分对数据库连接、建模、插入数据、查询、流式计算、缓存、数据订阅、用户自定义函数等功能都做了详细介绍,并配有各种编程语言的示例代码。大部分情况下,你只要复制粘贴示例代码,针对自己的应用稍作改动,就能跑起来。
|
||||
|
||||
|
|
|
@ -63,7 +63,7 @@ toc_max_heading_level: 4
|
|||
|
||||
1. 数据库(Database):数据库提供时序数据的高效存储和读取能力。在工业、物联网场景,由设备所产生的时序数据量是十分惊人的。从存储数据的角度来说,数据库需要把这些数据持久化到硬盘上并最大程度地压缩,从而降低存储成本。从读取数据的角度来说,数据库需要保证实时查询,以及历史数据的查询效率。比较传统的存储方案是使用 MySql、Oracle 等关系型数据库,也有 Hadoop 体系的 HBase,专用的时序数据库则有 InfluxDB、OpenTSDB、Prometheus 等。
|
||||
|
||||
2. 数据订阅(Data Subscription):很多时序数据应用都需要在第一时间订阅到业务所需的实时数据,从而及时了解被监测对对象的最新状态,用 AI 或其他工具做实时的数据分析。同时,由于数据的隐私以及安全,你只能容许应用订阅他有权限访问的数据。因此,一个时序数据处理平台一定需要具备数据订阅的能力,帮助应用实时获取最新数据。
|
||||
2. 数据订阅(Data Subscription):很多时序数据应用都需要在第一时间订阅到业务所需的实时数据,从而及时了解被监测对对象的最新状态,用 AI 或其他工具做实时的数据分析。同时,由于数据的隐私以及安全,你只能允许应用订阅他有权限访问的数据。因此,一个时序数据处理平台一定需要具备数据订阅的能力,帮助应用实时获取最新数据。
|
||||
|
||||
3. ETL(Extract, Transform, Load):在实际的物联网、工业场景中,时序数据的采集需要特定的 ETL 工具进行数据的提取、清洗和转换操作,才能把数据写入数据库中,以保证数据的质量。因为不同数据采集系统往往使用不同的标准,比如采集的温度的物理单位不一致,有的用摄氏度,有的用华氏度;系统之间所在的时区不一致,要进行转换;时间分辨率也可能不统一,因此这些从不同系统汇聚来的数据需要进行转换才能写入数据库。
|
||||
|
||||
|
@ -109,7 +109,7 @@ toc_max_heading_level: 4
|
|||
|
||||
5. 必须拥有高效的缓存功能:绝大部分场景,都需要能快速获取设备当前状态或其他信息,用以报警、大屏展示或其他。系统需要提供一高效机制,让用户可以获取全部、或符合过滤条件的部分设备的最新状态。
|
||||
|
||||
6. 必须拥有实时流式计算:各种实时预警或预测已经不是简单的基于某一个阈值进行,而是需要通过将一个或多个设备产生的数据流进行实时聚合计算,不只是基于一个时间点、而是基于一个时间窗口进行计算。不仅如此,计算的需求也相当复杂,因场景而异,应容许用户自定义函数进行计算。
|
||||
6. 必须拥有实时流式计算:各种实时预警或预测已经不是简单的基于某一个阈值进行,而是需要通过将一个或多个设备产生的数据流进行实时聚合计算,不只是基于一个时间点、而是基于一个时间窗口进行计算。不仅如此,计算的需求也相当复杂,因场景而异,应允许用户自定义函数进行计算。
|
||||
|
||||
7. 必须支持数据订阅:与通用大数据平台比较一致,同一组数据往往有很多应用都需要,因此系统应该提供订阅功能,只要有新的数据更新,就应该实时提醒应用。由于数据隐私和安全,而且这个订阅也应该是个性化的,只能订阅有权查看的数据,比如仅仅能订阅每小时的平均功率,而不能订阅原始的电流、电压值。
|
||||
|
||||
|
@ -119,7 +119,7 @@ toc_max_heading_level: 4
|
|||
|
||||
10. 必须支持灵活的多维度分析:对于联网设备产生的数据,需要进行各种维度的统计分析,比如从设备所处的地域进行分析,从设备的型号、供应商进行分析,从设备所使用的人员进行分析等等。而且这些维度的分析是无法事先想好的,是在实际运营过程中,根据业务发展的需求定下来的。因此时序大数据系统需要一个灵活的机制增加某个维度的分析。
|
||||
|
||||
11. 需要支持即席分析和查询。为提高大数据分析师的工作效率,系统应该提供一命令行工具或容许用户通过其他工具,执行 SQL 查询,而不是非要通过编程接口。查询分析的结果可以很方便的导出,再制作成各种图表。
|
||||
11. 需要支持即席分析和查询。为提高大数据分析师的工作效率,系统应该提供一命令行工具或允许用户通过其他工具,执行 SQL 查询,而不是非要通过编程接口。查询分析的结果可以很方便的导出,再制作成各种图表。
|
||||
|
||||
12. 必须支持数据降频、插值、特殊函数计算等操作。原始数据的采集频次可能很高,但具体分析往往不需要对原始数据执行,而是数据降频之后。系统需要提供高效的数据降频操作。设备是很难同步的,不同设备采集数据的时间点是很难对齐的,因此分析一个特定时间点的值,往往需要插值才能解决,系统需要提供线性插值、设置固定值等多种插值策略才行。工业互联网里,除通用的统计操作之外,往往还需要支持一些特殊函数,比如时间加权平均、累计求和、差值等。
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ toc_max_heading_level: 4
|
|||
|
||||
TDengine 是一个高性能、分布式的时序数据库。通过集成的缓存、数据订阅、流计算和数据清洗与转换等功能,TDengine 已经发展成为一个专为物联网、工业互联网、金融和 IT 运维等关键行业量身定制的时序大数据平台。该平台能够高效地汇聚、存储、分析、计算和分发来自海量数据采集点的大规模数据流,每日处理能力可达 TB 乃至 PB 级别。借助 TDengine,企业可以实现实时的业务监控和预警,进而发掘出有价值的商业洞察。
|
||||
|
||||
自 2019 年 7 月 以 来, 涛 思 数 据 陆 续 将 TDengine 的 不 同 版 本 开 源, 包 括 单 机版(2019 年 7 月)、集群版(2020 年 8 月)以及云原生版(2022 年 8 月)。开源之后,TDengine 迅速获得了全球开发者的关注,多次在 GitHub 网站全球趋势排行榜上位居榜首。截至编写本书时,TDengine 在 GitHub 网站上已积累近 2.3 万颗星,安装实例超过53 万个,覆盖 60 多个国家和地区,广泛应用于电力、石油、化工、新能源、智能制造、汽车、环境监测等行业或领域,赢得了全球开发者的广泛认可
|
||||
自 2019 年 7 月 以来, 涛思数据陆续将 TDengine 的不同版本开源,包括单机版(2019 年 7 月)、集群版(2020 年 8 月)以及云原生版(2022 年 8 月)。开源之后,TDengine 迅速获得了全球开发者的关注,多次在 GitHub 网站全球趋势排行榜上位居榜首,最新的关注热度见[涛思数据首页](https://www.taosdata.com/)。
|
||||
|
||||
## TDengine 产品
|
||||
|
||||
|
@ -20,9 +20,9 @@ TDengine OSS 是一个开源的高性能时序数据库,与其他时序数据
|
|||
|
||||
## TDengine 主要功能与特性
|
||||
|
||||
TDengine 经过特别优化,以适应时间序列数据的独特需求,引入了“一个数据采集点一张表”和“超级表”的创新数据组织策略。这些策略背后的支撑是一个革命性的存储引擎,它极大地提升了数据处理的速度和效率,无论是在数据的写入、查询还是存储方面。接下来,逐一探索 TDengine 的众多功能,帮助您全面了解这个为高效处理时间序列数据而生的大数据平台。
|
||||
TDengine 经过特别优化,以适应时间序列数据的独特需求,引入了 “一个数据采集点一张表” 和 “超级表” 的创新数据组织策略。这些策略背后的支撑是一个革命性的存储引擎,它极大地提升了数据处理的速度和效率,无论是在数据的写入、查询还是存储方面。接下来,逐一探索 TDengine 的众多功能,帮助您全面了解这个为高效处理时间序列数据而生的大数据平台。
|
||||
|
||||
1. 写入数据:TDengine 支持多种数据写入方式。首先,它完全兼容 SQL,允许用户使用标准的 SQL 语法进行数据写入。而且 TDengine 还支持无模式(Schemaless)写入,包括流行的 InfluxDB Line 协议、OpenTSDB 的 Telnet 和 JSON 协议,这些协议的加入使得数据的导入变得更加灵活和高效。更进一步,TDengine 与众多第三方工具实现了无缝集成,例如 Telegraf、Prometheus、EMQX、StatsD、collectd 和 HiveMQ 等。对于 TDengine Enterprise, TDengine 还提供了 MQTT、OPC-UA、OPC-DA、PI、Wonderware, Kafka 等连接器。这些工具通过简单的配置,无需一行代码,就可以将来自各种数据源的数据源源不断的写入数据库,极大地简化了数据收集和存储的过程。
|
||||
1. 写入数据:TDengine 支持多种数据写入方式。首先,它完全兼容 SQL,允许用户使用标准的 SQL 语法进行数据写入。而且 TDengine 还支持无模式(Schemaless)写入,包括流行的 InfluxDB Line 协议、OpenTSDB 的 Telnet 和 JSON 协议,这些协议的加入使得数据的导入变得更加灵活和高效。更进一步,TDengine 与众多第三方工具实现了无缝集成,例如 Telegraf、Prometheus、EMQX、StatsD、collectd 和 HiveMQ 等。在 TDengine Enterprise 中, 还提供了 MQTT、OPC-UA、OPC-DA、PI、Wonderware、Kafka、InfluxDB、OpenTSDB、MySQL、Oracle 和 SQL Server 等连接器。这些工具通过简单的配置,无需一行代码,就可以将来自各种数据源的数据源源不断的写入数据库,极大地简化了数据收集和存储的过程。
|
||||
|
||||
2. 查询数据:TDengine 提供标准的 SQL 查询语法,并针对时序数据和业务的特点优化和新增了许多语法和功能,例如降采样、插值、累计求和、时间加权平均、状态窗口、时间窗口、会话窗口、滑动窗口等。TDengine 还支持用户自定义函数(UDF)
|
||||
|
||||
|
@ -38,13 +38,11 @@ TDengine 经过特别优化,以适应时间序列数据的独特需求,引
|
|||
|
||||
8. 数据迁移:TDengine 提供了多种便捷的数据导入导出功能,包括脚本文件导入导出、数据文件导入导出、taosdump 工具导入导出等。企业版还支持边云协同、数据同步等场景,兼容多种数据源,如 AVEVA PI System 等。
|
||||
|
||||
9. 编程连接器:TDengine 提供不同语言的连接器,包括 C/C++、Java、Go、Node.js、Rust、Python、C#、R、PHP 等。而且 TDengine 支持 REST 接口,应用可以直接通过 HTTP POST 请求 BODY 中包含的 SQL 语句来操作数据库。
|
||||
9. 编程连接器:TDengine 提供不同语言的连接器,包括 C/C++、Java、Go、Node.js、Rust、Python、C#、R、PHP 等。这些连接器大多都支持原生连接和 WebSocket 两种连接方式。TDengine 也提供 REST 接口,任何语言的应用程序可以直接通过 HTTP 请求访问数据库。
|
||||
|
||||
10. 数据安全共享:TDengine 通过数据库视图功能和权限管理,确保数据访问的安全性。结合数据订阅功能实现灵活精细的数据分发控制,保护数据安全和隐私
|
||||
10. 数据安全:TDengine 提供了丰富的用户管理和权限管理功能以控制不同用户对数据库和表的访问权限,提供了 IP 白名单功能以控制不同帐号只能从特定的服务器接入集群。TDengine 支持系统管理员对不同数据库按需加密,数据加密后对读写完全透明且对性能的影响很小。还提供了审计日志功能以记录系统中的敏感操作。
|
||||
|
||||
11. 编程连接器:TDengine 提供了丰富的编程语言连接器,包括 C/C++、Java、Go、Node.js、Rust、Python、C#、R、PHP 等,并支持 REST ful 接口,方便应用通过HTTP POST 请求操作数据库。
|
||||
|
||||
12. 常用工具:TDengine 还提供了交互式命令行程序(CLI),便于管理集群、检查系统状态、做即时查询。压力测试工具 taosBenchmark,用于测试 TDengine 的性能。TDengine 还提供了图形化管理界面,简化了操作和管理过程。
|
||||
11. 常用工具:TDengine 还提供了交互式命令行程序(CLI),便于管理集群、检查系统状态、做即时查询。压力测试工具 taosBenchmark,用于测试 TDengine 的性能。TDengine 还提供了图形化管理界面,简化了操作和管理过程。
|
||||
|
||||
## TDengine 与典型时序数据库的区别
|
||||
|
||||
|
|
|
@ -71,4 +71,54 @@ taos>
|
|||
|
||||
## 快速体验
|
||||
|
||||
想要快速体验 TDengine 的写入和查询能力,请参考[快速体验](../use)
|
||||
### 体验写入
|
||||
|
||||
taosBenchmark 是一个专为测试 TDengine 性能而设计的工具,它能够全面评估TDengine 在写入、查询和订阅等方面的功能表现。该工具能够模拟大量设备产生的数据,并允许用户灵活控制数据库、超级表、标签列的数量和类型、数据列的数量和类型、子表数量、每张子表的数据量、写入数据的时间间隔、工作线程数量以及是否写入乱序数据等策略。
|
||||
|
||||
启动 TDengine 的服务,在终端中执行如下命令
|
||||
|
||||
```shell
|
||||
taosBenchmark -y
|
||||
```
|
||||
|
||||
系统将自动在数据库 test 下创建一张名为 meters的超级表。这张超级表将包含 10,000 张子表,表名从 d0 到 d9999,每张表包含 10,000条记录。每条记录包含 ts(时间戳)、current(电流)、voltage(电压)和 phase(相位)4个字段。时间戳范围从“2017-07-14 10:40:00 000” 到 “2017-07-14 10:40:09 999”。每张表还带有 location 和 groupId 两个标签,其中,groupId 设置为 1 到 10,而 location 则设置为 California.Campbell、California.Cupertino 等城市信息。
|
||||
|
||||
执行该命令后,系统将迅速完成 1 亿条记录的写入过程。实际所需时间取决于硬件性能,但即便在普通 PC 服务器上,这个过程通常也只需要十几秒。
|
||||
|
||||
taosBenchmark 提供了丰富的选项,允许用户自定义测试参数,如表的数目、记录条数等。要查看详细的参数列表,请在终端中输入如下命令
|
||||
```shell
|
||||
taosBenchmark --help
|
||||
```
|
||||
|
||||
有关taosBenchmark 的详细使用方法,请参考[taosBenchmark 参考手册](../../reference/components/taosbenchmark)
|
||||
|
||||
### 体验查询
|
||||
|
||||
使用上述 taosBenchmark 插入数据后,可以在 TDengine CLI(taos)输入查询命令,体验查询速度。
|
||||
|
||||
1. 查询超级表 meters 下的记录总条数
|
||||
```shell
|
||||
SELECT COUNT(*) FROM test.meters;
|
||||
```
|
||||
|
||||
2. 查询 1 亿条记录的平均值、最大值、最小值
|
||||
```shell
|
||||
SELECT AVG(current), MAX(voltage), MIN(phase) FROM test.meters;
|
||||
```
|
||||
|
||||
3. 查询 location = "California.SanFrancisco" 的记录总条数
|
||||
```shell
|
||||
SELECT COUNT(*) FROM test.meters WHERE location = "California.SanFrancisco";
|
||||
```
|
||||
|
||||
4. 查询 groupId = 10 的所有记录的平均值、最大值、最小值
|
||||
```shell
|
||||
SELECT AVG(current), MAX(voltage), MIN(phase) FROM test.meters WHERE groupId = 10;
|
||||
```
|
||||
|
||||
5. 对表 d1001 按每 10 秒进行平均值、最大值和最小值聚合统计
|
||||
```shell
|
||||
SELECT _wstart, AVG(current), MAX(voltage), MIN(phase) FROM test.d1001 INTERVAL(10s);
|
||||
```
|
||||
|
||||
在上面的查询中,使用系统提供的伪列_wstart 来给出每个窗口的开始时间。
|
|
@ -267,4 +267,54 @@ Query OK, 2 row(s) in set (0.003128s)
|
|||
|
||||
## 快速体验
|
||||
|
||||
想要快速体验 TDengine 的写入和查询能力,请参考[快速体验](../use)
|
||||
### 体验写入
|
||||
|
||||
taosBenchmark 是一个专为测试 TDengine 性能而设计的工具,它能够全面评估TDengine 在写入、查询和订阅等方面的功能表现。该工具能够模拟大量设备产生的数据,并允许用户灵活控制数据库、超级表、标签列的数量和类型、数据列的数量和类型、子表数量、每张子表的数据量、写入数据的时间间隔、工作线程数量以及是否写入乱序数据等策略。
|
||||
|
||||
启动 TDengine 的服务,在终端中执行如下命令
|
||||
|
||||
```shell
|
||||
taosBenchmark -y
|
||||
```
|
||||
|
||||
系统将自动在数据库 test 下创建一张名为 meters的超级表。这张超级表将包含 10,000 张子表,表名从 d0 到 d9999,每张表包含 10,000条记录。每条记录包含 ts(时间戳)、current(电流)、voltage(电压)和 phase(相位)4个字段。时间戳范围从 “2017-07-14 10:40:00 000” 到 “2017-07-14 10:40:09 999”。每张表还带有 location 和 groupId 两个标签,其中,groupId 设置为 1 到 10,而 location 则设置为 California.Campbell、California.Cupertino 等城市信息。
|
||||
|
||||
执行该命令后,系统将迅速完成 1 亿条记录的写入过程。实际所需时间取决于硬件性能,但即便在普通 PC 服务器上,这个过程通常也只需要十几秒。
|
||||
|
||||
taosBenchmark 提供了丰富的选项,允许用户自定义测试参数,如表的数目、记录条数等。要查看详细的参数列表,请在终端中输入如下命令
|
||||
```shell
|
||||
taosBenchmark --help
|
||||
```
|
||||
|
||||
有关taosBenchmark 的详细使用方法,请参考[taosBenchmark 参考手册](../../reference/components/taosbenchmark)
|
||||
|
||||
### 体验查询
|
||||
|
||||
使用上述 taosBenchmark 插入数据后,可以在 TDengine CLI(taos)输入查询命令,体验查询速度。
|
||||
|
||||
1. 查询超级表 meters 下的记录总条数
|
||||
```shell
|
||||
SELECT COUNT(*) FROM test.meters;
|
||||
```
|
||||
|
||||
2. 查询 1 亿条记录的平均值、最大值、最小值
|
||||
```shell
|
||||
SELECT AVG(current), MAX(voltage), MIN(phase) FROM test.meters;
|
||||
```
|
||||
|
||||
3. 查询 location = "California.SanFrancisco" 的记录总条数
|
||||
```shell
|
||||
SELECT COUNT(*) FROM test.meters WHERE location = "California.SanFrancisco";
|
||||
```
|
||||
|
||||
4. 查询 groupId = 10 的所有记录的平均值、最大值、最小值
|
||||
```shell
|
||||
SELECT AVG(current), MAX(voltage), MIN(phase) FROM test.meters WHERE groupId = 10;
|
||||
```
|
||||
|
||||
5. 对表 d1001 按每 10 秒进行平均值、最大值和最小值聚合统计
|
||||
```shell
|
||||
SELECT _wstart, AVG(current), MAX(voltage), MIN(phase) FROM test.d1001 INTERVAL(10s);
|
||||
```
|
||||
|
||||
在上面的查询中,使用系统提供的伪列_wstart 来给出每个窗口的开始时间。
|
|
@ -41,7 +41,7 @@ LIMIT 5
|
|||
|
||||
TDengine 支持通过 GROUP BY 子句,对数据进行聚合查询。SQL 语句包含 GROUP BY 子句时,SELECT 列表只能包含如下表达式:
|
||||
1. 常量
|
||||
2. 聚集函数
|
||||
2. 聚合函数
|
||||
3. 与 GROUP BY 后表达式相同的表达式
|
||||
4. 包含前面表达式的表达式
|
||||
|
||||
|
@ -158,7 +158,7 @@ window_clause: {
|
|||
|
||||
**注意** 在使用窗口子句时应注意以下规则:
|
||||
1. 窗口子句位于数据切分子句之后,不可以和 GROUP BY 子句一起使用。
|
||||
2. 窗口子句将数据按窗口进行切分,对每个窗口进行 SELECT 列表中的表达式的计算,SELECT 列表中的表达式只能包含:常量;伪列:_wstart 伪列、_wend 伪列和 _wduration 伪列;聚集函数(包括选择函数和可以由参数确定输出行数的时序特有函数)
|
||||
2. 窗口子句将数据按窗口进行切分,对每个窗口进行 SELECT 列表中的表达式的计算,SELECT 列表中的表达式只能包含:常量;伪列:_wstart 伪列、_wend 伪列和 _wduration 伪列;聚合函数(包括选择函数和可以由参数确定输出行数的时序特有函数)
|
||||
3. WHERE 语句可以指定查询的起止时间和其他过滤条件。
|
||||
|
||||
### 时间戳伪列
|
||||
|
|
|
@ -16,12 +16,12 @@ TDengine 采用了一种创新的时间驱动缓存管理策略,亦称为写
|
|||
|
||||
为了实现数据的分布式存储和高可用性,TDengine 引入了虚拟节点(vnode)的概念。每个 vnode 可以拥有多达 3 个副本,这些副本共同组成一个 vnode group,简称 vgroup。在创建数据库时,用户需要确定每个 vnode 的写入缓存大小,以确保数据的合理分配和高效存储。
|
||||
|
||||
创建数据库时的两个关键参数—vgroups 和 buffer—分别决定了数据库中的数据由多少个 vgroup 进行处理,以及为每个 vnode 分配多少写入缓存。通过合理配置这两个
|
||||
创建数据库时的两个关键参数 `vgroups` 和 `buffer` 分别决定了数据库中的数据由多少个 vgroup 进行处理,以及为每个 vnode 分配多少写入缓存。通过合理配置这两个
|
||||
参数,用户可以根据实际需求调整数据库的性能和存储容量,从而实现最佳的性能和成本效益。
|
||||
|
||||
例 如, 下面的 SQL 创建了包含 10 个 vgroup,每个 vnode 占 用 256MB 内存的数据库。
|
||||
```ssql
|
||||
create database power vgroups 10 buffer 256 cachemodel 'none' pages 128 pagesize 16
|
||||
```sql
|
||||
CREATE DATABASE POWER VGROUPS 10 BUFFER 256 CACHEMODEL 'NONE' PAGES 128 PAGESIZE 16;
|
||||
```
|
||||
|
||||
缓存越大越好,但超过一定阈值后再增加缓存对写入性能提升并无帮助。
|
||||
|
@ -43,7 +43,7 @@ create database power vgroups 10 buffer 256 cachemodel 'none' pages 128 pagesize
|
|||
为了提升查询和写入操作的效率,每个 vnode 都配备了缓存机制,用于存储其曾经获取过的元数据。这一元数据缓存的大小由创建数据库时的两个参数 pages 和 pagesize 共同决定。其中,pagesize 参数的单位是 KB,用于指定每个缓存页的大小。如下 SQL 会为数据库 power 的每个 vnode 创建 128 个 page、每个 page 16KB 的元数据缓存
|
||||
|
||||
```sql
|
||||
create database power pages 128 pagesize 16
|
||||
CREATE DATABASE POWER PAGES 128 PAGESIZE 16;
|
||||
```
|
||||
|
||||
## 文件系统缓存
|
||||
|
@ -57,7 +57,7 @@ TDengine 利用这些日志文件实现故障前的状态恢复。在写入 WAL
|
|||
- wal_fsync_period:当 wal_level 设置为 2 时,这个参数控制执行 fsync 的频率。设置为 0 表示每次写入后立即执行 fsync,这可以确保数据的安全性,但可能会牺牲一些性能。当设置为大于 0 的数值时,表示 fsync 周期,默认为 3000,范围是[1, 180000],单位毫秒。
|
||||
|
||||
```sql
|
||||
create database power wal_level 1 wal_fsync_period 3000
|
||||
CREATE DATABASE POWER WAL_LEVEL 1 WAL_FSYNC_PERIOD 3000;
|
||||
```
|
||||
|
||||
在创建数据库时可以选择不同的参数类型,来选择性能优先或者可靠性优先。
|
||||
|
|
|
@ -1,276 +1,276 @@
|
|||
---
|
||||
sidebar_label: 数据写入
|
||||
title: 零代码数据源接入
|
||||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
TDengine Enterprise 配备了一个强大的可视化数据管理工具—taosExplorer。借助 taosExplorer,用户只须在浏览器中简单配置,就能轻松地向 TDengine 提交任务,实现以零代码方式将来自不同数据源的数据无缝导入 TDengine。在导入过程中,TDengine 会对数据进行自动提取、过滤和转换,以保证导入的数据质量。通过这种零代码数据源接入方式,TDengine 成功转型为一个卓越的时序大数据汇聚平台。用户无须部署额外的 ETL 工具,从而大大简化整体架构的设计,提高了数据处理效率。
|
||||
|
||||
下图展示了零代码接入平台的系统架构。
|
||||
|
||||

|
||||
|
||||
## 支持的数据源
|
||||
|
||||
目前 TDengine 支持的数据源如下:
|
||||
|
||||
1. Aveva PI System:一个工业数据管理和分析平台,前身为 OSIsoft PI System,它能够实时采集、整合、分析和可视化工业数据,助力企业实现智能化决策和精细化管理
|
||||
2. Aveva Historian:一个工业大数据分析软件,前身为 Wonderware Historian,专为工业环境设计,用于存储、管理和分析来自各种工业设备、传感器的实时和历史数据。
|
||||
3. OPC DA/UA:OPC 是 Open Platform Communications 的缩写,是一种开放式、标准化的通信协议,用于不同厂商的自动化设备之间进行数据交换。它最初由微软公司开发,旨在解决工业控制领域中不同设备之间互操作性差的问题。OPC 协议最初于 1996 年发布,当时称为 OPC DA (Data Access),主要用于实时数据采集和控制;2006 年,OPC 基金会发布了 OPC UA (Unified Architecture) 标准,它是一种基于服务的面向对象的协议,具有更高的灵活性和可扩展性,已成为 OPC 协议的主流版本。
|
||||
4. MQTT:Message Queuing Telemetry Transport 的缩写,一种基于发布/订阅模式的轻量级通讯协议,专为低开销、低带宽占用的即时通讯设计,广泛适用于物联网、小型设备、移动应用等领域。
|
||||
5. Kafka:由 Apache 软件基金会开发的一个开源流处理平台,主要用于处理实时数据,并提供一个统一、高通量、低延迟的消息系统。它具备高速度、可伸缩性、持久性和分布式设计等特点,使得它能够在每秒处理数十万次的读写操作,支持上千个客户端,同时保持数据的可靠性和可用性。
|
||||
6. OpenTSDB:基于 HBase 的分布式、可伸缩的时序数据库。它主要用于存储、索引和提供从大规模集群(包括网络设备、操作系统、应用程序等)中收集的指标数据,使这些数据更易于访问和图形化展示。
|
||||
7. CSV:Comma Separated Values 的缩写,是一种以逗号分隔的纯文本文件格式,通常用于电子表格或数据库软件。
|
||||
8. TDengine 2:泛指运行 TDengine 2.x 版本的 TDengine 实例。
|
||||
9. TDengine 3:泛指运行 TDengine 3.x 版本的 TDengine 实例。
|
||||
10. MySQL, PostgreSQL, Oracle 等关系型数据库。
|
||||
|
||||
## 数据提取、过滤和转换
|
||||
|
||||
因为数据源可以有多个,每个数据源的物理单位可能不一样,命名规则也不一样,时区也可能不同。为解决这个问题,TDengine 内置 ETL 功能,可以从数据源的数据包中解析、提取需要的数据,并进行过滤和转换,以保证写入数据的质量,提供统一的命名空间。具体的功能如下:
|
||||
|
||||
1. 解析:使用 JSON Path 或正则表达式,从原始消息中解析字段
|
||||
2. 从列中提取或拆分:使用 split 或正则表达式,从一个原始字段中提取多个字段
|
||||
3. 过滤:只有表达式的值为 true 时,消息才会被写入 TDengine
|
||||
4. 转换:建立解析后的字段和 TDengine 超级表字段之间的转换与映射关系。
|
||||
|
||||
下面详细讲解数据转换规则
|
||||
|
||||
|
||||
### 解析
|
||||
|
||||
仅非结构化的数据源需要这个步骤,目前 MQTT 和 Kafka 数据源会使用这个步骤提供的规则来解析非结构化数据,以初步获取结构化数据,即可以以字段描述的行列数据。在 explorer 中您需要提供示例数据和解析规则,来预览解析出以表格呈现的结构化数据。
|
||||
|
||||
#### 示例数据
|
||||
|
||||

|
||||
|
||||
如图,textarea 输入框中就是示例数据,可以通过三种方式来获取示例数据:
|
||||
|
||||
1. 直接在 textarea 中输入示例数据;
|
||||
2. 点击右侧按钮 “从服务器检索” 则从配置的服务器获取示例数据,并追加到示例数据 textarea 中;
|
||||
3. 上传文件,将文件内容追加到示例数据 textarea 中。
|
||||
|
||||
#### 解析<a name="parse"></a>
|
||||
|
||||
解析就是通过解析规则,将非结构化字符串解析为结构化数据。消息体的解析规则目前支持 JSON、Regex 和 UDT。
|
||||
|
||||
##### JSON 解析
|
||||
|
||||
如下 JSON 示例数据,可自动解析出字段:`groupid`、`voltage`、`current`、`ts`、`inuse`、`location`。
|
||||
|
||||
``` json
|
||||
{"groupid": 170001, "voltage": "221V", "current": 12.3, "ts": "2023-12-18T22:12:00", "inuse": true, "location": "beijing.chaoyang.datun"}
|
||||
{"groupid": 170001, "voltage": "220V", "current": 12.2, "ts": "2023-12-18T22:12:02", "inuse": true, "location": "beijing.chaoyang.datun"}
|
||||
{"groupid": 170001, "voltage": "216V", "current": 12.5, "ts": "2023-12-18T22:12:04", "inuse": false, "location": "beijing.chaoyang.datun"}
|
||||
```
|
||||
|
||||
如下嵌套结构的 JSON 数据,可自动解析出字段`groupid`、`data_voltage`、`data_current`、`ts`、`inuse`、`location_0_province`、`location_0_city`、`location_0_datun`,也可以选择要解析的字段,并设置解析的别名。
|
||||
|
||||
``` json
|
||||
{"groupid": 170001, "data": { "voltage": "221V", "current": 12.3 }, "ts": "2023-12-18T22:12:00", "inuse": true, "location": [{"province": "beijing", "city":"chaoyang", "street": "datun"}]}
|
||||
```
|
||||
|
||||

|
||||
|
||||
##### Regex 正则表达式<a name="regex"></a>
|
||||
|
||||
可以使用正则表达式的**命名捕获组**从任何字符串(文本)字段中提取多个字段。如图所示,从 nginx 日志中提取访问ip、时间戳、访问的url等字段。
|
||||
|
||||
``` re
|
||||
(?<ip>\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b)\s-\s-\s\[(?<ts>\d{2}/\w{3}/\d{4}:\d{2}:\d{2}:\d{2}\s\+\d{4})\]\s"(?<method>[A-Z]+)\s(?<url>[^\s"]+).*(?<status>\d{3})\s(?<length>\d+)
|
||||
```
|
||||
|
||||

|
||||
|
||||
##### UDT 自定义解析脚本
|
||||
|
||||
自定义 rhai 语法脚本解析输入数据(参考 `https://rhai.rs/book/` ),脚本目前仅支持 json 格式原始数据。
|
||||
|
||||
**输入**:脚本中可以使用参数 data, data 是原始数据 json 解析后的 Object Map;
|
||||
|
||||
**输出**:输出的数据必须是数组。
|
||||
|
||||
例如对于数据,一次上报三相电压值,分别入到三个子表中。则需要对这类数据做解析
|
||||
|
||||
``` json
|
||||
{
|
||||
"ts": "2024-06-27 18:00:00",
|
||||
"voltage": "220.1,220.3,221.1",
|
||||
"dev_id": "8208891"
|
||||
}
|
||||
```
|
||||
|
||||
那么可以使用如下脚本来提取三个电压数据。
|
||||
|
||||
```
|
||||
let v3 = data["voltage"].split(",");
|
||||
|
||||
[
|
||||
#{"ts": data["ts"], "val": v3[0], "dev_id": data["dev_id"]},
|
||||
#{"ts": data["ts"], "val": v3[1], "dev_id": data["dev_id"]},
|
||||
#{"ts": data["ts"], "val": v3[2], "dev_id": data["dev_id"]}
|
||||
]
|
||||
```
|
||||
|
||||
最终解析结果如下所示:
|
||||
|
||||

|
||||
|
||||
### 提取或拆分
|
||||
|
||||
解析后的数据,可能还无法满足目标表的数据要求。比如智能表原始采集数据如下( json 格式):
|
||||
|
||||
``` json
|
||||
{"groupid": 170001, "voltage": "221V", "current": 12.3, "ts": "2023-12-18T22:12:00", "inuse": true, "location": "beijing.chaoyang.datun"}
|
||||
{"groupid": 170001, "voltage": "220V", "current": 12.2, "ts": "2023-12-18T22:12:02", "inuse": true, "location": "beijing.chaoyang.datun"}
|
||||
{"groupid": 170001, "voltage": "216V", "current": 12.5, "ts": "2023-12-18T22:12:04", "inuse": false, "location": "beijing.chaoyang.datun"}
|
||||
```
|
||||
|
||||
使用 json 规则解析出的电压是字符串表达的带单位形式,最终入库希望能使用 int 类型记录电压值和电流值,便于统计分析,此时就需要对电压进一步拆分;另外日期期望拆分为日期和时间入库。
|
||||
|
||||
如下图所示可以对源字段`ts`使用 split 规则拆分成日期和时间,对字段`voltage`使用 regex 提取出电压值和电压单位。split 规则需要设置**分隔符**和**拆分数量**,拆分后的字段命名规则为`{原字段名}_{顺序号}`,Regex 规则同解析过程中的一样,使用**命名捕获组**命名提取字段。
|
||||
|
||||

|
||||
|
||||
### 过滤<a name="filter"></a>
|
||||
|
||||
过滤功能可以设置过滤条件,满足条件的数据行 才会被写入目标表。过滤条件表达式的结果必须是 boolean 类型。在编写过滤条件前,必须确定 解析字段的类型,根据解析字段的类型,可以使用判断函数、比较操作符(`>`、`>=`、`<=`、`<`、`==`、`!=`)来判断。
|
||||
|
||||
#### 字段类型及转换
|
||||
|
||||
只有明确解析出的每个字段的类型,才能使用正确的语法做数据过滤。
|
||||
|
||||
使用 json 规则解析出的字段,按照属性值来自动设置类型:
|
||||
|
||||
1. bool 类型:"inuse": true
|
||||
2. int 类型:"voltage": 220
|
||||
3. float 类型:"current" : 12.2
|
||||
4. String 类型:"location": "MX001"
|
||||
|
||||
使用 regex 规则解析的数据都是 string 类型。
|
||||
使用 split 和 regex 提取或拆分的数据是 string 类型。
|
||||
|
||||
如果提取出的数据类型不是预期中的类型,可以做数据类型转换。常用的数据类型转换就是把字符串转换成为数值类型。支持的转换函数如下:
|
||||
|
||||
|Function|From type|To type|e.g.|
|
||||
|:----|:----|:----|:----|
|
||||
| parse_int | string | int | parse_int("56") // 结果为整数 56 |
|
||||
| parse_float | string | float | parse_float("12.3") // 结果为浮点数 12.3 |
|
||||
|
||||
#### 判断表达式
|
||||
|
||||
不同的数据类型有各自判断表达式的写法。
|
||||
|
||||
##### BOOL 类型
|
||||
|
||||
可以使用变量或者使用操作符`!`,比如对于字段 "inuse": true,可以编写以下表达式:
|
||||
|
||||
> 1. inuse
|
||||
> 2. !inuse
|
||||
|
||||
##### 数值类型(int/float)
|
||||
|
||||
数值类型支持使用比较操作符`==`、`!=`、`>`、`>=`、`<`、`<=`。
|
||||
|
||||
##### 字符串类型
|
||||
|
||||
使用比较操作符,比较字符串。
|
||||
|
||||
字符串函数
|
||||
|
||||
|Function|Description|e.g.|
|
||||
|:----|:----|:----|
|
||||
| is_empty | returns true if the string is empty | s.is_empty() |
|
||||
| contains | checks if a certain character or sub-string occurs in the string | s.contains("substring") |
|
||||
| starts_with | returns true if the string starts with a certain string | s.starts_with("prefix") |
|
||||
| ends_with | returns true if the string ends with a certain string | s.ends_with("suffix") |
|
||||
| len | returns the number of characters (not number of bytes) in the string,must be used with comparison operator | s.len == 5 判断字符串长度是否为5;len作为属性返回 int ,和前四个函数有区别,前四个直接返回 bool。 |
|
||||
|
||||
##### 复合表达式
|
||||
|
||||
多个判断表达式,可以使用逻辑操作符(&&、||、!)来组合。
|
||||
比如下面的表达式表示获取北京市安装的并且电压值大于 200 的智能表数据。
|
||||
|
||||
> location.starts_with("beijing") && voltage > 200
|
||||
|
||||
### 映射
|
||||
|
||||
映射是将解析、提取、拆分的**源字段**对应到**目标表字段**,可以直接对应,也可以通过一些规则计算后再映射到目标表。
|
||||
|
||||
#### 选择目标超级表
|
||||
|
||||
选择目标超级表后,会加载出超级表所有的 tags 和 columns。
|
||||
源字段根据名称自动使用 mapping 规则映射到目标超级表的 tag 和 column。
|
||||
例如有如下解析、提取、拆分后的预览数据:
|
||||
|
||||
#### 映射规则 <a name="expression"></a>
|
||||
|
||||
支持的映射规则如下表所示:
|
||||
|
||||
|rule|description|
|
||||
|:----|:----|
|
||||
| mapping | 直接映射,需要选择映射源字段。|
|
||||
| value | 常量,可以输入字符串常量,也可以是数值常量,输入的常量值直接入库。|
|
||||
| generator | 生成器,目前仅支持时间戳生成器 now,入库时会将当前时间入库。|
|
||||
| join | 字符串连接器,可指定连接字符拼接选择的多个源字段。|
|
||||
| format | **字符串格式化工具**,填写格式化字符串,比如有三个源字段 year, month, day 分别表示年月日,入库希望以yyyy-MM-dd的日期格式入库,则可以提供格式化字符串为 `${year}-${month}-${day}`。其中`${}`作为占位符,占位符中可以是一个源字段,也可以是 string 类型字段的函数处理|
|
||||
| sum | 选择多个数值型字段做加法计算。|
|
||||
| expr | **数值运算表达式**,可以对数值型字段做更加复杂的函数处理和数学运算。|
|
||||
|
||||
##### format 中支持的字符串处理函数
|
||||
|
||||
|Function|description|e.g.|
|
||||
|:----|:----|:----|
|
||||
| pad(len, pad_chars) | pads the string with a character or a string to at least a specified length | "1.2".pad(5, '0') // 结果为"1.200" |
|
||||
|trim|trims the string of whitespace at the beginning and end|" abc ee ".trim() // 结果为"abc ee"|
|
||||
|sub_string(start_pos, len)|extracts a sub-string,两个参数:<br />1. start position, counting from end if < 0<br />2. (optional) number of characters to extract, none if ≤ 0, to end if omitted|"012345678".sub_string(5) // "5678"<br />"012345678".sub_string(5, 2) // "56"<br />"012345678".sub_string(-2) // "78"|
|
||||
|replace(substring, replacement)|replaces a sub-string with another|"012345678".replace("012", "abc") // "abc345678"|
|
||||
|
||||
##### expr 数学计算表达式
|
||||
|
||||
基本数学运算支持加`+`、减`-`、乘`*`、除`/`。
|
||||
|
||||
比如数据源采集数值以设置度为单位,目标库存储华氏度温度值。那么就需要对采集的温度数据做转换。
|
||||
|
||||
解析的源字段为`temperature`,则需要使用表达式 `temperature * 1.8 + 32`。
|
||||
|
||||
数值表达式中也支持使用数学函数,可用的数学函数如下表所示:
|
||||
|
||||
|Function|description|e.g.|
|
||||
|:----|:----|:----|
|
||||
|sin、cos、tan、sinh、cosh|Trigonometry|a.sin() |
|
||||
|asin、acos、atan、 asinh、acosh|arc-trigonometry|a.asin()|
|
||||
|sqrt|Square root|a.sqrt() // 4.sqrt() == 2|
|
||||
|exp|Exponential|a.exp()|
|
||||
|ln、log|Logarithmic|a.ln() // e.ln() == 1<br />a.log() // 10.log() == 1|
|
||||
|floor、ceiling、round、int、fraction|rounding|a.floor() // (4.2).floor() == 4<br />a.ceiling() // (4.2).ceiling() == 5<br />a.round() // (4.2).round() == 4<br />a.int() // (4.2).int() == 4<br />a.fraction() // (4.2).fraction() == 0.2|
|
||||
|
||||
#### 子表名映射
|
||||
|
||||
子表名类型为字符串,可以使用映射规则中的字符串格式化 format 表达式定义子表名。
|
||||
|
||||
## 任务的创建
|
||||
|
||||
下面以 MQTT 数据源为例,介绍如何创建一个 MQTT 类型的任务,从 MQTT Broker 消费数据,并写入 TDengine。
|
||||
|
||||
1. 登录至 taosExplorer 以后,点击左侧导航栏上的“数据写入”,即可进入任务列表页面
|
||||
2. 在任务列表页面,点击“+ 新增数据源”,即可进入任务创建页面
|
||||
3. 输入任务名称后,选择类型为 MQTT, 然后可以创建一个新的代理,或者选择已创建的代理
|
||||
4. 输入 MQTT broker 的 IP 地址和端口号,例如:192.168.1.100:1883
|
||||
5. 配置认证和 SSL 加密:
|
||||
- 如果 MQTT broker 开启了用户认证,则在认证部分,输入 MQTT broker 的用户名和密码;
|
||||
- 如果 MQTT broker 开启了 SSL 加密,则可以打开页面上的 SSL 证书开关,并上传 CA 的证书,以及客户端的证书和私钥文件;
|
||||
6. 在“采集配置“部分,可选择 MQTT 协议的版本,目前支持 3.1, 3.1.1, 5.0 三个版本;配置 Client ID 时要注意,如果对同一个 MQTT broker 创建了多个任务,Client ID 应不同,否则会造成 Client ID 冲突,导致任务无法正常运行;在对主题和 QoS 进行配置时,需要使用 `<topic name>::<QoS>` 的形式,即订阅的主题与 QoS 之间要使用两个冒号分隔,其中 QoS 的取值范围为 0, 1, 2, 分别代表 at most once, at lease once, exactly once;配置完成以上信息后,可以点击“检查连通性”按钮,对以上配置进行检查,如果连通性检查失败,请按照页面上返回的具体错误提示进行修改;
|
||||
7. 在从 MQTT broker 同步数据的过程中,taosX 还支持对消息体中的字段进行提取,过滤、映射等操作。在位于 “Payload 转换”下方的文本框中,可以直接输入输入消息体样例,或是以上传文件的方式导入,以后还会支持直接从所配置的服务器中检索样例消息;
|
||||
8. 对消息体字段的提取,目前支持 2 种方式:JSON 和正则表达式。对于简单的 key/value 格式的 JSON 数据,可以直接点击提取按钮,即可展示解析出的字段名;对于复杂的 JSON 数据,可以使用 JSON Path 提取感兴趣的字段;当使用正则表达式提取字段时,要保证正则表达式的正确性;
|
||||
9. 消息体中的字段被解析后,可以基于解析出的字段名设置过滤规则,只有满足过滤规则的数据,才会写入 TDengine,否则会忽略该消息;例如:可以配置过滤规则为 voltage > 200,即只有当电压大于 200V 的数据才会被同步至 TDengine;
|
||||
10. 最后,在配置完消息体中的字段和超级表中的字段的映射规则后,就可以提交任务了;除了基本的映射以外,在这里还可以对消息中字段的值进行转换,例如:可以通过表达式 (expr) 将原消息体中的电压和电流,计算为功率后再写入 TDengine;
|
||||
11. 任务提交后,会自动返回任务列表页面,如果提交成功,任务的状态会切换至“运行中”,如果提交失败,可通过查看该任务的活动日志,查找错误原因;
|
||||
12. 对于运行中的任务,点击指标的查看按钮,可以查看该任务的详细运行指标,弹出窗口划分为 2 个标签页,分别展示该任务多次运行的累计指标和本次运行的指标,这些指标每 2 秒钟会自动刷新一次。
|
||||
|
||||
## 任务管理
|
||||
|
||||
---
|
||||
sidebar_label: 数据接入
|
||||
title: 零代码第三方数据接入
|
||||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
## 概述
|
||||
|
||||
TDengine Enterprise 配备了一个强大的可视化数据管理工具—taosExplorer。借助 taosExplorer,用户只须在浏览器中简单配置,就能轻松地向 TDengine 提交任务,实现以零代码方式将来自不同数据源的数据无缝导入 TDengine。在导入过程中,TDengine 会对数据进行自动提取、过滤和转换,以保证导入的数据质量。通过这种零代码数据源接入方式,TDengine 成功转型为一个卓越的时序大数据汇聚平台。用户无须部署额外的 ETL 工具,从而大大简化整体架构的设计,提高了数据处理效率。
|
||||
|
||||
下图展示了零代码接入平台的系统架构。
|
||||
|
||||

|
||||
|
||||
## 支持的数据源
|
||||
|
||||
目前 TDengine 支持的数据源如下:
|
||||
|
||||
1. Aveva PI System:一个工业数据管理和分析平台,前身为 OSIsoft PI System,它能够实时采集、整合、分析和可视化工业数据,助力企业实现智能化决策和精细化管理
|
||||
2. Aveva Historian:一个工业大数据分析软件,前身为 Wonderware Historian,专为工业环境设计,用于存储、管理和分析来自各种工业设备、传感器的实时和历史数据。
|
||||
3. OPC DA/UA:OPC 是 Open Platform Communications 的缩写,是一种开放式、标准化的通信协议,用于不同厂商的自动化设备之间进行数据交换。它最初由微软公司开发,旨在解决工业控制领域中不同设备之间互操作性差的问题。OPC 协议最初于 1996 年发布,当时称为 OPC DA (Data Access),主要用于实时数据采集和控制;2006 年,OPC 基金会发布了 OPC UA (Unified Architecture) 标准,它是一种基于服务的面向对象的协议,具有更高的灵活性和可扩展性,已成为 OPC 协议的主流版本。
|
||||
4. MQTT:Message Queuing Telemetry Transport 的缩写,一种基于发布/订阅模式的轻量级通讯协议,专为低开销、低带宽占用的即时通讯设计,广泛适用于物联网、小型设备、移动应用等领域。
|
||||
5. Kafka:由 Apache 软件基金会开发的一个开源流处理平台,主要用于处理实时数据,并提供一个统一、高通量、低延迟的消息系统。它具备高速度、可伸缩性、持久性和分布式设计等特点,使得它能够在每秒处理数十万次的读写操作,支持上千个客户端,同时保持数据的可靠性和可用性。
|
||||
6. OpenTSDB:基于 HBase 的分布式、可伸缩的时序数据库。它主要用于存储、索引和提供从大规模集群(包括网络设备、操作系统、应用程序等)中收集的指标数据,使这些数据更易于访问和图形化展示。
|
||||
7. CSV:Comma Separated Values 的缩写,是一种以逗号分隔的纯文本文件格式,通常用于电子表格或数据库软件。
|
||||
8. TDengine 2:泛指运行 TDengine 2.x 版本的 TDengine 实例。
|
||||
9. TDengine 3:泛指运行 TDengine 3.x 版本的 TDengine 实例。
|
||||
10. MySQL, PostgreSQL, Oracle 等关系型数据库。
|
||||
|
||||
## 数据提取、过滤和转换
|
||||
|
||||
因为数据源可以有多个,每个数据源的物理单位可能不一样,命名规则也不一样,时区也可能不同。为解决这个问题,TDengine 内置 ETL 功能,可以从数据源的数据包中解析、提取需要的数据,并进行过滤和转换,以保证写入数据的质量,提供统一的命名空间。具体的功能如下:
|
||||
|
||||
1. 解析:使用 JSON Path 或正则表达式,从原始消息中解析字段
|
||||
2. 从列中提取或拆分:使用 split 或正则表达式,从一个原始字段中提取多个字段
|
||||
3. 过滤:只有表达式的值为 true 时,消息才会被写入 TDengine
|
||||
4. 转换:建立解析后的字段和 TDengine 超级表字段之间的转换与映射关系。
|
||||
|
||||
下面详细讲解数据转换规则
|
||||
|
||||
|
||||
### 解析
|
||||
|
||||
仅非结构化的数据源需要这个步骤,目前 MQTT 和 Kafka 数据源会使用这个步骤提供的规则来解析非结构化数据,以初步获取结构化数据,即可以以字段描述的行列数据。在 explorer 中您需要提供示例数据和解析规则,来预览解析出以表格呈现的结构化数据。
|
||||
|
||||
#### 示例数据
|
||||
|
||||

|
||||
|
||||
如图,textarea 输入框中就是示例数据,可以通过三种方式来获取示例数据:
|
||||
|
||||
1. 直接在 textarea 中输入示例数据;
|
||||
2. 点击右侧按钮 “从服务器检索” 则从配置的服务器获取示例数据,并追加到示例数据 textarea 中;
|
||||
3. 上传文件,将文件内容追加到示例数据 textarea 中。
|
||||
|
||||
#### 解析<a name="parse"></a>
|
||||
|
||||
解析就是通过解析规则,将非结构化字符串解析为结构化数据。消息体的解析规则目前支持 JSON、Regex 和 UDT。
|
||||
|
||||
##### JSON 解析
|
||||
|
||||
如下 JSON 示例数据,可自动解析出字段:`groupid`、`voltage`、`current`、`ts`、`inuse`、`location`。
|
||||
|
||||
``` json
|
||||
{"groupid": 170001, "voltage": "221V", "current": 12.3, "ts": "2023-12-18T22:12:00", "inuse": true, "location": "beijing.chaoyang.datun"}
|
||||
{"groupid": 170001, "voltage": "220V", "current": 12.2, "ts": "2023-12-18T22:12:02", "inuse": true, "location": "beijing.chaoyang.datun"}
|
||||
{"groupid": 170001, "voltage": "216V", "current": 12.5, "ts": "2023-12-18T22:12:04", "inuse": false, "location": "beijing.chaoyang.datun"}
|
||||
```
|
||||
|
||||
如下嵌套结构的 JSON 数据,可自动解析出字段`groupid`、`data_voltage`、`data_current`、`ts`、`inuse`、`location_0_province`、`location_0_city`、`location_0_datun`,也可以选择要解析的字段,并设置解析的别名。
|
||||
|
||||
``` json
|
||||
{"groupid": 170001, "data": { "voltage": "221V", "current": 12.3 }, "ts": "2023-12-18T22:12:00", "inuse": true, "location": [{"province": "beijing", "city":"chaoyang", "street": "datun"}]}
|
||||
```
|
||||
|
||||

|
||||
|
||||
##### Regex 正则表达式<a name="regex"></a>
|
||||
|
||||
可以使用正则表达式的**命名捕获组**从任何字符串(文本)字段中提取多个字段。如图所示,从 nginx 日志中提取访问ip、时间戳、访问的url等字段。
|
||||
|
||||
``` re
|
||||
(?<ip>\b(?:[0-9]{1,3}\.){3}[0-9]{1,3}\b)\s-\s-\s\[(?<ts>\d{2}/\w{3}/\d{4}:\d{2}:\d{2}:\d{2}\s\+\d{4})\]\s"(?<method>[A-Z]+)\s(?<url>[^\s"]+).*(?<status>\d{3})\s(?<length>\d+)
|
||||
```
|
||||
|
||||

|
||||
|
||||
##### UDT 自定义解析脚本
|
||||
|
||||
自定义 rhai 语法脚本解析输入数据(参考 `https://rhai.rs/book/` ),脚本目前仅支持 json 格式原始数据。
|
||||
|
||||
**输入**:脚本中可以使用参数 data, data 是原始数据 json 解析后的 Object Map;
|
||||
|
||||
**输出**:输出的数据必须是数组。
|
||||
|
||||
例如对于数据,一次上报三相电压值,分别入到三个子表中。则需要对这类数据做解析
|
||||
|
||||
``` json
|
||||
{
|
||||
"ts": "2024-06-27 18:00:00",
|
||||
"voltage": "220.1,220.3,221.1",
|
||||
"dev_id": "8208891"
|
||||
}
|
||||
```
|
||||
|
||||
那么可以使用如下脚本来提取三个电压数据。
|
||||
|
||||
```
|
||||
let v3 = data["voltage"].split(",");
|
||||
|
||||
[
|
||||
#{"ts": data["ts"], "val": v3[0], "dev_id": data["dev_id"]},
|
||||
#{"ts": data["ts"], "val": v3[1], "dev_id": data["dev_id"]},
|
||||
#{"ts": data["ts"], "val": v3[2], "dev_id": data["dev_id"]}
|
||||
]
|
||||
```
|
||||
|
||||
最终解析结果如下所示:
|
||||
|
||||

|
||||
|
||||
### 提取或拆分
|
||||
|
||||
解析后的数据,可能还无法满足目标表的数据要求。比如智能表原始采集数据如下( json 格式):
|
||||
|
||||
``` json
|
||||
{"groupid": 170001, "voltage": "221V", "current": 12.3, "ts": "2023-12-18T22:12:00", "inuse": true, "location": "beijing.chaoyang.datun"}
|
||||
{"groupid": 170001, "voltage": "220V", "current": 12.2, "ts": "2023-12-18T22:12:02", "inuse": true, "location": "beijing.chaoyang.datun"}
|
||||
{"groupid": 170001, "voltage": "216V", "current": 12.5, "ts": "2023-12-18T22:12:04", "inuse": false, "location": "beijing.chaoyang.datun"}
|
||||
```
|
||||
|
||||
使用 json 规则解析出的电压是字符串表达的带单位形式,最终入库希望能使用 int 类型记录电压值和电流值,便于统计分析,此时就需要对电压进一步拆分;另外日期期望拆分为日期和时间入库。
|
||||
|
||||
如下图所示可以对源字段`ts`使用 split 规则拆分成日期和时间,对字段`voltage`使用 regex 提取出电压值和电压单位。split 规则需要设置**分隔符**和**拆分数量**,拆分后的字段命名规则为`{原字段名}_{顺序号}`,Regex 规则同解析过程中的一样,使用**命名捕获组**命名提取字段。
|
||||
|
||||

|
||||
|
||||
### 过滤<a name="filter"></a>
|
||||
|
||||
过滤功能可以设置过滤条件,满足条件的数据行 才会被写入目标表。过滤条件表达式的结果必须是 boolean 类型。在编写过滤条件前,必须确定 解析字段的类型,根据解析字段的类型,可以使用判断函数、比较操作符(`>`、`>=`、`<=`、`<`、`==`、`!=`)来判断。
|
||||
|
||||
#### 字段类型及转换
|
||||
|
||||
只有明确解析出的每个字段的类型,才能使用正确的语法做数据过滤。
|
||||
|
||||
使用 json 规则解析出的字段,按照属性值来自动设置类型:
|
||||
|
||||
1. bool 类型:"inuse": true
|
||||
2. int 类型:"voltage": 220
|
||||
3. float 类型:"current" : 12.2
|
||||
4. String 类型:"location": "MX001"
|
||||
|
||||
使用 regex 规则解析的数据都是 string 类型。
|
||||
使用 split 和 regex 提取或拆分的数据是 string 类型。
|
||||
|
||||
如果提取出的数据类型不是预期中的类型,可以做数据类型转换。常用的数据类型转换就是把字符串转换成为数值类型。支持的转换函数如下:
|
||||
|
||||
|Function|From type|To type|e.g.|
|
||||
|:----|:----|:----|:----|
|
||||
| parse_int | string | int | parse_int("56") // 结果为整数 56 |
|
||||
| parse_float | string | float | parse_float("12.3") // 结果为浮点数 12.3 |
|
||||
|
||||
#### 判断表达式
|
||||
|
||||
不同的数据类型有各自判断表达式的写法。
|
||||
|
||||
##### BOOL 类型
|
||||
|
||||
可以使用变量或者使用操作符`!`,比如对于字段 "inuse": true,可以编写以下表达式:
|
||||
|
||||
> 1. inuse
|
||||
> 2. !inuse
|
||||
|
||||
##### 数值类型(int/float)
|
||||
|
||||
数值类型支持使用比较操作符`==`、`!=`、`>`、`>=`、`<`、`<=`。
|
||||
|
||||
##### 字符串类型
|
||||
|
||||
使用比较操作符,比较字符串。
|
||||
|
||||
字符串函数
|
||||
|
||||
|Function|Description|e.g.|
|
||||
|:----|:----|:----|
|
||||
| is_empty | returns true if the string is empty | s.is_empty() |
|
||||
| contains | checks if a certain character or sub-string occurs in the string | s.contains("substring") |
|
||||
| starts_with | returns true if the string starts with a certain string | s.starts_with("prefix") |
|
||||
| ends_with | returns true if the string ends with a certain string | s.ends_with("suffix") |
|
||||
| len | returns the number of characters (not number of bytes) in the string,must be used with comparison operator | s.len == 5 判断字符串长度是否为5;len作为属性返回 int ,和前四个函数有区别,前四个直接返回 bool。 |
|
||||
|
||||
##### 复合表达式
|
||||
|
||||
多个判断表达式,可以使用逻辑操作符(&&、||、!)来组合。
|
||||
比如下面的表达式表示获取北京市安装的并且电压值大于 200 的智能表数据。
|
||||
|
||||
> location.starts_with("beijing") && voltage > 200
|
||||
|
||||
### 映射
|
||||
|
||||
映射是将解析、提取、拆分的**源字段**对应到**目标表字段**,可以直接对应,也可以通过一些规则计算后再映射到目标表。
|
||||
|
||||
#### 选择目标超级表
|
||||
|
||||
选择目标超级表后,会加载出超级表所有的 tags 和 columns。
|
||||
源字段根据名称自动使用 mapping 规则映射到目标超级表的 tag 和 column。
|
||||
例如有如下解析、提取、拆分后的预览数据:
|
||||
|
||||
#### 映射规则 <a name="expression"></a>
|
||||
|
||||
支持的映射规则如下表所示:
|
||||
|
||||
|rule|description|
|
||||
|:----|:----|
|
||||
| mapping | 直接映射,需要选择映射源字段。|
|
||||
| value | 常量,可以输入字符串常量,也可以是数值常量,输入的常量值直接入库。|
|
||||
| generator | 生成器,目前仅支持时间戳生成器 now,入库时会将当前时间入库。|
|
||||
| join | 字符串连接器,可指定连接字符拼接选择的多个源字段。|
|
||||
| format | **字符串格式化工具**,填写格式化字符串,比如有三个源字段 year, month, day 分别表示年月日,入库希望以yyyy-MM-dd的日期格式入库,则可以提供格式化字符串为 `${year}-${month}-${day}`。其中`${}`作为占位符,占位符中可以是一个源字段,也可以是 string 类型字段的函数处理|
|
||||
| sum | 选择多个数值型字段做加法计算。|
|
||||
| expr | **数值运算表达式**,可以对数值型字段做更加复杂的函数处理和数学运算。|
|
||||
|
||||
##### format 中支持的字符串处理函数
|
||||
|
||||
|Function|description|e.g.|
|
||||
|:----|:----|:----|
|
||||
| pad(len, pad_chars) | pads the string with a character or a string to at least a specified length | "1.2".pad(5, '0') // 结果为"1.200" |
|
||||
|trim|trims the string of whitespace at the beginning and end|" abc ee ".trim() // 结果为"abc ee"|
|
||||
|sub_string(start_pos, len)|extracts a sub-string,两个参数:<br />1. start position, counting from end if < 0<br />2. (optional) number of characters to extract, none if ≤ 0, to end if omitted|"012345678".sub_string(5) // "5678"<br />"012345678".sub_string(5, 2) // "56"<br />"012345678".sub_string(-2) // "78"|
|
||||
|replace(substring, replacement)|replaces a sub-string with another|"012345678".replace("012", "abc") // "abc345678"|
|
||||
|
||||
##### expr 数学计算表达式
|
||||
|
||||
基本数学运算支持加`+`、减`-`、乘`*`、除`/`。
|
||||
|
||||
比如数据源采集数值以设置度为单位,目标库存储华氏度温度值。那么就需要对采集的温度数据做转换。
|
||||
|
||||
解析的源字段为`temperature`,则需要使用表达式 `temperature * 1.8 + 32`。
|
||||
|
||||
数值表达式中也支持使用数学函数,可用的数学函数如下表所示:
|
||||
|
||||
|Function|description|e.g.|
|
||||
|:----|:----|:----|
|
||||
|sin、cos、tan、sinh、cosh|Trigonometry|a.sin() |
|
||||
|asin、acos、atan、 asinh、acosh|arc-trigonometry|a.asin()|
|
||||
|sqrt|Square root|a.sqrt() // 4.sqrt() == 2|
|
||||
|exp|Exponential|a.exp()|
|
||||
|ln、log|Logarithmic|a.ln() // e.ln() == 1<br />a.log() // 10.log() == 1|
|
||||
|floor、ceiling、round、int、fraction|rounding|a.floor() // (4.2).floor() == 4<br />a.ceiling() // (4.2).ceiling() == 5<br />a.round() // (4.2).round() == 4<br />a.int() // (4.2).int() == 4<br />a.fraction() // (4.2).fraction() == 0.2|
|
||||
|
||||
#### 子表名映射
|
||||
|
||||
子表名类型为字符串,可以使用映射规则中的字符串格式化 format 表达式定义子表名。
|
||||
|
||||
## 任务的创建
|
||||
|
||||
下面以 MQTT 数据源为例,介绍如何创建一个 MQTT 类型的任务,从 MQTT Broker 消费数据,并写入 TDengine。
|
||||
|
||||
1. 登录至 taosExplorer 以后,点击左侧导航栏上的“数据写入”,即可进入任务列表页面
|
||||
2. 在任务列表页面,点击“+ 新增数据源”,即可进入任务创建页面
|
||||
3. 输入任务名称后,选择类型为 MQTT, 然后可以创建一个新的代理,或者选择已创建的代理
|
||||
4. 输入 MQTT broker 的 IP 地址和端口号,例如:192.168.1.100:1883
|
||||
5. 配置认证和 SSL 加密:
|
||||
- 如果 MQTT broker 开启了用户认证,则在认证部分,输入 MQTT broker 的用户名和密码;
|
||||
- 如果 MQTT broker 开启了 SSL 加密,则可以打开页面上的 SSL 证书开关,并上传 CA 的证书,以及客户端的证书和私钥文件;
|
||||
6. 在“采集配置“部分,可选择 MQTT 协议的版本,目前支持 3.1, 3.1.1, 5.0 三个版本;配置 Client ID 时要注意,如果对同一个 MQTT broker 创建了多个任务,Client ID 应不同,否则会造成 Client ID 冲突,导致任务无法正常运行;在对主题和 QoS 进行配置时,需要使用 `<topic name>::<QoS>` 的形式,即订阅的主题与 QoS 之间要使用两个冒号分隔,其中 QoS 的取值范围为 0, 1, 2, 分别代表 at most once, at lease once, exactly once;配置完成以上信息后,可以点击“检查连通性”按钮,对以上配置进行检查,如果连通性检查失败,请按照页面上返回的具体错误提示进行修改;
|
||||
7. 在从 MQTT broker 同步数据的过程中,taosX 还支持对消息体中的字段进行提取,过滤、映射等操作。在位于 “Payload 转换”下方的文本框中,可以直接输入输入消息体样例,或是以上传文件的方式导入,以后还会支持直接从所配置的服务器中检索样例消息;
|
||||
8. 对消息体字段的提取,目前支持 2 种方式:JSON 和正则表达式。对于简单的 key/value 格式的 JSON 数据,可以直接点击提取按钮,即可展示解析出的字段名;对于复杂的 JSON 数据,可以使用 JSON Path 提取感兴趣的字段;当使用正则表达式提取字段时,要保证正则表达式的正确性;
|
||||
9. 消息体中的字段被解析后,可以基于解析出的字段名设置过滤规则,只有满足过滤规则的数据,才会写入 TDengine,否则会忽略该消息;例如:可以配置过滤规则为 voltage > 200,即只有当电压大于 200V 的数据才会被同步至 TDengine;
|
||||
10. 最后,在配置完消息体中的字段和超级表中的字段的映射规则后,就可以提交任务了;除了基本的映射以外,在这里还可以对消息中字段的值进行转换,例如:可以通过表达式 (expr) 将原消息体中的电压和电流,计算为功率后再写入 TDengine;
|
||||
11. 任务提交后,会自动返回任务列表页面,如果提交成功,任务的状态会切换至“运行中”,如果提交失败,可通过查看该任务的活动日志,查找错误原因;
|
||||
12. 对于运行中的任务,点击指标的查看按钮,可以查看该任务的详细运行指标,弹出窗口划分为 2 个标签页,分别展示该任务多次运行的累计指标和本次运行的指标,这些指标每 2 秒钟会自动刷新一次。
|
||||
|
||||
## 任务管理
|
||||
|
||||
在任务列表页面,还可以对任务进行启动、停止、查看、删除、复制等操作,也可以查看各个任务的运行情况,包括写入的记录条数、流量等。
|
|
@ -63,7 +63,7 @@ M = (T × S × 3 + (N / 4096) + 100)
|
|||
|
||||
TDengine 用户对 CPU 的需求主要受以下 3 个因素影响:
|
||||
- 数据分片:在 TDengine 中,每个 CPU 核心可以服务 1 至 2 个 vnode。假设一个集群配置了 100 个 vgroup,并且采用三副本策略,那么建议该集群的 CPU 核心数量为 150~300 个,以实现最佳性能。
|
||||
- 数据写入:TDengine 的单核每秒至少能处理 10 000 个写入请求。值得注意的是,每个写入请求可以包含多条记录,而且一次写入一条记录与同时写入 10 条记录相比,消耗的计算资源相差无几。因此,每次写入的记录数越多,写入效率越高。例如,如果一个写入请求包含 200 条以上记录,单核就能实现每秒写入 100 万条记录的速度。然而,这要求前端数据采集系统具备更高的能力,因为它需要缓存记录,然后批量写入。
|
||||
- 数据写入:TDengine 的单核每秒至少能处理 10,000 个写入请求。值得注意的是,每个写入请求可以包含多条记录,而且一次写入一条记录与同时写入 10 条记录相比,消耗的计算资源相差无几。因此,每次写入的记录数越多,写入效率越高。例如,如果一个写入请求包含 200 条以上记录,单核就能实现每秒写入 100 万条记录的速度。然而,这要求前端数据采集系统具备更高的能力,因为它需要缓存记录,然后批量写入。
|
||||
- 查询需求:虽然 TDengine 提供了高效的查询功能,但由于每个应用场景的查询差异较大,且查询频次也会发生变化,因此很难给出一个具体的数字来衡量查询所需的计算资源。用户需要根据自己的实际场景编写一些查询语句,以便更准确地确定所需的计算资源。
|
||||
|
||||
综上所述,对于数据分片和数据写入,CPU 的需求是可以预估的。然而,查询需求所消耗的计算资源则难以预测。在实际运行过程中,建议保持 CPU 使用率不超过 50%,以确保系统的稳定性和性能。一旦 CPU 使用率超过这一阈值,就需要考虑增加新的节点或增加 CPU 核心数量,以提供更多的计算资源。
|
||||
|
@ -152,5 +152,5 @@ TDengine 的多级存储功能在使用上还具备以下优点。
|
|||
|RESTful 接口 | 6041 |
|
||||
|WebSocket 接口 |6041 |
|
||||
|taosKeeper | 6043 |
|
||||
|taosX | 6055 |
|
||||
|taosX | 6050, 6055 |
|
||||
|taosExplorer | 6060 |
|
|
@ -8,9 +8,13 @@ sidebar_label: 集群维护
|
|||
|
||||
本节介绍 TDengine Enterprise 中提供的高阶集群维护手段,能够使 TDengine 集群长期运行得更健壮和高效。
|
||||
|
||||
## 节点管理
|
||||
|
||||
如何管理集群节点请参考[节点管理](../../reference/taos-sql/node)
|
||||
|
||||
## 数据重整
|
||||
|
||||
TDengine 面向多种写入场景,而很多写入场景下,TDengine 的存储会导致数据存储的放大或数据文件的空洞等。这一方面影响数据的存储效率,另一方面也会影响查询效率。为了解决上述问题,TDengine 企业版提供了对数据的重整功能,即 DATA COMPACT 功能,将存储的数据文件重新整理,删除文件空洞和无效数据,提高数据的组织度,从而提高存储和查询的效率。
|
||||
TDengine 面向多种写入场景,而很多写入场景下,TDengine 的存储会导致数据存储的放大或数据文件的空洞等。这一方面影响数据的存储效率,另一方面也会影响查询效率。为了解决上述问题,TDengine 企业版提供了对数据的重整功能,即 DATA COMPACT 功能,将存储的数据文件重新整理,删除文件空洞和无效数据,提高数据的组织度,从而提高存储和查询的效率。数据重整功能在 3.0.3.0 版本第一次发布,此后又经过了多次迭代优化,建议使用最新版本。
|
||||
|
||||
### 语法
|
||||
|
||||
|
@ -39,7 +43,7 @@ KILL COMPACT compact_id;
|
|||
|
||||
## Vgroup Leader 再平衡
|
||||
|
||||
当多副本集群中的一个或多个节点因为升级或其它原因而重启后,有可能出现集群中各个 dnode 负载不均衡的现象,极端情况下会出现所有 vgroup 的 leader 都位于同一个 dnode 的情况。为了解决这个问题,可以使用下面的命令
|
||||
当多副本集群中的一个或多个节点因为升级或其它原因而重启后,有可能出现集群中各个 dnode 负载不均衡的现象,极端情况下会出现所有 vgroup 的 leader 都位于同一个 dnode 的情况。为了解决这个问题,可以使用下面的命令,该命令在 3.0.4.0 版本中首次发布,建议尽可能使用最新版本。
|
||||
|
||||
```SQL
|
||||
balance vgroup leader; # 再平衡所有 vgroup 的 leader
|
||||
|
@ -73,7 +77,7 @@ restore qnode on dnode <dnode_id>;# 恢复dnode上的qnode
|
|||
|
||||
## 分裂虚拟组
|
||||
|
||||
当一个 vgroup 因为子表数过多而导致 CPU 或 Disk 资源使用量负载过高时,增加 dnode 节点后,可通过split vgroup命令把该vgroup分裂为两个虚拟组。分裂完成后,新产生的两个 vgroup 承担原来由一个 vgroup 提供的读写服务。
|
||||
当一个 vgroup 因为子表数过多而导致 CPU 或 Disk 资源使用量负载过高时,增加 dnode 节点后,可通过split vgroup命令把该vgroup分裂为两个虚拟组。分裂完成后,新产生的两个 vgroup 承担原来由一个 vgroup 提供的读写服务。该命令在 3.0.6.0 版本第一次发布,建议尽可能使用最新版本。
|
||||
|
||||
```sql
|
||||
split vgroup <vgroup_id>
|
||||
|
@ -97,7 +101,7 @@ split vgroup <vgroup_id>
|
|||
|
||||
## 双副本
|
||||
|
||||
双副本是一种特殊的数据库高可用配置,本节对它的使用和维护操作进行特别说明。
|
||||
双副本是一种特殊的数据库高可用配置,本节对它的使用和维护操作进行特别说明。该功能在 3.3.0.0 版本中第一次发布,建议尽可能使用最新版本。
|
||||
|
||||
### 查看 Vgroups 的状态
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ toc_max_heading_level: 4
|
|||
TDengine 支持 WAL 机制,实现数据的容错能力,保证数据的高可靠。TDengine 接收到应用程序的请求数据包时,会先将请求的原始数据包写入数据库日志文件,等数据成功写入数据库数据文件后,再删除相应的 WAL。这样保证了 TDengine 能够在断电等因素导致的服务重启时,从数据库日志文件中恢复数据,避免数据丢失。涉及的配置参数有如下两个:
|
||||
|
||||
- wal_level :WAL 级别,1 表示写 WAL,但不执行 fsync ; 2 表示写 WAL,而且执行 fsync。默认值为 1。
|
||||
- wal_fsync_period:当 wal_level 设置为 2 时,执行 fsync 的周期;当 wal-level 设置为 0 时,表示每次写入,立即执行 fsync。
|
||||
- wal_fsync_period:当 wal_level 设置为 2 时,执行 fsync 的周期;当 wal_fsync_period 设置为 0 时,表示每次写入,立即执行 fsync。
|
||||
|
||||
如果要 100% 保证数据不丢失,则需要将 wal_level 设置为 2,wal_fsync_period 设置为 0。这时写入速度将会下降。但如果应用程序侧启动的写数据的线程数达到一定的数量(超过 50),那么写入数据的性能也会很不错,只会比 wal_fsync_period 设置为 3000ms 下降 30% 左右。
|
||||
|
||||
|
|
|
@ -4,14 +4,13 @@ title: 多级存储与对象存储
|
|||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
TDengine 特有的多级存储功能,其作用是将较近的热度较高的数据存储在高速介质上,而时间久远热度很低的数据存储在低成本介质上,达成了以下目标:
|
||||
本节介绍 TDengine Enterprise 特有的多级存储功能,其作用是将较近的热度较高的数据存储在高速介质上,而时间久远热度很低的数据存储在低成本介质上,达成了以下目标:
|
||||
- 降低存储成本 -- 将数据分级存储后,海量极冷数据存入廉价存储介质带来显著经济性
|
||||
- 提升写入性能 -- 得益于每级存储可支持多个挂载点,WAL 预写日志也支持 0 级的多挂载点并行写入,极大提升写入性能(实际场景测得支持持续写入每秒 3 亿测点以上),在机械硬盘上可获得极高磁盘 IO 吞吐(实测可达 2GB/s)
|
||||
- 方便维护 -- 配置好各级存储挂载点后,系统数据迁移等工作,无需人工干预;存储扩容更灵活、方便
|
||||
- 对 SQL 透明 -- 无论查询的数据是否跨级,一条 SQL 可返回所有数据,简单高效
|
||||
|
||||
|
||||
多级存储所涉及的各层存储介质都是本地存储设备。除了本地存储设备之外,TDengine 还支持使用对象存储(S3),将最冷的一批数据保存在最廉价的介质上,以进一步降低存储成本,并在必要时仍然可以进行查询,且数据存储在哪里也对 SQL 透明。
|
||||
多级存储所涉及的各层存储介质都是本地存储设备。除了本地存储设备之外,TDengine Enterprise 还支持使用对象存储(S3),将最冷的一批数据保存在最廉价的介质上,以进一步降低存储成本,并在必要时仍然可以进行查询,且数据存储在哪里也对 SQL 透明。支持对象存储在 3.3.0.0 版本中首次发布,建议使用最新版本。
|
||||
|
||||
## 多级存储
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ title: 用户和权限管理
|
|||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
TDengine 默认仅配置了一个 root 用户,该用户拥有最高权限。TDengine 支持对系统资源、库、表、视图和主题的访问权限控制。root 用户可以为每个用户针对不同的资源设置不同的访问权限。本节介绍 TDengine 中的用户和权限管理。
|
||||
TDengine 默认仅配置了一个 root 用户,该用户拥有最高权限。TDengine 支持对系统资源、库、表、视图和主题的访问权限控制。root 用户可以为每个用户针对不同的资源设置不同的访问权限。本节介绍 TDengine 中的用户和权限管理。用户和权限管理是 TDengine Enterprise 特有功能。
|
||||
|
||||
## 用户管理
|
||||
|
||||
|
@ -76,7 +76,7 @@ drop user user_name
|
|||
在 TDengine 中,库和表的权限分为 read (读)和 write (写)两种。这些权限可以单独授予,也可以同时授予用户。
|
||||
|
||||
- read 权限:拥有 read 权限的用户仅能查询库或表中的数据,而无法对数据进行修改或删除。这种权限适用于需要访问数据但不需要对数据进行写入操作的场景,如数据分析师、报表生成器等。
|
||||
- write 权限:拥有 write 权限的用户既可以查询库或表中的数据,也可以向库或表中写入数据。这种权限适用于需要对数据进行写入操作的场景,如数据采集器、数据处理器等。
|
||||
- write 权限:拥有 write 权限的用户可以向库或表中写入数据。这种权限适用于需要对数据进行写入操作的场景,如数据采集器、数据处理器等。如果只拥有 write 权限而没有 read 权限,则只能写入数据但不能查询数据。
|
||||
|
||||
对某个用户进行库和表访问授权的语法如下。
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ title: 更多安全策略
|
|||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
除了传统的用户和权限管理之外,TDengine 还有其他的安全策略,例如 IP 白名单、审计日志、数据加密等。
|
||||
除了传统的用户和权限管理之外,TDengine 还有其他的安全策略,例如 IP 白名单、审计日志、数据加密等,这些都是 TDengine Enterprise 特有功能,其中白名单功能在 3.2.0.0 版本首次发布,审计日志在 3.1.1.0 版本中首次发布,数据库加密在 3.3.0.0 中首次发布,建议使用最新版本。
|
||||
|
||||
## IP 白名单
|
||||
|
||||
|
@ -18,13 +18,13 @@ alter user test add host host_name1
|
|||
|
||||
查询 IP 白名单的 SQL 如下。
|
||||
```sql
|
||||
select test, allowed_host from ins_user_privileges;
|
||||
show users;
|
||||
SELECT TEST, ALLOWED_HOST FROM INS_USERS;
|
||||
SHOW USERS;
|
||||
```
|
||||
|
||||
删除 IP 白名单的命令如下。
|
||||
```sql
|
||||
alter user test drop host host_name1
|
||||
ALTER USER TEST DROP HOST HOST_NAME1
|
||||
```
|
||||
|
||||
## 审计日志
|
||||
|
|
|
@ -6,7 +6,7 @@ toc_max_heading_level: 4
|
|||
|
||||
## 简介
|
||||
|
||||
1. 部分用户因为部署环境的特殊性只能部署两台服务器,同时希望实现一定的服务高可用和数据高可靠。本文主要描述基于数据复制和客户端 Failover 两项关键技术的 TDengine 双活系统的产品行为,包括双活系统的架构、配置、运维等。TDengine 双活既可以用于前面所述资源受限的环境,也可用于在两套 TDengine 集群(不限资源)之间的灾备场景。
|
||||
1. 部分用户因为部署环境的特殊性只能部署两台服务器,同时希望实现一定的服务高可用和数据高可靠。本文主要描述基于数据复制和客户端 Failover 两项关键技术的 TDengine 双活系统的产品行为,包括双活系统的架构、配置、运维等。TDengine 双活既可以用于前面所述资源受限的环境,也可用于在两套 TDengine 集群(不限资源)之间的灾备场景。双活是 TDengine Enterprise 特有功能,在 3.3.0.0 版本中第一次发布,建议使用最新版本。
|
||||
|
||||
2. 双活系统的定义是:业务系统中有且仅有两台服务器,其上分别部署一套服务,在业务层看来这两台机器和两套服务是一个完整的系统,对其中的细节业务层不需要感知。双活中的两个节点通常被称为 Master-Slave,意为”主从“或”主备“,本文档中可能会出现混用的情况。
|
||||
|
||||
|
|
|
@ -41,7 +41,7 @@ TDengine 提供了丰富的应用程序开发接口,为了便于用户快速
|
|||
3. 使用 Websocket 连接,用户也无需安装客户端驱动程序 taosc。
|
||||
4. 连接云服务实例,必须使用 REST 连接 或 Websocket 连接。
|
||||
|
||||
一般我们建议使用 **Websocket 连接**。
|
||||
**推荐使用 WebSocket 连接**
|
||||
|
||||
## 安装客户端驱动 taosc
|
||||
|
||||
|
|
|
@ -11,7 +11,9 @@ import TabItem from "@theme/TabItem";
|
|||
|
||||
- 减少解析时间:通过参数绑定,SQL 语句的结构在第一次执行时就已经确定,后续的执行只需要替换参数值,这样可以避免每次执行时都进行语法解析,从而减少解析时间。
|
||||
- 预编译:当使用参数绑定时,SQL 语句可以被预编译并缓存,后续使用不同的参数值执行时,可以直接使用预编译的版本,提高执行效率。
|
||||
- 减少网络开销:参数绑定还可以减少发送到数据库的数据量,因为只需要发送参数值而不是完整的 SQL 语句,特别是在执行大量相似的插入或更新操作时,这种差异尤为明显。
|
||||
- 减少网络开销:参数绑定还可以减少发送到数据库的数据量,因为只需要发送参数值而不是完整的 SQL 语句,特别是在执行大量相似的插入或更新操作时,这种差异尤为明显。
|
||||
|
||||
**Tips: 数据写入推荐使用参数绑定方式**
|
||||
|
||||
下面我们继续以智能电表为例,展示各语言连接器使用参数绑定高效写入的功能:
|
||||
1. 准备一个参数化的 SQL 插入语句,用于向超级表 `meters` 中插入数据。这个语句允许动态地指定子表名、标签和列值。
|
||||
|
|
|
@ -6,29 +6,28 @@ toc_max_heading_level: 4
|
|||
|
||||
## UDF 简介
|
||||
|
||||
在某些应用场景中,应用逻辑需要的查询功能无法直接使用TDengine内置的函数来实现。TDengine允许编写用户自定义函数(UDF),以便解决特殊应用场景中的使用需求。UDF在集群中注册成功后,可以像系统内置函数一样在SQL中调用,就使用角度而言没有任何区别。UDF分为标量函数和聚合函数。标量函数对每行数据输出一个值,如求绝对值abs、正弦函数sin、字符串拼接函数concat等。聚合函数对多行数据输出一个值,如求平均数avg、取最大值max等。
|
||||
在某些应用场景中,应用逻辑需要的查询功能无法直接使用内置函数来实现,TDengine 允许编写用户自定义函数(UDF),以便解决特殊应用场景中的使用需求。UDF 在集群中注册成功后,可以像系统内置函数一样在 SQL 中调用,就使用角度而言没有任何区别。UDF 分为标量函数和聚合函数。标量函数对每行数据输出一个值,如求绝对值(abs)、正弦函数(sin)、字符串拼接函数(concat)等。聚合函数对多行数据输出一个值,如求平均数(avg)、取最大值(max)等。
|
||||
|
||||
TDengine支持用C和Python两种编程语言编写UDF。C语言编写的UDF与内置函数的性能几乎相同,Python语言编写的UDF可以利用丰富的Python运算库。为了避免UDF执行中发生异常影响数据库服务,TDengine使用了进程分离技术,把UDF的执行放到另一个进程中完成,即使用户编写的UDF崩溃,也不会影响TDengine的正常运行。
|
||||
TDengine 支持用 C 和 Python 两种编程语言编写 UDF。C 语言编写的 UDF 与内置函数的性能几乎相同,Python 语言编写的 UDF 可以利用丰富的 Python 运算库。为了避免 UDF 执行中发生异常影响数据库服务,TDengine 使用了进程分离技术,把 UDF 的执行放到另一个进程中完成,即使用户编写的 UDF 崩溃,也不会影响 TDengine 的正常运行。
|
||||
|
||||
## 用 C 语言开发 UDF
|
||||
|
||||
使用 C 语言实现 UDF 时,需要实现规定的接口函数
|
||||
- 标量函数需要实现标量接口函数 scalarfn 。
|
||||
- 聚合函数需要实现聚合接口函数 aggfn_start , aggfn , aggfn_finish。
|
||||
- 如果需要初始化,实现 udf_init;如果需要清理工作,实现udf_destroy。
|
||||
|
||||
接口函数的名称是 UDF 名称,或者是 UDF 名称和特定后缀(`_start`, `_finish`, `_init`, `_destroy`)的连接。列表中的scalarfn,aggfn, udf需要替换成udf函数名。
|
||||
- 聚合函数需要实现聚合接口函数 aggfn_start、aggfn、aggfn_finish。
|
||||
- 如果需要初始化,实现 udf_init。
|
||||
- 如果需要清理工作,实现 udf_destroy。
|
||||
|
||||
### 接口定义
|
||||
|
||||
在TDengine中,UDF的接口函数名称可以是UDF名称,也可以是UDF名称和特定后缀(如_start、_finish、_init、_destroy)的连接。后面内容中描述的函数名称,例如scalarfn、aggfn,需要替换成UDF名称。。
|
||||
接口函数的名称是 UDF 名称,或者是 UDF 名称和特定后缀(_start、_finish、_init、_destroy)的连接。后面内容中描述的函数名称,例如 scalarfn、aggfn,需要替换成 UDF 名称。
|
||||
|
||||
#### 标量函数接口
|
||||
|
||||
标量函数是一种将输入数据转换为输出数据的函数,通常用于对单个数据值进行计算和转换。标量函数的接口函数原型如下。
|
||||
|
||||
```c
|
||||
int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn *resultColumn)
|
||||
int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn *resultColumn);
|
||||
```
|
||||
主要参数说明如下。
|
||||
- inputDataBlock:输入的数据块。
|
||||
|
@ -37,23 +36,22 @@ int32_t scalarfn(SUdfDataBlock* inputDataBlock, SUdfColumn *resultColumn)
|
|||
#### 聚合函数接口
|
||||
|
||||
聚合函数是一种特殊的函数,用于对数据进行分组和计算,从而生成汇总信息。聚合函数的工作原理如下。
|
||||
- 初始化结果缓冲区:首先调用aggfn_start函数,生成一个结果缓冲区(result buffer),用于存储中间结果。
|
||||
- 初始化结果缓冲区:首先调用 aggfn_start 函数,生成一个结果缓冲区(result buffer),用于存储中间结果。
|
||||
- 分组数据:相关数据会被分为多个行数据块(row data block),每个行数据块包含一组具有相同分组键(grouping key)的数据。
|
||||
- 更新中间结果:对于每个数据块,调用aggfn函数更新中间结果。aggfn函数会根据聚合函数的类型(如sum、avg、count等)对数据进行相应的计算,并将计算结
|
||||
- 更新中间结果:对于每个数据块,调用 aggfn 函数更新中间结果。aggfn 函数会根据聚合函数的类型(如 sum、avg、count 等)对数据进行相应的计算,并将计算结
|
||||
果存储在结果缓冲区中。
|
||||
- 生成最终结果:在所有数据块的中间结果更新完成后,调用aggfn_finish函数从结果缓冲区中提取最终结果。最终结果通常只包含0条或1条数据,具体取决于聚
|
||||
- 生成最终结果:在所有数据块的中间结果更新完成后,调用 aggfn_finish 函数从结果缓冲区中提取最终结果。最终结果只包含 0 条或 1 条数据,具体取决于聚
|
||||
合函数的类型和输入数据。
|
||||
|
||||
聚合函数的接口函数原型如下。
|
||||
|
||||
```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_start(SUdfInterBuf *interBuf);
|
||||
int32_t aggfn(SUdfDataBlock* inputBlock, SUdfInterBuf *interBuf, SUdfInterBuf *newInterBuf);
|
||||
int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result);
|
||||
```
|
||||
|
||||
|
||||
其中 aggfn 是函数名的占位符。首先调用aggfn_start生成结果buffer,然后相关的数据会被分为多个行数据块,对每个数据块调用 aggfn 用数据块更新中间结果,最后再调用 aggfn_finish 从中间结果产生最终结果,最终结果只能含 0 或 1 条结果数据。
|
||||
其中 aggfn 是函数名的占位符。首先调用 aggfn_start 生成结果 buffer,然后相关的数据会被分为多个行数据块,对每个数据块调用 aggfn 用数据块更新中间结果,最后再调用 aggfn_finish 从中间结果产生最终结果,最终结果只能含 0 或 1 条结果数据。
|
||||
|
||||
主要参数说明如下。
|
||||
- interBuf:中间结果缓存区。
|
||||
|
@ -61,29 +59,49 @@ int32_t aggfn_finish(SUdfInterBuf* interBuf, SUdfInterBuf *result)
|
|||
- newInterBuf:新的中间结果缓冲区。
|
||||
- result:最终结果。
|
||||
|
||||
|
||||
#### 初始化和销毁接口
|
||||
|
||||
初始化和销毁接口是标量函数和聚合函数共同使用的接口,相关API如下。
|
||||
初始化和销毁接口是标量函数和聚合函数共同使用的接口,相关 API 如下。
|
||||
|
||||
```c
|
||||
int32_t udf_init()
|
||||
int32_t udf_destroy()
|
||||
```
|
||||
|
||||
其中,udf_init函数完成初始化工作,udf_destroy函数完成清理工作。如果没有初始化工作,无须定义udf_init函数;如果没有清理工作,无须定义udf_destroy函数。
|
||||
其中,udf_init 函数完成初始化工作,udf_destroy 函数完成清理工作。如果没有初始化工作,无须定义 udf_init 函数;如果没有清理工作,无须定义 udf_destroy 函数。
|
||||
|
||||
### 标量函数模板
|
||||
|
||||
用C语言开发标量函数的模板如下。
|
||||
用 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;
|
||||
}
|
||||
```
|
||||
|
@ -91,53 +109,211 @@ int32_t scalarfn_destroy() {
|
|||
|
||||
用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;
|
||||
}
|
||||
```
|
||||
|
||||
### 编译
|
||||
|
||||
在TDengine中,为了实现UDF,需要编写C语言源代码,并按照TDengine的规范编译为动态链接库文件。
|
||||
按照前面描述的规则,准备UDF的源代码bit_and.c。以Linux操作系统为例,执行如下指令,编译得到动态链接库文件。
|
||||
在 TDengine 中,为了实现 UDF,需要编写 C 语言源代码,并按照 TDengine 的规范编译为动态链接库文件。
|
||||
按照前面描述的规则,准备 UDF 的源代码 bit_and.c。以 Linux 操作系统为例,执行如下指令,编译得到动态链接库文件。
|
||||
```shell
|
||||
gcc-g-O0-fPIC-sharedbit_and.c-olibbitand.so
|
||||
gcc -g -O0 -fPIC -shared bit_and.c -o libbitand.so
|
||||
```
|
||||
|
||||
为了保证可靠运行,推荐使用7.5及以上版本的GCC。
|
||||
为了保证可靠运行,推荐使用 7.5 及以上版本的 GCC。
|
||||
|
||||
### 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 示例代码
|
||||
|
||||
#### 标量函数示例 [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>
|
||||
|
||||
```c
|
||||
{{#include tests/script/sh/bit_and.c}}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
#### 聚合函数示例1 返回值为数值类型 [l2norm](https://github.com/taosdata/TDengine/blob/3.0/tests/script/sh/l2norm.c)
|
||||
|
||||
l2norm 实现了输入列的所有数据的二阶范数,即对每个数据先平方,再累加求和,最后开方。
|
||||
|
||||
<details>
|
||||
<summary>l2norm.c</summary>
|
||||
|
||||
```c
|
||||
{{#include tests/script/sh/l2norm.c}}
|
||||
```
|
||||
|
||||
</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
|
||||
{{#include tests/script/sh/max_vol.c}}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## 用 Python 语言开发 UDF
|
||||
|
||||
### 准备环境
|
||||
|
||||
准备环境的具体步骤如下:
|
||||
- 第1步,准备好Python运行环境。
|
||||
- 第2步,安装Python包taospyudf。命令如下。
|
||||
- 第1步,准备好 Python 运行环境。
|
||||
- 第2步,安装 Python 包 taospyudf。命令如下。
|
||||
```shell
|
||||
pip3 install taospyudf
|
||||
```
|
||||
- 第3步,执行命令ldconfig。
|
||||
- 第4步,启动taosd服务。
|
||||
- 第3步,执行命令 ldconfig。
|
||||
- 第4步,启动 taosd 服务。
|
||||
|
||||
安装过程中会编译 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
|
||||
```
|
||||
|
||||
### 接口定义
|
||||
|
||||
当使用Python语言开发UDF时,需要实现规定的接口函数。具体要求如下。
|
||||
- 标量函数需要实现标量接口函数process。
|
||||
- 聚合函数需要实现聚合接口函数start、reduce、finish。
|
||||
- 如果需要初始化,则应实现函数init。
|
||||
- 如果需要清理工作,则实现函数destroy。
|
||||
当使用 Python 语言开发 UDF 时,需要实现规定的接口函数。具体要求如下。
|
||||
- 标量函数需要实现标量接口函数 process。
|
||||
- 聚合函数需要实现聚合接口函数 start、reduce、finish。
|
||||
- 如果需要初始化,则应实现函数 init。
|
||||
- 如果需要清理工作,则实现函数 destroy。
|
||||
|
||||
#### 标量函数接口
|
||||
|
||||
|
@ -147,7 +323,7 @@ def process(input: datablock) -> tuple[output_type]:
|
|||
```
|
||||
|
||||
主要参数说明如下:
|
||||
- input:datablock 类似二维矩阵,通过成员方法 data(row,col)返回位于 row 行,col 列的 python 对象
|
||||
- input:datablock 类似二维矩阵,通过成员方法 data(row, col) 读取位于 row 行、col 列的 python 对象
|
||||
- 返回值是一个 Python 对象元组,每个元素类型为输出类型。
|
||||
|
||||
#### 聚合函数接口
|
||||
|
@ -159,13 +335,13 @@ def reduce(inputs: datablock, buf: bytes) -> bytes
|
|||
def finish(buf: bytes) -> output_type:
|
||||
```
|
||||
|
||||
上述代码定义了3个函数,分别用于实现一个自定义的聚合函数。具体过程如下。
|
||||
上述代码定义了 3 个函数,分别用于实现一个自定义的聚合函数。具体过程如下。
|
||||
|
||||
首先,调用start函数生成最初的结果缓冲区。这个结果缓冲区用于存储聚合函数的内部状态,随着输入数据的处理而不断更新。
|
||||
首先,调用 start 函数生成最初的结果缓冲区。这个结果缓冲区用于存储聚合函数的内部状态,随着输入数据的处理而不断更新。
|
||||
|
||||
然后,输入数据会被分为多个行数据块。对于每个行数据块,调用reduce函数,并将当前行数据块(inputs)和当前的中间结果(buf)作为参数传递。reduce函数会根据输入数据和当前状态来更新聚合函数的内部状态,并返回新的中间结果
|
||||
然后,输入数据会被分为多个行数据块。对于每个行数据块,调用 reduce 函数,并将当前行数据块(inputs)和当前的中间结果(buf)作为参数传递。reduce 函数会根据输入数据和当前状态来更新聚合函数的内部状态,并返回新的中间结果。
|
||||
|
||||
最后,当所有行数据块都处理完毕后,调用finish函数。这个函数接收最终的中间结果(buf)作为参数,并从中生成最终的输出。由于聚合函数的特性,最终输出只能包含0条或1条数据。这个输出结果将作为聚合函数的计算结果返回给调用者。
|
||||
最后,当所有行数据块都处理完毕后,调用 finish 函数。这个函数接收最终的中间结果(buf)作为参数,并从中生成最终的输出。由于聚合函数的特性,最终输出只能包含 0 条或 1 条数据。这个输出结果将作为聚合函数的计算结果返回给调用者。
|
||||
|
||||
#### 初始化和销毁接口
|
||||
|
||||
|
@ -179,7 +355,7 @@ def destroy()
|
|||
- init 完成初始化工作
|
||||
- destroy 完成清理工作
|
||||
|
||||
**注意** 用Python开发UDF时必须定义init函数和destroy函数
|
||||
**注意** 用 Python 开发 UDF 时必须定义 init 函数和 destroy 函数
|
||||
|
||||
### 标量函数模板
|
||||
|
||||
|
@ -204,7 +380,7 @@ def start() -> bytes:
|
|||
def reduce(inputs: datablock, buf: bytes) -> bytes
|
||||
# deserialize buf to state
|
||||
# reduce the inputs and state into new_state.
|
||||
# use inputs.data(i,j) to access python object of location(i,j)
|
||||
# use inputs.data(i, j) to access python object of location(i, j)
|
||||
# serialize new_state into new_state_bytes
|
||||
return new_state_bytes
|
||||
def finish(buf: bytes) -> output_type:
|
||||
|
@ -217,13 +393,13 @@ def finish(buf: bytes) -> output_type:
|
|||
|
||||
| **TDengine SQL数据类型** | **Python数据类型** |
|
||||
| :-----------------------: | ------------ |
|
||||
|TINYINT / SMALLINT / INT / BIGINT | int |
|
||||
|TINYINT UNSIGNED / SMALLINT UNSIGNED / INT UNSIGNED / BIGINT UNSIGNED | int |
|
||||
|FLOAT / DOUBLE | float |
|
||||
|BOOL | bool |
|
||||
|BINARY / VARCHAR / NCHAR | bytes|
|
||||
|TIMESTAMP | int |
|
||||
|JSON and other types | 不支持 |
|
||||
| TINYINT / SMALLINT / INT / BIGINT | int |
|
||||
| TINYINT UNSIGNED / SMALLINT UNSIGNED / INT UNSIGNED / BIGINT UNSIGNED | int |
|
||||
| FLOAT / DOUBLE | float |
|
||||
| BOOL | bool |
|
||||
| BINARY / VARCHAR / NCHAR | bytes|
|
||||
| TIMESTAMP | int |
|
||||
| JSON and other types | 不支持 |
|
||||
|
||||
### 开发示例
|
||||
|
||||
|
@ -234,7 +410,7 @@ def finish(buf: bytes) -> output_type:
|
|||
#### 示例一
|
||||
|
||||
编写一个只接收一个整数的 UDF 函数: 输入 n, 输出 ln(n^2 + 1)。
|
||||
首先编写一个 Python 文件,存在系统某个目录,比如 /root/udf/myfun.py 内容如下
|
||||
首先编写一个 Python 文件,存在系统某个目录,比如 /root/udf/myfun.py 内容如下。
|
||||
|
||||
```python
|
||||
from math import log
|
||||
|
@ -250,23 +426,25 @@ def process(block):
|
|||
return [log(block.data(i, 0) ** 2 + 1) for i in range(rows)]
|
||||
```
|
||||
|
||||
这个文件包含 3 个函数, init 和 destroy 都是空函数,它们是 UDF 的生命周期函数,即使什么都不做也要定义。最关键的是 process 函数, 它接受一个数据块,这个数据块对象有两个方法:
|
||||
这个文件包含 3 个函数, init 和 destroy 都是空函数,它们是 UDF 的生命周期函数,即使什么都不做也要定义。最关键的是 process 函数, 它接受一个数据块,这个数据块对象有两个方法。
|
||||
1. shape() 返回数据块的行数和列数
|
||||
2. data(i, j) 返回 i 行 j 列的数据
|
||||
标量函数的 process 方法传人的数据块有多少行,就需要返回多少个数据。上述代码中我们忽略的列数,因为我们只想对每行的第一个数做计算。
|
||||
接下来我们创建对应的 UDF 函数,在 TDengine CLI 中执行下面语句:
|
||||
|
||||
标量函数的 process 方法传入的数据块有多少行,就需要返回多少行数据。上述代码忽略列数,因为只需对每行的第一列做计算。
|
||||
|
||||
接下来创建对应的 UDF 函数,在 TDengine CLI 中执行下面语句。
|
||||
|
||||
```sql
|
||||
create function myfun as '/root/udf/myfun.py' outputtype double language 'Python'
|
||||
```
|
||||
其输出如下
|
||||
其输出如下。
|
||||
|
||||
```shell
|
||||
taos> create function myfun as '/root/udf/myfun.py' outputtype double language 'Python';
|
||||
taos> create function myfun as '/root/udf/myfun.py' outputtype double language 'Python';
|
||||
Create OK, 0 row(s) affected (0.005202s)
|
||||
```
|
||||
|
||||
看起来很顺利,接下来 show 一下系统中所有的自定义函数,确认创建成功:
|
||||
看起来很顺利,接下来查看系统中所有的自定义函数,确认创建成功。
|
||||
|
||||
```text
|
||||
taos> show functions;
|
||||
|
@ -276,7 +454,7 @@ taos> show functions;
|
|||
Query OK, 1 row(s) in set (0.005767s)
|
||||
```
|
||||
|
||||
接下来就来测试一下这个函数,测试之前先执行下面的 SQL 命令,制造些测试数据,在 TDengine CLI 中执行下述命令
|
||||
生成测试数据,可以在 TDengine CLI 中执行下述命令。
|
||||
|
||||
```sql
|
||||
create database test;
|
||||
|
@ -286,7 +464,7 @@ insert into t values('2023-05-03 08:09:10', 2, 3, 4);
|
|||
insert into t values('2023-05-10 07:06:05', 3, 4, 5);
|
||||
```
|
||||
|
||||
测试 myfun 函数:
|
||||
测试 myfun 函数。
|
||||
|
||||
```sql
|
||||
taos> select myfun(v1, v2) from t;
|
||||
|
@ -294,14 +472,13 @@ taos> select myfun(v1, v2) from t;
|
|||
DB error: udf function execution failure (0.011088s)
|
||||
```
|
||||
|
||||
不幸的是执行失败了,什么原因呢?
|
||||
查看 udfd 进程的日志
|
||||
不幸的是执行失败了,什么原因呢?查看 udfd 进程的日志。
|
||||
|
||||
```shell
|
||||
tail -10 /var/log/taos/udfd.log
|
||||
```
|
||||
|
||||
发现以下错误信息:
|
||||
发现以下错误信息。
|
||||
|
||||
```text
|
||||
05/24 22:46:28.733545 01665799 UDF ERROR can not load library libtaospyudf.so. error: operation not permitted
|
||||
|
@ -310,7 +487,7 @@ tail -10 /var/log/taos/udfd.log
|
|||
|
||||
错误很明确:没有加载到 Python 插件 libtaospyudf.so,如果遇到此错误,请参考前面的准备环境一节。
|
||||
|
||||
修复环境错误后再次执行,如下:
|
||||
修复环境错误后再次执行,如下。
|
||||
|
||||
```sql
|
||||
taos> select myfun(v1) from t;
|
||||
|
@ -325,7 +502,7 @@ taos> select myfun(v1) from t;
|
|||
|
||||
#### 示例二
|
||||
|
||||
上面的 myfun 虽然测试测试通过了,但是有两个缺点:
|
||||
上面的 myfun 虽然测试测试通过了,但是有两个缺点。
|
||||
|
||||
1. 这个标量函数只接受 1 列数据作为输入,如果用户传入了多列也不会抛异常。
|
||||
|
||||
|
@ -338,8 +515,7 @@ taos> select myfun(v1, v2) from t;
|
|||
2.302585093 |
|
||||
```
|
||||
|
||||
2. 没有处理 null 值。我们期望如果输入有 null,则会抛异常终止执行。
|
||||
因此 process 函数改进如下:
|
||||
2. 没有处理 null 值。我们期望如果输入有 null,则会抛异常终止执行。因此 process 函数改进如下。
|
||||
|
||||
```python
|
||||
def process(block):
|
||||
|
@ -349,13 +525,13 @@ def process(block):
|
|||
return [ None if block.data(i, 0) is None else log(block.data(i, 0) ** 2 + 1) for i in range(rows)]
|
||||
```
|
||||
|
||||
然后执行下面的语句更新已有的 UDF:
|
||||
执行如下语句更新已有的 UDF。
|
||||
|
||||
```sql
|
||||
create or replace function myfun as '/root/udf/myfun.py' outputtype double language 'Python';
|
||||
```
|
||||
|
||||
再传入 myfun 两个参数,就会执行失败了
|
||||
再传入 myfun 两个参数,就会执行失败了。
|
||||
|
||||
```sql
|
||||
taos> select myfun(v1, v2) from t;
|
||||
|
@ -363,7 +539,7 @@ taos> select myfun(v1, v2) from t;
|
|||
DB error: udf function execution failure (0.014643s)
|
||||
```
|
||||
|
||||
但遗憾的是我们自定义的异常信息没有展示给用户,而是在插件的日志文件 /var/log/taos/taospyudf.log 中:
|
||||
自定义的异常信息打印在插件的日志文件 /var/log/taos/taospyudf.log 中。
|
||||
|
||||
```text
|
||||
2023-05-24 23:21:06.790 ERROR [1666188] [doPyUdfScalarProc@507] call pyUdfScalar proc function. context 0x7faade26d180. error: Exception: require 1 parameter but given 2
|
||||
|
@ -378,18 +554,17 @@ At:
|
|||
|
||||
#### 示例三
|
||||
|
||||
编写一个 UDF:输入(x1, x2, ..., xn), 输出每个值和它们的序号的乘积的和: 1 * x1 + 2 * x2 + ... + n * xn。如果 x1 至 xn 中包含 null,则结果为 null。
|
||||
这个示例与示例一的区别是,可以接受任意多列作为输入,且要处理每一列的值。编写 UDF 文件 /root/udf/nsum.py:
|
||||
输入(x1, x2, ..., xn), 输出每个值和它们的序号的乘积的和:1 * x1 + 2 * x2 + ... + n * xn。如果 x1 至 xn 中包含 null,则结果为 null。
|
||||
|
||||
本例与示例一的区别是,可以接受任意多列作为输入,且要处理每一列的值。编写 UDF 文件 /root/udf/nsum.py。
|
||||
|
||||
```python
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
def destroy():
|
||||
pass
|
||||
|
||||
|
||||
def process(block):
|
||||
rows, cols = block.shape()
|
||||
result = []
|
||||
|
@ -405,13 +580,13 @@ def process(block):
|
|||
return result
|
||||
```
|
||||
|
||||
创建 UDF:
|
||||
创建 UDF。
|
||||
|
||||
```sql
|
||||
create function nsum as '/root/udf/nsum.py' outputtype double language 'Python';
|
||||
```
|
||||
|
||||
测试 UDF:
|
||||
测试 UDF。
|
||||
|
||||
```sql
|
||||
taos> insert into t values('2023-05-25 09:09:15', 6, null, 8);
|
||||
|
@ -430,22 +605,20 @@ Query OK, 4 row(s) in set (0.010653s)
|
|||
#### 示例四
|
||||
|
||||
编写一个 UDF,输入一个时间戳,输出距离这个时间最近的下一个周日。比如今天是 2023-05-25, 则下一个周日是 2023-05-28。
|
||||
完成这个函数要用到第三方库 momen。先安装这个库:
|
||||
完成这个函数要用到第三方库 momen。先安装这个库。
|
||||
|
||||
```shell
|
||||
pip3 install moment
|
||||
```
|
||||
|
||||
然后编写 UDF 文件 /root/udf/nextsunday.py
|
||||
然后编写 UDF 文件 /root/udf/nextsunday.py。
|
||||
|
||||
```python
|
||||
import moment
|
||||
|
||||
|
||||
def init():
|
||||
pass
|
||||
|
||||
|
||||
def destroy():
|
||||
pass
|
||||
|
||||
|
@ -460,13 +633,13 @@ def process(block):
|
|||
for i in range(rows)]
|
||||
```
|
||||
|
||||
UDF 框架会将 TDengine 的 timestamp 类型映射为 Python 的 int 类型,所以这个函数只接受一个表示毫秒数的整数。process 方法先做参数检查,然后用 moment 包替换时间的星期为星期日,最后格式化输出。输出的字符串长度是固定的10个字符长,因此可以这样创建 UDF 函数:
|
||||
UDF 框架会将 TDengine 的 timestamp 类型映射为 Python 的 int 类型,所以这个函数只接受一个表示毫秒数的整数。process 方法先做参数检查,然后用 moment 包替换时间的星期为星期日,最后格式化输出。输出的字符串长度是固定的 10 个字符长,因此可以这样创建 UDF 函数。
|
||||
|
||||
```sql
|
||||
create function nextsunday as '/root/udf/nextsunday.py' outputtype binary(10) language 'Python';
|
||||
```
|
||||
|
||||
此时测试函数,如果你是用 systemctl 启动的 taosd,肯定会遇到错误:
|
||||
此时测试函数,如果你是用 systemctl 启动的 taosd,肯定会遇到错误。
|
||||
|
||||
```sql
|
||||
taos> select ts, nextsunday(ts) from t;
|
||||
|
@ -475,11 +648,11 @@ DB error: udf function execution failure (1.123615s)
|
|||
```
|
||||
|
||||
```shell
|
||||
tail -20 taospyudf.log
|
||||
tail -20 taospyudf.log
|
||||
2023-05-25 11:42:34.541 ERROR [1679419] [PyUdf::PyUdf@217] py udf load module failure. error ModuleNotFoundError: No module named 'moment'
|
||||
```
|
||||
|
||||
这是因为 “moment” 所在位置不在 python udf 插件默认的库搜索路径中。怎么确认这一点呢?通过以下命令搜索 taospyudf.log:
|
||||
这是因为 “moment” 所在位置不在 python udf 插件默认的库搜索路径中。怎么确认这一点呢?通过以下命令搜索 taospyudf.log。
|
||||
|
||||
```shell
|
||||
grep 'sys path' taospyudf.log | tail -1
|
||||
|
@ -492,7 +665,7 @@ grep 'sys path' taospyudf.log | tail -1
|
|||
```
|
||||
|
||||
发现 python udf 插件默认搜索的第三方库安装路径是: /lib/python3/dist-packages,而 moment 默认安装到了 /usr/local/lib/python3.8/dist-packages。下面我们修改 python udf 插件默认的库搜索路径。
|
||||
先打开 python3 命令行,查看当前的 sys.path
|
||||
先打开 python3 命令行,查看当前的 sys.path。
|
||||
|
||||
```python
|
||||
>>> import sys
|
||||
|
@ -500,13 +673,13 @@ grep 'sys path' taospyudf.log | tail -1
|
|||
'/usr/lib/python3.8:/usr/lib/python3.8/lib-dynload:/usr/local/lib/python3.8/dist-packages:/usr/lib/python3/dist-packages'
|
||||
```
|
||||
|
||||
复制上面脚本的输出的字符串,然后编辑 /var/taos/taos.cfg 加入以下配置:
|
||||
复制上面脚本的输出的字符串,然后编辑 /var/taos/taos.cfg 加入以下配置。
|
||||
|
||||
```shell
|
||||
UdfdLdLibPath /usr/lib/python3.8:/usr/lib/python3.8/lib-dynload:/usr/local/lib/python3.8/dist-packages:/usr/lib/python3/dist-packages
|
||||
```
|
||||
|
||||
保存后执行 systemctl restart taosd, 再测试就不报错了:
|
||||
保存后执行 systemctl restart taosd, 再测试就不报错了。
|
||||
|
||||
```sql
|
||||
taos> select ts, nextsunday(ts) from t;
|
||||
|
@ -522,7 +695,7 @@ 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:
|
||||
聚合函数与标量函数的区别是:标量函数是多行输入对应多个输出,聚合函数是多行输入对应一个输出。聚合函数的执行过程有点像经典的 map-reduce 框架的执行过程,框架把数据分成若干块,每个 mapper 处理一个块,reducer 再把 mapper 的结果做聚合。不一样的地方在于,对于 TDengine Python UDF 中的 reduce 函数既有 map 的功能又有 reduce 的功能。reduce 函数接受两个参数:一个是自己要处理的数据,一个是别的任务执行 reduce 函数的处理结果。如下面的示例 /root/udf/myspread.py。
|
||||
|
||||
```python
|
||||
import io
|
||||
|
@ -531,26 +704,21 @@ import pickle
|
|||
|
||||
LOG_FILE: io.TextIOBase = None
|
||||
|
||||
|
||||
def init():
|
||||
global LOG_FILE
|
||||
LOG_FILE = open("/var/log/taos/spread.log", "wt")
|
||||
log("init function myspead success")
|
||||
|
||||
|
||||
def log(o):
|
||||
LOG_FILE.write(str(o) + '\n')
|
||||
|
||||
|
||||
def destroy():
|
||||
log("close log file: spread.log")
|
||||
LOG_FILE.close()
|
||||
|
||||
|
||||
def start():
|
||||
return pickle.dumps((-math.inf, math.inf))
|
||||
|
||||
|
||||
def reduce(block, buf):
|
||||
max_number, min_number = pickle.loads(buf)
|
||||
log(f"initial max_number={max_number}, min_number={min_number}")
|
||||
|
@ -565,26 +733,26 @@ def reduce(block, buf):
|
|||
min_number = v
|
||||
return pickle.dumps((max_number, min_number))
|
||||
|
||||
|
||||
def finish(buf):
|
||||
max_number, min_number = pickle.loads(buf)
|
||||
return max_number - min_number
|
||||
```
|
||||
|
||||
在这个示例中我们不光定义了一个聚合函数,还添加记录执行日志的功能,讲解如下:
|
||||
1. init 函数不再是空函数,而是打开了一个文件用于写执行日志
|
||||
2. log 函数是记录日志的工具,自动将传入的对象转成字符串,加换行符输出
|
||||
3. destroy 函数用来在执行结束关闭文件
|
||||
4. start 返回了初始的 buffer,用来存聚合函数的中间结果,我们把最大值初始化为负无穷大,最小值初始化为正无穷大
|
||||
5. reduce 处理每个数据块并聚合结果
|
||||
6. finish 函数将最终的 buffer 转换成最终的输出
|
||||
执行下面的 SQL语句创建对应的 UDF:
|
||||
在这个示例中,我们不但定义了一个聚合函数,还增加了记录执行日志的功能。
|
||||
1. init 函数打开一个文件用于记录日志
|
||||
2. log 函数记录日志,自动将传入的对象转成字符串,加换行符输出
|
||||
3. destroy 函数在执行结束后关闭日志文件
|
||||
4. start 函数返回初始的 buffer,用来存聚合函数的中间结果,把最大值初始化为负无穷大,最小值初始化为正无穷大
|
||||
5. reduce 函数处理每个数据块并聚合结果
|
||||
6. finish 函数将 buffer 转换成最终的输出
|
||||
|
||||
执行下面 SQL 语句创建对应的 UDF。
|
||||
|
||||
```sql
|
||||
create or replace aggregate function myspread as '/root/udf/myspread.py' outputtype double bufsize 128 language 'Python';
|
||||
```
|
||||
|
||||
这个 SQL 语句与创建标量函数的 SQL 语句有两个重要区别:
|
||||
这个 SQL 语句与创建标量函数的 SQL 语句有两个重要区别。
|
||||
1. 增加了 aggregate 关键字
|
||||
2. 增加了 bufsize 关键字,用来指定存储中间结果的内存大小,这个数值可以大于实际使用的数值。本例中间结果是两个浮点数组成的 tuple,序列化后实际占用大小只有 32 个字节,但指定的 bufsize 是128,可以用 python 命令行打印实际占用的字节数
|
||||
|
||||
|
@ -609,7 +777,7 @@ taos> select spread(v1) from t;
|
|||
Query OK, 1 row(s) in set (0.005501s)
|
||||
```
|
||||
|
||||
最后,查看我们自己打印的执行日志,从日志可以看出,reduce 函数被执行了 3 次。执行过程中 max 值被更新了 4 次, min 值只被更新 1 次。
|
||||
最后,查看执行日志,可以看到 reduce 函数被执行了 3 次,执行过程中 max 值被更新了 4 次,min 值只被更新 1 次。
|
||||
|
||||
```shell
|
||||
root@slave11 /var/log/taos $ cat spread.log
|
||||
|
@ -627,39 +795,77 @@ close log file: spread.log
|
|||
|
||||
通过这个示例,我们学会了如何定义聚合函数,并打印自定义的日志信息。
|
||||
|
||||
### 更多 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}}
|
||||
```
|
||||
|
||||
</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的过程涉及创建、使用和维护这些函数。用户可以通过SQL在集群中创建和管理UDF,一旦创建成功,集群的所有用户都可以在SQL中使用这些函数。由于UDF存储在集群的mnode上,因此即使重启集群,已经创建的UDF也仍然可用。
|
||||
在集群中管理 UDF 的过程涉及创建、使用和维护这些函数。用户可以通过 SQL 在集群中创建和管理 UDF,一旦创建成功,集群的所有用户都可以在 SQL 中使用这些函数。由于 UDF 存储在集群的 mnode 上,因此即使重启集群,已经创建的 UDF 也仍然可用。
|
||||
|
||||
在创建UDF时,需要区分标量函数和聚合函数。标量函数接受零个或多个输入参数,并返回一个单一的值。聚合函数接受一组输入值,并通过对这些值进行某种计算(如求和、计数等)来返回一个单一的值。如果创建时声明了错误的函数类别,则通过SQL调用函数时会报错。
|
||||
在创建 UDF 时,需要区分标量函数和聚合函数。标量函数接受零个或多个输入参数,并返回一个单一的值。聚合函数接受一组输入值,并通过对这些值进行某种计算(如求和、计数等)来返回一个单一的值。如果创建时声明了错误的函数类别,则通过 SQL 调用函数时会报错。
|
||||
|
||||
此外,用户需要确保输入数据类型与UDF程序匹配,UDF输出的数据类型与outputtype匹配。这意味着在创建UDF时,需要为输入参数和输出值指定正确的数据类型。这有助于确保在调用UDF时,输入数据能够正确地传递给UDF,并且UDF的输出值与预期的数据类型相匹配。
|
||||
此外,用户需要确保输入数据类型与 UDF 程序匹配,UDF 输出的数据类型与 outputtype 匹配。这意味着在创建 UDF 时,需要为输入参数和输出值指定正确的数据类型。这有助于确保在调用 UDF 时,输入数据能够正确地传递给 UDF,并且 UDF 的输出值与预期的数据类型相匹配。
|
||||
|
||||
### 创建标量函数
|
||||
|
||||
创建标量函数的SQL语法如下。
|
||||
创建标量函数的 SQL 语法如下。
|
||||
```sql
|
||||
CREATE FUNCTION function_name AS library_path OUTPUTTYPE output_type LANGUAGE 'Python';
|
||||
CREATE OR REPLACE 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文件路径。路径需要用英文单引号或英文双引号括起来。
|
||||
- language:支持 C 语言和 Python 语言(3.7 及以上版本),默认为 C。
|
||||
- library_path:如果编程语言是 C,则路径是包含 UDF 实现的动态链接库的库文件绝对路径,通常指向一个 so 文件。如果编程语言是 Python,则路径是包含 UDF
|
||||
实现的 Python 文件路径。路径需要用英文单引号或英文双引号括起来。
|
||||
- output_type:函数计算结果的数据类型名称。
|
||||
|
||||
|
||||
### 创建聚合函数
|
||||
|
||||
创建聚合函数的SQL语法如下。
|
||||
创建聚合函数的 SQL 语法如下。
|
||||
```sql
|
||||
CREATE AGGREGATE FUNCTION function_name library_path OUTPUTTYPE output_type LANGUAGE 'Python';
|
||||
CREATE OR REPLACE AGGREGATE FUNCTION function_name library_path OUTPUTTYPE output_type LANGUAGE 'Python';
|
||||
```
|
||||
|
||||
其中,buffer_size 表示中间计算结果的缓冲区大小,单位是字节。其他参数的含义与标量函数相同。
|
||||
|
||||
如下SQL创建一个名为 l2norm 的UDF。
|
||||
如下 SQL 创建一个名为 l2norm 的 UDF。
|
||||
```sql
|
||||
CREATE AGGREGATE FUNCTION l2norm AS "/home/taos/udf_example/libl2norm.so" OUTPUTTYPE DOUBLE bufsize 8;
|
||||
```
|
||||
|
@ -673,8 +879,15 @@ DROP FUNCTION function_name;
|
|||
|
||||
### 查看 UDF
|
||||
|
||||
显示集群中当前可用的所有UDF的SQL如下。
|
||||
显示集群中当前可用的所有 UDF 的 SQL 如下。
|
||||
```sql
|
||||
show functions;
|
||||
```
|
||||
|
||||
### 查看函数信息
|
||||
|
||||
同名的 UDF 每更新一次,版本号会增加 1。
|
||||
```sql
|
||||
select * from ins_functions \G;
|
||||
```
|
||||
|
||||
|
|
|
@ -192,13 +192,13 @@ charset 的有效值是 UTF-8。
|
|||
|
||||
### 压缩参数
|
||||
|
||||
| 参数名称 | 参数说明 |
|
||||
| :-------------: | :----------------------------------------------------------------------------------------------------------------------------------------------: |
|
||||
| compressMsgSize | 是否对 RPC 消息进行压缩;-1: 所有消息都不压缩; 0: 所有消息都压缩; N (N>0): 只有大于 N 个字节的消息才压缩;缺省值 -1 |
|
||||
| fPrecision | 设置 float 类型浮点数压缩精度 ,取值范围:0.1 ~ 0.00000001 ,默认值 0.00000001 , 小于此值的浮点数尾数部分将被截断 |
|
||||
| dPrecision | 设置 double 类型浮点数压缩精度 , 取值范围:0.1 ~ 0.0000000000000001 , 缺省值 0.0000000000000001 , 小于此值的浮点数尾数部分将被截取 |
|
||||
| lossyColumn | 对 float 和/或 double 类型启用 TSZ 有损压缩;取值范围: float, double, none;缺省值: none,表示关闭无损压缩 |
|
||||
| ifAdtFse | 在启用 TSZ 有损压缩时,使用 FSE 算法替换 HUFFMAN 算法, FSE 算法压缩速度更快,但解压稍慢,追求压缩速度可选用此算法; 0: 关闭,1:打开;默认值为 0 |
|
||||
| 参数名称 | 参数说明 |
|
||||
|:-------------:|:----------------------------------------------------------------:|
|
||||
| compressMsgSize | 是否对 RPC 消息进行压缩;-1: 所有消息都不压缩; 0: 所有消息都压缩; N (N>0): 只有大于 N 个字节的消息才压缩;缺省值 -1 |
|
||||
| fPrecision | 设置 float 类型浮点数压缩精度 ,取值范围:0.1 ~ 0.00000001 ,默认值 0.00000001 , 小于此值的浮点数尾数部分将被截断 |
|
||||
|dPrecision | 设置 double 类型浮点数压缩精度 , 取值范围:0.1 ~ 0.0000000000000001 , 缺省值 0.0000000000000001 , 小于此值的浮点数尾数部分将被截取 |
|
||||
|lossyColumn | 对 float 和/或 double 类型启用 TSZ 有损压缩;取值范围: float, double, none;缺省值: none,表示关闭无损压缩。**注意:此参数在 3.3.0.0 及更高版本中不再使用** |
|
||||
|ifAdtFse | 在启用 TSZ 有损压缩时,使用 FSE 算法替换 HUFFMAN 算法, FSE 算法压缩速度更快,但解压稍慢,追求压缩速度可选用此算法; 0: 关闭,1:打开;默认值为 0 |
|
||||
|
||||
|
||||
**补充说明**
|
||||
|
|
|
@ -3,7 +3,7 @@ title: taosX 参考手册
|
|||
sidebar_label: taosX
|
||||
---
|
||||
|
||||
taosX 是 TDengine 中的一个核心组件,提供零代码数据接入的能力,taosX 支持两种运行模式:服务模式和命令行模式。本节讲述如何以这两种方式使用 taosX。要想使用 taosX 需要先安装 TDengine Enterprise 安装包。
|
||||
taosX 是 TDengine Enterprise 中的一个核心组件,提供零代码数据接入的能力,taosX 支持两种运行模式:服务模式和命令行模式。本节讲述如何以这两种方式使用 taosX。要想使用 taosX 需要先安装 TDengine Enterprise 安装包。
|
||||
|
||||
## 命令行模式
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ title: taosX-Agent 参考手册
|
|||
sidebar_label: taosX-Agent
|
||||
---
|
||||
|
||||
本节讲述如何部署 `Agent` (for `taosX`)。使用之前需要安装 TDengine Enterprise 安装包之后。
|
||||
本节讲述如何部署 `Agent` (for `taosX`)。使用之前需要安装 TDengine Enterprise 安装包之后,taosX-Agent 用于在部分数据接入场景,如 Pi, OPC UA, OPC DA 等对访问数据源有一定限制或者网络环境特殊的场景下,可以将 taosX-Agent 部署在靠近数据源的环境中甚至与数据源在相同的服务器上,由 taosX-Agent 负责从数据源读取数据并发送给 taosX。
|
||||
|
||||
## 配置
|
||||
|
||||
|
|
|
@ -4,27 +4,27 @@ sidebar_label: taosExplorer
|
|||
toc_max_heading_level: 4
|
||||
---
|
||||
|
||||
taos-explorer 是一个为用户提供 TDengine 实例的可视化管理交互工具的 web 服务。本节主要讲述其安装和部署。它的各项功能都是基于简单易上手的图形界面,可以直接尝试,如果有需要也可以考高级功能和运维指南中的相关内容。
|
||||
taosExplorer 是一个为用户提供 TDengine 实例的可视化管理交互工具的 web 服务。本节主要讲述其安装和部署。它的各项功能都是基于简单易上手的图形界面,可以直接尝试,如果有需要也可以考高级功能和运维指南中的相关内容。
|
||||
|
||||
## 安装
|
||||
|
||||
taos-explorer 无需单独安装,从 TDengine 3.3.0.0 版本开始,它随着 TDengine Enterprise Server 安装包一起发布,安装完成后,就可以看到 `taos-explorer` 服务。
|
||||
taosEexplorer 无需单独安装,从 TDengine 3.3.0.0 版本开始,它随着 TDengine Enterprise Server 安装包一起发布,安装完成后,就可以看到 `taos-explorer` 服务。
|
||||
|
||||
## 配置
|
||||
|
||||
在启动 Explorer 之前,请确保配置文件中的内容正确。
|
||||
在启动 taosExplorer 之前,请确保配置文件中的内容正确。
|
||||
|
||||
```TOML
|
||||
# Explorer listen port
|
||||
# listen port
|
||||
port = 6060
|
||||
|
||||
# Explorer listen address for IPv4
|
||||
# listen address for IPv4
|
||||
addr = "0.0.0.0"
|
||||
|
||||
# Explorer listen address for IPv4
|
||||
# listen address for IPv4
|
||||
#ipv6 = "::1"
|
||||
|
||||
# Explorer log level. Possible: error,warn,info,debug,trace
|
||||
# log level. Possible: error,warn,info,debug,trace
|
||||
log_level = "info"
|
||||
|
||||
# taosAdapter address.
|
||||
|
@ -49,9 +49,9 @@ cors = false
|
|||
|
||||
说明:
|
||||
|
||||
- `port`:Explorer 服务绑定的端口。
|
||||
- `addr`:Explorer 服务绑定的 IPv4 地址,默认为 `0.0.0.0`。如需修改,请配置为 `localhost` 之外的地址以对外提供服务。
|
||||
- `ipv6`:Explorer 服务绑定的 IPv6 地址,默认不绑定 IPv6 地址。
|
||||
- `port`:taosExplorer 服务绑定的端口。
|
||||
- `addr`:taosExplorer 服务绑定的 IPv4 地址,默认为 `0.0.0.0`。如需修改,请配置为 `localhost` 之外的地址以对外提供服务。
|
||||
- `ipv6`:taosExplorer 服务绑定的 IPv6 地址,默认不绑定 IPv6 地址。
|
||||
- `log_level`:日志级别,可选值为 "error", "warn", "info", "debug", "trace"。
|
||||
- `cluster`:TDengine 集群的 taosAdapter 地址。
|
||||
- `x_api`:taosX 的 gRPC 地址。
|
||||
|
@ -62,7 +62,7 @@ cors = false
|
|||
|
||||
## 启动停止
|
||||
|
||||
然后启动 Explorer,可以直接在命令行执行 taos-explorer 或者使用 systemctl 命令:
|
||||
然后启动 taosExplorer,可以直接在命令行执行 taos-explorer 或者使用 systemctl 命令:
|
||||
|
||||
```bash
|
||||
systemctl start taos-explorer # Linux
|
||||
|
@ -78,7 +78,7 @@ sc.exe stop taos-explorer # Windows
|
|||
## 问题排查
|
||||
|
||||
1. 当通过浏览器打开 Explorer 站点遇到“无法访问此网站”的错误信息时,请通过命令行登录 taosExplorer 所在机器,并使用命令 `systemctl status taos-explorer` 检查服务的状态,如果返回的状态是 `inactive`,请使用命令`systemctl start taos-explorer` 启动服务。
|
||||
2. 如果需要获取 Explorer 的详细日志,可通过命令 `journalctl -u taos-explorer`。
|
||||
2. 如果需要获取 taosExplorer 的详细日志,可通过命令 `journalctl -u taos-explorer`。
|
||||
3. 当使用 Nginx 或其他工具进行转发时,注意进行 CORS 设置或在配置文件中使用 `cors = true`。
|
||||
|
||||
这是一个 Nginx 配置文件 CORS 设置的例子:
|
||||
|
|
|
@ -1,6 +1,13 @@
|
|||
---
|
||||
sidebar_label: 产品组件
|
||||
title: 产品组件
|
||||
toc_max_heading_level: 4
|
||||
description: TDengine 产品组件参考手册
|
||||
---
|
||||
---
|
||||
title: 产品组件
|
||||
description: TDengine 产品组件参考手册
|
||||
---
|
||||
|
||||
本节详细说明 TDengine 中的主要产品组件的功能、命令行参数、配置参数等。
|
||||
|
||||
```mdx-code-block
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
||||
|
||||
<DocCardList items={useCurrentSidebarCategory().items}/>
|
||||
```
|
|
@ -13,26 +13,26 @@ database_options:
|
|||
database_option ...
|
||||
|
||||
database_option: {
|
||||
BUFFER value
|
||||
VGROUPS value
|
||||
| PRECISION {'ms' | 'us' | 'ns'}
|
||||
| REPLICA value
|
||||
| BUFFER value
|
||||
| PAGES value
|
||||
| PAGESIZE value
|
||||
| CACHEMODEL {'none' | 'last_row' | 'last_value' | 'both'}
|
||||
| CACHESIZE value
|
||||
| COMP {0 | 1 | 2}
|
||||
| DURATION value
|
||||
| WAL_FSYNC_PERIOD value
|
||||
| MAXROWS value
|
||||
| MINROWS value
|
||||
| KEEP value
|
||||
| PAGES value
|
||||
| PAGESIZE value
|
||||
| PRECISION {'ms' | 'us' | 'ns'}
|
||||
| REPLICA value
|
||||
| WAL_LEVEL {1 | 2}
|
||||
| VGROUPS value
|
||||
| SINGLE_STABLE {0 | 1}
|
||||
| STT_TRIGGER value
|
||||
| SINGLE_STABLE {0 | 1}
|
||||
| TABLE_PREFIX value
|
||||
| TABLE_SUFFIX value
|
||||
| TSDB_PAGESIZE value
|
||||
| WAL_LEVEL {1 | 2}
|
||||
| WAL_FSYNC_PERIOD value
|
||||
| WAL_RETENTION_PERIOD value
|
||||
| WAL_RETENTION_SIZE value
|
||||
}
|
||||
|
@ -40,7 +40,14 @@ database_option: {
|
|||
|
||||
### 参数说明
|
||||
|
||||
- VGROUPS:数据库中初始 vgroup 的数目。
|
||||
- PRECISION:数据库的时间戳精度。ms 表示毫秒,us 表示微秒,ns 表示纳秒,默认 ms 毫秒。
|
||||
- REPLICA:表示数据库副本数,取值为 1、2 或 3,默认为 1; 2 仅在企业版 3.3.0.0 及以后版本中可用。在集群中使用,副本数必须小于或等于 DNODE 的数目。且使用时存在以下限制:
|
||||
- 暂不支持对双副本数据库相关 Vgroup 进行 SPLITE VGROUP 或 REDISTRIBUTE VGROUP 操作
|
||||
- 单副本数据库可变更为双副本数据库,但不支持从双副本变更为其它副本数,也不支持从三副本变更为双副本
|
||||
- BUFFER: 一个 VNODE 写入内存池大小,单位为 MB,默认为 256,最小为 3,最大为 16384。
|
||||
- PAGES:一个 VNODE 中元数据存储引擎的缓存页个数,默认为 256,最小 64。一个 VNODE 元数据存储占用 PAGESIZE \* PAGES,默认情况下为 1MB 内存。
|
||||
- PAGESIZE:一个 VNODE 中元数据存储引擎的页大小,单位为 KB,默认为 4 KB。范围为 1 到 16384,即 1 KB 到 16 MB。
|
||||
- CACHEMODEL:表示是否在内存中缓存子表的最近数据。默认为 none。
|
||||
- none:表示不缓存。
|
||||
- last_row:表示缓存子表最近一行数据。这将显著改善 LAST_ROW 函数的性能表现。
|
||||
|
@ -53,27 +60,20 @@ database_option: {
|
|||
- 1:表示一阶段压缩。
|
||||
- 2:表示两阶段压缩。
|
||||
- DURATION:数据文件存储数据的时间跨度。可以使用加单位的表示形式,如 DURATION 100h、DURATION 10d 等,支持 m(分钟)、h(小时)和 d(天)三个单位。不加时间单位时默认单位为天,如 DURATION 50 表示 50 天。
|
||||
- WAL_FSYNC_PERIOD:当 WAL_LEVEL 参数设置为 2 时,用于设置落盘的周期。默认为 3000,单位毫秒。最小为 0,表示每次写入立即落盘;最大为 180000,即三分钟。
|
||||
- MAXROWS:文件块中记录的最大条数,默认为 4096 条。
|
||||
- MINROWS:文件块中记录的最小条数,默认为 100 条。
|
||||
- KEEP:表示数据文件保存的天数,缺省值为 3650,取值范围 [1, 365000],且必须大于或等于3倍的 DURATION 参数值。数据库会自动删除保存时间超过 KEEP 值的数据。KEEP 可以使用加单位的表示形式,如 KEEP 100h、KEEP 10d 等,支持 m(分钟)、h(小时)和 d(天)三个单位。也可以不写单位,如 KEEP 50,此时默认单位为天。企业版支持[多级存储](https://docs.taosdata.com/tdinternal/arch/#%E5%A4%9A%E7%BA%A7%E5%AD%98%E5%82%A8)功能, 因此, 可以设置多个保存时间(多个以英文逗号分隔,最多 3 个,满足 keep 0 \<= keep 1 \<= keep 2,如 KEEP 100h,100d,3650d); 社区版不支持多级存储功能(即使配置了多个保存时间, 也不会生效, KEEP 会取最大的保存时间)。
|
||||
- PAGES:一个 VNODE 中元数据存储引擎的缓存页个数,默认为 256,最小 64。一个 VNODE 元数据存储占用 PAGESIZE \* PAGES,默认情况下为 1MB 内存。
|
||||
- PAGESIZE:一个 VNODE 中元数据存储引擎的页大小,单位为 KB,默认为 4 KB。范围为 1 到 16384,即 1 KB 到 16 MB。
|
||||
- PRECISION:数据库的时间戳精度。ms 表示毫秒,us 表示微秒,ns 表示纳秒,默认 ms 毫秒。
|
||||
- REPLICA:表示数据库副本数,取值为 1、2 或 3,默认为 1; 2 仅在企业版 3.3.0.0 及以后版本中可用。在集群中使用,副本数必须小于或等于 DNODE 的数目。且使用时存在以下限制:
|
||||
- 暂不支持对双副本数据库相关 Vgroup 进行 SPLITE VGROUP 或 REDISTRIBUTE VGROUP 操作
|
||||
- 单副本数据库可变更为双副本数据库,但不支持从双副本变更为其它副本数,也不支持从三副本变更为双副本
|
||||
- WAL_LEVEL:WAL 级别,默认为 1。
|
||||
- 1:写 WAL,但不执行 fsync。
|
||||
- 2:写 WAL,而且执行 fsync。
|
||||
- VGROUPS:数据库中初始 vgroup 的数目。
|
||||
- STT_TRIGGER:表示落盘文件触发文件合并的个数。默认为 1,范围 1 到 16。对于少表高频场景,此参数建议使用默认配置,或较小的值;而对于多表低频场景,此参数建议配置较大的值。
|
||||
- SINGLE_STABLE:表示此数据库中是否只可以创建一个超级表,用于超级表列非常多的情况。
|
||||
- 0:表示可以创建多张超级表。
|
||||
- 1:表示只可以创建一张超级表。
|
||||
- STT_TRIGGER:表示落盘文件触发文件合并的个数。默认为 1,范围 1 到 16。对于少表高频场景,此参数建议使用默认配置,或较小的值;而对于多表低频场景,此参数建议配置较大的值。
|
||||
- TABLE_PREFIX:当其为正值时,在决定把一个表分配到哪个 vgroup 时要忽略表名中指定长度的前缀;当其为负值时,在决定把一个表分配到哪个 vgroup 时只使用表名中指定长度的前缀;例如,假定表名为 "v30001",当 TSDB_PREFIX = 2 时 使用 "0001" 来决定分配到哪个 vgroup ,当 TSDB_PREFIX = -2 时使用 "v3" 来决定分配到哪个 vgroup
|
||||
- TABLE_SUFFIX:当其为正值时,在决定把一个表分配到哪个 vgroup 时要忽略表名中指定长度的后缀;当其为负值时,在决定把一个表分配到哪个 vgroup 时只使用表名中指定长度的后缀;例如,假定表名为 "v30001",当 TSDB_SUFFIX = 2 时 使用 "v300" 来决定分配到哪个 vgroup ,当 TSDB_SUFFIX = -2 时使用 "01" 来决定分配到哪个 vgroup。
|
||||
- TSDB_PAGESIZE:一个 VNODE 中时序数据存储引擎的页大小,单位为 KB,默认为 4 KB。范围为 1 到 16384,即 1 KB到 16 MB。
|
||||
- WAL_LEVEL:WAL 级别,默认为 1。
|
||||
- 1:写 WAL,但不执行 fsync。
|
||||
- 2:写 WAL,而且执行 fsync。
|
||||
- WAL_FSYNC_PERIOD:当 WAL_LEVEL 参数设置为 2 时,用于设置落盘的周期。默认为 3000,单位毫秒。最小为 0,表示每次写入立即落盘;最大为 180000,即三分钟。
|
||||
- WAL_RETENTION_PERIOD: 为了数据订阅消费,需要WAL日志文件额外保留的最大时长策略。WAL日志清理,不受订阅客户端消费状态影响。单位为 s。默认为 3600,表示在 WAL 保留最近 3600 秒的数据,请根据数据订阅的需要修改这个参数为适当值。
|
||||
- WAL_RETENTION_SIZE:为了数据订阅消费,需要WAL日志文件额外保留的最大累计大小策略。单位为 KB。默认为 0,表示累计大小无上限。
|
||||
### 创建数据库示例
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
sidebar_label: 集群管理
|
||||
title: 集群管理
|
||||
description: 管理集群的 SQL 命令的详细解析
|
||||
sidebar_label: 节点管理
|
||||
title: 节点管理
|
||||
description: 管理集群节点的 SQL 命令的详细解析
|
||||
---
|
||||
|
||||
组成 TDengine 集群的物理实体是 dnode (data node 的缩写),它是一个运行在操作系统之上的进程。在 dnode 中可以建立负责时序数据存储的 vnode (virtual node),在多节点集群环境下当某个数据库的 replica 为 3 时,该数据库中的每个 vgroup 由 3 个 vnode 组成;当数据库的 replica 为 1 时,该数据库中的每个 vgroup 由 1 个 vnode 组成。如果要想配置某个数据库为多副本,则集群中的 dnode 数量至少为 3。在 dnode 还可以创建 mnode (management node),单个集群中最多可以创建三个 mnode。在 TDengine 3.0.0.0 中为了支持存算分离,引入了一种新的逻辑节点 qnode (query node),qnode 和 vnode 既可以共存在一个 dnode 中,也可以完全分离在不同的 dnode 上。
|
||||
|
|
|
@ -202,7 +202,7 @@ SELECT ... FROM table_name1 LEFT|RIGHT ASOF JOIN table_name2 [ON ...] [JLIMIT jl
|
|||
|
||||
表 d1001 电压值大于 220V 且表 d1002 中同一时刻或稍早前最后时刻出现电压大于 220V 的时间及各自的电压值:
|
||||
```sql
|
||||
SELECT a.ts, a.voltage, a.ts, b.voltage FROM d1001 a LEFT ASOF JOIN d1002 b ON a.ts >= b.ts where a.voltage > 220 and b.voltage > 220
|
||||
SELECT a.ts, a.voltage, b.ts, b.voltage FROM d1001 a LEFT ASOF JOIN d1002 b ON a.ts >= b.ts where a.voltage > 220 and b.voltage > 220
|
||||
```
|
||||
|
||||
### Left/Right Window Join
|
||||
|
|
|
@ -123,3 +123,9 @@ import VerifyMacOS from "./_verify_macos.mdx";
|
|||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
```mdx-code-block
|
||||
import DocCardList from '@theme/DocCardList';
|
||||
import {useCurrentSidebarCategory} from '@docusaurus/theme-common';
|
||||
|
||||
<DocCardList items={useCurrentSidebarCategory().items}/>
|
||||
```
|
|
@ -178,7 +178,7 @@ TDengine 集群可以容纳单个、多个甚至几千个数据节点。应用
|
|||
|
||||
TDengine 存储的数据包括采集的时序数据以及库、表相关的元数据、标签数据等,这些数据具体分为三部分:
|
||||
|
||||
- 时序数据:TDengine 的核心存储对象,存放于 vnode 里,由 data、head 和 last 三个文件组成,数据量大,查询量取决于应用场景。容许乱序写入,但暂时不支持删除操作,并且仅在 update 参数设置为 1 时允许更新操作。通过采用一个采集点一张表的模型,一个时间段的数据是连续存储,对单张表的写入是简单的追加操作,一次读,可以读到多条记录,这样保证对单个采集点的插入和查询操作,性能达到最优。
|
||||
- 时序数据:TDengine 的核心存储对象,存放于 vnode 里,由 data、head 和 last 三个文件组成,数据量大,查询量取决于应用场景。允许乱序写入,但暂时不支持删除操作,并且仅在 update 参数设置为 1 时允许更新操作。通过采用一个采集点一张表的模型,一个时间段的数据是连续存储,对单张表的写入是简单的追加操作,一次读,可以读到多条记录,这样保证对单个采集点的插入和查询操作,性能达到最优。
|
||||
- 数据表元数据:包含标签信息和 Table Schema 信息,存放于 vnode 里的 meta 文件,支持增删改查四个标准操作。数据量很大,有 N 张表,就有 N 条记录,因此采用 LRU 存储,支持标签数据的索引。TDengine 支持多核多线程并发查询。只要计算内存足够,元数据全内存存储,千万级别规模的标签数据过滤结果能毫秒级返回。在内存资源不足的情况下,仍然可以支持数千万张表的快速查询。
|
||||
- 数据库元数据:存放于 mnode 里,包含系统节点、用户、DB、STable Schema 等信息,支持增删改查四个标准操作。这部分数据的量不大,可以全内存保存,而且由于客户端有缓存,查询量也不大。因此目前的设计虽是集中式存储管理,但不会构成性能瓶颈。
|
||||
|
||||
|
@ -301,7 +301,7 @@ TDengine 采用了一种数据驱动的策略来实现缓存数据的持久化
|
|||
|
||||
对于采集的数据,通常会有一定的保留期限,该期限由数据库参数 keep 指定。超出设定天数的数据文件将被集群自动移除,并释放相应的存储空间。
|
||||
|
||||
当设置 duration 和 keep 两个参数后,一个处于典型工作状态的 vnode 中,总的数据文件数量应为向上取整 (keep/duration)+1 个。数据文件的总个数应保持在一个合理的范围内,不宜过多也不宜过少,通常介于 10 到 100 之间较为适宜。基于这一原则,可以合理设置 duration 参数。在本书编写时的版本中,可以调整参数 keep,但参数 duration 一旦设定,则无法更改。
|
||||
当设置 duration 和 keep 两个参数后,一个处于典型工作状态的 vnode 中,总的数据文件数量应为向上取整 (keep/duration)+1 个。数据文件的总个数应保持在一个合理的范围内,不宜过多也不宜过少,通常介于 10 到 100 之间较为适宜。基于这一原则,可以合理设置 duration 参数。可以调整参数 keep,但参数 duration 一旦设定,则无法更改。
|
||||
|
||||
在每个数据文件中,表的数据是以块的形式存储的。一张表可能包含一到多个数据文件块。在一个文件块内,数据采用列式存储,占据连续的存储空间,这有助于显著提高读取度。文件块的大小由数据库参数 maxRows(每块最大记录条数)控制,默认值为 4096。这个值应适中,过大可能导致定位特定时间段数据的搜索时间变长,影响读取速度;过小则可能导致数据文件块的索引过大,压缩效率降低,同样影响读取速度。
|
||||
|
Before Width: | Height: | Size: 319 KiB After Width: | Height: | Size: 319 KiB |
Before Width: | Height: | Size: 179 KiB After Width: | Height: | Size: 179 KiB |
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 117 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 476 KiB After Width: | Height: | Size: 476 KiB |
Before Width: | Height: | Size: 105 KiB After Width: | Height: | Size: 105 KiB |
Before Width: | Height: | Size: 359 KiB After Width: | Height: | Size: 359 KiB |
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB |
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 96 KiB |
Before Width: | Height: | Size: 133 KiB After Width: | Height: | Size: 133 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 163 KiB After Width: | Height: | Size: 163 KiB |
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 192 KiB After Width: | Height: | Size: 192 KiB |
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB |
Before Width: | Height: | Size: 232 KiB After Width: | Height: | Size: 232 KiB |
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 19 KiB After Width: | Height: | Size: 19 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 75 KiB After Width: | Height: | Size: 75 KiB |
Before Width: | Height: | Size: 72 KiB After Width: | Height: | Size: 72 KiB |
Before Width: | Height: | Size: 231 KiB After Width: | Height: | Size: 231 KiB |
Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 61 KiB |
Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB |
Before Width: | Height: | Size: 215 KiB After Width: | Height: | Size: 215 KiB |
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB |
Before Width: | Height: | Size: 106 KiB After Width: | Height: | Size: 106 KiB |
Before Width: | Height: | Size: 155 KiB After Width: | Height: | Size: 155 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
@ -270,7 +270,10 @@ DLL_EXPORT TAOS_RES *taos_schemaless_insert_raw_ttl(TAOS *taos, char *lines, int
|
|||
int precision, int32_t ttl);
|
||||
DLL_EXPORT TAOS_RES *taos_schemaless_insert_raw_ttl_with_reqid(TAOS *taos, char *lines, int len, int32_t *totalRows,
|
||||
int protocol, int precision, int32_t ttl, int64_t reqid);
|
||||
|
||||
DLL_EXPORT TAOS_RES *taos_schemaless_insert_raw_ttl_with_reqid_tbname_key(TAOS *taos, char *lines, int len, int32_t *totalRows,
|
||||
int protocol, int precision, int32_t ttl, int64_t reqid, char *tbnameKey);
|
||||
DLL_EXPORT TAOS_RES *taos_schemaless_insert_ttl_with_reqid_tbname_key(TAOS *taos, char *lines[], int numLines, int protocol,
|
||||
int precision, int32_t ttl, int64_t reqid, char *tbnameKey);
|
||||
/* --------------------------TMQ INTERFACE------------------------------- */
|
||||
|
||||
typedef struct tmq_t tmq_t;
|
||||
|
|
|
@ -13,8 +13,8 @@ extern "C" {
|
|||
|
||||
void stopRsync();
|
||||
int32_t startRsync();
|
||||
int32_t uploadByRsync(const char* id, const char* path);
|
||||
int32_t downloadRsync(const char* id, const char* path);
|
||||
int32_t uploadByRsync(const char* id, const char* path, int64_t checkpointId);
|
||||
int32_t downloadByRsync(const char* id, const char* path, int64_t checkpointId);
|
||||
int32_t deleteRsync(const char* id);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
|
|
@ -245,12 +245,12 @@ typedef struct SStoreSnapshotFn {
|
|||
} SStoreSnapshotFn;
|
||||
|
||||
typedef struct SStoreMeta {
|
||||
SMTbCursor* (*openTableMetaCursor)(void* pVnode); // metaOpenTbCursor
|
||||
void (*closeTableMetaCursor)(SMTbCursor* pTbCur); // metaCloseTbCursor
|
||||
void (*pauseTableMetaCursor)(SMTbCursor* pTbCur); // metaPauseTbCursor
|
||||
void (*resumeTableMetaCursor)(SMTbCursor* pTbCur, int8_t first, int8_t move); // metaResumeTbCursor
|
||||
int32_t (*cursorNext)(SMTbCursor* pTbCur, ETableType jumpTableType); // metaTbCursorNext
|
||||
int32_t (*cursorPrev)(SMTbCursor* pTbCur, ETableType jumpTableType); // metaTbCursorPrev
|
||||
SMTbCursor* (*openTableMetaCursor)(void* pVnode); // metaOpenTbCursor
|
||||
void (*closeTableMetaCursor)(SMTbCursor* pTbCur); // metaCloseTbCursor
|
||||
void (*pauseTableMetaCursor)(SMTbCursor* pTbCur); // metaPauseTbCursor
|
||||
int32_t (*resumeTableMetaCursor)(SMTbCursor* pTbCur, int8_t first, int8_t move); // metaResumeTbCursor
|
||||
int32_t (*cursorNext)(SMTbCursor* pTbCur, ETableType jumpTableType); // metaTbCursorNext
|
||||
int32_t (*cursorPrev)(SMTbCursor* pTbCur, ETableType jumpTableType); // metaTbCursorPrev
|
||||
|
||||
int32_t (*getTableTags)(void* pVnode, uint64_t suid, SArray* uidList);
|
||||
int32_t (*getTableTagsByUid)(void* pVnode, int64_t suid, SArray* uidList);
|
||||
|
|
|
@ -131,6 +131,14 @@ static FORCE_INLINE char *udfColDataGetData(const SUdfColumn *pColumn, int32_t r
|
|||
}
|
||||
}
|
||||
|
||||
static FORCE_INLINE int32_t udfColDataGetDataLen(const SUdfColumn *pColumn, int32_t row) {
|
||||
if (IS_VAR_DATA_TYPE(pColumn->colMeta.type)) {
|
||||
return *(uint16_t*)(pColumn->colData.varLenCol.payload + pColumn->colData.varLenCol.varOffsets[row]);
|
||||
} else {
|
||||
return pColumn->colMeta.bytes;
|
||||
}
|
||||
}
|
||||
|
||||
static FORCE_INLINE bool udfColDataIsNull(const SUdfColumn *pColumn, int32_t row) {
|
||||
if (IS_VAR_DATA_TYPE(pColumn->colMeta.type)) {
|
||||
if (pColumn->colMeta.type == TSDB_DATA_TYPE_JSON) {
|
||||
|
@ -320,6 +328,30 @@ typedef int32_t (*TScriptUdfDestoryFunc)(void *udfCtx);
|
|||
typedef int32_t (*TScriptOpenFunc)(SScriptUdfEnvItem *items, int numItems);
|
||||
typedef int32_t (*TScriptCloseFunc)();
|
||||
|
||||
// clang-format off
|
||||
#ifdef WINDOWS
|
||||
#define fnFatal(...) {}
|
||||
#define fnError(...) {}
|
||||
#define fnWarn(...) {}
|
||||
#define fnInfo(...) {}
|
||||
#define fnDebug(...) {}
|
||||
#define fnTrace(...) {}
|
||||
#else
|
||||
DLL_EXPORT void taosPrintLog(const char *flags, int32_t level, int32_t dflag, const char *format, ...)
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 4, 5)))
|
||||
#endif
|
||||
;
|
||||
extern int32_t udfDebugFlag;
|
||||
#define udfFatal(...) { if (udfDebugFlag & 1) { taosPrintLog("UDF FATAL ", 1, 255, __VA_ARGS__); }}
|
||||
#define udfError(...) { if (udfDebugFlag & 1) { taosPrintLog("UDF ERROR ", 1, 255, __VA_ARGS__); }}
|
||||
#define udfWarn(...) { if (udfDebugFlag & 2) { taosPrintLog("UDF WARN ", 2, 255, __VA_ARGS__); }}
|
||||
#define udfInfo(...) { if (udfDebugFlag & 2) { taosPrintLog("UDF ", 2, 255, __VA_ARGS__); }}
|
||||
#define udfDebug(...) { if (udfDebugFlag & 4) { taosPrintLog("UDF ", 4, udfDebugFlag, __VA_ARGS__); }}
|
||||
#define udfTrace(...) { if (udfDebugFlag & 8) { taosPrintLog("UDF ", 8, udfDebugFlag, __VA_ARGS__); }}
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -636,7 +636,7 @@ bool nodesExprsHasColumn(SNodeList* pList);
|
|||
void* nodesGetValueFromNode(SValueNode* pNode);
|
||||
int32_t nodesSetValueNodeValue(SValueNode* pNode, void* value);
|
||||
char* nodesGetStrValueFromNode(SValueNode* pNode);
|
||||
void nodesValueNodeToVariant(const SValueNode* pNode, SVariant* pVal);
|
||||
int32_t nodesValueNodeToVariant(const SValueNode* pNode, SVariant* pVal);
|
||||
int32_t nodesMakeValueNodeFromString(char* literal, SValueNode** ppValNode);
|
||||
int32_t nodesMakeValueNodeFromBool(bool b, SValueNode** ppValNode);
|
||||
int32_t nodesMakeValueNodeFromInt32(int32_t value, SNode** ppNode);
|
||||
|
|
|
@ -25,9 +25,9 @@ extern "C" {
|
|||
#include "tarray.h"
|
||||
#include "thash.h"
|
||||
#include "tlog.h"
|
||||
#include "tsimplehash.h"
|
||||
#include "tmsg.h"
|
||||
#include "tmsgcb.h"
|
||||
#include "tsimplehash.h"
|
||||
|
||||
typedef enum {
|
||||
JOB_TASK_STATUS_NULL = 0,
|
||||
|
@ -69,16 +69,16 @@ typedef enum {
|
|||
#define QUERY_MSG_MASK_SHOW_REWRITE() (1 << 0)
|
||||
#define QUERY_MSG_MASK_AUDIT() (1 << 1)
|
||||
#define QUERY_MSG_MASK_VIEW() (1 << 2)
|
||||
#define TEST_SHOW_REWRITE_MASK(m) (((m) & QUERY_MSG_MASK_SHOW_REWRITE()) != 0)
|
||||
#define TEST_AUDIT_MASK(m) (((m) & QUERY_MSG_MASK_AUDIT()) != 0)
|
||||
#define TEST_VIEW_MASK(m) (((m) & QUERY_MSG_MASK_VIEW()) != 0)
|
||||
#define TEST_SHOW_REWRITE_MASK(m) (((m)&QUERY_MSG_MASK_SHOW_REWRITE()) != 0)
|
||||
#define TEST_AUDIT_MASK(m) (((m)&QUERY_MSG_MASK_AUDIT()) != 0)
|
||||
#define TEST_VIEW_MASK(m) (((m)&QUERY_MSG_MASK_VIEW()) != 0)
|
||||
|
||||
typedef struct STableComInfo {
|
||||
uint8_t numOfTags; // the number of tags in schema
|
||||
uint8_t precision; // the number of precision
|
||||
col_id_t numOfColumns; // the number of columns
|
||||
int16_t numOfPKs;
|
||||
int32_t rowSize; // row size of the schema
|
||||
int32_t rowSize; // row size of the schema
|
||||
} STableComInfo;
|
||||
|
||||
typedef struct SIndexMeta {
|
||||
|
@ -119,8 +119,9 @@ typedef struct STableMeta {
|
|||
int32_t sversion;
|
||||
int32_t tversion;
|
||||
STableComInfo tableInfo;
|
||||
SSchemaExt* schemaExt; // There is no additional memory allocation, and the pointer is fixed to the next address of the schema content.
|
||||
SSchema schema[];
|
||||
SSchemaExt* schemaExt; // There is no additional memory allocation, and the pointer is fixed to the next address of
|
||||
// the schema content.
|
||||
SSchema schema[];
|
||||
} STableMeta;
|
||||
#pragma pack(pop)
|
||||
|
||||
|
@ -196,9 +197,9 @@ typedef struct SBoundColInfo {
|
|||
} SBoundColInfo;
|
||||
|
||||
typedef struct STableColsData {
|
||||
char tbName[TSDB_TABLE_NAME_LEN];
|
||||
SArray* aCol;
|
||||
bool getFromHash;
|
||||
char tbName[TSDB_TABLE_NAME_LEN];
|
||||
SArray* aCol;
|
||||
bool getFromHash;
|
||||
} STableColsData;
|
||||
|
||||
typedef struct STableVgUid {
|
||||
|
@ -207,15 +208,14 @@ typedef struct STableVgUid {
|
|||
} STableVgUid;
|
||||
|
||||
typedef struct STableBufInfo {
|
||||
void* pCurBuff;
|
||||
SArray* pBufList;
|
||||
int64_t buffUnit;
|
||||
int64_t buffSize;
|
||||
int64_t buffIdx;
|
||||
int64_t buffOffset;
|
||||
void* pCurBuff;
|
||||
SArray* pBufList;
|
||||
int64_t buffUnit;
|
||||
int64_t buffSize;
|
||||
int64_t buffIdx;
|
||||
int64_t buffOffset;
|
||||
} STableBufInfo;
|
||||
|
||||
|
||||
typedef struct STableDataCxt {
|
||||
STableMeta* pMeta;
|
||||
STSchema* pSchema;
|
||||
|
@ -237,23 +237,22 @@ typedef struct SStbInterlaceInfo {
|
|||
void* pRequest;
|
||||
uint64_t requestId;
|
||||
int64_t requestSelf;
|
||||
bool tbFromHash;
|
||||
bool tbFromHash;
|
||||
SHashObj* pVgroupHash;
|
||||
SArray* pVgroupList;
|
||||
SSHashObj* pTableHash;
|
||||
int64_t tbRemainNum;
|
||||
STableBufInfo tbBuf;
|
||||
char firstName[TSDB_TABLE_NAME_LEN];
|
||||
STSchema *pTSchema;
|
||||
STableDataCxt *pDataCtx;
|
||||
void *boundTags;
|
||||
STSchema* pTSchema;
|
||||
STableDataCxt* pDataCtx;
|
||||
void* boundTags;
|
||||
|
||||
bool tableColsReady;
|
||||
SArray *pTableCols;
|
||||
int32_t pTableColsIdx;
|
||||
bool tableColsReady;
|
||||
SArray* pTableCols;
|
||||
int32_t pTableColsIdx;
|
||||
} SStbInterlaceInfo;
|
||||
|
||||
|
||||
typedef int32_t (*__async_send_cb_fn_t)(void* param, SDataBuf* pMsg, int32_t code);
|
||||
typedef int32_t (*__async_exec_fn_t)(void* param);
|
||||
|
||||
|
@ -308,6 +307,8 @@ void destroyAhandle(void* ahandle);
|
|||
int32_t asyncSendMsgToServerExt(void* pTransporter, SEpSet* epSet, int64_t* pTransporterId, SMsgSendInfo* pInfo,
|
||||
bool persistHandle, void* ctx);
|
||||
|
||||
int32_t asyncFreeConnById(void* pTransporter, int64_t pid);
|
||||
;
|
||||
/**
|
||||
* Asynchronously send message to server, after the response received, the callback will be incured.
|
||||
*
|
||||
|
@ -325,7 +326,7 @@ void initQueryModuleMsgHandle();
|
|||
|
||||
const SSchema* tGetTbnameColumnSchema();
|
||||
bool tIsValidSchema(struct SSchema* pSchema, int32_t numOfCols, int32_t numOfTags);
|
||||
int32_t getAsofJoinReverseOp(EOperatorType op);
|
||||
int32_t getAsofJoinReverseOp(EOperatorType op);
|
||||
|
||||
int32_t queryCreateCTableMetaFromMsg(STableMetaRsp* msg, SCTableMeta* pMeta);
|
||||
int32_t queryCreateTableMetaFromMsg(STableMetaRsp* msg, bool isSuperTable, STableMeta** pMeta);
|
||||
|
@ -384,7 +385,7 @@ extern int32_t (*queryProcessMsgRsp[TDMT_MAX])(void* output, char* msg, int32_t
|
|||
|
||||
#define NEED_CLIENT_RM_TBLMETA_REQ(_type) \
|
||||
((_type) == TDMT_VND_CREATE_TABLE || (_type) == TDMT_MND_CREATE_STB || (_type) == TDMT_VND_DROP_TABLE || \
|
||||
(_type) == TDMT_MND_DROP_STB || (_type) == TDMT_MND_CREATE_VIEW || (_type) == TDMT_MND_DROP_VIEW || \
|
||||
(_type) == TDMT_MND_DROP_STB || (_type) == TDMT_MND_CREATE_VIEW || (_type) == TDMT_MND_DROP_VIEW || \
|
||||
(_type) == TDMT_MND_CREATE_TSMA || (_type) == TDMT_MND_DROP_TSMA || (_type) == TDMT_MND_DROP_TB_WITH_TSMA)
|
||||
|
||||
#define NEED_SCHEDULER_REDIRECT_ERROR(_code) \
|
||||
|
|
|
@ -164,6 +164,7 @@ int32_t tDecodeStreamTaskCheckpointReq(SDecoder* pDecoder, SStreamTaskCheckpoint
|
|||
typedef struct SStreamHbMsg {
|
||||
int32_t vgId;
|
||||
int32_t msgId;
|
||||
int64_t ts;
|
||||
int32_t numOfTasks;
|
||||
SArray* pTaskStatus; // SArray<STaskStatusEntry>
|
||||
SArray* pUpdateNodes; // SArray<int32_t>, needs update the epsets in stream tasks for those nodes.
|
||||
|
|
|
@ -125,6 +125,7 @@ typedef struct SRpcInit {
|
|||
int32_t timeToGetConn;
|
||||
int8_t supportBatch; // 0: no batch, 1. batch
|
||||
int32_t batchSize;
|
||||
int8_t notWaitAvaliableConn; // 1: wait to get, 0: no wait
|
||||
void *parent;
|
||||
} SRpcInit;
|
||||
|
||||
|
@ -158,18 +159,21 @@ void *rpcReallocCont(void *ptr, int64_t contLen);
|
|||
// Because taosd supports multi-process mode
|
||||
// These functions should not be used on the server side
|
||||
// Please use tmsg<xx> functions, which are defined in tmsgcb.h
|
||||
int rpcSendRequest(void *thandle, const SEpSet *pEpSet, SRpcMsg *pMsg, int64_t *rid);
|
||||
int rpcSendResponse(const SRpcMsg *pMsg);
|
||||
int rpcRegisterBrokenLinkArg(SRpcMsg *msg);
|
||||
int rpcReleaseHandle(void *handle, int8_t type); // just release conn to rpc instance, no close sock
|
||||
int32_t rpcSendRequest(void *thandle, const SEpSet *pEpSet, SRpcMsg *pMsg, int64_t *rid);
|
||||
int32_t rpcSendResponse(const SRpcMsg *pMsg);
|
||||
int32_t rpcRegisterBrokenLinkArg(SRpcMsg *msg);
|
||||
int32_t rpcReleaseHandle(void *handle, int8_t type); // just release conn to rpc instance, no close sock
|
||||
|
||||
// These functions will not be called in the child process
|
||||
int rpcSendRequestWithCtx(void *thandle, const SEpSet *pEpSet, SRpcMsg *pMsg, int64_t *rid, SRpcCtx *ctx);
|
||||
int rpcSendRecv(void *shandle, SEpSet *pEpSet, SRpcMsg *pReq, SRpcMsg *pRsp);
|
||||
int rpcSendRecvWithTimeout(void *shandle, SEpSet *pEpSet, SRpcMsg *pMsg, SRpcMsg *pRsp, int8_t *epUpdated,
|
||||
int32_t rpcSendRequestWithCtx(void *thandle, const SEpSet *pEpSet, SRpcMsg *pMsg, int64_t *rid, SRpcCtx *ctx);
|
||||
int32_t rpcSendRecv(void *shandle, SEpSet *pEpSet, SRpcMsg *pReq, SRpcMsg *pRsp);
|
||||
int32_t rpcSendRecvWithTimeout(void *shandle, SEpSet *pEpSet, SRpcMsg *pMsg, SRpcMsg *pRsp, int8_t *epUpdated,
|
||||
int32_t timeoutMs);
|
||||
int rpcSetDefaultAddr(void *thandle, const char *ip, const char *fqdn);
|
||||
void *rpcAllocHandle();
|
||||
|
||||
int32_t rpcFreeConnById(void *shandle, int64_t connId);
|
||||
|
||||
int32_t rpcSetDefaultAddr(void *thandle, const char *ip, const char *fqdn);
|
||||
int32_t rpcAllocHandle(int64_t *refId);
|
||||
int32_t rpcSetIpWhite(void *thandl, void *arg);
|
||||
|
||||
int32_t rpcUtilSIpRangeToStr(SIpV4Range *pRange, char *buf);
|
||||
|
|
|
@ -529,6 +529,7 @@ int32_t taosGetErrSize();
|
|||
#define TSDB_CODE_VND_META_DATA_UNSAFE_DELETE TAOS_DEF_ERROR_CODE(0, 0x0535)
|
||||
#define TSDB_CODE_VND_COLUMN_COMPRESS_ALREADY_EXIST TAOS_DEF_ERROR_CODE(0, 0x0536)
|
||||
#define TSDB_CODE_VND_ARB_NOT_SYNCED TAOS_DEF_ERROR_CODE(0, 0x0537) // internal
|
||||
#define TSDB_CODE_VND_WRITE_DISABLED TAOS_DEF_ERROR_CODE(0, 0x0538) // internal
|
||||
|
||||
// tsdb
|
||||
#define TSDB_CODE_TDB_INVALID_TABLE_ID TAOS_DEF_ERROR_CODE(0, 0x0600)
|
||||
|
|
|
@ -49,6 +49,7 @@ int32_t InitRegexCache();
|
|||
void DestroyRegexCache();
|
||||
int32_t patternMatch(const char *pattern, size_t psize, const char *str, size_t ssize, const SPatternCompareInfo *pInfo);
|
||||
int32_t checkRegexPattern(const char *pPattern);
|
||||
void DestoryThreadLocalRegComp();
|
||||
|
||||
int32_t wcsPatternMatch(const TdUcs4 *pattern, size_t psize, const TdUcs4 *str, size_t ssize, const SPatternCompareInfo *pInfo);
|
||||
|
||||
|
|
|
@ -74,13 +74,13 @@ void taosCloseLog();
|
|||
void taosResetLog();
|
||||
void taosDumpData(uint8_t *msg, int32_t len);
|
||||
|
||||
void taosPrintLog(const char *flags, ELogLevel level, int32_t dflag, const char *format, ...)
|
||||
void taosPrintLog(const char *flags, int32_t level, int32_t dflag, const char *format, ...)
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 4, 5)))
|
||||
#endif
|
||||
;
|
||||
|
||||
void taosPrintLongString(const char *flags, ELogLevel level, int32_t dflag, const char *format, ...)
|
||||
void taosPrintLongString(const char *flags, int32_t level, int32_t dflag, const char *format, ...)
|
||||
#ifdef __GNUC__
|
||||
__attribute__((format(printf, 4, 5)))
|
||||
#endif
|
||||
|
|
|
@ -80,6 +80,11 @@ static FORCE_INLINE void taosEncryptPass_c(uint8_t *inBuf, size_t len, char *tar
|
|||
(void)memcpy(target, buf, TSDB_PASSWORD_LEN);
|
||||
}
|
||||
|
||||
static FORCE_INLINE int32_t taosHashBinary(char* pBuf, int32_t len) {
|
||||
uint64_t hashVal = MurmurHash3_64(pBuf, len);
|
||||
return sprintf(pBuf, "%" PRIu64, hashVal);
|
||||
}
|
||||
|
||||
static FORCE_INLINE int32_t taosCreateMD5Hash(char *pBuf, int32_t len) {
|
||||
T_MD5_CTX ctx;
|
||||
tMD5Init(&ctx);
|
||||
|
@ -87,11 +92,10 @@ static FORCE_INLINE int32_t taosCreateMD5Hash(char *pBuf, int32_t len) {
|
|||
tMD5Final(&ctx);
|
||||
char *p = pBuf;
|
||||
int32_t resLen = 0;
|
||||
for (uint8_t i = 0; i < tListLen(ctx.digest); ++i) {
|
||||
resLen += snprintf(p, 3, "%02x", ctx.digest[i]);
|
||||
p += 2;
|
||||
}
|
||||
return resLen;
|
||||
return sprintf(pBuf, "%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", ctx.digest[0], ctx.digest[1],
|
||||
ctx.digest[2], ctx.digest[3], ctx.digest[4], ctx.digest[5], ctx.digest[6], ctx.digest[7],
|
||||
ctx.digest[8], ctx.digest[9], ctx.digest[10], ctx.digest[11], ctx.digest[12], ctx.digest[13],
|
||||
ctx.digest[14], ctx.digest[15]);
|
||||
}
|
||||
|
||||
static FORCE_INLINE int32_t taosGetTbHashVal(const char *tbname, int32_t tblen, int32_t method, int32_t prefix,
|
||||
|
|
|
@ -204,6 +204,7 @@ typedef struct {
|
|||
STableMeta *currSTableMeta;
|
||||
STableDataCxt *currTableDataCtx;
|
||||
bool needModifySchema;
|
||||
char *tbnameKey;
|
||||
} SSmlHandle;
|
||||
|
||||
extern int64_t smlFactorNS[];
|
||||
|
@ -219,9 +220,10 @@ bool smlParseNumberOld(SSmlKv *kvVal, SSmlMsgBuf *msg);
|
|||
void smlBuildInvalidDataMsg(SSmlMsgBuf *pBuf, const char *msg1, const char *msg2);
|
||||
int32_t smlParseNumber(SSmlKv *kvVal, SSmlMsgBuf *msg);
|
||||
int64_t smlGetTimeValue(const char *value, int32_t len, uint8_t fromPrecision, uint8_t toPrecision);
|
||||
int32_t smlBuildTableInfo(int numRows, const char* measure, int32_t measureLen, SSmlTableInfo** tInfo);
|
||||
|
||||
int32_t smlBuildTableInfo(int numRows, const char* measure, int32_t measureLen, SSmlTableInfo** tInfo);
|
||||
int32_t smlBuildSTableMeta(bool isDataFormat, SSmlSTableMeta** sMeta);
|
||||
int32_t smlSetCTableName(SSmlTableInfo *oneTable);
|
||||
int32_t smlSetCTableName(SSmlTableInfo *oneTable, char *tbnameKey);
|
||||
int32_t getTableUid(SSmlHandle *info, SSmlLineInfo *currElement, SSmlTableInfo *tinfo);
|
||||
int32_t smlGetMeta(SSmlHandle *info, const void* measure, int32_t measureLen, STableMeta **pTableMeta);
|
||||
int32_t is_same_child_table_telnet(const void *a, const void *b);
|
||||
|
|
|
@ -374,8 +374,8 @@ int32_t openTransporter(const char *user, const char *auth, int32_t numOfThread,
|
|||
|
||||
*pDnodeConn = rpcOpen(&rpcInit);
|
||||
if (*pDnodeConn == NULL) {
|
||||
tscError("failed to init connection to server.");
|
||||
code = TSDB_CODE_FAILED;
|
||||
tscError("failed to init connection to server since %s", tstrerror(terrno));
|
||||
code = terrno;
|
||||
}
|
||||
|
||||
return code;
|
||||
|
@ -526,19 +526,17 @@ int32_t createRequest(uint64_t connId, int32_t type, int64_t reqid, SRequestObj
|
|||
int32_t code = TSDB_CODE_SUCCESS;
|
||||
*pRequest = (SRequestObj *)taosMemoryCalloc(1, sizeof(SRequestObj));
|
||||
if (NULL == *pRequest) {
|
||||
return TSDB_CODE_OUT_OF_MEMORY;
|
||||
return terrno;
|
||||
}
|
||||
|
||||
STscObj *pTscObj = acquireTscObj(connId);
|
||||
if (pTscObj == NULL) {
|
||||
code = TSDB_CODE_TSC_DISCONNECTED;
|
||||
goto _return;
|
||||
TSC_ERR_JRET(TSDB_CODE_TSC_DISCONNECTED);
|
||||
}
|
||||
SSyncQueryParam *interParam = taosMemoryCalloc(1, sizeof(SSyncQueryParam));
|
||||
if (interParam == NULL) {
|
||||
releaseTscObj(connId);
|
||||
code = TSDB_CODE_OUT_OF_MEMORY;
|
||||
goto _return;
|
||||
TSC_ERR_JRET(terrno);
|
||||
}
|
||||
TSC_ERR_JRET(tsem_init(&interParam->sem, 0, 0));
|
||||
interParam->pRequest = *pRequest;
|
||||
|
@ -566,7 +564,11 @@ int32_t createRequest(uint64_t connId, int32_t type, int64_t reqid, SRequestObj
|
|||
|
||||
return TSDB_CODE_SUCCESS;
|
||||
_return:
|
||||
doDestroyRequest(*pRequest);
|
||||
if ((*pRequest)->pTscObj) {
|
||||
doDestroyRequest(*pRequest);
|
||||
} else {
|
||||
taosMemoryFree(*pRequest);
|
||||
}
|
||||
return code;
|
||||
}
|
||||
|
||||
|
@ -869,7 +871,7 @@ _return:
|
|||
TSC_ERR_RET(terrno);
|
||||
}
|
||||
|
||||
return TSDB_CODE_SUCCESS;
|
||||
return code;
|
||||
}
|
||||
|
||||
void tscStopCrashReport() {
|
||||
|
|
|
@ -1405,7 +1405,7 @@ _return:
|
|||
TSC_ERR_RET(terrno);
|
||||
}
|
||||
|
||||
return TSDB_CODE_SUCCESS;
|
||||
return code;
|
||||
}
|
||||
|
||||
static void hbStopThread() {
|
||||
|
|
|
@ -2467,7 +2467,8 @@ TSDB_SERVER_STATUS taos_check_server_status(const char* fqdn, int port, char* de
|
|||
|
||||
clientRpc = rpcOpen(&rpcInit);
|
||||
if (clientRpc == NULL) {
|
||||
tscError("failed to init server status client");
|
||||
code = terrno;
|
||||
tscError("failed to init server status client since %s", tstrerror(code));
|
||||
goto _OVER;
|
||||
}
|
||||
|
||||
|
|
|
@ -402,7 +402,7 @@ int32_t smlProcessChildTable(SSmlHandle *info, SSmlLineInfo *elements) {
|
|||
if (kv->valueEscaped) kv->value = NULL;
|
||||
}
|
||||
|
||||
code = smlSetCTableName(tinfo);
|
||||
code = smlSetCTableName(tinfo, info->tbnameKey);
|
||||
if (code != TSDB_CODE_SUCCESS){
|
||||
smlDestroyTableInfo(&tinfo);
|
||||
return code;
|
||||
|
@ -486,10 +486,10 @@ int32_t smlParseEndLine(SSmlHandle *info, SSmlLineInfo *elements, SSmlKv *kvTs)
|
|||
return TSDB_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
static int32_t smlParseTableName(SArray *tags, char *childTableName) {
|
||||
static int32_t smlParseTableName(SArray *tags, char *childTableName, char *tbnameKey) {
|
||||
bool autoChildName = false;
|
||||
size_t delimiter = strlen(tsSmlAutoChildTableNameDelimiter);
|
||||
if (delimiter > 0) {
|
||||
if(delimiter > 0 && tbnameKey == NULL){
|
||||
size_t totalNameLen = delimiter * (taosArrayGetSize(tags) - 1);
|
||||
for (int i = 0; i < taosArrayGetSize(tags); i++) {
|
||||
SSmlKv *tag = (SSmlKv *)taosArrayGet(tags, i);
|
||||
|
@ -517,8 +517,11 @@ static int32_t smlParseTableName(SArray *tags, char *childTableName) {
|
|||
if (tsSmlDot2Underline) {
|
||||
smlStrReplace(childTableName, strlen(childTableName));
|
||||
}
|
||||
} else {
|
||||
size_t childTableNameLen = strlen(tsSmlChildTableName);
|
||||
}else{
|
||||
if (tbnameKey == NULL){
|
||||
tbnameKey = tsSmlChildTableName;
|
||||
}
|
||||
size_t childTableNameLen = strlen(tbnameKey);
|
||||
if (childTableNameLen <= 0) return TSDB_CODE_SUCCESS;
|
||||
|
||||
for (int i = 0; i < taosArrayGetSize(tags); i++) {
|
||||
|
@ -527,7 +530,7 @@ static int32_t smlParseTableName(SArray *tags, char *childTableName) {
|
|||
return TSDB_CODE_SML_INVALID_DATA;
|
||||
}
|
||||
// handle child table name
|
||||
if (childTableNameLen == tag->keyLen && strncmp(tag->key, tsSmlChildTableName, tag->keyLen) == 0) {
|
||||
if (childTableNameLen == tag->keyLen && strncmp(tag->key, tbnameKey, tag->keyLen) == 0) {
|
||||
(void)memset(childTableName, 0, TSDB_TABLE_NAME_LEN);
|
||||
(void)strncpy(childTableName, tag->value, (tag->length < TSDB_TABLE_NAME_LEN ? tag->length : TSDB_TABLE_NAME_LEN));
|
||||
if (tsSmlDot2Underline) {
|
||||
|
@ -542,8 +545,8 @@ static int32_t smlParseTableName(SArray *tags, char *childTableName) {
|
|||
return TSDB_CODE_SUCCESS;
|
||||
}
|
||||
|
||||
int32_t smlSetCTableName(SSmlTableInfo *oneTable) {
|
||||
int32_t code = smlParseTableName(oneTable->tags, oneTable->childTableName);
|
||||
int32_t smlSetCTableName(SSmlTableInfo *oneTable, char *tbnameKey) {
|
||||
int32_t code = smlParseTableName(oneTable->tags, oneTable->childTableName, tbnameKey);
|
||||
if(code != TSDB_CODE_SUCCESS){
|
||||
return code;
|
||||
}
|
||||
|
@ -2127,7 +2130,7 @@ void smlSetReqSQL(SRequestObj *request, char *lines[], char *rawLine, char *rawL
|
|||
}
|
||||
|
||||
TAOS_RES *taos_schemaless_insert_inner(TAOS *taos, char *lines[], char *rawLine, char *rawLineEnd, int numLines,
|
||||
int protocol, int precision, int32_t ttl, int64_t reqid) {
|
||||
int protocol, int precision, int32_t ttl, int64_t reqid, char *tbnameKey) {
|
||||
int32_t code = TSDB_CODE_SUCCESS;
|
||||
if (NULL == taos) {
|
||||
uError("SML:taos_schemaless_insert error taos is null");
|
||||
|
@ -2159,6 +2162,7 @@ TAOS_RES *taos_schemaless_insert_inner(TAOS *taos, char *lines[], char *rawLine,
|
|||
info->msgBuf.buf = info->pRequest->msgBuf;
|
||||
info->msgBuf.len = ERROR_MSG_BUF_DEFAULT_SIZE;
|
||||
info->lineNum = numLines;
|
||||
info->tbnameKey = tbnameKey;
|
||||
|
||||
smlSetReqSQL(request, lines, rawLine, rawLineEnd);
|
||||
|
||||
|
@ -2237,9 +2241,14 @@ end:
|
|||
* @return TAOS_RES
|
||||
*/
|
||||
|
||||
TAOS_RES *taos_schemaless_insert_ttl_with_reqid_tbname_key(TAOS *taos, char *lines[], int numLines, int protocol,
|
||||
int precision, int32_t ttl, int64_t reqid, char *tbnameKey){
|
||||
return taos_schemaless_insert_inner(taos, lines, NULL, NULL, numLines, protocol, precision, ttl, reqid, tbnameKey);
|
||||
}
|
||||
|
||||
TAOS_RES *taos_schemaless_insert_ttl_with_reqid(TAOS *taos, char *lines[], int numLines, int protocol, int precision,
|
||||
int32_t ttl, int64_t reqid) {
|
||||
return taos_schemaless_insert_inner(taos, lines, NULL, NULL, numLines, protocol, precision, ttl, reqid);
|
||||
return taos_schemaless_insert_ttl_with_reqid_tbname_key(taos, lines, numLines, protocol, precision, ttl, reqid, NULL);
|
||||
}
|
||||
|
||||
TAOS_RES *taos_schemaless_insert(TAOS *taos, char *lines[], int numLines, int protocol, int precision) {
|
||||
|
@ -2272,10 +2281,15 @@ static void getRawLineLen(char *lines, int len, int32_t *totalRows, int protocol
|
|||
}
|
||||
}
|
||||
|
||||
TAOS_RES *taos_schemaless_insert_raw_ttl_with_reqid_tbname_key(TAOS *taos, char *lines, int len, int32_t *totalRows,
|
||||
int protocol, int precision, int32_t ttl, int64_t reqid, char *tbnameKey){
|
||||
getRawLineLen(lines, len, totalRows, protocol);
|
||||
return taos_schemaless_insert_inner(taos, NULL, lines, lines + len, *totalRows, protocol, precision, ttl, reqid, tbnameKey);
|
||||
}
|
||||
|
||||
TAOS_RES *taos_schemaless_insert_raw_ttl_with_reqid(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol,
|
||||
int precision, int32_t ttl, int64_t reqid) {
|
||||
getRawLineLen(lines, len, totalRows, protocol);
|
||||
return taos_schemaless_insert_inner(taos, NULL, lines, lines + len, *totalRows, protocol, precision, ttl, reqid);
|
||||
return taos_schemaless_insert_raw_ttl_with_reqid_tbname_key(taos, lines, len, totalRows, protocol, precision, ttl, reqid, NULL);
|
||||
}
|
||||
|
||||
TAOS_RES *taos_schemaless_insert_raw_with_reqid(TAOS *taos, char *lines, int len, int32_t *totalRows, int protocol,
|
||||
|
|
|
@ -1275,7 +1275,7 @@ tmq_t* tmq_consumer_new(tmq_conf_t* conf, char* errstr, int32_t errstrLen) {
|
|||
|
||||
// init semaphore
|
||||
if (tsem2_init(&pTmq->rspSem, 0, 0) != 0) {
|
||||
tscError("consumer:0x %" PRIx64 " setup failed since %s, consumer group %s", pTmq->consumerId, terrstr(),
|
||||
tscError("consumer:0x %" PRIx64 " setup failed since %s, consumer group %s", pTmq->consumerId, tstrerror(TAOS_SYSTEM_ERROR(errno)),
|
||||
pTmq->groupId);
|
||||
SET_ERROR_MSG_TMQ("init t_sem failed")
|
||||
goto _failed;
|
||||
|
@ -2141,6 +2141,7 @@ static void* tmqHandleAllRsp(tmq_t* tmq, int64_t timeout) {
|
|||
taosWUnLockLatch(&tmq->lock);
|
||||
}
|
||||
setVgIdle(tmq, pollRspWrapper->topicName, pollRspWrapper->vgId);
|
||||
tmqFreeRspWrapper(pRspWrapper);
|
||||
taosFreeQitem(pRspWrapper);
|
||||
} else if (pRspWrapper->tmqRspType == TMQ_MSG_TYPE__POLL_DATA_RSP) {
|
||||
SMqPollRspWrapper* pollRspWrapper = (SMqPollRspWrapper*)pRspWrapper;
|
||||
|
@ -2844,6 +2845,7 @@ int32_t askEpCb(void* param, SDataBuf* pMsg, int32_t code) {
|
|||
pWrapper->epoch = head->epoch;
|
||||
(void)memcpy(&pWrapper->msg, pMsg->pData, sizeof(SMqRspHead));
|
||||
if (tDecodeSMqAskEpRsp(POINTER_SHIFT(pMsg->pData, sizeof(SMqRspHead)), &pWrapper->msg) == NULL){
|
||||
tmqFreeRspWrapper((SMqRspWrapper*)pWrapper);
|
||||
taosFreeQitem(pWrapper);
|
||||
}else{
|
||||
(void)taosWriteQitem(tmq->mqueue, pWrapper);
|
||||
|
|
|
@ -163,7 +163,7 @@ int32_t startRsync() {
|
|||
return code;
|
||||
}
|
||||
|
||||
int32_t uploadByRsync(const char* id, const char* path) {
|
||||
int32_t uploadByRsync(const char* id, const char* path, int64_t checkpointId) {
|
||||
int64_t st = taosGetTimestampMs();
|
||||
char command[PATH_MAX] = {0};
|
||||
|
||||
|
@ -203,12 +203,12 @@ int32_t uploadByRsync(const char* id, const char* path) {
|
|||
// prepare the data directory
|
||||
int32_t code = execCommand(command);
|
||||
if (code != 0) {
|
||||
uError("[rsync] s-task:%s prepare checkpoint data in %s to %s failed, code:%d," ERRNO_ERR_FORMAT, id, path,
|
||||
uError("[rsync] s-task:%s prepare checkpoint dir in %s to %s failed, code:%d," ERRNO_ERR_FORMAT, id, path,
|
||||
tsSnodeAddress, code, ERRNO_ERR_DATA);
|
||||
code = TAOS_SYSTEM_ERROR(errno);
|
||||
} else {
|
||||
int64_t el = (taosGetTimestampMs() - st);
|
||||
uDebug("[rsync] s-task:%s prepare checkpoint data in:%s to %s successfully, elapsed time:%" PRId64 "ms", id, path,
|
||||
uDebug("[rsync] s-task:%s prepare checkpoint dir in:%s to %s successfully, elapsed time:%" PRId64 "ms", id, path,
|
||||
tsSnodeAddress, el);
|
||||
}
|
||||
|
||||
|
@ -222,7 +222,7 @@ int32_t uploadByRsync(const char* id, const char* path) {
|
|||
#endif
|
||||
snprintf(command, PATH_MAX,
|
||||
"rsync -av --debug=all --log-file=%s/rsynclog --delete --timeout=10 --bwlimit=100000 %s/ "
|
||||
"rsync://%s/checkpoint/%s/data/",
|
||||
"rsync://%s/checkpoint/%s/%" PRId64 "/",
|
||||
tsLogDir,
|
||||
#ifdef WINDOWS
|
||||
pathTransform
|
||||
|
@ -230,11 +230,11 @@ int32_t uploadByRsync(const char* id, const char* path) {
|
|||
path
|
||||
#endif
|
||||
,
|
||||
tsSnodeAddress, id);
|
||||
tsSnodeAddress, id, checkpointId);
|
||||
} else {
|
||||
snprintf(command, PATH_MAX,
|
||||
"rsync -av --debug=all --log-file=%s/rsynclog --delete --timeout=10 --bwlimit=100000 %s "
|
||||
"rsync://%s/checkpoint/%s/data/",
|
||||
"rsync://%s/checkpoint/%s/%" PRId64 "/",
|
||||
tsLogDir,
|
||||
#ifdef WINDOWS
|
||||
pathTransform
|
||||
|
@ -242,7 +242,7 @@ int32_t uploadByRsync(const char* id, const char* path) {
|
|||
path
|
||||
#endif
|
||||
,
|
||||
tsSnodeAddress, id);
|
||||
tsSnodeAddress, id, checkpointId);
|
||||
}
|
||||
|
||||
code = execCommand(command);
|
||||
|
@ -260,7 +260,7 @@ int32_t uploadByRsync(const char* id, const char* path) {
|
|||
}
|
||||
|
||||
// abort from retry if quit
|
||||
int32_t downloadRsync(const char* id, const char* path) {
|
||||
int32_t downloadByRsync(const char* id, const char* path, int64_t checkpointId) {
|
||||
int64_t st = taosGetTimestampMs();
|
||||
int32_t MAX_RETRY = 10;
|
||||
int32_t times = 0;
|
||||
|
@ -274,8 +274,9 @@ int32_t downloadRsync(const char* id, const char* path) {
|
|||
char command[PATH_MAX] = {0};
|
||||
snprintf(
|
||||
command, PATH_MAX,
|
||||
"rsync -av --debug=all --log-file=%s/rsynclog --timeout=10 --bwlimit=100000 rsync://%s/checkpoint/%s/data/ %s",
|
||||
tsLogDir, tsSnodeAddress, id,
|
||||
"rsync -av --debug=all --log-file=%s/rsynclog --timeout=10 --bwlimit=100000 rsync://%s/checkpoint/%s/%" PRId64
|
||||
"/ %s",
|
||||
tsLogDir, tsSnodeAddress, id, checkpointId,
|
||||
#ifdef WINDOWS
|
||||
pathTransform
|
||||
#else
|
||||
|
@ -283,19 +284,49 @@ int32_t downloadRsync(const char* id, const char* path) {
|
|||
#endif
|
||||
);
|
||||
|
||||
uDebug("[rsync] %s start to sync data from remote to:%s, %s", id, path, command);
|
||||
uDebug("[rsync] %s start to sync data from remote to:%s, cmd:%s", id, path, command);
|
||||
|
||||
code = execCommand(command);
|
||||
if (code != TSDB_CODE_SUCCESS) {
|
||||
uError("[rsync] %s download checkpointId:%" PRId64
|
||||
" data:%s failed, retry after 1sec, times:%d, code:%d," ERRNO_ERR_FORMAT,
|
||||
id, checkpointId, path, times, code, ERRNO_ERR_DATA);
|
||||
} else {
|
||||
int32_t el = taosGetTimestampMs() - st;
|
||||
uDebug("[rsync] %s download checkpointId:%" PRId64 " data:%s successfully, elapsed time:%dms", id, checkpointId,
|
||||
path, el);
|
||||
}
|
||||
|
||||
if (code != TSDB_CODE_SUCCESS) { // if failed, try to load it from data directory
|
||||
#ifdef WINDOWS
|
||||
memset(pathTransform, 0, PATH_MAX);
|
||||
changeDirFromWindowsToLinux(path, pathTransform);
|
||||
#endif
|
||||
|
||||
memset(command, 0, PATH_MAX);
|
||||
snprintf(
|
||||
command, PATH_MAX,
|
||||
"rsync -av --debug=all --log-file=%s/rsynclog --timeout=10 --bwlimit=100000 rsync://%s/checkpoint/%s/data/ %s",
|
||||
tsLogDir, tsSnodeAddress, id,
|
||||
#ifdef WINDOWS
|
||||
pathTransform
|
||||
#else
|
||||
path
|
||||
#endif
|
||||
);
|
||||
|
||||
uDebug("[rsync] %s start to sync data from remote data dir to:%s, cmd:%s", id, path, command);
|
||||
|
||||
while (times++ < MAX_RETRY) {
|
||||
code = execCommand(command);
|
||||
if (code != TSDB_CODE_SUCCESS) {
|
||||
uError("[rsync] %s download checkpoint data:%s failed, retry after 1sec, times:%d, code:%d," ERRNO_ERR_FORMAT, id,
|
||||
path, times, code, ERRNO_ERR_DATA);
|
||||
taosSsleep(1);
|
||||
code = TAOS_SYSTEM_ERROR(errno);
|
||||
uError("[rsync] %s download checkpointId:%" PRId64
|
||||
" data:%s failed, retry after 1sec, times:%d, code:%d," ERRNO_ERR_FORMAT,
|
||||
id, checkpointId, path, times, code, ERRNO_ERR_DATA);
|
||||
code = TAOS_SYSTEM_ERROR(code);
|
||||
} else {
|
||||
int32_t el = taosGetTimestampMs() - st;
|
||||
uDebug("[rsync] %s download checkpoint data:%s successfully, elapsed time:%dms", id, path, el);
|
||||
break;
|
||||
uDebug("[rsync] %s download checkpointId:%" PRId64 " data:%s successfully, elapsed time:%dms", id, checkpointId,
|
||||
path, el);
|
||||
}
|
||||
}
|
||||
return code;
|
||||
|
|
|
@ -10140,6 +10140,7 @@ void *tDecodeMqSubTopicEp(void *buf, SMqSubTopicEp *pTopicEp) {
|
|||
buf = tDecodeSMqSubVgEp(buf, &vgEp);
|
||||
if (taosArrayPush(pTopicEp->vgs, &vgEp) == NULL) {
|
||||
taosArrayDestroy(pTopicEp->vgs);
|
||||
pTopicEp->vgs = NULL;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -815,6 +815,12 @@ int64_t taosTimeTruncate(int64_t ts, const SInterval* pInterval) {
|
|||
if (IS_CALENDAR_TIME_DURATION(pInterval->intervalUnit)) {
|
||||
int64_t news = (ts / pInterval->sliding) * pInterval->sliding;
|
||||
ASSERT(news <= ts);
|
||||
if (pInterval->slidingUnit == 'd' || pInterval->slidingUnit == 'w') {
|
||||
#if defined(WINDOWS) && _MSC_VER >= 1900
|
||||
int64_t timezone = _timezone;
|
||||
#endif
|
||||
news += (int64_t)(timezone * TSDB_TICK_PER_SECOND(precision));
|
||||
}
|
||||
|
||||
if (news <= ts) {
|
||||
int64_t prev = news;
|
||||
|
|
|
@ -293,7 +293,7 @@ int32_t dmStartNotifyThread(SDnodeMgmt *pMgmt) {
|
|||
(void)taosThreadAttrSetDetachState(&thAttr, PTHREAD_CREATE_JOINABLE);
|
||||
if (taosThreadCreate(&pMgmt->notifyThread, &thAttr, dmNotifyThreadFp, pMgmt) != 0) {
|
||||
code = TAOS_SYSTEM_ERROR(errno);
|
||||
dError("failed to create notify thread since %s", strerror(code));
|
||||
dError("failed to create notify thread since %s", tstrerror(code));
|
||||
return code;
|
||||
}
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ static void dmProcessRpcMsg(SDnode *pDnode, SRpcMsg *pRpc, SEpSet *pEpSet) {
|
|||
int32_t svrVer = 0;
|
||||
(void)taosVersionStrToInt(version, &svrVer);
|
||||
if ((code = taosCheckVersionCompatible(pRpc->info.cliVer, svrVer, 3)) != 0) {
|
||||
dError("Version not compatible, cli ver: %d, svr ver: %d", pRpc->info.cliVer, svrVer);
|
||||
dError("Version not compatible, cli ver: %d, svr ver: %d, ip:0x%x", pRpc->info.cliVer, svrVer, pRpc->info.conn.clientIp);
|
||||
goto _OVER;
|
||||
}
|
||||
|
||||
|
@ -387,13 +387,14 @@ int32_t dmInitClient(SDnode *pDnode) {
|
|||
rpcInit.supportBatch = 1;
|
||||
rpcInit.batchSize = 8 * 1024;
|
||||
rpcInit.timeToGetConn = tsTimeToGetAvailableConn;
|
||||
rpcInit.notWaitAvaliableConn = 1;
|
||||
|
||||
(void)taosVersionStrToInt(version, &(rpcInit.compatibilityVer));
|
||||
|
||||
pTrans->clientRpc = rpcOpen(&rpcInit);
|
||||
if (pTrans->clientRpc == NULL) {
|
||||
dError("failed to init dnode rpc client");
|
||||
return -1;
|
||||
dError("failed to init dnode rpc client since:%s", tstrerror(terrno));
|
||||
return terrno;
|
||||
}
|
||||
|
||||
dDebug("dnode rpc client is initialized");
|
||||
|
@ -436,8 +437,8 @@ int32_t dmInitStatusClient(SDnode *pDnode) {
|
|||
|
||||
pTrans->statusRpc = rpcOpen(&rpcInit);
|
||||
if (pTrans->statusRpc == NULL) {
|
||||
dError("failed to init dnode rpc status client");
|
||||
return TSDB_CODE_OUT_OF_MEMORY;
|
||||
dError("failed to init dnode rpc status client since %s", tstrerror(terrno));
|
||||
return terrno;
|
||||
}
|
||||
|
||||
dDebug("dnode rpc status client is initialized");
|
||||
|
@ -481,8 +482,8 @@ int32_t dmInitSyncClient(SDnode *pDnode) {
|
|||
|
||||
pTrans->syncRpc = rpcOpen(&rpcInit);
|
||||
if (pTrans->syncRpc == NULL) {
|
||||
dError("failed to init dnode rpc sync client");
|
||||
return TSDB_CODE_OUT_OF_MEMORY;
|
||||
dError("failed to init dnode rpc sync client since %s", tstrerror(terrno));
|
||||
return terrno;
|
||||
}
|
||||
|
||||
dDebug("dnode rpc sync client is initialized");
|
||||
|
@ -531,7 +532,7 @@ int32_t dmInitServer(SDnode *pDnode) {
|
|||
(void)taosVersionStrToInt(version, &(rpcInit.compatibilityVer));
|
||||
pTrans->serverRpc = rpcOpen(&rpcInit);
|
||||
if (pTrans->serverRpc == NULL) {
|
||||
dError("failed to init dnode rpc server");
|
||||
dError("failed to init dnode rpc server since:%s", tstrerror(terrno));
|
||||
return terrno;
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ const char *mndConsumerStatusName(int status);
|
|||
#define MND_TMQ_NULL_CHECK(c) \
|
||||
do { \
|
||||
if (c == NULL) { \
|
||||
code = TSDB_CODE_OUT_OF_MEMORY; \
|
||||
code = TAOS_GET_TERRNO(TSDB_CODE_OUT_OF_MEMORY); \
|
||||
goto END; \
|
||||
} \
|
||||
} while (0)
|
||||
|
|
|
@ -57,6 +57,12 @@ typedef struct SStreamTaskResetMsg {
|
|||
int32_t transId;
|
||||
} SStreamTaskResetMsg;
|
||||
|
||||
typedef struct SChkptReportInfo {
|
||||
SArray* pTaskList;
|
||||
int64_t reportChkpt;
|
||||
int64_t streamId;
|
||||
} SChkptReportInfo;
|
||||
|
||||
typedef struct SStreamExecInfo {
|
||||
bool initTaskList;
|
||||
SArray *pNodeList;
|
||||
|
@ -66,9 +72,9 @@ typedef struct SStreamExecInfo {
|
|||
SArray *pTaskList;
|
||||
TdThreadMutex lock;
|
||||
SHashObj *pTransferStateStreams;
|
||||
SHashObj *pChkptStreams;
|
||||
SHashObj *pChkptStreams; // use to update the checkpoint info, if all tasks send the checkpoint-report msgs
|
||||
SHashObj *pStreamConsensus;
|
||||
SArray *pKilledChkptTrans; // SArray<SStreamTaskResetMsg>
|
||||
SArray *pKilledChkptTrans; // SArray<SStreamTaskResetMsg>
|
||||
} SStreamExecInfo;
|
||||
|
||||
extern SStreamExecInfo execInfo;
|
||||
|
@ -79,6 +85,8 @@ typedef struct SNodeEntry {
|
|||
bool stageUpdated; // the stage has been updated due to the leader/follower change or node reboot.
|
||||
SEpSet epset; // compare the epset to identify the vgroup tranferring between different dnodes.
|
||||
int64_t hbTimestamp; // second
|
||||
int32_t lastHbMsgId; // latest hb msgId
|
||||
int64_t lastHbMsgTs;
|
||||
} SNodeEntry;
|
||||
|
||||
typedef struct {
|
||||
|
@ -151,6 +159,8 @@ int32_t mndGetConsensusInfo(SHashObj *pHash, int64_t streamId, int32_t numOfTask
|
|||
void mndAddConsensusTasks(SCheckpointConsensusInfo *pInfo, const SRestoreCheckpointInfo *pRestoreInfo);
|
||||
void mndClearConsensusRspEntry(SCheckpointConsensusInfo *pInfo);
|
||||
int64_t mndClearConsensusCheckpointId(SHashObj* pHash, int64_t streamId);
|
||||
int64_t mndClearChkptReportInfo(SHashObj* pHash, int64_t streamId);
|
||||
int32_t mndResetChkptReportInfo(SHashObj* pHash, int64_t streamId);
|
||||
|
||||
int32_t setStreamAttrInResBlock(SStreamObj *pStream, SSDataBlock *pBlock, int32_t numOfRows);
|
||||
int32_t setTaskAttrInResBlock(SStreamObj *pStream, SStreamTask *pTask, SSDataBlock *pBlock, int32_t numOfRows);
|
||||
|
|
|
@ -142,7 +142,7 @@ static int32_t mndProcessConsumerClearMsg(SRpcMsg *pMsg) {
|
|||
mndConsumerStatusName(pConsumer->status));
|
||||
|
||||
MND_TMQ_RETURN_CHECK(tNewSMqConsumerObj(pConsumer->consumerId, pConsumer->cgroup, -1, NULL, NULL, &pConsumerNew));
|
||||
pTrans = mndTransCreate(pMnode, TRN_POLICY_ROLLBACK, TRN_CONFLICT_NOTHING, pMsg, "clear-csm");
|
||||
pTrans = mndTransCreate(pMnode, TRN_POLICY_RETRY, TRN_CONFLICT_NOTHING, pMsg, "clear-csm");
|
||||
MND_TMQ_NULL_CHECK(pTrans);
|
||||
MND_TMQ_RETURN_CHECK(mndSetConsumerDropLogs(pTrans, pConsumerNew));
|
||||
code = mndTransPrepare(pMnode, pTrans);
|
||||
|
|
|
@ -443,7 +443,7 @@ static int32_t mndInitTimer(SMnode *pMnode) {
|
|||
(void)taosThreadAttrInit(&thAttr);
|
||||
(void)taosThreadAttrSetDetachState(&thAttr, PTHREAD_CREATE_JOINABLE);
|
||||
if ((code = taosThreadCreate(&pMnode->thread, &thAttr, mndThreadFp, pMnode)) != 0) {
|
||||
mError("failed to create timer thread since %s", strerror(errno));
|
||||
mError("failed to create timer thread since %s", tstrerror(code));
|
||||
TAOS_RETURN(code);
|
||||
}
|
||||
|
||||
|
|