From ad8178139ace0fe483714db8fd97b82b60b39aaf Mon Sep 17 00:00:00 2001 From: dingbo Date: Mon, 18 Apr 2022 12:42:50 +0800 Subject: [PATCH] docs: add markdown files for taos tools --- docs-cn/14-reference/04-taosadapter.md | 413 +++++++++++++++ docs-cn/14-reference/05-taosbenchmark.md | 482 ++++++++++++++++++ docs-cn/14-reference/06-taosdump.md | 102 ++++ docs-cn/14-reference/07-tdinsight.md | 427 ++++++++++++++++ .../_category_.yml | 0 .../index.md} | 0 .../_category_.yml | 0 .../06-keywords.md => 10-keywords/index.md} | 0 .../{07-docker => 11-docker}/_category_.yml | 0 .../07-docker.md => 11-docker/index.md} | 0 .../_category_.yml | 0 .../index.md} | 1 + .../14-reference/taosAdapter-architecture.png | Bin 0 -> 38383 bytes 13 files changed, 1425 insertions(+) create mode 100644 docs-cn/14-reference/04-taosadapter.md create mode 100644 docs-cn/14-reference/05-taosbenchmark.md create mode 100644 docs-cn/14-reference/06-taosdump.md create mode 100644 docs-cn/14-reference/07-tdinsight.md rename docs-cn/14-reference/{05-support-platform => 09-support-platform}/_category_.yml (100%) rename docs-cn/14-reference/{05-support-platform/05-support-platform.md => 09-support-platform/index.md} (100%) rename docs-cn/14-reference/{06-keywords => 10-keywords}/_category_.yml (100%) rename docs-cn/14-reference/{06-keywords/06-keywords.md => 10-keywords/index.md} (100%) rename docs-cn/14-reference/{07-docker => 11-docker}/_category_.yml (100%) rename docs-cn/14-reference/{07-docker/07-docker.md => 11-docker/index.md} (100%) rename docs-cn/14-reference/{08-server-config => 12-config}/_category_.yml (100%) rename docs-cn/14-reference/{08-server-config/08-server-config.md => 12-config/index.md} (99%) create mode 100644 docs-cn/14-reference/taosAdapter-architecture.png diff --git a/docs-cn/14-reference/04-taosadapter.md b/docs-cn/14-reference/04-taosadapter.md new file mode 100644 index 0000000000..4c72ba1efc --- /dev/null +++ b/docs-cn/14-reference/04-taosadapter.md @@ -0,0 +1,413 @@ +--- +title: "taosAdapter" +description: "taosAdapter 是一个 TDengine 的配套工具,是 TDengine 集群和应用程序之间的桥梁和适配器。它提供了一种易于使用和高效的方式来直接从数据收集代理软件(如 Telegraf、StatsD、collectd 等)摄取数据。它还提供了 InfluxDB/OpenTSDB 兼容的数据摄取接口,允许 InfluxDB/OpenTSDB 应用程序无缝移植到 TDengine" +sidebar_label: "taosAdapter" +--- + +taosAdapter 是一个 TDengine 的配套工具,是 TDengine 集群和应用程序之间的桥梁和适配器。它提供了一种易于使用和高效的方式来直接从数据收集代理软件(如 Telegraf、StatsD、collectd 等)摄取数据。它还提供了 InfluxDB/OpenTSDB 兼容的数据摄取接口,允许 InfluxDB/OpenTSDB 应用程序无缝移植到 TDengine。 + +taosAdapter 提供以下功能: + +- RESTful 接口 +- 兼容 InfluxDB v1 写接口 +- 兼容 OpenTSDB JSON 和 telnet 格式写入 +- 无缝连接到 Telegraf +- 无缝连接到 collectd +- 无缝连接到 StatsD +- 支持 Prometheus remote_read 和 remote_write + +## taosAdapter 架构图 + +![taosAdapter Architecture](taosAdapter-architecture-for-public.png) + +## taosAdapter 部署方法 + +### 安装 taosAdapter + +taosAdapter 从 TDengine v2.3.0.0 版本开始成为 TDengine 服务端软件 的一部分,如果您使用 TDengine server 您不需要任何额外的步骤来安装 taosAdapter。您可以从[涛思数据官方网站](https://taosdata.com/cn/all-downloads/)下载 TDengine server(taosAdapter 包含在 v2.3.0.0 及以上版本)安装包。如果需要将 taosAdapter 分离部署在 TDengine server 之外的服务器上,则应该在该服务器上安装完整的 TDengine 来安装 taosAdapter。如果您需要使用源代码编译生成 taosAdapter,您可以参考[构建 taosAdapter](https://github.com/taosdata/taosadapter/blob/develop/BUILD-CN.md)文档。 + +### start/stop taosAdapter + +在 Linux 系统上 taosAdapter 服务默认由 systemd 管理。使用命令 `systemctl start taosadapter` 可以启动 taosAdapter 服务。使用命令 `systemctl stop taosadapter` 可以停止 taosAdapter 服务。 + +### 移除 taosAdapter + +使用命令 rmtaos 可以移除包括 taosAdapter 在内的 TDengine server 软件。 + +### 升级 taosAdapter + +taosAdapter 和 TDengine server 需要使用相同版本。请通过升级 TDengine server 来升级 taosAdapter。 +与 taosd 分离部署的 taosAdapter 必须通过升级其所在服务器的 TDengine server 才能得到升级。 + +## taosAdapter 参数列表 + +taosAdapter 支持通过命令行参数、环境变量和配置文件来进行配置。默认配置文件是 /etc/taos/taosadapter.toml。 + +命令行参数优先于环境变量优先于配置文件,命令行用法是 arg=val,如 taosadapter -p=30000 --debug=true,详细列表如下: + +```shell +Usage of taosAdapter: + --collectd.db string collectd db name. Env "TAOS_ADAPTER_COLLECTD_DB" (default "collectd") + --collectd.enable enable collectd. Env "TAOS_ADAPTER_COLLECTD_ENABLE" (default true) + --collectd.password string collectd password. Env "TAOS_ADAPTER_COLLECTD_PASSWORD" (default "taosdata") + --collectd.port int collectd server port. Env "TAOS_ADAPTER_COLLECTD_PORT" (default 6045) + --collectd.user string collectd user. Env "TAOS_ADAPTER_COLLECTD_USER" (default "root") + --collectd.worker int collectd write worker. Env "TAOS_ADAPTER_COLLECTD_WORKER" (default 10) + -c, --config string config path default /etc/taos/taosadapter.toml + --cors.allowAllOrigins cors allow all origins. Env "TAOS_ADAPTER_CORS_ALLOW_ALL_ORIGINS" (default true) + --cors.allowCredentials cors allow credentials. Env "TAOS_ADAPTER_CORS_ALLOW_Credentials" + --cors.allowHeaders stringArray cors allow HEADERS. Env "TAOS_ADAPTER_ALLOW_HEADERS" + --cors.allowOrigins stringArray cors allow origins. Env "TAOS_ADAPTER_ALLOW_ORIGINS" + --cors.allowWebSockets cors allow WebSockets. Env "TAOS_ADAPTER_CORS_ALLOW_WebSockets" + --cors.exposeHeaders stringArray cors expose headers. Env "TAOS_ADAPTER_Expose_Headers" + --debug enable debug mode. Env "TAOS_ADAPTER_DEBUG" + --help Print this help message and exit + --influxdb.enable enable influxdb. Env "TAOS_ADAPTER_INFLUXDB_ENABLE" (default true) + --log.path string log path. Env "TAOS_ADAPTER_LOG_PATH" (default "/var/log/taos") + --log.rotationCount uint log rotation count. Env "TAOS_ADAPTER_LOG_ROTATION_COUNT" (default 30) + --log.rotationSize string log rotation size(KB MB GB), must be a positive integer. Env "TAOS_ADAPTER_LOG_ROTATION_SIZE" (default "1GB") + --log.rotationTime duration log rotation time. Env "TAOS_ADAPTER_LOG_ROTATION_TIME" (default 24h0m0s) + --logLevel string log level (panic fatal error warn warning info debug trace). Env "TAOS_ADAPTER_LOG_LEVEL" (default "info") + --monitor.collectDuration duration Set monitor duration. Env "TAOS_MONITOR_COLLECT_DURATION" (default 3s) + --monitor.identity string The identity of the current instance, or 'hostname:port' if it is empty. Env "TAOS_MONITOR_IDENTITY" + --monitor.incgroup Whether running in cgroup. Env "TAOS_MONITOR_INCGROUP" + --monitor.password string TDengine password. Env "TAOS_MONITOR_PASSWORD" (default "taosdata") + --monitor.pauseAllMemoryThreshold float Memory percentage threshold for pause all. Env "TAOS_MONITOR_PAUSE_ALL_MEMORY_THRESHOLD" (default 80) + --monitor.pauseQueryMemoryThreshold float Memory percentage threshold for pause query. Env "TAOS_MONITOR_PAUSE_QUERY_MEMORY_THRESHOLD" (default 70) + --monitor.user string TDengine user. Env "TAOS_MONITOR_USER" (default "root") + --monitor.writeInterval duration Set write to TDengine interval. Env "TAOS_MONITOR_WRITE_INTERVAL" (default 30s) + --monitor.writeToTD Whether write metrics to TDengine. Env "TAOS_MONITOR_WRITE_TO_TD" (default true) + --node_exporter.caCertFile string node_exporter ca cert file path. Env "TAOS_ADAPTER_NODE_EXPORTER_CA_CERT_FILE" + --node_exporter.certFile string node_exporter cert file path. Env "TAOS_ADAPTER_NODE_EXPORTER_CERT_FILE" + --node_exporter.db string node_exporter db name. Env "TAOS_ADAPTER_NODE_EXPORTER_DB" (default "node_exporter") + --node_exporter.enable enable node_exporter. Env "TAOS_ADAPTER_NODE_EXPORTER_ENABLE" + --node_exporter.gatherDuration duration node_exporter gather duration. Env "TAOS_ADAPTER_NODE_EXPORTER_GATHER_DURATION" (default 5s) + --node_exporter.httpBearerTokenString string node_exporter http bearer token. Env "TAOS_ADAPTER_NODE_EXPORTER_HTTP_BEARER_TOKEN_STRING" + --node_exporter.httpPassword string node_exporter http password. Env "TAOS_ADAPTER_NODE_EXPORTER_HTTP_PASSWORD" + --node_exporter.httpUsername string node_exporter http username. Env "TAOS_ADAPTER_NODE_EXPORTER_HTTP_USERNAME" + --node_exporter.insecureSkipVerify node_exporter skip ssl check. Env "TAOS_ADAPTER_NODE_EXPORTER_INSECURE_SKIP_VERIFY" (default true) + --node_exporter.keyFile string node_exporter cert key file path. Env "TAOS_ADAPTER_NODE_EXPORTER_KEY_FILE" + --node_exporter.password string node_exporter password. Env "TAOS_ADAPTER_NODE_EXPORTER_PASSWORD" (default "taosdata") + --node_exporter.responseTimeout duration node_exporter response timeout. Env "TAOS_ADAPTER_NODE_EXPORTER_RESPONSE_TIMEOUT" (default 5s) + --node_exporter.urls strings node_exporter urls. Env "TAOS_ADAPTER_NODE_EXPORTER_URLS" (default [http://localhost:9100]) + --node_exporter.user string node_exporter user. Env "TAOS_ADAPTER_NODE_EXPORTER_USER" (default "root") + --opentsdb.enable enable opentsdb. Env "TAOS_ADAPTER_OPENTSDB_ENABLE" (default true) + --opentsdb_telnet.dbs strings opentsdb_telnet db names. Env "TAOS_ADAPTER_OPENTSDB_TELNET_DBS" (default [opentsdb_telnet,collectd_tsdb,icinga2_tsdb,tcollector_tsdb]) + --opentsdb_telnet.enable enable opentsdb telnet,warning: without auth info(default false). Env "TAOS_ADAPTER_OPENTSDB_TELNET_ENABLE" + --opentsdb_telnet.maxTCPConnections int max tcp connections. Env "TAOS_ADAPTER_OPENTSDB_TELNET_MAX_TCP_CONNECTIONS" (default 250) + --opentsdb_telnet.password string opentsdb_telnet password. Env "TAOS_ADAPTER_OPENTSDB_TELNET_PASSWORD" (default "taosdata") + --opentsdb_telnet.ports ints opentsdb telnet tcp port. Env "TAOS_ADAPTER_OPENTSDB_TELNET_PORTS" (default [6046,6047,6048,6049]) + --opentsdb_telnet.tcpKeepAlive enable tcp keep alive. Env "TAOS_ADAPTER_OPENTSDB_TELNET_TCP_KEEP_ALIVE" + --opentsdb_telnet.user string opentsdb_telnet user. Env "TAOS_ADAPTER_OPENTSDB_TELNET_USER" (default "root") + --pool.idleTimeout duration Set idle connection timeout. Env "TAOS_ADAPTER_POOL_IDLE_TIMEOUT" (default 1h0m0s) + --pool.maxConnect int max connections to taosd. Env "TAOS_ADAPTER_POOL_MAX_CONNECT" (default 4000) + --pool.maxIdle int max idle connections to taosd. Env "TAOS_ADAPTER_POOL_MAX_IDLE" (default 4000) + -P, --port int http port. Env "TAOS_ADAPTER_PORT" (default 6041) + --prometheus.enable enable prometheus. Env "TAOS_ADAPTER_PROMETHEUS_ENABLE" (default true) + --restfulRowLimit int restful returns the maximum number of rows (-1 means no limit). Env "TAOS_ADAPTER_RESTFUL_ROW_LIMIT" (default -1) + --ssl.certFile string ssl cert file path. Env "TAOS_ADAPTER_SSL_CERT_FILE" + --ssl.enable enable ssl. Env "TAOS_ADAPTER_SSL_ENABLE" + --ssl.keyFile string ssl key file path. Env "TAOS_ADAPTER_SSL_KEY_FILE" + --statsd.allowPendingMessages int statsd allow pending messages. Env "TAOS_ADAPTER_STATSD_ALLOW_PENDING_MESSAGES" (default 50000) + --statsd.db string statsd db name. Env "TAOS_ADAPTER_STATSD_DB" (default "statsd") + --statsd.deleteCounters statsd delete counter cache after gather. Env "TAOS_ADAPTER_STATSD_DELETE_COUNTERS" (default true) + --statsd.deleteGauges statsd delete gauge cache after gather. Env "TAOS_ADAPTER_STATSD_DELETE_GAUGES" (default true) + --statsd.deleteSets statsd delete set cache after gather. Env "TAOS_ADAPTER_STATSD_DELETE_SETS" (default true) + --statsd.deleteTimings statsd delete timing cache after gather. Env "TAOS_ADAPTER_STATSD_DELETE_TIMINGS" (default true) + --statsd.enable enable statsd. Env "TAOS_ADAPTER_STATSD_ENABLE" (default true) + --statsd.gatherInterval duration statsd gather interval. Env "TAOS_ADAPTER_STATSD_GATHER_INTERVAL" (default 5s) + --statsd.maxTCPConnections int statsd max tcp connections. Env "TAOS_ADAPTER_STATSD_MAX_TCP_CONNECTIONS" (default 250) + --statsd.password string statsd password. Env "TAOS_ADAPTER_STATSD_PASSWORD" (default "taosdata") + --statsd.port int statsd server port. Env "TAOS_ADAPTER_STATSD_PORT" (default 6044) + --statsd.protocol string statsd protocol [tcp or udp]. Env "TAOS_ADAPTER_STATSD_PROTOCOL" (default "udp") + --statsd.tcpKeepAlive enable tcp keep alive. Env "TAOS_ADAPTER_STATSD_TCP_KEEP_ALIVE" + --statsd.user string statsd user. Env "TAOS_ADAPTER_STATSD_USER" (default "root") + --statsd.worker int statsd write worker. Env "TAOS_ADAPTER_STATSD_WORKER" (default 10) + --taosConfigDir string load taos client config path. Env "TAOS_ADAPTER_TAOS_CONFIG_FILE" + --version Print the version and exit +``` + +备注: +使用浏览器进行接口调用请根据实际情况设置如下跨源资源共享(CORS)参数: + +```text +AllowAllOrigins +AllowOrigins +AllowHeaders +ExposeHeaders +AllowCredentials +AllowWebSockets +``` + +如果不通过浏览器进行接口调用无需关心这几项配置。 + +关于 CORS 协议细节请参考:[https://www.w3.org/wiki/CORS_Enabled](https://www.w3.org/wiki/CORS_Enabled) 或 [https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS)。 + +示例配置文件参见 [example/config/taosadapter.toml](https://github.com/taosdata/taosadapter/blob/develop/example/config/taosadapter.toml)。 + +## 功能列表 + +- 与 RESTful 接口兼容 + [https://www.taosdata.com/cn/documentation/connector#restful](https://www.taosdata.com/cn/documentation/connector#restful) +- 兼容 InfluxDB v1 写接口 + [https://docs.influxdata.com/influxdb/v2.0/reference/api/influxdb-1x/write/](https://docs.influxdata.com/influxdb/v2.0/reference/api/influxdb-1x/write/) +- 兼容 OpenTSDB JSON 和 telnet 格式写入 + - + - +- 与 collectd 无缝连接 + collectd 是一个系统统计收集守护程序,请访问 [https://collectd.org/](https://collectd.org/) 了解更多信息。 +- Seamless connection with StatsD + StatsD 是一个简单而强大的统计信息汇总的守护程序。请访问 [https://github.com/statsd/statsd](https://github.com/statsd/statsd) 了解更多信息。 +- 与 icinga2 的无缝连接 + icinga2 是一个收集检查结果指标和性能数据的软件。请访问 [https://icinga.com/docs/icinga-2/latest/doc/14-features/#opentsdb-writer](https://icinga.com/docs/icinga-2/latest/doc/14-features/#opentsdb-writer) 了解更多信息。 +- 与 tcollector 无缝连接 + TCollector 是一个客户端进程,从本地收集器收集数据,并将数据推送到 OpenTSDB。请访问 [http://opentsdb.net/docs/build/html/user_guide/utilities/tcollector.html](http://opentsdb.net/docs/build/html/user_guide/utilities/tcollector.html) 了解更多信息。 +- 无缝连接 node_exporter + node_export 是一个机器指标的导出器。请访问 [https://github.com/prometheus/node_exporter](https://github.com/prometheus/node_exporter) 了解更多信息。 +- 支持 Prometheus remote_read 和 remote_write + remote_read 和 remote_write 是 Prometheus 数据读写分离的集群方案。请访问[https://prometheus.io/blog/2019/10/10/remote-read-meets-streaming/#remote-apis](https://prometheus.io/blog/2019/10/10/remote-read-meets-streaming/#remote-apis) 了解更多信息。 + +## 接口 + +### TDengine RESTful 接口 + +您可以使用任何支持 http 协议的客户端通过访问 RESTful 接口地址 `http://:6041/` 来写入数据到 TDengine 或从 TDengine 中查询数据。细节请参考[官方文档](https://www.taosdata.com/cn/documentation/connector#restful)。支持如下 EndPoint : + +```text +/rest/sql +/rest/sqlt +/rest/sqlutc +``` + +### InfluxDB + +您可以使用任何支持 http 协议的客户端访问 Restful 接口地址 `http://:6041/` 来写入 InfluxDB 兼容格式的数据到 TDengine。EndPoint 如下: + +```text +/influxdb/v1/write +``` + +支持 InfluxDB 查询参数如下: + +- `db` 指定 TDengine 使用的数据库名 +- `precision` TDengine 使用的时间精度 +- `u` TDengine 用户名 +- `p` TDengine 密码 + +注意: 目前不支持 InfluxDB 的 token 验证方式只支持 Basic 验证和查询参数验证。 + +### OpenTSDB + +您可以使用任何支持 http 协议的客户端访问 Restful 接口地址 `http://:6041/` 来写入 OpenTSDB 兼容格式的数据到 TDengine。EndPoint 如下: + +```text +/opentsdb/v1/put/json/:db +/opentsdb/v1/put/telnet/:db +``` + +### collectd + +#### 直接采集 + +修改 collectd 配置文件 `/etc/collectd/collectd.conf`,taosAdapter 默认使用端口 6045 来接收 collectd 直接采集方式的数据。 + +```text +LoadPlugin network + + Server "127.0.0.1" "6045" + +``` + +#### tsdb 写入方式 + +修改 collectd 配置文件 `/etc/collectd/collectd.conf`,taosAdapter 默认使用端口 6047 来接收 collectd tsdb 写入方式的数据。 + +```text +LoadPlugin write_tsdb + + + Host "localhost" + Port "6047" + HostTags "status=production" + StoreRates false + AlwaysAppendDS false + + +``` + +### StatsD + +修改 StatsD 配置文件 `config.js`,taosAdapter 默认使用 6044 端口接收 StatsD 的写入数据。 + +- `backends` add `"./backends/repeater"` +- `repeater` add `{ host:'host to taosAdapter', port: 6044}` + +配置文件示例 + +```js +{ + port: 8125, + backends: ["./backends/repeater"], + repeater: [{ host: '127.0.0.1', port: 6044}] +} +``` + +### icinga2 OpenTSDB writer + +使用 icinga2 收集监控数据的方法参见: + +- 参考文档: + opentsdb-writer [https://icinga.com/docs/icinga-2/latest/doc/14-features/#opentsdb-writer](https://icinga.com/docs/icinga-2/latest/doc/14-features/#opentsdb-writer) +- 使能 taosAdapter `opentsdb_telnet.enable` 来支持写入 +- 修改配置文件 `/etc/icinga2/features-enabled/opentsdb.conf`, taosAdapter 默认使用 6048 端口接收 icinga2 的数据。 + +```text +object OpenTsdbWriter "opentsdb" { + host = "host to taosAdapter" + port = 6048 +} +``` + +### TCollector + +Tcollector 是一个客户端进程,它从本地收集器中收集数据并将数据推送到 OpenTSDB。您在您的所有主机上运行它,它完成将每个主机的数据发送到 TSD (OpenTSDB 后台服务进程)的工作。 + +- 启用 taosAdapter 配置 opentsdb_telnet.enable +- 修改 TCollector 配置文件,将 OpenTSDB 主机修改为部署 taosAdapter 的主机,并修改端口为 6049 + +### node_exporter + +Prometheus 使用的由\*NIX 内核暴露的硬件和操作系统指标的输出器 + +- 启用 taosAdapter 的配置 node_exporter.enable +- 设置 node_exporter 的相关配置 +- 重新启动 taosAdapter + +### prometheus + +remote_read 和 remote_write 是 Prometheus 数据读写分离的集群方案。 +只需要将 remote_read 和 remote_write url 指向 taosAdapter 对应的 url 同时设置 Basic 验证即可使用。 + +- remote_read url : `http://host_to_taosAdapter:port(default 6041)/prometheus/v1/remote_read/:db` +- remote_write url : `http://host_to_taosAdapter:port(default 6041)/prometheus/v1/remote_write/:db` + +Basic 验证: + +- username: TDengine 连接用户名 +- password: TDengine 连接密码 + +示例 prometheus.yml 如下: + +```yaml +remote_write: + - url: "http://localhost:6041/prometheus/v1/remote_write/prometheus_data" + basic_auth: + username: root + password: taosdata + +remote_read: + - url: "http://localhost:6041/prometheus/v1/remote_read/prometheus_data" + basic_auth: + username: root + password: taosdata + remote_timeout: 10s + read_recent: true +``` + +## 内存使用优化方法 + +taosAdapter 将监测自身运行过程中内存使用率并通过两个阈值进行调节。有效值范围为 -1 到 100 的整数,单位为系统物理内存的百分比。 + +- pauseQueryMemoryThreshold +- pauseAllMemoryThreshold + +当超过 pauseQueryMemoryThreshold 阈值时时停止处理查询请求。 + +http 返回内容: + +- code 503 +- body "query memory exceeds threshold" + +当超过 pauseAllMemoryThreshold 阈值时停止处理所有写入和查询请求。 + +http 返回内容: + +- code 503 +- body "memory exceeds threshold" + +当内存回落到阈值之下时恢复对应功能。 + +状态检查接口 `http://:6041/-/ping` + +- 正常返回 `code 200` +- 无参数 如果内存超过 pauseAllMemoryThreshold 将返回 `code 503` +- 请求参数 `action=query` 如果内存超过 pauseQueryMemoryThreshold 或 pauseAllMemoryThreshold 将返回 `code 503` + +对应配置参数 + +```text + monitor.collectDuration 监测间隔 环境变量 "TAOS_MONITOR_COLLECT_DURATION" (默认值 3s) + monitor.incgroup 是否是cgroup中运行(容器中运行设置为 true) 环境变量 "TAOS_MONITOR_INCGROUP" + monitor.pauseAllMemoryThreshold 不再进行插入和查询的内存阈值 环境变量 "TAOS_MONITOR_PAUSE_ALL_MEMORY_THRESHOLD" (默认值 80) + monitor.pauseQueryMemoryThreshold 不再进行查询的内存阈值 环境变量 "TAOS_MONITOR_PAUSE_QUERY_MEMORY_THRESHOLD" (默认值 70) +``` + +您可以根据具体项目应用场景和运营策略进行相应调整,并建议使用运营监控软件及时进行系统内存状态监控。负载均衡器也可以通过这个接口检查 taosAdapter 运行状态。 + +## taosAdapter 监控指标 + +taosAdapter 采集 http 相关指标、cpu 百分比和内存百分比。 + +### http 接口 + +提供符合 [OpenMetrics](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md) 接口: + +```text +http://:6041/metrics +``` + +### 写入 TDengine + +taosAdapter 支持将 http 监控、cpu 百分比和内存百分比写入 TDengine。 + +有关配置参数 + +| **配置项** | **描述** | **默认值** | +| ----------------------- | --------------------------------------------------------- | ---------- | +| monitor.collectDuration | cpu 和内存采集间隔 | 3s | +| monitor.identity | 当前 taosadapter 的标识符如果不设置将使用 'hostname:port' | | +| monitor.incgroup | 是否是 cgroup 中运行(容器中运行设置为 true) | false | +| monitor.writeToTD | 是否写入到 TDengine | true | +| monitor.user | TDengine 连接用户名 | root | +| monitor.password | TDengine 连接密码 | taosdata | +| monitor.writeInterval | 写入 TDengine 间隔 | 30s | + +## 结果返回条数限制 + +taosAdapter 通过参数 `restfulRowLimit` 来控制结果的返回条数,-1 代表无限制,默认无限制。 + +该参数控制以下接口返回 + +- `http://:6041/rest/sql` +- `http://:6041/rest/sqlt` +- `http://:6041/rest/sqlutc` +- `http://:6041/prometheus/v1/remote_read/:db` + +## 故障解决 + +您可以通过命令 `systemctl status taosadapter` 来检查 taosAdapter 运行状态。 + +您也可以通过设置 --logLevel 参数或者环境变量 TAOS_ADAPTER_LOG_LEVEL 来调节 taosAdapter 日志输出详细程度。有效值包括: panic、fatal、error、warn、warning、info、debug 以及 trace。 + +## 如何从旧版本 TDengine 迁移到 taosAdapter + +在 TDengine server 2.2.x.x 或更早期版本中,taosd 进程包含一个内嵌的 http 服务。如前面所述,taosAdapter 是一个使用 systemd 管理的独立软件,拥有自己的进程。并且两者有一些配置参数和行为是不同的,请见下表: + +| **#** | **embedded httpd** | **taosAdapter** | **comment** | +| ----- | ------------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | +| 1 | httpEnableRecordSql | --logLevel=debug | | +| 2 | httpMaxThreads | n/a | taosAdapter 自动管理线程池,无需此参数 | +| 3 | telegrafUseFieldNum | 请参考 taosAdapter telegraf 配置方法 | +| 4 | restfulRowLimit | restfulRowLimit | 内嵌 httpd 默认输出 10240 行数据,最大允许值为 102400。taosAdapter 也提供 restfulRowLimit 但是默认不做限制。您可以根据实际场景需求进行配置 | +| 5 | httpDebugFlag | 不适用 | httpdDebugFlag 对 taosAdapter 不起作用 | +| 6 | httpDBNameMandatory | 不适用 | taosAdapter 要求 URL 中必须指定数据库名 | diff --git a/docs-cn/14-reference/05-taosbenchmark.md b/docs-cn/14-reference/05-taosbenchmark.md new file mode 100644 index 0000000000..41ec6b9202 --- /dev/null +++ b/docs-cn/14-reference/05-taosbenchmark.md @@ -0,0 +1,482 @@ +--- +title: taosBenchmark +sidebar_label: taosBenchmark +--- + +## 简介 + +taosBenchmark 是一个用于 TDengine 的性能测试的应用程序。taosBenchmark 可以测试 TDengine 的插入、查询和订阅功能,它可以模拟由大量设备产生的大量数据,还可以灵活地控制列的数量,数据类型、线程等。以前叫 taosdemo,现在改名为为 taosBenchmark,安装包提供 taosdemo 作为 taosBenchmark 的软链接。 +配置,一种是[命令行配置](#cli),另一种是[JSON 文件](#json)。 + +## CLI + +| 选项名称 | 描述 | +| :--------------------------------------- | --------------------------------------------------------- | +| [-f/--file](#json) | JSON 配置文件, 与命令行其他参数不能同时使用 | +| -c/--config-dir | 配置文件所在的目录,默认路径是 /etc/taos/ | +| -h/--host | 用于连接 taosd 服务器的 FQDN,默认值为 localhost。 | +| -P/--port | 用于连接 taosd 服务器的端口号,默认值为 6030。 | +| [-I/--interface](#insert-mode) | taosBenchmark 如何插入数据,默认值为 taosc。 | +| -u/--user | 用于连接 taosd 服务器的用户名,默认值为 root。 | +| -p/--password | 用于连接 taosd 服务器的密码,默认值为 taosdata。 | +| -o/--output | 指定结果输出文件的路径,默认值为 ./output.txt。 | +| -T/--thread | 指定插入数据的线程数,默认值为 8 | +| [-i/--insert-interval](#insert-interval) | 隔行插入模式的插入间隔,单位为 ms,默认值为 0。 | +| -S/--timestampstep | 每个子表中每条记录的插入时间戳步长,单位是 ms,默认值是 1 | +| [-B/--interlace-rows](#interlace-rows) | 向子表插入交错行的数量 | +| [-r/--rec-per-req](#record-per-request) | 每次插入请求的记录数,默认值为 30000 | +| -t/--tables | 子表的数量,默认值为 10000。 | +| -n/--records | 每个子表插入的记录数,默认值为 10000。 | +| -d/--database | 数据库的名称,默认值为 test。 | +| [-l/--columns](#columns) | 子表的列数,将使用 int 数据类型的列。 | +| [-A/--tag-type](#tag-type) | 子表的标签的数据类型。 | +| [-b/--data-type](#data-type) | 子表的列的数据类型 | +| -w/--binwidth | 调整 nchar 和 binary 数据类型的默认长度,默认值为 64。 | +| -m/--table-prefix | 子表名称的前缀,默认值为 d | +| -E/--escape-character | 在超级表和子表名称中使用转义字符,可选。 | +| -C/--chinese | nchar 和 binary 是否基本的 Unicode 中文字符,可选。 | +| [-N/--normal-table](#normal-table) | 只创建普通表,不创建超级表,可选。 | +| [-M/--random](#random) | 数据源否使用随机的,可选。 | +| -x/--aggr-func | 插入后查询聚合函数,可选。 | +| -y/--answer-yes | 通过确认提示继续,可选。 | +| [-R/--disorder-range](#disorder-range) | 失序时间戳的范围,基于数据库的精度,默认值为 1000 | +| [-O/--disorder](#disorder-ratio) | 插入无序时间戳的数据的概率,默认为 0。 | +| [-F/--prepare_rand](#prepared-rand) | 生产随机数据的随机个数,默认为 10000 | +| -a/--replica | 创建数据库时的副本数量,默认值为 1。 | +| -V/--version | 显示版本信息并退出 | +| -?/--help | 显示帮助信息并退出。 | + +## JSON + +### 1、插入 json 配置文件 + +```json +{ + "filetype": "insert", + "cfgdir": "/etc/taos", + "host": "127.0.0.1", + "port": 6030, + "user": "root", + "password": "taosdata", + "connection_pool_size": 8, + "thread_count": 4, + "result_file": "./insert_res.txt", + "confirm_parameter_prompt": "no", + "insert_interval": 0, + "interlace_rows": 100, + "num_of_records_per_req": 100, + "prepared_rand": 10000, + "chinese": "no", + "databases": [ + { + "dbinfo": { + "name": "db", + "drop": "yes", + "replica": 1, + "days": 10, + "cache": 16, + "blocks": 8, + "precision": "ms", + "keep": 3650, + "minRows": 100, + "maxRows": 4096, + "comp": 2, + "walLevel": 1, + "cachelast": 0, + "quorum": 1, + "fsync": 3000, + "update": 0 + }, + "super_tables": [ + { + "name": "stb", + "child_table_exists": "no", + "childtable_count": 100, + "childtable_prefix": "stb_", + "escape_character": "yes", + "auto_create_table": "no", + "batch_create_tbl_num": 5, + "data_source": "rand", + "insert_mode": "taosc", + "line_protocol": "line", + "insert_rows": 100000, + "childtable_limit": 10, + "childtable_offset": 100, + "interlace_rows": 0, + "insert_interval": 0, + "partial_col_num": 0, + "disorder_ratio": 0, + "disorder_range": 1000, + "timestamp_step": 10, + "start_timestamp": "2020-10-01 00:00:00.000", + "sample_format": "csv", + "sample_file": "./sample.csv", + "use_sample_ts": "no", + "tags_file": "", + "columns": [ + { "type": "INT", "name": "id" }, + { "type": "DOUBLE", "count": 10 }, + { "type": "BINARY", "len": 16, "count": 3 }, + { "type": "BINARY", "len": 32, "count": 6 } + ], + "tags": [ + { "type": "TINYINT", "count": 2, "max": 10, "min": 98 }, + { + "type": "BINARY", + "len": 16, + "count": 5, + "values": ["beijing", "shanghai"] + } + ] + } + ] + } + ] +} +``` + +### 参数 + +| 组 | 选项名称 | 描述 | +| ------------ | --------------------------------------------- | ----------------------------------------------------------------------------------------- | +| | filetype | 文件类型,指定哪种测试,对于插入测试,需要为 insert。 | +| | cfgdir | taosd 配置文件所在的目录,默认值是 /etc/taos。 | +| | host | taosd 服务器的 FQDN,默认为 localhost。 | +| | port | taosd 服务器的端口号,默认为 6030。 | +| | user | 连接 taosd 服务器的用户名,默认为 root。 | +| | password | 连接 taosd 服务器的密码,默认为 taosdata。 | +| | [connection_pool_size](#connection-pool-size) | taos 连接池的大小,默认为线程数。 | +| | thread_count | 插入和创建表的线程数,默认为 8。 | +| | result_file | 保存输出结果的文件路径,默认为 ./output.txt。 | +| | confirm_parameter_prompt | 在执行过程中传递确认提示,默认为无。 | +| | [insert_interval](#insert-interval) | 插入隔行扫描模式的间隔时间,默认值为 0 | +| | [interlace_rows](#interlace-rows) | 每个子表的交错行数,默认值为 0。 | +| | [num_of_records_per_req](#record-per-request) | 每个请求中的记录数,默认值为 30000。 | +| | [prepare_rand](#prepared-rand) | 随机产生的数据数量,默认值为 10000 | +| | chinese | nchar 和 binary 都是 rand 中文,默认值为否。 | +| dbinfo | name | 数据库名称,必填 | +| dbinfo | drop | 插入测试前是否删除数据库,默认值为是。 | +| dbinfo | replica | 复制的数量,默认值是 1。 | +| dbinfo | days | 在文件中存储数据的时间跨度,默认值为 10。 | +| dbinfo | cache | 内存块的大小,单位是 MB,默认值是 16。 | +| dbinfo | blocks | 每个 vnode(tsdb) 中的缓存大小的内存块的数量,默认值为 6。 | +| dbinfo | precision | 数据库时间精度,默认值为 "ms"。 | +| dbinfo | keep | 保留数据的天数,默认值为 3650。 | +| dbinfo | minRows | 文件块中的最小记录数,默认值为 100 | +| dbinfo | minRows | 文件块中的最大记录数,默认值为 4096 | +| dbinfo | comp | 文件压缩标志,默认值为 2。 | +| dbinfo | walLevel | wal 级别,默认值是 1。 | +| dbinfo | cachelast | 是否允许将每个表的最后一条记录保留在内存中,默认值为 0 | +| dbinfo | quorum | 异步写需要的确认次数,默认为 1 | +| dbinfo | fsync | 当 wal 设置为 2 时,fsync 的间隔时间,单位为 ms,默认值为 3000。 | +| dbinfo | update | 是否支持数据更新,默认值为 0。 | +| super_tables | name | 超级表的名称,必须填写。 | +| super_tables | child_table_exists | 子表是否已经存在,默认为否。 | +| super_tables | child_table_count | 子表的数量,必填 | +| super_tables | childtable_prefix | 子表名称的前缀,必须填写。 | +| super_tables | escape_character | 超级表和子表的名称包括转义字符,默认为否。 | +| super_tables | [auto_create_table](#auto-create-table) | SQL 语句是否自动创建子表。 | +| Super_tables | [batch_create_tbl_num](#batch-create-tbl-num) | 为每个请求创建的子表数量,默认为 10。 | +| super_tables | [data_source](#data-source) | 数据资源类型,选项:rand, sample。 | +| super_tables | [insert_mode](#insert-mode) | 插入模式,选项:taosc, rest, stmt, sml,默认为 taosc。 | +| super_tables | [non_stop_mode](#non-stop-mode) | 插入模式是否为持续不停的写入,默认为 no | +| super_tables | [line_protocol](#line-protocol) | 行协议,可选项:line, telnet, json, 默认为 line。 | +| super_tables | [tcp_transfer](#tcp-transfer) | 使用 tcp 还是 http 协议,默认为 http。 | +| super_tables | insert_rows | 每个子表的记录数,默认为 0。 | +| super_tables | [childtable_offset](#childtable-offset) | 子表的偏移量。 | +| super_tables | [childtable_limit](#childtable-limit) | 插入数据的子表数量。 | +| super_tables | [interlace_rows](#interlace-rows) | 每个子表的间隔行,默认为 0。 | +| super_tables | [insert_interval](#insert-interval) | 两个请求之间的插入时间间隔,当 interlace_rows 大于 0 时有效。 | +| super_tables | [partial_col_num](#partial-col-num) | 指定仅向前多少列写入,默认为 0。 | +| super_tables | [disorder_ratio](#disorder-ratio) | 紊乱时间戳的数据比例,默认为 0 | +| super_tables | [disorder_range](#disorder-range) | 无序时间戳的范围,只有当 disorder_ratio 大于 0 时才有效,默认为 1000。 | +| super_tables | timestamp_step | 每条记录的时间戳步骤,默认为 1。 | +| super_tables | start_timestamp | 每个子表的时间戳起始值,默认值是 now。 | +| super_tables | sample_format | 样本数据文件的类型,现在只支持 csv。 | +| super_tables | [sample_file](#sample-file) | 样本文件,仅当 data_source 为 "sample "时有效。 | +| super_tables | [use_sample_ts](#use-sample-ts) | 样本文件是否包含时间戳,默认为否。 | +| super_tables | [tags_file](#tags-file) | 原理与[sample_file](#sample-file)相同,标签数据样本文件,仅支持 taosc、rest insert 模式。 | +| columns/tags | [type](#type) | 数据类型,必填 | +| columns/tags | [len](#len) | 数据长度,默认为 8。 | +| columns/tags | [count](#count) | 该列的连续数,默认为 1。 | +| columns/tags | [name](#name) | 这一列的名称,连续的列名将是 name\_#{number}。 | +| columns/tags | min | 数字数据类型列/标签的最小值 | +| columns/tags | max | 数字数据类型列/标签的最大值 | +| columns/tags | values | nchar/binary 列/标签的值,将从值中随机选择。 | + +### 2、查询测试 json 配置文件 + +```json +{ + "filetype": "query", + "cfgdir": "/etc/taos", + "host": "127.0.0.1", + "port": 6030, + "user": "root", + "password": "taosdata", + "confirm_parameter_prompt": "no", + "databases": "db", + "query_times": 2, + "query_mode": "taosc", + "specified_table_query": { + "query_interval": 1, + "concurrent": 3, + "sqls": [ + { + "sql": "select last_row(*) from stb0 ", + "result": "./query_res0.txt" + }, + { + "sql": "select count(*) from stb00_1", + "result": "./query_res1.txt" + } + ] + }, + "super_table_query": { + "stblname": "stb1", + "query_interval": 1, + "threads": 3, + "sqls": [ + { + "sql": "select last_row(ts) from xxxx", + "result": "./query_res2.txt" + } + ] + } +} +``` + +### 查询测试 JSON 文件的参数 + +| 组 | 选项 | 描述 | +| --------------------------------------- | ------------------------ | --------------------------------------------- | ------------ | +| | filetype | 文件类型,指定哪种测试,对于查询测试,需要 | +| | cfgdir | taosd 配置文件所在的目录。 | +| | host | taosd 服务器的 FQDN,默认为 localhost。 | +| | port | taosd 服务器的端口号,默认为 6030。 | +| | user | 连接 taosd 服务器的用户名,默认为 root。 | +| | password | 连接 taosd 服务器的密码,默认为 taosdata。 | +| | confirm_parameter_prompt | 在执行过程中传递确认提示,默认为否。 | +| | database | 数据库的名称,需要 | +| | query_times | 查询次数 | +| | query mode | 查询模式,选项:taosc 和 rest,默认为 taosc。 | +| specified_table_query/super_table_query | query_interval | 查询时间间隔,单位是秒,默认是 0 | +| specified_table_query/super_table_query | concurrent/threads | 执行 SQL 的线程数,默认为 1。 | +| super_table_query | stblname | supertable name, required | 超级表名称。 | +| sqls | [sql](#sql) | SQL 命令,必填 | +| sqls | result | 查询结果的结果文件,没有则为空。 | + +### 3、订阅 json 配置文件 + +```json +{ + "filetype": "subscribe", + "cfgdir": "/etc/taos", + "host": "127.0.0.1", + "port": 6030, + "user": "root", + "password": "taosdata", + "databases": "db", + "confirm_parameter_prompt": "no", + "specified_table_query": { + "concurrent": 1, + "interval": 0, + "restart": "yes", + "keepProgress": "yes", + "sqls": [ + { + "sql": "select * from stb00_0 ;", + "result": "./subscribe_res0.txt" + } + ] + }, + "super_table_query": { + "stblname": "stb0", + "threads": 1, + "interval": 10000, + "restart": "yes", + "keepProgress": "yes", + "sqls": [ + { + "sql": "select * from xxxx where ts > '2021-02-25 11:35:00.000' ;", + "result": "./subscribe_res1.txt" + } + ] + } +} +``` + +### 订阅测试 JSON 文件的参数 + +| 组 | 选项 | 描述 | +| --------------------------------------- | ------------------------ | ------------------------------------------ | +| | filetype | 文件类型,指定哪种测试,对于订阅测试,需要 | +| | cfgdir | taosd 配置文件的目录。 | +| | host | taosd 服务器的 FQDN,默认为 localhost。 | +| | port | taosd 服务器的端口号,默认为 6030。 | +| | user | 连接 taosd 服务器的用户名,默认为 root。 | +| | password | 连接 taosd 服务器的密码,默认为 taosdata。 | +| | databases | 数据库名称,需要 | +| | confirm_parameter_prompt | 在执行过程中是否通过确认提示。 | +| specified_table_query/super_table_query | concurrent/threads | 执行 SQL 的线程数,默认为 1。 | +| specified_table_query/super_table_query | interval | 执行订阅的时间间隔,默认为 0。 | +| specified_table_query/super_table_query | restart | no: 继续之前的订阅,yes: 开始新的订阅。 | +| specified_table_query/super_table_query | keepProgress | 是否保留订阅的进度。 | +| specified_table_query/super_table_query | resubAfterConsume | 是否取消订阅,然后再次订阅? | +| super_table_query | stblname | supertable 的名称,必须的。 | +| sqls | [sql](#sql) | SQL 命令,必填 | +| sqls | result | 查询结果的结果文件,没有则为空。 | + +## 参数具体说明 + +#### [返回](#taosbenchmark) + +- #### insert mode + + 可选项有 taosc, rest, stmt, sml, sml-rest, 分别对应 c 接口,restful,参数绑定,schemaless 的 c 接口,taosAdapter schemaless 写入 + +- #### insert interval + + 只有当[interlace rows](#interlace-rows)大于 0 时才起作用。 + 意味着线程在为每个子表插入隔行扫描记录后,会等待该值的时间再进行下一轮写入。 + +- #### partial col num + + 若该值为 5, 则仅向前 5 列写入,仅当 [insert_mode](#insert-mode) 为 taosc 和 rest 时生效,为 0 则是向全部列写入。 + +- #### batch create tbl num + + 创建子表时的批数,默认为 10。 + + 注:实际的批数不一定与该值相同,当执行的 SQL 语句大于支持的最大长度时,会自动截断再执行,继续创建。 + +- #### auto create table + + 仅当 [insert_mode](#insert-mode) 为 taosc, rest, stmt 时 并且 childtable_exists 为 ”no“ 时生效,此参数表示执行写入的 SQL 语句,若为 “yes” 则为 ` insert into tb using stb tags (xxx,xxx,...) values (xxx,xxx,...)` 这样的格式,若为 “no“ 则为 ` insert into tb values (xxx,xxx,...)` 这样的格式,前者不需要提前建立子表,后者需要。 + +- #### interlace rows + + 如果它的值为 0,意味着逐个子表逐个子表插入,如果它的值大于零,比如 5,将首先插入 + 到第一个子表的 5 条记录,然后是第二个子表 5 条记录、然后第三个,以此类推。当所有的子表都写入 5 条记录后,线程将从第一个子表继续写入,以此类推。 + +- #### record per request + + 每次插入数据请求/调用 api 时包含的数据行数,也是批次数,当批次数过大时,taos 客户端会返回相应的错误信息,此时需要调整这个数来满足写入要求。 + +- #### columns + + 如果同时设置了该参数和[-b/--data-type](#data-type),会比较该参数与[-b/--data-type](#data-type)设置的列类型的列的个数,如果前者大于后者,比如: `-l 5 -b float,double`, 那么最后的列为 `FLOAT,DOUBLE,INT,INT,INT`。如果前者小于等于后者,比如: `-l 3 -b float,double,float,bigint`,那么最后的列为 `FLOAT,DOUBLE,FLOAT,BIGINT`。 + +- #### tag type + + 设置超级表的标签类型,nchar 和 binary 也可以设置长度,例如: + + ``` + taosBenchmark -A INT,DOUBLE,NCHAR,BINARY(16) + ``` + + 默认是 INT,BINARY(16)。 + + 注意:在有的 shell 比如 bash 命令里面 “()” 需要转义,则上述指令应为: + + ``` + taosBenchmark -A INT,DOUBLE,NCHAR,BINARY\(16\) + ``` + +- #### data type + + 与[-A/--tag-type](#tag-type)相同,但用于列,默认为 FLOAT,INT,FLOAT + +- #### random + + 默认的情况下,数据是模拟电表的采集点的数据,数据值有特定的大小范围,若配置次参数,数据将随机从支持最大的正负 32 位整数中产生。若有修改表结构的其他参数,如 [-l](#columns), [-b](#data-type), [-A](#tag-type)等...,将自动使用从支持最大的正负 32 位整数内随机产生的数据。 + +- #### disorder ratio + + 随机乱序时间戳的概率,最大为 50,即为 50%。随机的乱序时间戳为当前应插入数据的时间戳倒退随机[disorder-range](#disorder-range)内的时间戳。 + +- #### disorder range + + 只有当[-O/--disorder](#disorder-ratio)大于 0 时才有效,单位与数据库的时间精确度相同。 + +- #### prepared rand + + 作为数据源预先生成的随机数据的数量,小的 prepare_rand + 会节省内存,但会减少数据种类。若为 1,则生成的所有同类型的数据相同。 + +- #### connection pool size + + 所有与 taos c 客户端相关的交互都需要 taos 实例,实例会预先创建,以防止运行过程中由于中断导致的失败,该值决定 taos 实例预先创建的数量。若不配置,则与线程数相同。 + +- #### data source + + 数据的来源,默认为 taosBenchmark 随机产生,可以配置为 sample,即为使用 sample_file 参数指定的文件内的数据。 + +- #### line protocol + + 行协议,仅当 [insert_mode](#insert-mode) 为 sml 与 sml-rest 时生效,可选项为 line, telnet, json。 + +- #### non stop mode + + 写入模式是否为不停的持续写入,若为 “yes” 则 insert_rows 失效,直到 Ctrl + C 停止程序,写入才会停止。 + + 注:即使 insert_rows 失效,但是也必须是一个非零正整数。 + +- #### tcp transfer + + 仅当 [insert_mode](#insert-mode) 为 sml-rest 并且 [line_protocol](#line-protocol) 为 telnet 时生效,支持两种通讯协议: tcp 与 http, 默认为 http。 + +- #### normal table + + 仅当 [insert_mode](#insert-mode) 为 taosc, stmt, rest 模式下可以使用,不创建超级表,只创建普通表。 + +- #### childtable limit + + 仅当 childtable_exists 为 yes 时生效,为使用语句 + ` select tbname from stable limit x offset y;` + 中的 limit 的值 x,即为获取插入的现有的子表数量。 + +- #### childtable offset + + 仅当 childtable_exists 为 yes 时生效,为使用语句 + ` select tbname from stable limit x offset y;` + 中的 offset 的值 y,即为获取插入子表的偏移量。 + +- #### sample file + + 是否使用以 csv 格式的数据作为数据源,仅当 data_source 为 sample 时生效。 + + 注:这里与最终生成的数据不同,最终数据源的数据与[prepared_rand](#prepared-rand)的值有关,若 csv 文件内的数据行数小于 prepared_rand,那么会循环读取 csv 文件数据直到与 prepared_rand 相同,若大于,则会只读取 prepared_rand 个数的行的数据。 + +- #### use sample ts + + 仅当 data_source 为 sample 时生效,sample_file 指定的 csv 文件内是否包含第一列时间戳,默认为 no, 若为 yes, 则使用 csv 文件第一列的时间戳,由于同一子表时间戳不能重复,生成的数据个数必须与 csv 文件内的数据行数相同,此时 insert_rows 失效。 + +- #### tags file + + 仅当 [insert_mode](#insert-mode) 为 taosc, rest 的模式下生效。 + + 注:这里原理与 [sample-file](#sample-file) 类似,最终的 tag 的数值与 childtable_count 有关,如果 csv 文件内的 tag 数据行小于给定的子表数量,那么会循环读取 csv 文件数据直到与子表数量相同,若大于,则只会读取 childtable_count 行 tag 数据。 + +- #### type + + 可选值请参考官方支持的数据类型(https://www.taosdata.com/docs/cn/v2.0/taos-sql#data-type)。 + + 注:JSON 数据类型比较特殊,只有在 tags 里面可以选择,并且有且仅有一列 JSON tag 才可以,此时 [count](#count) 和 [len](#len) 代表的意义分别是 JSON tag 内的 key-value pair 的个数和每个 KV pair 的 value 的值的长度,value 默认为 string。 + +- #### count + + 该列连续的个数,比如我们想测试 4096 个列的性能时,不用罗列出 4096 个列来表示,直接使用 `"count": 4096` 即可。 + +- #### len + + 该数据类型的长度,对 NCHAR,BINARY 和 JSON 数据类型有效,若对其他数据类型配置,若为 0 , 则代表这列始终都是以 null 值写入,若为其他数,则没有意义,不建议配置。 + +- #### name + + 列的名字,若与 count 同时使用,比如 `"name":"current", "count":3`, 则 3 个列的名字分别为 current, current_2. current_3 + +- #### sql + + 对于超级表的查询 SQL,在 SQL 命令中保留 "xxxx",程序会自动将其替换为超级表的所有子表名。 + 替换为超级表中所有的子表名。 diff --git a/docs-cn/14-reference/06-taosdump.md b/docs-cn/14-reference/06-taosdump.md new file mode 100644 index 0000000000..3f70786624 --- /dev/null +++ b/docs-cn/14-reference/06-taosdump.md @@ -0,0 +1,102 @@ +--- +title: taosdump +--- + +## 简介 + +taosdump 是一个支持从运行中的 TDengine 服务器或集群中备份数据并将备份的数据恢复 +到相同或另一个运行中的 TDengine 服务器或集群中的工具应用程序。 + +taosdump 可以用数据库、超级表或普通表作为逻辑数据单元备份,也可以对数据库、超级 +表和普通表中指定时间段内的数据记录进行备份。您可以指定数据备份的目录路径,如果 +不指定位置,taosdump 默认会将数据备份到当前目录。 + +如果指定的位置已经有数据文件,taosdump 会提示用户并立即退出,避免数据被覆盖。 +如果您看到提示,请小心操作。 + +taosdump 是一个逻辑备份工具。它不打算或不应被期望用于备份任何原始数据、环境设置、 +硬件信息、服务端配置或集群的拓扑结构。它使用 +[Apache AVRO](https://avro.apache.org/)作为数据文件格式来存储备份数据。 + +## 常用使用场景 + +### taosdump 备份数据 + +1. 指定 -A 或 --all-databases 参数指定所有数据库; +2. 使用 -D db1,db2,... 参数备份指定的多个数据库; +3. 使用 dbname stbname1 stbname2 tbname1 tbname2 ... 参数方式备份指定数据库中的某些个超级表或普通表,注意这种输入序列第一个参数为数据库名称,且只支持一个数据库,第二个和之后的参数为该数据库中的超级表或普通表名称,中间以空格分隔; +4. TDengine 服务器或集群通常会包含一个系统数据库,名为 log,这个数据库内的数据为 TDengine 自我运行的数据,taosdump 默认不会对 log 库进行备份。如果有特定需求对 log 库进行备份,可以使用 -a 或 --allow-sys 命令行参数。 +5. taosdump 1.4.1 之后的版本提供 -n 参数和 -L 参数,用于备份数据时不使用转义字符和“宽容”模式,可以在表名、列名、标签名没使用转义字符的情况下减少备份数据时间和备份数据占用空间。如果不确定符合使用 -n 和 -L 条件时请使用默认参数进行“严格”模式进行备份。转义字符的说明请参考[官方文档](https://tdengine.com/docs/cn/v2.0/taos-sql)。 +6. taosdump 1.4.1 之后的版本提供 -I 参数,用于解析 avro 文件 schema 和数据,如果指定 -s 参数将只解析 schema。 +7. taosdump 1.4.2 之后的备份使用 -B 参数指定的批次数,默认值为 16384,如果在某些环境下由于网络速度或磁盘性能不足导致 "Error actual dump .. batch .." 可以通过 -B 参数挑战为更小的值进行尝试。 + +### taosdump 恢复数据 + +1. -i 加上数据文件所在路径作为参数进行备份指定路径下的数据文件。如前面提及,不应该使用同一个目录备份不同数据集合,也不应该在同一路径多次备份同一数据集,否则备份数据会造成覆盖或多次备份。 +2. taosdump 内部使用 TDengine stmt binding API 进行恢复数据的写入,为提高数据恢复性能,目前使用 16384 为一次写入批次。如果备份数据中有比较多列数据,可能会导致产生 "WAL size exceeds limit" 错误,此时可以通过使用 -B 参数调整为一个更小的值进行尝试。 + +## 详细命令行参数列表 + +以下为 taosdump 详细命令行参数列表: + +``` +Usage: taosdump [OPTION...] dbname [tbname ...] + or: taosdump [OPTION...] --databases db1,db2,... + or: taosdump [OPTION...] --all-databases + or: taosdump [OPTION...] -i inpath + or: taosdump [OPTION...] -o outpath + + -h, --host=HOST Server host dumping data from. Default is + localhost. + -p, --password User password to connect to server. Default is + taosdata. + -P, --port=PORT Port to connect + -u, --user=USER User name used to connect to server. Default is + root. + -c, --config-dir=CONFIG_DIR Configure directory. Default is /etc/taos + -i, --inpath=INPATH Input file path. + -o, --outpath=OUTPATH Output file path. + -r, --resultFile=RESULTFILE DumpOut/In Result file path and name. + -a, --allow-sys Allow to dump system database + -A, --all-databases Dump all databases. + -D, --databases=DATABASES Dump inputted databases. Use comma to separate + databases' name. + -N, --without-property Dump database without its properties. + -s, --schemaonly Only dump tables' schema. + -y, --answer-yes Input yes for prompt. It will skip data file + checking! + -d, --avro-codec=snappy Choose an avro codec among null, deflate, snappy, + and lzma. + -S, --start-time=START_TIME Start time to dump. Either epoch or + ISO8601/RFC3339 format is acceptable. ISO8601 + format example: 2017-10-01T00:00:00.000+0800 or + 2017-10-0100:00:00:000+0800 or '2017-10-01 + 00:00:00.000+0800' + -E, --end-time=END_TIME End time to dump. Either epoch or ISO8601/RFC3339 + format is acceptable. ISO8601 format example: + 2017-10-01T00:00:00.000+0800 or + 2017-10-0100:00:00.000+0800 or '2017-10-01 + 00:00:00.000+0800' + -B, --data-batch=DATA_BATCH Number of data per query/insert statement when + backup/restore. Default value is 16384. If you see + 'error actual dump .. batch ..' when backup or if + you see 'WAL size exceeds limit' error when + restore, please adjust the value to a smaller one + and try. The workable value is related to the + length of the row and type of table schema. + -I, --inspect inspect avro file content and print on screen + -L, --loose-mode Using loose mode if the table name and column name + use letter and number only. Default is NOT. + -n, --no-escape No escape char '`'. Default is using it. + -T, --thread-num=THREAD_NUM Number of thread for dump in file. Default is + 5. + -g, --debug Print debug info. + -?, --help Give this help list + --usage Give a short usage message + -V, --version Print program version + +Mandatory or optional arguments to long options are also mandatory or optional +for any corresponding short options. + +Report bugs to . +``` diff --git a/docs-cn/14-reference/07-tdinsight.md b/docs-cn/14-reference/07-tdinsight.md new file mode 100644 index 0000000000..fb97169429 --- /dev/null +++ b/docs-cn/14-reference/07-tdinsight.md @@ -0,0 +1,427 @@ +--- +title: TDinsight - 基于Grafana的TDengine零依赖监控解决方案 +sidebar_label: TDinsight +--- + +TDinsight 是使用 [TDengine] 原生监控数据库和 [Grafana] 对 TDengine 进行监控的解决方案。 + +TDengine 启动后,会自动创建一个监测数据库 log,并自动将服务器的 CPU、内存、硬盘空间、带宽、请求数、磁盘读写速度、慢查询等信息定时写入该数据库,并对重要的系统操作(比如登录、创建、删除数据库等)以及各种错误报警信息进行记录。通过 [Grafana] 和 [TDengine 数据源插件](https://github.com/taosdata/grafanaplugin/releases),TDinsight 将集群状态、节点信息、插入及查询请求、资源使用情况等进行可视化展示,同时还支持 vnode、dnode、mnode 节点状态异常告警,为开发者实时监控 TDengine 集群运行状态提供了便利。本文将指导用户安装 Grafana 服务器并通过 `TDinsight.sh` 安装脚本自动安装 TDengine 数据源插件及部署 TDinsight 可视化面板。 + +## 系统要求 + +要部署 TDinsight,需要一个单节点的 TDengine 服务器或一个多节点的 [TDengine] 集群,以及一个[Grafana]服务器。此仪表盘需要 TDengine 2.3.3.0 及以上,并启用 `log` 数据库(`monitor = 1`)。 + +## 安装 Grafana + +我们建议在此处使用最新的[Grafana] 7 或 8 版本。您可以在任何[支持的操作系统](https://grafana.com/docs/grafana/latest/installation/requirements/#supported-operating-systems)中,按照 [Grafana 官方文档安装说明](https://grafana.com/docs/grafana/latest/installation/) 安装 [Grafana]。 + +### 在 Debian 或 Ubuntu 上安装 Grafana + +对于 Debian 或 Ubuntu 操作系统,建议使用 Grafana 镜像仓库。使用如下命令从零开始安装: + +```bash +sudo apt-get install -y apt-transport-https +sudo apt-get install -y software-properties-common wget +wget -q -O - https://packages.grafana.com/gpg.key |\ + sudo apt-key add - +echo "deb https://packages.grafana.com/oss/deb stable main" |\ + sudo tee -a /etc/apt/sources.list.d/grafana.list +sudo apt-get update +sudo apt-get install grafana +``` + +### 在 CentOS / RHEL 上安装 Grafana + +您可以从官方 YUM 镜像仓库安装。 + +```bash +sudo tee /etc/yum.repos.d/grafana.repo << EOF +[grafana] +name=grafana +baseurl=https://packages.grafana.com/oss/rpm +repo_gpgcheck=1 +enabled=1 +gpgcheck=1 +gpgkey=https://packages.grafana.com/gpg.key +sslverify=1 +sslcacert=/etc/pki/tls/certs/ca-bundle.crt +EOF +sudo yum install grafana +``` + +或者用 RPM 安装: + +```bash +wget https://dl.grafana.com/oss/release/grafana-7.5.11-1.x86_64.rpm +sudo yum install grafana-7.5.11-1.x86_64.rpm +# or +sudo yum install \ + https://dl.grafana.com/oss/release/grafana-7.5.11-1.x86_64.rpm +``` + +## 自动部署 TDinsight + +我们提供了一个自动化安装脚本 [`TDinsight.sh`](https://github.com/taosdata/grafanaplugin/releases/latest/download/TDinsight.sh) 脚本以便用户快速进行安装配置。 + +您可以通过 `wget` 或其他工具下载该脚本: + +```bash +wget https://github.com/taosdata/grafanaplugin/releases/latest/download/TDinsight.sh +chmod +x TDinsight.sh +``` + +这个脚本会自动下载最新的[Grafana TDengine 数据源插件](https://github.com/taosdata/grafanaplugin/releases/latest) 和 [TDinsight 仪表盘](https://grafana.com/grafana/dashboards/15167) ,将命令行选项中的可配置参数转为 [Grafana Provisioning](https://grafana.com/docs/grafana/latest/administration/provisioning/) 配置文件,以进行自动化部署及更新等操作。利用该脚本提供的告警设置选项,你还可以获得内置的阿里云短信告警通知支持。 + +假设您在同一台主机上使用 TDengine 和 Grafana 的默认服务。运行 `./TDinsight.sh` 并打开 Grafana 浏览器窗口就可以看到 TDinsight 仪表盘了。 + +下面是 TDinsight.sh 的用法说明: + +```bash +Usage: + ./TDinsight.sh + ./TDinsight.sh -h|--help + ./TDinsight.sh -n -a -u -p + +Install and configure TDinsight dashboard in Grafana on ubuntu 18.04/20.04 system. + +-h, -help, --help Display help + +-V, -verbose, --verbose Run script in verbose mode. Will print out each step of execution. + +-v, --plugin-version TDengine datasource plugin version, [default: latest] + +-P, --grafana-provisioning-dir Grafana provisioning directory, [default: /etc/grafana/provisioning/] +-G, --grafana-plugins-dir Grafana plugins directory, [default: /var/lib/grafana/plugins] +-O, --grafana-org-id Grafana orgnization id. [default: 1] + +-n, --tdengine-ds-name TDengine datasource name, no space. [default: TDengine] +-a, --tdengine-api TDengine REST API endpoint. [default: http://127.0.0.1:6041] +-u, --tdengine-user TDengine user name. [default: root] +-p, --tdengine-password TDengine password. [default: taosdata] + +-i, --tdinsight-uid Replace with a non-space ascii code as the dashboard id. [default: tdinsight] +-t, --tdinsight-title Dashboard title. [default: TDinsight] +-e, --tdinsight-editable If the provisioning dashboard could be editable. [default: false] + +-E, --external-notifier Apply external notifier uid to TDinsight dashboard. + +Aliyun SMS as Notifier: +-s, --sms-enabled To enable tdengine-datasource plugin builtin aliyun sms webhook. +-N, --sms-notifier-name Provisioning notifier name.[default: TDinsight Builtin SMS] +-U, --sms-notifier-uid Provisioning notifier uid, use lowercase notifier name by default. +-D, --sms-notifier-is-default Set notifier as default. +-I, --sms-access-key-id Aliyun sms access key id +-K, --sms-access-key-secret Aliyun sms access key secret +-S, --sms-sign-name Sign name +-C, --sms-template-code Template code +-T, --sms-template-param Template param, a escaped json string like '{"alarm_level":"%s","time":"%s","name":"%s","content":"%s"}' +-B, --sms-phone-numbers Comma-separated numbers list, eg "189xxxxxxxx,132xxxxxxxx" +-L, --sms-listen-addr [default: 127.0.0.1:9100] +``` + +大多数命令行选项都可以通过环境变量获得同样的效果。 + +| 短选项 | 长选项 | 环境变量 | 说明 | +| ------ | -------------------------- | ---------------------------- | --------------------------------------------------------------------------- | +| -v | --plugin-version | TDENGINE_PLUGIN_VERSION | TDengine 数据源插件版本,默认使用最新版。 | +| -P | --grafana-provisioning-dir | GF_PROVISIONING_DIR | Grafana 配置目录,默认为`/etc/grafana/provisioning/` | +| -G | --grafana-plugins-dir | GF_PLUGINS_DIR | Grafana 插件目录,默认为`/var/lib/grafana/plugins`。 | +| -O | --grafana-org-id | GF_ORG_ID | Grafana 组织 ID,默认为 1。 | +| -n | --tdengine-ds-name | TDENGINE_DS_NAME | TDengine 数据源名称,默认为 TDengine。 | +| -a | --tdengine-api | TDENGINE_API | TDengine REST API 端点。默认为`http://127.0.0.1:6041`。 | +| -u | --tdengine-user | TDENGINE_USER | TDengine 用户名。 [默认值:root] | +| -p | --tdengine-密码 | TDENGINE_PASSWORD | TDengine 密码。 [默认:taosdata] | +| -i | --tdinsight-uid | TDINSIGHT_DASHBOARD_UID | TDinsight 仪表盘`uid`。 [默认值:tdinsight] | +| -t | --tdinsight-title | TDINSIGHT_DASHBOARD_TITLE | TDinsight 仪表盘标题。 [默认:TDinsight] | +| -e | --tdinsight-可编辑 | TDINSIGHT_DASHBOARD_EDITABLE | 如果配置仪表盘可以编辑。 [默认值:false] | +| -E | --external-notifier | EXTERNAL_NOTIFIER | 将外部通知程序 uid 应用于 TDinsight 仪表盘。 | +| -s | --sms-enabled | SMS_ENABLED | 启用阿里云短信 webhook 内置的 tdengine-datasource 插件。 | +| -N | --sms-notifier-name | SMS_NOTIFIER_NAME | 供应通知程序名称。[默认:`TDinsight Builtin SMS`] | +| -U | --sms-notifier-uid | SMS_NOTIFIER_UID | "Notification Channel" `uid`,默认使用程序名称的小写,其他字符用 “-” 代替。 | +| -D | --sms-notifier-is-default | SMS_NOTIFIER_IS_DEFAULT | 将内置短信通知设置为默认值。 | +| -I | --sms-access-key-id | SMS_ACCESS_KEY_ID | 阿里云短信访问密钥 id | +| -K | --sms-access-key-secret | SMS_ACCESS_KEY_SECRET | 阿里云短信访问秘钥 | +| -S | --sms-sign-name | SMS_SIGN_NAME | 签名 | +| -C | --sms-template-code | SMS_TEMPLATE_CODE | 模板代码 | +| -T | --sms-template-param | SMS_TEMPLATE_PARAM | 模板参数的 JSON 模板 | +| -B | --sms-phone-numbers | SMS_PHONE_NUMBERS | 逗号分隔的手机号列表,例如`"189xxxxxxxx,132xxxxxxxx"` | +| -L | --sms-listen-addr | SMS_LISTEN_ADDR | 内置 sms webhook 监听地址,默认为`127.0.0.1:9100` | + +假设您在主机 `tdengine` 上启动 TDengine 数据库,HTTP API 端口为 `6041`,用户为 `root1`,密码为 `pass5ord`。执行脚本: + +```bash +sudo ./TDinsight.sh -a http://tdengine:6041 -u root1 -p pass5ord +``` + +我们提供了一个“-E”选项,用于从命令行配置 TDinsight 使用现有的通知通道(Notification Channel)。假设你的 Grafana 用户和密码是 `admin:admin`,使用以下命令获取已有的通知通道的`uid`: + +```bash +curl --no-progress-meter -u admin:admin http://localhost:3000/api/alert-notifications | jq +``` + +使用上面获取的 `uid` 值作为 `-E` 输入。 + +```bash +sudo ./TDinsight.sh -a http://tdengine:6041 -u root1 -p pass5ord -E existing-notifier +``` + +如果你想使用[阿里云短信](https://www.aliyun.com/product/sms)服务作为通知渠道,你应该使用`-s`标志启用并添加以下参数: + +- `-N`:Notification Channel 名,默认为`TDinsight Builtin SMS`。 +- `-U`:Channel uid,默认是 `name` 的小写,任何其他字符都替换为 - ,对于默认的 `-N`,其 uid 为 `tdinsight-builtin-sms`。 +- `-I`:阿里云短信访问密钥 id。 +- `-K`:阿里云短信访问秘钥。 +- `-S`:阿里云短信签名。 +- `-C`:阿里云短信模板 ID。 +- `-T`:阿里云短信模板参数,为 JSON 格式模板,示例如下 `'{"alarm_level":"%s","time":"%s","name":"%s","content":"%s "}'`。有四个参数:告警级别、时间、名称和告警内容。 +- `-B`:电话号码列表,以逗号`,`分隔。 + +如果要监控多个 TDengine 集群,则需要设置多个 TDinsight 仪表盘。设置非默认 TDinsight 需要进行一些更改: `-n` `-i` `-t` 选项需要更改为非默认名称,如果使用 内置短信告警功能,`-N` 和 `-L` 也应该改变。 + +```bash +sudo ./TDengine.sh -n TDengine-Env1 -a http://another:6041 -u root -p taosdata -i tdinsight-env1 -t 'TDinsight Env1' +# 如果使用内置短信通知 +sudo ./TDengine.sh -n TDengine-Env1 -a http://another:6041 -u root -p taosdata -i tdinsight-env1 -t 'TDinsight Env1' \ + -s -N 'Env1 SMS' -I xx -K xx -S xx -C SMS_XX -T '' -B 00000000000 -L 127.0.0.01:10611 +``` + +请注意,配置数据源、通知 Channel 和仪表盘在前端是不可更改的。您应该再次通过此脚本更新配置或手动更改 `/etc/grafana/provisioning` 目录(这是 Grafana 的默认目录,根据需要使用`-P`选项更改)中的配置文件。 + +特别地,当您使用 Grafana Cloud 或其他组织时,`-O` 可用于设置组织 ID。 `-G` 可指定 Grafana 插件安装目录。 `-e` 参数将仪表盘设置为可编辑。 + +## 手动设置 TDinsight + +### 安装 TDengine 数据源插件 + +从 GitHub 安装 TDengine 最新版数据源插件。 + +```bash +get_latest_release() { + curl --silent "https://api.github.com/repos/taosdata/grafanaplugin/releases/latest" | + grep '"tag_name":' | + sed -E 's/.*"v([^"]+)".*/\1/' +} +TDENGINE_PLUGIN_VERSION=$(get_latest_release) +sudo grafana-cli \ + --pluginUrl https://github.com/taosdata/grafanaplugin/releases/download/v$TDENGINE_PLUGIN_VERSION/tdengine-datasource-$TDENGINE_PLUGIN_VERSION.zip \ + plugins install tdengine-datasource +``` + +### 配置 Grafana + +将以下设置添加到配置文件 `/etc/grafana/grafana.ini`,以启用未签名插件。 + +```ini +[plugins] +allow_loading_unsigned_plugins = tdengine-datasource +``` + +### 启动 Grafana 服务 + +```bash +sudo systemctl start grafana-server +sudo systemctl enable grafana-server +``` + +### 登录到 Grafana + +在 Web 浏览器中打开默认的 Grafana 网址:`http://localhost:3000`。 +默认用户名/密码都是 `admin`。Grafana 会要求在首次登录后更改密码。 + +### 添加 TDengine 数据源 + +指向 **Configurations** -> **Data Sources** 菜单,然后点击 **Add data source** 按钮。 + +![添加数据源按钮](../assets/howto-add-datasource-button.png) + +搜索并选择**TDengine**。 + +![添加数据源](../assets/howto-add-datasource-tdengine.png) + +配置 TDengine 数据源。 + +![数据源配置](../assets/howto-add-datasource.png) + +保存并测试,正常情况下会报告 'TDengine Data source is working'。 + +![数据源测试](../assets/howto-add-datasource-test.png) + +### 导入仪表盘 + +指向 **+** / **Create** - **import**(或 `/dashboard/import` url)。 + +![导入仪表盘和配置](../assets/import_dashboard.png) + +在 **Import via grafana.com** 位置键入仪表盘 ID `15167` 并 **Load**。 + +![通过 grafana.com 导入](../assets/import-dashboard-15167.png) + +导入完成后,TDinsight 的完整页面视图如下所示。 + +![显示](../assets/TDinsight-full.png) + +## TDinsight 仪表盘详细信息 + +TDinsight 仪表盘旨在提供 TDengine 相关资源使用情况[dnodes, mdodes, vnodes](https://www.taosdata.com/cn/documentation/architecture#cluster)或数据库的使用情况和状态。 + +指标详情如下: + +### 集群状态 + +![tdinsight-mnodes-overview](../assets/TDinsight-1-cluster-status.png) + +这部分包括集群当前信息和状态,告警信息也在此处(从左到右,从上到下)。 + +- **First EP**:当前 TDengine 集群中的`firstEp`设置。 +- **Version**:TDengine 服务器版本(master mnode)。 +- **Master Uptime**: 当前 Master MNode 被选举为 Master 后经过的时间。 +- **Expire Time** - 企业版过期时间。 +- **Used Measuring Points** - 企业版已使用的测点数。 +- **Databases** - 数据库个数。 +- **Connections** - 当前连接个数。 +- **DNodes/MNodes/VGroups/VNodes**:每种资源的总数和存活数。 +- **DNodes/MNodes/VGroups/VNodes Alive Percent**:每种资源的存活数/总数的比例,启用告警规则,并在资源存活率(1 分钟内平均健康资源比例)不足 100%时触发。 +- **Messuring Points Used**:启用告警规则的测点数用量(社区版无数据,默认情况下是健康的)。 +- **Grants Expire Time**:启用告警规则的企业版过期时间(社区版无数据,默认情况是健康的)。 +- **Error Rate**:启用警报的集群总合错误率(每秒平均错误数)。 +- **Variables**:`show variables` 表格展示。 + +### DNodes 状态 + +![tdinsight-mnodes-overview](../assets/TDinsight-2-dnodes.png) + +- **DNodes Status**:`show dnodes` 的简单表格视图。 +- **DNodes Lifetime**:从创建 dnode 开始经过的时间。 +- **DNodes Number**:DNodes 数量变化。 +- **Offline Reason**:如果有任何 dnode 状态为离线,则以饼图形式展示离线原因。 + +### MNode 概述 + +![tdinsight-mnodes-overview](../assets/TDinsight-3-mnodes.png) + +1. **MNodes Status**:`show mnodes` 的简单表格视图。 +2. **MNodes Number**:类似于`DNodes Number`,MNodes 数量变化。 + +### 请求 + +![tdinsight-requests](../assets/TDinsight-4-requests.png) + +1. **Requests Rate(Inserts per Second)**:平均每秒插入次数。 +2. **Requests (Selects)**:查询请求数及变化率(count of second)。 +3. **Requests (HTTP)**:HTTP 请求数和请求速率(count of second)。 + +### 数据库 + +![tdinsight-database](../assets/TDinsight-5-database.png) + +数据库使用情况,对变量 `$database` 的每个值即每个数据库进行重复多行展示。 + +1. **STables**:超级表数量。 +2. **Total Tables**:所有表数量。 +3. **Sub Tables**:所有超级表子表的数量。 +4. **Tables**:所有普通表数量随时间变化图。 +5. **Tables Number Foreach VGroups**:每个 VGroups 包含的表数量。 + +### DNode 资源使用情况 + +![dnode-usage](../assets/TDinsight-6-dnode-usage.png) + +数据节点资源使用情况展示,对变量 `$fqdn` 即每个数据节点进行重复多行展示。包括: + +1. **Uptime**:从创建 dnode 开始经过的时间。 +2. **Has MNodes?**:当前 dnode 是否为 mnode。 +3. **CPU Cores**:CPU 核数。 +4. **VNodes Number**:当前 dnode 的 VNodes 数量。 +5. **VNodes Masters**:处于 master 角色的 vnode 数量。 +6. **Current CPU Usage of taosd**:taosd 进程的 CPU 使用率。 +7. **Current Memory Usage of taosd**:taosd 进程的内存使用情况。 +8. **Disk Used**:taosd 数据目录的总磁盘使用百分比。 +9. **CPU Usage**:进程和系统 CPU 使用率。 +10. **RAM Usage**:RAM 使用指标时间序列视图。 +11. **Disk Used**:多级存储下每个级别使用的磁盘(默认为 level0 级)。 +12. **Disk Increasing Rate per Minute**:每分钟磁盘用量增加或减少的百分比。 +13. **Disk IO**:磁盘 IO 速率。 +14. **Net IO**:网络 IO,除本机网络之外的总合网络 IO 速率。 + +### 登录历史 + +![登录历史](../assets/TDinsight-7-login-history.png) + +目前只报告每分钟登录次数。 + +### TaosAdapter + +![taosadapter](../assets/TDinsight-8-taosadaper.png) + +包含 taosAdapter 请求统计和状态详情。包括: + +1. **http_request**: 包含总请求数,请求失败数以及正在处理的请求数 +2. **top 3 request endpoint**: 按终端分组,请求排名前三的数据 +3. **Memory Used**: taosAdapter 内存使用情况 +4. **latency_quantile(ms)**: (1, 2, 5, 9, 99)阶段的分位数 +5. **top 3 failed request endpoint**: 按终端分组,请求失败排名前三的数据 +6. **CPU Used**: taosAdapter cpu 使用情况 + +## 升级 + +通过 `TDinsight.sh` 脚本安装的 TDinsight,可以通过重新运行该脚本就可以升级到最新的 Grafana 插件和 TDinsight Dashboard。 + +手动安装的情况下,可按照上述步骤自行安装新的 Grafana 插件和 Dashboard。 + +## 卸载 + +通过 `TDinsight.sh` 脚本安装的 TDinsight,可以使用命令行 `TDinsight.sh -R` 清理相关资源。 + +手动安装时,要完全卸载 TDinsight,需要清理以下内容: + +1. Grafana 中的 TDinsight Dashboard。 +2. Grafana 中的 Data Source 数据源。 +3. 从插件安装目录删除 `tdengine-datasource` 插件。 + +## 整合的 Docker 示例 + +```bash +git clone --depth 1 https://github.com/taosdata/grafanaplugin.git +cd grafanaplugin +``` + +根据需要在 `docker-compose.yml` 文件中修改: + +```yaml +version: "3.7" + +services: + grafana: + image: grafana/grafana:7.5.10 + volumes: + - ./dist:/var/lib/grafana/plugins/tdengine-datasource + - ./grafana/grafana.ini:/etc/grafana/grafana.ini + - ./grafana/provisioning/:/etc/grafana/provisioning/ + - grafana-data:/var/lib/grafana + environment: + TDENGINE_API: ${TDENGINE_API} + TDENGINE_USER: ${TDENGINE_USER} + TDENGINE_PASS: ${TDENGINE_PASS} + SMS_ACCESS_KEY_ID: ${SMS_ACCESS_KEY_ID} + SMS_ACCESS_KEY_SECRET: ${SMS_ACCESS_KEY_SECRET} + SMS_SIGN_NAME: ${SMS_SIGN_NAME} + SMS_TEMPLATE_CODE: ${SMS_TEMPLATE_CODE} + SMS_TEMPLATE_PARAM: "${SMS_TEMPLATE_PARAM}" + SMS_PHONE_NUMBERS: $SMS_PHONE_NUMBERS + SMS_LISTEN_ADDR: ${SMS_LISTEN_ADDR} + ports: + - 3000:3000 +volumes: + grafana-data: +``` + +替换`docker-compose.yml`中的环境变量或保存环境变量到`.env`文件,然后用`docker-compose up`启动 Grafana。`docker-compose` 工具的具体用法参见 [Docker Compose Reference](https://docs.docker.com/compose/) + +```bash +docker-compose up -d +``` + +TDinsight 已经通过 Provisioning 部署完毕,请到 http://localhost:3000/d/tdinsight/ 查看仪表盘。 + +[grafana]: https://grafana.com +[tdengine]: https://www.taosdata.com diff --git a/docs-cn/14-reference/05-support-platform/_category_.yml b/docs-cn/14-reference/09-support-platform/_category_.yml similarity index 100% rename from docs-cn/14-reference/05-support-platform/_category_.yml rename to docs-cn/14-reference/09-support-platform/_category_.yml diff --git a/docs-cn/14-reference/05-support-platform/05-support-platform.md b/docs-cn/14-reference/09-support-platform/index.md similarity index 100% rename from docs-cn/14-reference/05-support-platform/05-support-platform.md rename to docs-cn/14-reference/09-support-platform/index.md diff --git a/docs-cn/14-reference/06-keywords/_category_.yml b/docs-cn/14-reference/10-keywords/_category_.yml similarity index 100% rename from docs-cn/14-reference/06-keywords/_category_.yml rename to docs-cn/14-reference/10-keywords/_category_.yml diff --git a/docs-cn/14-reference/06-keywords/06-keywords.md b/docs-cn/14-reference/10-keywords/index.md similarity index 100% rename from docs-cn/14-reference/06-keywords/06-keywords.md rename to docs-cn/14-reference/10-keywords/index.md diff --git a/docs-cn/14-reference/07-docker/_category_.yml b/docs-cn/14-reference/11-docker/_category_.yml similarity index 100% rename from docs-cn/14-reference/07-docker/_category_.yml rename to docs-cn/14-reference/11-docker/_category_.yml diff --git a/docs-cn/14-reference/07-docker/07-docker.md b/docs-cn/14-reference/11-docker/index.md similarity index 100% rename from docs-cn/14-reference/07-docker/07-docker.md rename to docs-cn/14-reference/11-docker/index.md diff --git a/docs-cn/14-reference/08-server-config/_category_.yml b/docs-cn/14-reference/12-config/_category_.yml similarity index 100% rename from docs-cn/14-reference/08-server-config/_category_.yml rename to docs-cn/14-reference/12-config/_category_.yml diff --git a/docs-cn/14-reference/08-server-config/08-server-config.md b/docs-cn/14-reference/12-config/index.md similarity index 99% rename from docs-cn/14-reference/08-server-config/08-server-config.md rename to docs-cn/14-reference/12-config/index.md index a9d34c14df..4a4b1aba04 100644 --- a/docs-cn/14-reference/08-server-config/08-server-config.md +++ b/docs-cn/14-reference/12-config/index.md @@ -1,5 +1,6 @@ --- title: 配置参数 +description: "TDengine 客户端和服务配置列表" --- ## 为服务端指定配置文件 diff --git a/docs-cn/14-reference/taosAdapter-architecture.png b/docs-cn/14-reference/taosAdapter-architecture.png new file mode 100644 index 0000000000000000000000000000000000000000..08a9018553aae6f86b42d127b372d0cecfa9bdf8 GIT binary patch literal 38383 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

-Ml|@OVgcW|Ady!pF#|u6%GD zamOeOCUcU&21is;?2~9mFWC>@5@dLoPvL#|eSyS(mS-2=^KIcFS=D`T3PX*(!pNwi zpJhX*kpAWDu!L89r6FEA>~nhwTye6$km;9KRc<%XnNJY!Kz%PeC9R(3*B`A;@UJjG^@cxJ}J93_}IKG#k*m z6e*b6Xcr5XuyB9=Tf6&bqIGb@xc)tzG>02ESY@SyR!S=K7*geuew$=tT17}#aLIr} z2sHT1-%M(e)7>{*>s9yqA;QxGRDcY02U3@xXF-v*=q{AOjn%MC{|D7f9kjKH{+pq} z&)Vuj5NH|pIY?k<==?MhBTEs%dIo!4iio+Ewfv{qYS~SIEkTqCP3^hiW05Kmv|QTS z8te9rHnuZ3E+OFVTSHuAvbcAZTeUk{?o1i}(cuT^Z>k+q7M?NpVrdhZw(u#viN zJU!#_6jyoSOq(Vfi3K!Ewjgi)-+aS#a+elk0Cr;^7J43%iHu~Q21u3VwwH+y?j(p? zg=wW*M}EeE=h^?33DzH7zj*wCJ^g8ZaE51c5T^Gptw9 zJ$l!nlBGOz!{QbI78m?A1nBZaH

vsSmJnS~7!TMAFR2YAEuR^{a#eXTG?U)kIGY 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