From 4fae3b5a79d7f0608aa26a8ff92fb719d532cd30 Mon Sep 17 00:00:00 2001 From: gccgdb1234 Date: Sat, 21 May 2022 09:42:38 +0800 Subject: [PATCH] docs: copy 2.6 documentation to 3.0 as the base --- docs-cn/01-index.md | 25 + docs-cn/02-intro.md | 125 + docs-cn/04-concept/_category_.yml | 1 + docs-cn/04-concept/index.md | 173 + docs-cn/05-get-started/_apt_get_install.mdx | 26 + docs-cn/05-get-started/_category_.yml | 1 + docs-cn/05-get-started/_pkg_install.mdx | 17 + docs-cn/05-get-started/index.md | 173 + docs-cn/07-develop/01-connect/_category_.yml | 1 + docs-cn/07-develop/01-connect/_connect_c.mdx | 3 + docs-cn/07-develop/01-connect/_connect_cs.mdx | 8 + docs-cn/07-develop/01-connect/_connect_go.mdx | 17 + .../07-develop/01-connect/_connect_java.mdx | 15 + .../07-develop/01-connect/_connect_node.mdx | 7 + .../07-develop/01-connect/_connect_php.mdx | 3 + .../07-develop/01-connect/_connect_python.mdx | 3 + docs-cn/07-develop/01-connect/_connect_r.mdx | 3 + .../07-develop/01-connect/_connect_rust.mdx | 8 + docs-cn/07-develop/01-connect/index.md | 284 ++ docs-cn/07-develop/02-model/_category_.yml | 2 + docs-cn/07-develop/02-model/index.mdx | 86 + .../03-insert-data/01-sql-writing.mdx | 137 + .../03-insert-data/02-influxdb-line.mdx | 70 + .../03-insert-data/03-opentsdb-telnet.mdx | 84 + .../03-insert-data/04-opentsdb-json.mdx | 99 + docs-cn/07-develop/03-insert-data/_c_line.mdx | 3 + .../03-insert-data/_c_opts_json.mdx | 3 + .../03-insert-data/_c_opts_telnet.mdx | 3 + docs-cn/07-develop/03-insert-data/_c_sql.mdx | 3 + docs-cn/07-develop/03-insert-data/_c_stmt.mdx | 6 + .../07-develop/03-insert-data/_category_.yml | 1 + .../07-develop/03-insert-data/_cs_line.mdx | 3 + .../03-insert-data/_cs_opts_json.mdx | 3 + .../03-insert-data/_cs_opts_telnet.mdx | 3 + docs-cn/07-develop/03-insert-data/_cs_sql.mdx | 3 + .../07-develop/03-insert-data/_cs_stmt.mdx | 3 + .../07-develop/03-insert-data/_go_line.mdx | 3 + .../03-insert-data/_go_opts_json.mdx | 3 + .../03-insert-data/_go_opts_telnet.mdx | 3 + docs-cn/07-develop/03-insert-data/_go_sql.mdx | 3 + .../07-develop/03-insert-data/_go_stmt.mdx | 8 + .../07-develop/03-insert-data/_java_line.mdx | 3 + .../03-insert-data/_java_opts_json.mdx | 3 + .../03-insert-data/_java_opts_telnet.mdx | 3 + .../07-develop/03-insert-data/_java_sql.mdx | 3 + .../07-develop/03-insert-data/_java_stmt.mdx | 3 + .../07-develop/03-insert-data/_js_line.mdx | 3 + .../03-insert-data/_js_opts_json.mdx | 3 + .../03-insert-data/_js_opts_telnet.mdx | 3 + docs-cn/07-develop/03-insert-data/_js_sql.mdx | 3 + .../07-develop/03-insert-data/_js_stmt.mdx | 12 + .../07-develop/03-insert-data/_php_sql.mdx | 3 + .../07-develop/03-insert-data/_php_stmt.mdx | 3 + .../07-develop/03-insert-data/_py_line.mdx | 3 + .../03-insert-data/_py_opts_json.mdx | 3 + .../03-insert-data/_py_opts_telnet.mdx | 3 + docs-cn/07-develop/03-insert-data/_py_sql.mdx | 3 + .../07-develop/03-insert-data/_py_stmt.mdx | 12 + .../07-develop/03-insert-data/_rust_line.mdx | 3 + .../03-insert-data/_rust_opts_json.mdx | 3 + .../03-insert-data/_rust_opts_telnet.mdx | 3 + .../07-develop/03-insert-data/_rust_sql.mdx | 3 + .../07-develop/03-insert-data/_rust_stmt.mdx | 3 + docs-cn/07-develop/03-insert-data/index.md | 12 + docs-cn/07-develop/04-query-data/_c.mdx | 3 + docs-cn/07-develop/04-query-data/_c_async.mdx | 3 + .../07-develop/04-query-data/_category_.yml | 1 + docs-cn/07-develop/04-query-data/_cs.mdx | 3 + .../07-develop/04-query-data/_cs_async.mdx | 3 + docs-cn/07-develop/04-query-data/_go.mdx | 3 + .../07-develop/04-query-data/_go_async.mdx | 3 + docs-cn/07-develop/04-query-data/_java.mdx | 3 + docs-cn/07-develop/04-query-data/_js.mdx | 3 + .../07-develop/04-query-data/_js_async.mdx | 3 + docs-cn/07-develop/04-query-data/_php.mdx | 3 + docs-cn/07-develop/04-query-data/_py.mdx | 11 + .../07-develop/04-query-data/_py_async.mdx | 8 + docs-cn/07-develop/04-query-data/_rust.mdx | 3 + docs-cn/07-develop/04-query-data/index.mdx | 181 + docs-cn/07-develop/05-continuous-query.mdx | 84 + docs-cn/07-develop/06-subscribe.mdx | 253 ++ docs-cn/07-develop/07-cache.md | 21 + docs-cn/07-develop/08-udf.md | 208 + docs-cn/07-develop/_category_.yml | 1 + docs-cn/07-develop/_sub_c.mdx | 3 + docs-cn/07-develop/_sub_cs.mdx | 3 + docs-cn/07-develop/_sub_go.mdx | 3 + docs-cn/07-develop/_sub_java.mdx | 7 + docs-cn/07-develop/_sub_node.mdx | 3 + docs-cn/07-develop/_sub_python.mdx | 3 + docs-cn/07-develop/_sub_rust.mdx | 3 + docs-cn/07-develop/index.md | 24 + docs-cn/10-cluster/01-deploy.md | 138 + docs-cn/10-cluster/02-cluster-mgmt.md | 216 ++ docs-cn/10-cluster/03-ha-and-lb.md | 87 + docs-cn/10-cluster/_category_.yml | 1 + docs-cn/10-cluster/index.md | 14 + docs-cn/12-taos-sql/01-data-type.md | 50 + docs-cn/12-taos-sql/02-database.md | 128 + docs-cn/12-taos-sql/03-table.md | 123 + docs-cn/12-taos-sql/04-stable.md | 125 + docs-cn/12-taos-sql/05-insert.md | 149 + docs-cn/12-taos-sql/06-select.md | 462 +++ docs-cn/12-taos-sql/07-function.md | 1794 +++++++++ docs-cn/12-taos-sql/08-interval.md | 113 + docs-cn/12-taos-sql/09-limit.md | 54 + docs-cn/12-taos-sql/10-json.md | 91 + docs-cn/12-taos-sql/11-escape.md | 30 + .../12-taos-sql/12-keywords/_category_.yml | 1 + docs-cn/12-taos-sql/12-keywords/index.md | 87 + docs-cn/12-taos-sql/_category_.yml | 1 + docs-cn/12-taos-sql/index.md | 40 + docs-cn/13-operation/01-pkg-install.md | 283 ++ docs-cn/13-operation/02-planning.mdx | 81 + docs-cn/13-operation/03-tolerance.md | 28 + docs-cn/13-operation/06-admin.md | 42 + docs-cn/13-operation/07-import.md | 61 + docs-cn/13-operation/08-export.md | 20 + docs-cn/13-operation/09-status.md | 53 + docs-cn/13-operation/10-monitor.md | 54 + docs-cn/13-operation/11-optimize.md | 100 + docs-cn/13-operation/17-diagnose.md | 131 + docs-cn/13-operation/_category_.yml | 1 + docs-cn/13-operation/index.md | 12 + .../14-reference/02-rest-api/02-rest-api.mdx | 307 ++ .../14-reference/02-rest-api/_category_.yml | 1 + .../03-connector/03-connector.mdx | 117 + .../14-reference/03-connector/_category_.yml | 1 + .../03-connector/_linux_install.mdx | 30 + .../03-connector/_preparition.mdx | 10 + .../03-connector/_verify_linux.mdx | 14 + .../03-connector/_verify_windows.mdx | 14 + .../03-connector/_windows_install.mdx | 31 + docs-cn/14-reference/03-connector/cpp.mdx | 451 +++ docs-cn/14-reference/03-connector/csharp.mdx | 189 + docs-cn/14-reference/03-connector/go.mdx | 411 ++ docs-cn/14-reference/03-connector/java.mdx | 839 ++++ docs-cn/14-reference/03-connector/node.mdx | 259 ++ docs-cn/14-reference/03-connector/python.mdx | 349 ++ docs-cn/14-reference/03-connector/rust.mdx | 388 ++ .../03-connector/tdengine-jdbc-connector.png | Bin 0 -> 192539 bytes docs-cn/14-reference/04-taosadapter.md | 338 ++ docs-cn/14-reference/05-taosbenchmark.md | 434 +++ docs-cn/14-reference/06-taosdump.md | 118 + .../15146-tdengine-monitor-dashboard.json | 3191 ++++++++++++++++ .../assets/15155-tdengine-alert-demo.json | 212 ++ .../assets/TDinsight-1-cluster-status.png | Bin 0 -> 34238 bytes .../assets/TDinsight-2-dnodes.png | Bin 0 -> 17122 bytes .../assets/TDinsight-3-mnodes.png | Bin 0 -> 10545 bytes .../assets/TDinsight-4-requests.png | Bin 0 -> 107576 bytes .../assets/TDinsight-5-database.png | Bin 0 -> 12161 bytes .../assets/TDinsight-6-dnode-usage.png | Bin 0 -> 124535 bytes .../assets/TDinsight-7-login-history.png | Bin 0 -> 7828 bytes .../assets/TDinsight-8-taosadapter.png | Bin 0 -> 25542 bytes .../07-tdinsight/assets/TDinsight-full.png | Bin 0 -> 127303 bytes .../assets/alert-manager-status.png | Bin 0 -> 12870 bytes .../assets/alert-notification-channel.png | Bin 0 -> 19295 bytes .../07-tdinsight/assets/alert-query-demo.png | Bin 0 -> 24329 bytes .../alert-rule-condition-notifications.png | Bin 0 -> 24634 bytes .../07-tdinsight/assets/alert-rule-test.png | Bin 0 -> 25144 bytes .../assets/howto-add-datasource-button.png | Bin 0 -> 7226 bytes .../assets/howto-add-datasource-tdengine.png | Bin 0 -> 7433 bytes .../assets/howto-add-datasource-test.png | Bin 0 -> 3624 bytes .../assets/howto-add-datasource.png | Bin 0 -> 13701 bytes .../assets/howto-dashboard-display.png | Bin 0 -> 192943 bytes .../assets/howto-dashboard-import-options.png | Bin 0 -> 11685 bytes .../assets/howto-import-dashboard.png | Bin 0 -> 33342 bytes .../assets/import-dashboard-15167.png | Bin 0 -> 6406 bytes .../assets/import-dashboard-for-tdengine.png | Bin 0 -> 22959 bytes .../assets/import-via-grafana-dot-com.png | Bin 0 -> 11025 bytes .../07-tdinsight/assets/import_dashboard.png | Bin 0 -> 21299 bytes .../assets/tdengine-grafana-7.x.json | 3358 +++++++++++++++++ .../07-tdinsight/assets/tdengine-grafana.json | 627 +++ .../assets/tdengine_dashboard.png | Bin 0 -> 60299 bytes docs-cn/14-reference/07-tdinsight/index.md | 428 +++ docs-cn/14-reference/08-taos-shell.md | 88 + .../09-support-platform/_category_.yml | 1 + .../14-reference/09-support-platform/index.md | 40 + docs-cn/14-reference/11-docker/_category_.yml | 1 + docs-cn/14-reference/11-docker/index.md | 515 +++ docs-cn/14-reference/12-config/_category_.yml | 1 + docs-cn/14-reference/12-config/index.md | 1131 ++++++ docs-cn/14-reference/12-directory.md | 42 + .../13-schemaless/13-schemaless.md | 165 + .../14-reference/13-schemaless/_category_.yml | 1 + docs-cn/14-reference/_category_.yml | 1 + docs-cn/14-reference/_collectd.mdx | 85 + docs-cn/14-reference/_icinga2.mdx | 44 + docs-cn/14-reference/_prometheus.mdx | 31 + docs-cn/14-reference/_statsd.mdx | 55 + docs-cn/14-reference/_tcollector.mdx | 82 + docs-cn/14-reference/_telegraf.mdx | 27 + docs-cn/14-reference/index.md | 12 + .../14-reference/taosAdapter-architecture.png | Bin 0 -> 38383 bytes docs-cn/20-third-party/01-grafana.mdx | 105 + docs-cn/20-third-party/02-prometheus.md | 89 + docs-cn/20-third-party/03-telegraf.md | 67 + docs-cn/20-third-party/05-collectd.md | 73 + docs-cn/20-third-party/06-statsd.md | 68 + docs-cn/20-third-party/07-icinga2.md | 74 + docs-cn/20-third-party/08-tcollector.md | 67 + docs-cn/20-third-party/09-emq-broker.md | 192 + docs-cn/20-third-party/10-hive-mq-broker.md | 6 + docs-cn/20-third-party/11-kafka.md | 379 ++ docs-cn/20-third-party/_category_.yml | 1 + docs-cn/20-third-party/_deploytaosadapter.mdx | 18 + .../emqx/add-action-handler.png | Bin 0 -> 61718 bytes .../emqx/check-result-in-taos.png | Bin 0 -> 79554 bytes .../emqx/check-rule-matched.png | Bin 0 -> 50751 bytes docs-cn/20-third-party/emqx/client-num.png | Bin 0 -> 29672 bytes .../20-third-party/emqx/create-resource.png | Bin 0 -> 80723 bytes docs-cn/20-third-party/emqx/create-rule.png | Bin 0 -> 116331 bytes docs-cn/20-third-party/emqx/edit-action.png | Bin 0 -> 72083 bytes docs-cn/20-third-party/emqx/edit-resource.png | Bin 0 -> 88002 bytes .../20-third-party/emqx/login-dashboard.png | Bin 0 -> 79817 bytes docs-cn/20-third-party/emqx/rule-engine.png | Bin 0 -> 42200 bytes .../emqx/rule-header-key-value.png | Bin 0 -> 84450 bytes docs-cn/20-third-party/emqx/run-mock.png | Bin 0 -> 17237 bytes docs-cn/20-third-party/index.md | 14 + .../20-third-party/kafka/Kafka_Connect.png | Bin 0 -> 27111 bytes .../kafka/confluentPlatform.png | Bin 0 -> 286533 bytes ...reaming-integration-with-kafka-connect.png | Bin 0 -> 583117 bytes docs-cn/21-tdinternal/01-arch.md | 302 ++ docs-cn/21-tdinternal/02-replica.md | 256 ++ docs-cn/21-tdinternal/03-taosd.md | 119 + docs-cn/21-tdinternal/12-tsz-compress.md | 44 + docs-cn/21-tdinternal/30-iot-big-data.md | 9 + docs-cn/21-tdinternal/_category_.yml | 1 + docs-cn/21-tdinternal/index.md | 10 + docs-cn/25-application/01-telegraf.md | 82 + docs-cn/25-application/02-collectd.md | 95 + docs-cn/25-application/03-immigrate.md | 423 +++ docs-cn/25-application/_category_.yml | 1 + docs-cn/25-application/index.md | 10 + docs-cn/27-train-faq/01-faq.md | 195 + docs-cn/27-train-faq/02-video.mdx | 25 + docs-cn/27-train-faq/03-docker.md | 330 ++ docs-cn/27-train-faq/_category_.yml | 1 + docs-cn/27-train-faq/index.md | 10 + docs-cn/eco_system.png | Bin 0 -> 46061 bytes docs-en/01-index.md | 27 + docs-en/02-intro/_category_.yml | 1 + docs-en/02-intro/eco_system.png | Bin 0 -> 46061 bytes docs-en/02-intro/index.md | 113 + docs-en/04-concept/_category_.yml | 1 + docs-en/04-concept/index.md | 170 + docs-en/05-get-started/_apt_get_install.mdx | 26 + docs-en/05-get-started/_category_.yml | 1 + docs-en/05-get-started/_pkg_install.mdx | 17 + docs-en/05-get-started/index.md | 171 + docs-en/07-develop/01-connect/_category_.yml | 1 + docs-en/07-develop/01-connect/_connect_c.mdx | 3 + docs-en/07-develop/01-connect/_connect_cs.mdx | 8 + docs-en/07-develop/01-connect/_connect_go.mdx | 17 + .../07-develop/01-connect/_connect_java.mdx | 15 + .../07-develop/01-connect/_connect_node.mdx | 7 + .../07-develop/01-connect/_connect_python.mdx | 3 + docs-en/07-develop/01-connect/_connect_r.mdx | 3 + .../07-develop/01-connect/_connect_rust.mdx | 8 + docs-en/07-develop/01-connect/index.md | 241 ++ docs-en/07-develop/02-model/_category_.yml | 2 + docs-en/07-develop/02-model/index.mdx | 84 + .../03-insert-data/01-sql-writing.mdx | 130 + .../03-insert-data/02-influxdb-line.mdx | 70 + .../03-insert-data/03-opentsdb-telnet.mdx | 84 + .../03-insert-data/04-opentsdb-json.mdx | 99 + docs-en/07-develop/03-insert-data/_c_line.mdx | 3 + .../03-insert-data/_c_opts_json.mdx | 3 + .../03-insert-data/_c_opts_telnet.mdx | 3 + docs-en/07-develop/03-insert-data/_c_sql.mdx | 3 + docs-en/07-develop/03-insert-data/_c_stmt.mdx | 6 + .../07-develop/03-insert-data/_category_.yml | 1 + .../07-develop/03-insert-data/_cs_line.mdx | 3 + .../03-insert-data/_cs_opts_json.mdx | 3 + .../03-insert-data/_cs_opts_telnet.mdx | 3 + docs-en/07-develop/03-insert-data/_cs_sql.mdx | 3 + .../07-develop/03-insert-data/_cs_stmt.mdx | 3 + .../07-develop/03-insert-data/_go_line.mdx | 3 + .../03-insert-data/_go_opts_json.mdx | 3 + .../03-insert-data/_go_opts_telnet.mdx | 3 + docs-en/07-develop/03-insert-data/_go_sql.mdx | 3 + .../07-develop/03-insert-data/_go_stmt.mdx | 8 + .../07-develop/03-insert-data/_java_line.mdx | 3 + .../03-insert-data/_java_opts_json.mdx | 3 + .../03-insert-data/_java_opts_telnet.mdx | 3 + .../07-develop/03-insert-data/_java_sql.mdx | 3 + .../07-develop/03-insert-data/_java_stmt.mdx | 3 + .../07-develop/03-insert-data/_js_line.mdx | 3 + .../03-insert-data/_js_opts_json.mdx | 3 + .../03-insert-data/_js_opts_telnet.mdx | 3 + docs-en/07-develop/03-insert-data/_js_sql.mdx | 3 + .../07-develop/03-insert-data/_js_stmt.mdx | 12 + .../07-develop/03-insert-data/_py_line.mdx | 3 + .../03-insert-data/_py_opts_json.mdx | 3 + .../03-insert-data/_py_opts_telnet.mdx | 3 + docs-en/07-develop/03-insert-data/_py_sql.mdx | 3 + .../07-develop/03-insert-data/_py_stmt.mdx | 12 + .../07-develop/03-insert-data/_rust_line.mdx | 3 + .../03-insert-data/_rust_opts_json.mdx | 3 + .../03-insert-data/_rust_opts_telnet.mdx | 3 + .../07-develop/03-insert-data/_rust_sql.mdx | 3 + .../07-develop/03-insert-data/_rust_stmt.mdx | 3 + docs-en/07-develop/03-insert-data/index.md | 12 + docs-en/07-develop/04-query-data/_c.mdx | 3 + docs-en/07-develop/04-query-data/_c_async.mdx | 3 + .../07-develop/04-query-data/_category_.yml | 1 + docs-en/07-develop/04-query-data/_cs.mdx | 3 + .../07-develop/04-query-data/_cs_async.mdx | 3 + docs-en/07-develop/04-query-data/_go.mdx | 3 + .../07-develop/04-query-data/_go_async.mdx | 3 + docs-en/07-develop/04-query-data/_java.mdx | 3 + docs-en/07-develop/04-query-data/_js.mdx | 3 + .../07-develop/04-query-data/_js_async.mdx | 3 + docs-en/07-develop/04-query-data/_py.mdx | 11 + .../07-develop/04-query-data/_py_async.mdx | 8 + docs-en/07-develop/04-query-data/_rust.mdx | 3 + docs-en/07-develop/04-query-data/index.mdx | 186 + docs-en/07-develop/05-continuous-query.mdx | 83 + docs-en/07-develop/06-subscribe.mdx | 257 ++ docs-en/07-develop/07-cache.md | 19 + docs-en/07-develop/08-udf.md | 218 ++ docs-en/07-develop/_category_.yml | 1 + docs-en/07-develop/_sub_c.mdx | 3 + docs-en/07-develop/_sub_cs.mdx | 3 + docs-en/07-develop/_sub_go.mdx | 3 + docs-en/07-develop/_sub_java.mdx | 7 + docs-en/07-develop/_sub_node.mdx | 3 + docs-en/07-develop/_sub_python.mdx | 3 + docs-en/07-develop/_sub_rust.mdx | 3 + docs-en/07-develop/index.md | 25 + docs-en/10-cluster/01-deploy.md | 114 + docs-en/10-cluster/02-cluster-mgmt.md | 213 ++ docs-en/10-cluster/03-ha-and-lb.md | 80 + docs-en/10-cluster/_category_.yml | 1 + docs-en/10-cluster/index.md | 15 + docs-en/12-taos-sql/01-data-type.md | 49 + docs-en/12-taos-sql/02-database.md | 127 + docs-en/12-taos-sql/03-table.md | 127 + docs-en/12-taos-sql/04-stable.md | 118 + docs-en/12-taos-sql/05-insert.md | 164 + docs-en/12-taos-sql/06-select.md | 449 +++ docs-en/12-taos-sql/07-function.md | 1868 +++++++++ docs-en/12-taos-sql/08-interval.md | 112 + docs-en/12-taos-sql/09-limit.md | 77 + docs-en/12-taos-sql/10-json.md | 82 + docs-en/12-taos-sql/11-escape.md | 30 + docs-en/12-taos-sql/12-keywords.md | 48 + docs-en/12-taos-sql/_category_.yml | 1 + docs-en/12-taos-sql/index.md | 33 + docs-en/13-operation/01-pkg-install.md | 282 ++ docs-en/13-operation/02-planning.mdx | 82 + docs-en/13-operation/03-tolerance.md | 29 + docs-en/13-operation/06-admin.md | 50 + docs-en/13-operation/07-import.md | 61 + docs-en/13-operation/08-export.md | 19 + docs-en/13-operation/09-status.md | 54 + docs-en/13-operation/10-monitor.md | 60 + docs-en/13-operation/11-optimize.md | 100 + docs-en/13-operation/17-diagnose.md | 122 + docs-en/13-operation/_category_.yml | 1 + docs-en/13-operation/index.md | 12 + .../14-reference/02-rest-api/02-rest-api.mdx | 307 ++ .../14-reference/02-rest-api/_category_.yml | 1 + .../03-connector/03-connector.mdx | 115 + .../14-reference/03-connector/_category_.yml | 1 + .../03-connector/_linux_install.mdx | 34 + .../03-connector/_preparation.mdx | 10 + .../03-connector/_verify_linux.mdx | 14 + .../03-connector/_verify_windows.mdx | 14 + .../03-connector/_windows_install.mdx | 31 + docs-en/14-reference/03-connector/cpp.mdx | 452 +++ docs-en/14-reference/03-connector/csharp.mdx | 190 + docs-en/14-reference/03-connector/go.mdx | 412 ++ docs-en/14-reference/03-connector/java.mdx | 841 +++++ docs-en/14-reference/03-connector/node.mdx | 255 ++ docs-en/14-reference/03-connector/python.mdx | 346 ++ docs-en/14-reference/03-connector/rust.mdx | 384 ++ .../03-connector/tdengine-jdbc-connector.png | Bin 0 -> 168047 bytes docs-en/14-reference/04-taosadapter.md | 338 ++ docs-en/14-reference/05-taosbenchmark.md | 434 +++ docs-en/14-reference/06-taosdump.md | 115 + .../15146-tdengine-monitor-dashboard.json | 3191 ++++++++++++++++ .../assets/15155-tdengine-alert-demo.json | 212 ++ .../assets/TDinsight-1-cluster-status.png | Bin 0 -> 34238 bytes .../assets/TDinsight-2-dnodes.png | Bin 0 -> 17122 bytes .../assets/TDinsight-3-mnodes.png | Bin 0 -> 10545 bytes .../assets/TDinsight-4-requests.png | Bin 0 -> 107576 bytes .../assets/TDinsight-5-database.png | Bin 0 -> 12161 bytes .../assets/TDinsight-6-dnode-usage.png | Bin 0 -> 124535 bytes .../assets/TDinsight-7-login-history.png | Bin 0 -> 7828 bytes .../assets/TDinsight-8-taosadapter.png | Bin 0 -> 25542 bytes .../07-tdinsight/assets/TDinsight-full.png | Bin 0 -> 127303 bytes .../assets/alert-manager-status.png | Bin 0 -> 12870 bytes .../assets/alert-notification-channel.png | Bin 0 -> 19295 bytes .../07-tdinsight/assets/alert-query-demo.png | Bin 0 -> 24329 bytes .../alert-rule-condition-notifications.png | Bin 0 -> 24634 bytes .../07-tdinsight/assets/alert-rule-test.png | Bin 0 -> 25144 bytes .../assets/howto-add-datasource-button.png | Bin 0 -> 7226 bytes .../assets/howto-add-datasource-tdengine.png | Bin 0 -> 7433 bytes .../assets/howto-add-datasource-test.png | Bin 0 -> 3624 bytes .../assets/howto-add-datasource.png | Bin 0 -> 13701 bytes .../assets/howto-dashboard-display.png | Bin 0 -> 192943 bytes .../assets/howto-dashboard-import-options.png | Bin 0 -> 11685 bytes .../assets/howto-import-dashboard.png | Bin 0 -> 33342 bytes .../assets/import-dashboard-15167.png | Bin 0 -> 6406 bytes .../assets/import-dashboard-for-tdengine.png | Bin 0 -> 22959 bytes .../assets/import-via-grafana-dot-com.png | Bin 0 -> 11025 bytes .../07-tdinsight/assets/import_dashboard.png | Bin 0 -> 21299 bytes .../assets/tdengine-grafana-7.x.json | 3358 +++++++++++++++++ .../07-tdinsight/assets/tdengine-grafana.json | 627 +++ .../assets/tdengine_dashboard.png | Bin 0 -> 60299 bytes docs-en/14-reference/07-tdinsight/index.md | 428 +++ docs-en/14-reference/08-taos-shell.md | 86 + .../09-support-platform/_category_.yml | 1 + .../14-reference/09-support-platform/index.md | 34 + docs-en/14-reference/11-docker/_category_.yml | 1 + docs-en/14-reference/11-docker/index.md | 515 +++ docs-en/14-reference/12-config/_category_.yml | 1 + docs-en/14-reference/12-config/index.md | 1120 ++++++ docs-en/14-reference/12-directory.md | 40 + .../13-schemaless/13-schemaless.md | 157 + .../14-reference/13-schemaless/_category_.yml | 1 + docs-en/14-reference/_category_.yml | 1 + docs-en/14-reference/_collectd.mdx | 84 + docs-en/14-reference/_icinga2.mdx | 46 + docs-en/14-reference/_prometheus.mdx | 31 + docs-en/14-reference/_statsd.mdx | 55 + docs-en/14-reference/_tcollector.mdx | 81 + docs-en/14-reference/_telegraf.mdx | 26 + docs-en/14-reference/index.md | 12 + .../14-reference/taosAdapter-architecture.png | Bin 0 -> 38383 bytes docs-en/20-third-party/01-grafana.mdx | 103 + docs-en/20-third-party/02-prometheus.md | 91 + docs-en/20-third-party/03-telegraf.md | 67 + docs-en/20-third-party/05-collectd.md | 74 + docs-en/20-third-party/06-statsd.md | 68 + docs-en/20-third-party/07-icinga2.md | 74 + docs-en/20-third-party/08-tcollector.md | 67 + docs-en/20-third-party/09-emq-broker.md | 190 + docs-en/20-third-party/10-hive-mq-broker.md | 6 + docs-en/20-third-party/11-kafka.md | 377 ++ docs-en/20-third-party/_category_.yml | 2 + docs-en/20-third-party/_deploytaosadapter.mdx | 17 + .../emqx/add-action-handler.png | Bin 0 -> 61718 bytes .../emqx/check-result-in-taos.png | Bin 0 -> 79554 bytes .../emqx/check-rule-matched.png | Bin 0 -> 50751 bytes docs-en/20-third-party/emqx/client-num.png | Bin 0 -> 29672 bytes .../20-third-party/emqx/create-resource.png | Bin 0 -> 80723 bytes docs-en/20-third-party/emqx/create-rule.png | Bin 0 -> 116331 bytes docs-en/20-third-party/emqx/edit-action.png | Bin 0 -> 72083 bytes docs-en/20-third-party/emqx/edit-resource.png | Bin 0 -> 88002 bytes .../20-third-party/emqx/login-dashboard.png | Bin 0 -> 79817 bytes docs-en/20-third-party/emqx/rule-engine.png | Bin 0 -> 42200 bytes .../emqx/rule-header-key-value.png | Bin 0 -> 84450 bytes docs-en/20-third-party/emqx/run-mock.png | Bin 0 -> 17237 bytes .../grafana/add_datasource1.jpg | Bin 0 -> 54717 bytes .../grafana/add_datasource2.jpg | Bin 0 -> 45044 bytes .../grafana/add_datasource3.jpg | Bin 0 -> 56309 bytes .../grafana/add_datasource4.jpg | Bin 0 -> 61678 bytes .../grafana/create_dashboard1.jpg | Bin 0 -> 92983 bytes .../grafana/create_dashboard2.jpg | Bin 0 -> 99202 bytes docs-en/20-third-party/index.md | 12 + .../20-third-party/kafka/Kafka_Connect.png | Bin 0 -> 27111 bytes .../kafka/confluentPlatform.png | Bin 0 -> 286533 bytes ...reaming-integration-with-kafka-connect.png | Bin 0 -> 583117 bytes docs-en/21-tdinternal/01-arch.md | 285 ++ docs-en/21-tdinternal/30-iot-big-data.md | 10 + docs-en/21-tdinternal/_category_.yml | 1 + docs-en/21-tdinternal/dnode.png | Bin 0 -> 99250 bytes docs-en/21-tdinternal/index.md | 10 + docs-en/21-tdinternal/message.png | Bin 0 -> 44198 bytes docs-en/21-tdinternal/modules.png | Bin 0 -> 89009 bytes docs-en/21-tdinternal/multi_tables.png | Bin 0 -> 67254 bytes docs-en/21-tdinternal/replica-forward.png | Bin 0 -> 37508 bytes docs-en/21-tdinternal/replica-master.png | Bin 0 -> 117209 bytes docs-en/21-tdinternal/replica-restore.png | Bin 0 -> 93250 bytes docs-en/21-tdinternal/structure.png | Bin 0 -> 94536 bytes docs-en/21-tdinternal/vnode.png | Bin 0 -> 55635 bytes docs-en/21-tdinternal/write_master.png | Bin 0 -> 72497 bytes docs-en/21-tdinternal/write_slave.png | Bin 0 -> 57672 bytes docs-en/25-application/01-telegraf.md | 83 + docs-en/25-application/02-collectd.md | 104 + docs-en/25-application/03-immigrate.md | 435 +++ docs-en/25-application/_category_.yml | 1 + docs-en/25-application/index.md | 10 + docs-en/27-train-faq/01-faq.md | 114 + docs-en/27-train-faq/03-docker.md | 285 ++ docs-en/27-train-faq/_category_.yml | 1 + docs-en/27-train-faq/index.md | 10 + 489 files changed, 48235 insertions(+) create mode 100644 docs-cn/01-index.md create mode 100644 docs-cn/02-intro.md create mode 100644 docs-cn/04-concept/_category_.yml create mode 100644 docs-cn/04-concept/index.md create mode 100644 docs-cn/05-get-started/_apt_get_install.mdx create mode 100644 docs-cn/05-get-started/_category_.yml create mode 100644 docs-cn/05-get-started/_pkg_install.mdx create mode 100644 docs-cn/05-get-started/index.md create mode 100644 docs-cn/07-develop/01-connect/_category_.yml create mode 100644 docs-cn/07-develop/01-connect/_connect_c.mdx create mode 100644 docs-cn/07-develop/01-connect/_connect_cs.mdx create mode 100644 docs-cn/07-develop/01-connect/_connect_go.mdx create mode 100644 docs-cn/07-develop/01-connect/_connect_java.mdx create mode 100644 docs-cn/07-develop/01-connect/_connect_node.mdx create mode 100644 docs-cn/07-develop/01-connect/_connect_php.mdx create mode 100644 docs-cn/07-develop/01-connect/_connect_python.mdx create mode 100644 docs-cn/07-develop/01-connect/_connect_r.mdx create mode 100644 docs-cn/07-develop/01-connect/_connect_rust.mdx create mode 100644 docs-cn/07-develop/01-connect/index.md create mode 100644 docs-cn/07-develop/02-model/_category_.yml create mode 100644 docs-cn/07-develop/02-model/index.mdx create mode 100644 docs-cn/07-develop/03-insert-data/01-sql-writing.mdx create mode 100644 docs-cn/07-develop/03-insert-data/02-influxdb-line.mdx create mode 100644 docs-cn/07-develop/03-insert-data/03-opentsdb-telnet.mdx create mode 100644 docs-cn/07-develop/03-insert-data/04-opentsdb-json.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_c_line.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_c_opts_json.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_c_opts_telnet.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_c_sql.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_c_stmt.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_category_.yml create mode 100644 docs-cn/07-develop/03-insert-data/_cs_line.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_cs_opts_json.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_cs_opts_telnet.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_cs_sql.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_cs_stmt.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_go_line.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_go_opts_json.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_go_opts_telnet.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_go_sql.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_go_stmt.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_java_line.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_java_opts_json.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_java_opts_telnet.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_java_sql.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_java_stmt.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_js_line.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_js_opts_json.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_js_opts_telnet.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_js_sql.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_js_stmt.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_php_sql.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_php_stmt.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_py_line.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_py_opts_json.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_py_opts_telnet.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_py_sql.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_py_stmt.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_rust_line.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_rust_opts_json.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_rust_opts_telnet.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_rust_sql.mdx create mode 100644 docs-cn/07-develop/03-insert-data/_rust_stmt.mdx create mode 100644 docs-cn/07-develop/03-insert-data/index.md create mode 100644 docs-cn/07-develop/04-query-data/_c.mdx create mode 100644 docs-cn/07-develop/04-query-data/_c_async.mdx create mode 100644 docs-cn/07-develop/04-query-data/_category_.yml create mode 100644 docs-cn/07-develop/04-query-data/_cs.mdx create mode 100644 docs-cn/07-develop/04-query-data/_cs_async.mdx create mode 100644 docs-cn/07-develop/04-query-data/_go.mdx create mode 100644 docs-cn/07-develop/04-query-data/_go_async.mdx create mode 100644 docs-cn/07-develop/04-query-data/_java.mdx create mode 100644 docs-cn/07-develop/04-query-data/_js.mdx create mode 100644 docs-cn/07-develop/04-query-data/_js_async.mdx create mode 100644 docs-cn/07-develop/04-query-data/_php.mdx create mode 100644 docs-cn/07-develop/04-query-data/_py.mdx create mode 100644 docs-cn/07-develop/04-query-data/_py_async.mdx create mode 100644 docs-cn/07-develop/04-query-data/_rust.mdx create mode 100644 docs-cn/07-develop/04-query-data/index.mdx create mode 100644 docs-cn/07-develop/05-continuous-query.mdx create mode 100644 docs-cn/07-develop/06-subscribe.mdx create mode 100644 docs-cn/07-develop/07-cache.md create mode 100644 docs-cn/07-develop/08-udf.md create mode 100644 docs-cn/07-develop/_category_.yml create mode 100644 docs-cn/07-develop/_sub_c.mdx create mode 100644 docs-cn/07-develop/_sub_cs.mdx create mode 100644 docs-cn/07-develop/_sub_go.mdx create mode 100644 docs-cn/07-develop/_sub_java.mdx create mode 100644 docs-cn/07-develop/_sub_node.mdx create mode 100644 docs-cn/07-develop/_sub_python.mdx create mode 100644 docs-cn/07-develop/_sub_rust.mdx create mode 100644 docs-cn/07-develop/index.md create mode 100644 docs-cn/10-cluster/01-deploy.md create mode 100644 docs-cn/10-cluster/02-cluster-mgmt.md create mode 100644 docs-cn/10-cluster/03-ha-and-lb.md create mode 100644 docs-cn/10-cluster/_category_.yml create mode 100644 docs-cn/10-cluster/index.md create mode 100644 docs-cn/12-taos-sql/01-data-type.md create mode 100644 docs-cn/12-taos-sql/02-database.md create mode 100644 docs-cn/12-taos-sql/03-table.md create mode 100644 docs-cn/12-taos-sql/04-stable.md create mode 100644 docs-cn/12-taos-sql/05-insert.md create mode 100644 docs-cn/12-taos-sql/06-select.md create mode 100644 docs-cn/12-taos-sql/07-function.md create mode 100644 docs-cn/12-taos-sql/08-interval.md create mode 100644 docs-cn/12-taos-sql/09-limit.md create mode 100644 docs-cn/12-taos-sql/10-json.md create mode 100644 docs-cn/12-taos-sql/11-escape.md create mode 100644 docs-cn/12-taos-sql/12-keywords/_category_.yml create mode 100644 docs-cn/12-taos-sql/12-keywords/index.md create mode 100644 docs-cn/12-taos-sql/_category_.yml create mode 100644 docs-cn/12-taos-sql/index.md create mode 100644 docs-cn/13-operation/01-pkg-install.md create mode 100644 docs-cn/13-operation/02-planning.mdx create mode 100644 docs-cn/13-operation/03-tolerance.md create mode 100644 docs-cn/13-operation/06-admin.md create mode 100644 docs-cn/13-operation/07-import.md create mode 100644 docs-cn/13-operation/08-export.md create mode 100644 docs-cn/13-operation/09-status.md create mode 100644 docs-cn/13-operation/10-monitor.md create mode 100644 docs-cn/13-operation/11-optimize.md create mode 100644 docs-cn/13-operation/17-diagnose.md create mode 100644 docs-cn/13-operation/_category_.yml create mode 100644 docs-cn/13-operation/index.md create mode 100644 docs-cn/14-reference/02-rest-api/02-rest-api.mdx create mode 100644 docs-cn/14-reference/02-rest-api/_category_.yml create mode 100644 docs-cn/14-reference/03-connector/03-connector.mdx create mode 100644 docs-cn/14-reference/03-connector/_category_.yml create mode 100644 docs-cn/14-reference/03-connector/_linux_install.mdx create mode 100644 docs-cn/14-reference/03-connector/_preparition.mdx create mode 100644 docs-cn/14-reference/03-connector/_verify_linux.mdx create mode 100644 docs-cn/14-reference/03-connector/_verify_windows.mdx create mode 100644 docs-cn/14-reference/03-connector/_windows_install.mdx create mode 100644 docs-cn/14-reference/03-connector/cpp.mdx create mode 100644 docs-cn/14-reference/03-connector/csharp.mdx create mode 100644 docs-cn/14-reference/03-connector/go.mdx create mode 100644 docs-cn/14-reference/03-connector/java.mdx create mode 100644 docs-cn/14-reference/03-connector/node.mdx create mode 100644 docs-cn/14-reference/03-connector/python.mdx create mode 100644 docs-cn/14-reference/03-connector/rust.mdx create mode 100644 docs-cn/14-reference/03-connector/tdengine-jdbc-connector.png 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/assets/15146-tdengine-monitor-dashboard.json create mode 100644 docs-cn/14-reference/07-tdinsight/assets/15155-tdengine-alert-demo.json create mode 100644 docs-cn/14-reference/07-tdinsight/assets/TDinsight-1-cluster-status.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/TDinsight-2-dnodes.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/TDinsight-3-mnodes.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/TDinsight-4-requests.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/TDinsight-5-database.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/TDinsight-6-dnode-usage.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/TDinsight-7-login-history.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/TDinsight-8-taosadapter.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/TDinsight-full.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/alert-manager-status.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/alert-notification-channel.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/alert-query-demo.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/alert-rule-condition-notifications.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/alert-rule-test.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/howto-add-datasource-button.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/howto-add-datasource-tdengine.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/howto-add-datasource-test.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/howto-add-datasource.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/howto-dashboard-display.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/howto-dashboard-import-options.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/howto-import-dashboard.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/import-dashboard-15167.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/import-dashboard-for-tdengine.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/import-via-grafana-dot-com.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/import_dashboard.png create mode 100644 docs-cn/14-reference/07-tdinsight/assets/tdengine-grafana-7.x.json create mode 100644 docs-cn/14-reference/07-tdinsight/assets/tdengine-grafana.json create mode 100644 docs-cn/14-reference/07-tdinsight/assets/tdengine_dashboard.png create mode 100644 docs-cn/14-reference/07-tdinsight/index.md create mode 100644 docs-cn/14-reference/08-taos-shell.md create mode 100644 docs-cn/14-reference/09-support-platform/_category_.yml create mode 100644 docs-cn/14-reference/09-support-platform/index.md create mode 100644 docs-cn/14-reference/11-docker/_category_.yml create mode 100644 docs-cn/14-reference/11-docker/index.md create mode 100644 docs-cn/14-reference/12-config/_category_.yml create mode 100644 docs-cn/14-reference/12-config/index.md create mode 100644 docs-cn/14-reference/12-directory.md create mode 100644 docs-cn/14-reference/13-schemaless/13-schemaless.md create mode 100644 docs-cn/14-reference/13-schemaless/_category_.yml create mode 100644 docs-cn/14-reference/_category_.yml create mode 100644 docs-cn/14-reference/_collectd.mdx create mode 100644 docs-cn/14-reference/_icinga2.mdx create mode 100644 docs-cn/14-reference/_prometheus.mdx create mode 100644 docs-cn/14-reference/_statsd.mdx create mode 100644 docs-cn/14-reference/_tcollector.mdx create mode 100644 docs-cn/14-reference/_telegraf.mdx create mode 100644 docs-cn/14-reference/index.md create mode 100644 docs-cn/14-reference/taosAdapter-architecture.png create mode 100644 docs-cn/20-third-party/01-grafana.mdx create mode 100644 docs-cn/20-third-party/02-prometheus.md create mode 100644 docs-cn/20-third-party/03-telegraf.md create mode 100644 docs-cn/20-third-party/05-collectd.md create mode 100644 docs-cn/20-third-party/06-statsd.md create mode 100644 docs-cn/20-third-party/07-icinga2.md create mode 100644 docs-cn/20-third-party/08-tcollector.md create mode 100644 docs-cn/20-third-party/09-emq-broker.md create mode 100644 docs-cn/20-third-party/10-hive-mq-broker.md create mode 100644 docs-cn/20-third-party/11-kafka.md create mode 100644 docs-cn/20-third-party/_category_.yml create mode 100644 docs-cn/20-third-party/_deploytaosadapter.mdx create mode 100644 docs-cn/20-third-party/emqx/add-action-handler.png create mode 100644 docs-cn/20-third-party/emqx/check-result-in-taos.png create mode 100644 docs-cn/20-third-party/emqx/check-rule-matched.png create mode 100644 docs-cn/20-third-party/emqx/client-num.png create mode 100644 docs-cn/20-third-party/emqx/create-resource.png create mode 100644 docs-cn/20-third-party/emqx/create-rule.png create mode 100644 docs-cn/20-third-party/emqx/edit-action.png create mode 100644 docs-cn/20-third-party/emqx/edit-resource.png create mode 100644 docs-cn/20-third-party/emqx/login-dashboard.png create mode 100644 docs-cn/20-third-party/emqx/rule-engine.png create mode 100644 docs-cn/20-third-party/emqx/rule-header-key-value.png create mode 100644 docs-cn/20-third-party/emqx/run-mock.png create mode 100644 docs-cn/20-third-party/index.md create mode 100644 docs-cn/20-third-party/kafka/Kafka_Connect.png create mode 100644 docs-cn/20-third-party/kafka/confluentPlatform.png create mode 100644 docs-cn/20-third-party/kafka/streaming-integration-with-kafka-connect.png create mode 100644 docs-cn/21-tdinternal/01-arch.md create mode 100644 docs-cn/21-tdinternal/02-replica.md create mode 100644 docs-cn/21-tdinternal/03-taosd.md create mode 100644 docs-cn/21-tdinternal/12-tsz-compress.md create mode 100644 docs-cn/21-tdinternal/30-iot-big-data.md create mode 100644 docs-cn/21-tdinternal/_category_.yml create mode 100644 docs-cn/21-tdinternal/index.md create mode 100644 docs-cn/25-application/01-telegraf.md create mode 100644 docs-cn/25-application/02-collectd.md create mode 100644 docs-cn/25-application/03-immigrate.md create mode 100644 docs-cn/25-application/_category_.yml create mode 100644 docs-cn/25-application/index.md create mode 100644 docs-cn/27-train-faq/01-faq.md create mode 100644 docs-cn/27-train-faq/02-video.mdx create mode 100644 docs-cn/27-train-faq/03-docker.md create mode 100644 docs-cn/27-train-faq/_category_.yml create mode 100644 docs-cn/27-train-faq/index.md create mode 100644 docs-cn/eco_system.png create mode 100644 docs-en/01-index.md create mode 100644 docs-en/02-intro/_category_.yml create mode 100644 docs-en/02-intro/eco_system.png create mode 100644 docs-en/02-intro/index.md create mode 100644 docs-en/04-concept/_category_.yml create mode 100644 docs-en/04-concept/index.md create mode 100644 docs-en/05-get-started/_apt_get_install.mdx create mode 100644 docs-en/05-get-started/_category_.yml create mode 100644 docs-en/05-get-started/_pkg_install.mdx create mode 100644 docs-en/05-get-started/index.md create mode 100644 docs-en/07-develop/01-connect/_category_.yml create mode 100644 docs-en/07-develop/01-connect/_connect_c.mdx create mode 100644 docs-en/07-develop/01-connect/_connect_cs.mdx create mode 100644 docs-en/07-develop/01-connect/_connect_go.mdx create mode 100644 docs-en/07-develop/01-connect/_connect_java.mdx create mode 100644 docs-en/07-develop/01-connect/_connect_node.mdx create mode 100644 docs-en/07-develop/01-connect/_connect_python.mdx create mode 100644 docs-en/07-develop/01-connect/_connect_r.mdx create mode 100644 docs-en/07-develop/01-connect/_connect_rust.mdx create mode 100644 docs-en/07-develop/01-connect/index.md create mode 100644 docs-en/07-develop/02-model/_category_.yml create mode 100644 docs-en/07-develop/02-model/index.mdx create mode 100644 docs-en/07-develop/03-insert-data/01-sql-writing.mdx create mode 100644 docs-en/07-develop/03-insert-data/02-influxdb-line.mdx create mode 100644 docs-en/07-develop/03-insert-data/03-opentsdb-telnet.mdx create mode 100644 docs-en/07-develop/03-insert-data/04-opentsdb-json.mdx create mode 100644 docs-en/07-develop/03-insert-data/_c_line.mdx create mode 100644 docs-en/07-develop/03-insert-data/_c_opts_json.mdx create mode 100644 docs-en/07-develop/03-insert-data/_c_opts_telnet.mdx create mode 100644 docs-en/07-develop/03-insert-data/_c_sql.mdx create mode 100644 docs-en/07-develop/03-insert-data/_c_stmt.mdx create mode 100644 docs-en/07-develop/03-insert-data/_category_.yml create mode 100644 docs-en/07-develop/03-insert-data/_cs_line.mdx create mode 100644 docs-en/07-develop/03-insert-data/_cs_opts_json.mdx create mode 100644 docs-en/07-develop/03-insert-data/_cs_opts_telnet.mdx create mode 100644 docs-en/07-develop/03-insert-data/_cs_sql.mdx create mode 100644 docs-en/07-develop/03-insert-data/_cs_stmt.mdx create mode 100644 docs-en/07-develop/03-insert-data/_go_line.mdx create mode 100644 docs-en/07-develop/03-insert-data/_go_opts_json.mdx create mode 100644 docs-en/07-develop/03-insert-data/_go_opts_telnet.mdx create mode 100644 docs-en/07-develop/03-insert-data/_go_sql.mdx create mode 100644 docs-en/07-develop/03-insert-data/_go_stmt.mdx create mode 100644 docs-en/07-develop/03-insert-data/_java_line.mdx create mode 100644 docs-en/07-develop/03-insert-data/_java_opts_json.mdx create mode 100644 docs-en/07-develop/03-insert-data/_java_opts_telnet.mdx create mode 100644 docs-en/07-develop/03-insert-data/_java_sql.mdx create mode 100644 docs-en/07-develop/03-insert-data/_java_stmt.mdx create mode 100644 docs-en/07-develop/03-insert-data/_js_line.mdx create mode 100644 docs-en/07-develop/03-insert-data/_js_opts_json.mdx create mode 100644 docs-en/07-develop/03-insert-data/_js_opts_telnet.mdx create mode 100644 docs-en/07-develop/03-insert-data/_js_sql.mdx create mode 100644 docs-en/07-develop/03-insert-data/_js_stmt.mdx create mode 100644 docs-en/07-develop/03-insert-data/_py_line.mdx create mode 100644 docs-en/07-develop/03-insert-data/_py_opts_json.mdx create mode 100644 docs-en/07-develop/03-insert-data/_py_opts_telnet.mdx create mode 100644 docs-en/07-develop/03-insert-data/_py_sql.mdx create mode 100644 docs-en/07-develop/03-insert-data/_py_stmt.mdx create mode 100644 docs-en/07-develop/03-insert-data/_rust_line.mdx create mode 100644 docs-en/07-develop/03-insert-data/_rust_opts_json.mdx create mode 100644 docs-en/07-develop/03-insert-data/_rust_opts_telnet.mdx create mode 100644 docs-en/07-develop/03-insert-data/_rust_sql.mdx create mode 100644 docs-en/07-develop/03-insert-data/_rust_stmt.mdx create mode 100644 docs-en/07-develop/03-insert-data/index.md create mode 100644 docs-en/07-develop/04-query-data/_c.mdx create mode 100644 docs-en/07-develop/04-query-data/_c_async.mdx create mode 100644 docs-en/07-develop/04-query-data/_category_.yml create mode 100644 docs-en/07-develop/04-query-data/_cs.mdx create mode 100644 docs-en/07-develop/04-query-data/_cs_async.mdx create mode 100644 docs-en/07-develop/04-query-data/_go.mdx create mode 100644 docs-en/07-develop/04-query-data/_go_async.mdx create mode 100644 docs-en/07-develop/04-query-data/_java.mdx create mode 100644 docs-en/07-develop/04-query-data/_js.mdx create mode 100644 docs-en/07-develop/04-query-data/_js_async.mdx create mode 100644 docs-en/07-develop/04-query-data/_py.mdx create mode 100644 docs-en/07-develop/04-query-data/_py_async.mdx create mode 100644 docs-en/07-develop/04-query-data/_rust.mdx create mode 100644 docs-en/07-develop/04-query-data/index.mdx create mode 100644 docs-en/07-develop/05-continuous-query.mdx create mode 100644 docs-en/07-develop/06-subscribe.mdx create mode 100644 docs-en/07-develop/07-cache.md create mode 100644 docs-en/07-develop/08-udf.md create mode 100644 docs-en/07-develop/_category_.yml create mode 100644 docs-en/07-develop/_sub_c.mdx create mode 100644 docs-en/07-develop/_sub_cs.mdx create mode 100644 docs-en/07-develop/_sub_go.mdx create mode 100644 docs-en/07-develop/_sub_java.mdx create mode 100644 docs-en/07-develop/_sub_node.mdx create mode 100644 docs-en/07-develop/_sub_python.mdx create mode 100644 docs-en/07-develop/_sub_rust.mdx create mode 100644 docs-en/07-develop/index.md create mode 100644 docs-en/10-cluster/01-deploy.md create mode 100644 docs-en/10-cluster/02-cluster-mgmt.md create mode 100644 docs-en/10-cluster/03-ha-and-lb.md create mode 100644 docs-en/10-cluster/_category_.yml create mode 100644 docs-en/10-cluster/index.md create mode 100644 docs-en/12-taos-sql/01-data-type.md create mode 100644 docs-en/12-taos-sql/02-database.md create mode 100644 docs-en/12-taos-sql/03-table.md create mode 100644 docs-en/12-taos-sql/04-stable.md create mode 100644 docs-en/12-taos-sql/05-insert.md create mode 100644 docs-en/12-taos-sql/06-select.md create mode 100644 docs-en/12-taos-sql/07-function.md create mode 100644 docs-en/12-taos-sql/08-interval.md create mode 100644 docs-en/12-taos-sql/09-limit.md create mode 100644 docs-en/12-taos-sql/10-json.md create mode 100644 docs-en/12-taos-sql/11-escape.md create mode 100644 docs-en/12-taos-sql/12-keywords.md create mode 100644 docs-en/12-taos-sql/_category_.yml create mode 100644 docs-en/12-taos-sql/index.md create mode 100644 docs-en/13-operation/01-pkg-install.md create mode 100644 docs-en/13-operation/02-planning.mdx create mode 100644 docs-en/13-operation/03-tolerance.md create mode 100644 docs-en/13-operation/06-admin.md create mode 100644 docs-en/13-operation/07-import.md create mode 100644 docs-en/13-operation/08-export.md create mode 100644 docs-en/13-operation/09-status.md create mode 100644 docs-en/13-operation/10-monitor.md create mode 100644 docs-en/13-operation/11-optimize.md create mode 100644 docs-en/13-operation/17-diagnose.md create mode 100644 docs-en/13-operation/_category_.yml create mode 100644 docs-en/13-operation/index.md create mode 100644 docs-en/14-reference/02-rest-api/02-rest-api.mdx create mode 100644 docs-en/14-reference/02-rest-api/_category_.yml create mode 100644 docs-en/14-reference/03-connector/03-connector.mdx create mode 100644 docs-en/14-reference/03-connector/_category_.yml create mode 100644 docs-en/14-reference/03-connector/_linux_install.mdx create mode 100644 docs-en/14-reference/03-connector/_preparation.mdx create mode 100644 docs-en/14-reference/03-connector/_verify_linux.mdx create mode 100644 docs-en/14-reference/03-connector/_verify_windows.mdx create mode 100644 docs-en/14-reference/03-connector/_windows_install.mdx create mode 100644 docs-en/14-reference/03-connector/cpp.mdx create mode 100644 docs-en/14-reference/03-connector/csharp.mdx create mode 100644 docs-en/14-reference/03-connector/go.mdx create mode 100644 docs-en/14-reference/03-connector/java.mdx create mode 100644 docs-en/14-reference/03-connector/node.mdx create mode 100644 docs-en/14-reference/03-connector/python.mdx create mode 100644 docs-en/14-reference/03-connector/rust.mdx create mode 100644 docs-en/14-reference/03-connector/tdengine-jdbc-connector.png create mode 100644 docs-en/14-reference/04-taosadapter.md create mode 100644 docs-en/14-reference/05-taosbenchmark.md create mode 100644 docs-en/14-reference/06-taosdump.md create mode 100644 docs-en/14-reference/07-tdinsight/assets/15146-tdengine-monitor-dashboard.json create mode 100644 docs-en/14-reference/07-tdinsight/assets/15155-tdengine-alert-demo.json create mode 100644 docs-en/14-reference/07-tdinsight/assets/TDinsight-1-cluster-status.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/TDinsight-2-dnodes.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/TDinsight-3-mnodes.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/TDinsight-4-requests.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/TDinsight-5-database.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/TDinsight-6-dnode-usage.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/TDinsight-7-login-history.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/TDinsight-8-taosadapter.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/TDinsight-full.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/alert-manager-status.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/alert-notification-channel.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/alert-query-demo.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/alert-rule-condition-notifications.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/alert-rule-test.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/howto-add-datasource-button.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/howto-add-datasource-tdengine.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/howto-add-datasource-test.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/howto-add-datasource.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/howto-dashboard-display.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/howto-dashboard-import-options.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/howto-import-dashboard.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/import-dashboard-15167.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/import-dashboard-for-tdengine.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/import-via-grafana-dot-com.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/import_dashboard.png create mode 100644 docs-en/14-reference/07-tdinsight/assets/tdengine-grafana-7.x.json create mode 100644 docs-en/14-reference/07-tdinsight/assets/tdengine-grafana.json create mode 100644 docs-en/14-reference/07-tdinsight/assets/tdengine_dashboard.png create mode 100644 docs-en/14-reference/07-tdinsight/index.md create mode 100644 docs-en/14-reference/08-taos-shell.md create mode 100644 docs-en/14-reference/09-support-platform/_category_.yml create mode 100644 docs-en/14-reference/09-support-platform/index.md create mode 100644 docs-en/14-reference/11-docker/_category_.yml create mode 100644 docs-en/14-reference/11-docker/index.md create mode 100644 docs-en/14-reference/12-config/_category_.yml create mode 100644 docs-en/14-reference/12-config/index.md create mode 100644 docs-en/14-reference/12-directory.md create mode 100644 docs-en/14-reference/13-schemaless/13-schemaless.md create mode 100644 docs-en/14-reference/13-schemaless/_category_.yml create mode 100644 docs-en/14-reference/_category_.yml create mode 100644 docs-en/14-reference/_collectd.mdx create mode 100644 docs-en/14-reference/_icinga2.mdx create mode 100644 docs-en/14-reference/_prometheus.mdx create mode 100644 docs-en/14-reference/_statsd.mdx create mode 100644 docs-en/14-reference/_tcollector.mdx create mode 100644 docs-en/14-reference/_telegraf.mdx create mode 100644 docs-en/14-reference/index.md create mode 100644 docs-en/14-reference/taosAdapter-architecture.png create mode 100644 docs-en/20-third-party/01-grafana.mdx create mode 100644 docs-en/20-third-party/02-prometheus.md create mode 100644 docs-en/20-third-party/03-telegraf.md create mode 100644 docs-en/20-third-party/05-collectd.md create mode 100644 docs-en/20-third-party/06-statsd.md create mode 100644 docs-en/20-third-party/07-icinga2.md create mode 100644 docs-en/20-third-party/08-tcollector.md create mode 100644 docs-en/20-third-party/09-emq-broker.md create mode 100644 docs-en/20-third-party/10-hive-mq-broker.md create mode 100644 docs-en/20-third-party/11-kafka.md create mode 100644 docs-en/20-third-party/_category_.yml create mode 100644 docs-en/20-third-party/_deploytaosadapter.mdx create mode 100644 docs-en/20-third-party/emqx/add-action-handler.png create mode 100644 docs-en/20-third-party/emqx/check-result-in-taos.png create mode 100644 docs-en/20-third-party/emqx/check-rule-matched.png create mode 100644 docs-en/20-third-party/emqx/client-num.png create mode 100644 docs-en/20-third-party/emqx/create-resource.png create mode 100644 docs-en/20-third-party/emqx/create-rule.png create mode 100644 docs-en/20-third-party/emqx/edit-action.png create mode 100644 docs-en/20-third-party/emqx/edit-resource.png create mode 100644 docs-en/20-third-party/emqx/login-dashboard.png create mode 100644 docs-en/20-third-party/emqx/rule-engine.png create mode 100644 docs-en/20-third-party/emqx/rule-header-key-value.png create mode 100644 docs-en/20-third-party/emqx/run-mock.png create mode 100644 docs-en/20-third-party/grafana/add_datasource1.jpg create mode 100644 docs-en/20-third-party/grafana/add_datasource2.jpg create mode 100644 docs-en/20-third-party/grafana/add_datasource3.jpg create mode 100644 docs-en/20-third-party/grafana/add_datasource4.jpg create mode 100644 docs-en/20-third-party/grafana/create_dashboard1.jpg create mode 100644 docs-en/20-third-party/grafana/create_dashboard2.jpg create mode 100644 docs-en/20-third-party/index.md create mode 100644 docs-en/20-third-party/kafka/Kafka_Connect.png create mode 100644 docs-en/20-third-party/kafka/confluentPlatform.png create mode 100644 docs-en/20-third-party/kafka/streaming-integration-with-kafka-connect.png create mode 100644 docs-en/21-tdinternal/01-arch.md create mode 100644 docs-en/21-tdinternal/30-iot-big-data.md create mode 100644 docs-en/21-tdinternal/_category_.yml create mode 100644 docs-en/21-tdinternal/dnode.png create mode 100644 docs-en/21-tdinternal/index.md create mode 100644 docs-en/21-tdinternal/message.png create mode 100644 docs-en/21-tdinternal/modules.png create mode 100644 docs-en/21-tdinternal/multi_tables.png create mode 100644 docs-en/21-tdinternal/replica-forward.png create mode 100644 docs-en/21-tdinternal/replica-master.png create mode 100644 docs-en/21-tdinternal/replica-restore.png create mode 100644 docs-en/21-tdinternal/structure.png create mode 100644 docs-en/21-tdinternal/vnode.png create mode 100644 docs-en/21-tdinternal/write_master.png create mode 100644 docs-en/21-tdinternal/write_slave.png create mode 100644 docs-en/25-application/01-telegraf.md create mode 100644 docs-en/25-application/02-collectd.md create mode 100644 docs-en/25-application/03-immigrate.md create mode 100644 docs-en/25-application/_category_.yml create mode 100644 docs-en/25-application/index.md create mode 100644 docs-en/27-train-faq/01-faq.md create mode 100644 docs-en/27-train-faq/03-docker.md create mode 100644 docs-en/27-train-faq/_category_.yml create mode 100644 docs-en/27-train-faq/index.md diff --git a/docs-cn/01-index.md b/docs-cn/01-index.md new file mode 100644 index 0000000000..d2e6706892 --- /dev/null +++ b/docs-cn/01-index.md @@ -0,0 +1,25 @@ +--- +title: TDengine 文档 +sidebar_label: 文档首页 +slug: / +--- + +TDengine 是一款[高性能](https://www.taosdata.com/fast)、[分布式](https://www.taosdata.com/scalable)、[支持 SQL](https://www.taosdata.com/sql-support) 的时序数据库 (Database)。本文档是 TDengine 用户手册,主要是介绍 TDengine 的基本概念、安装、使用、功能、开发接口、运营维护、TDengine 内核设计等等,它主要是面向架构师、开发者与系统管理员的。 + +TDengine 充分利用了时序数据的特点,提出了“一个数据采集点一张表”与“超级表”的概念,设计了创新的存储引擎,让数据的写入、查询和存储效率都得到极大的提升。为正确理解并使用TDengine, 无论如何,请您仔细阅读[基本概念](./concept)一章。 + +如果你是开发者,请一定仔细阅读[开发指南](./develop)一章,该部分对数据库连接、建模、插入数据、查询、连续查询、缓存、数据订阅、用户自定义函数等功能都做了详细介绍,并配有各种编程语言的示例代码。大部分情况下,你只要把示例代码拷贝粘贴,针对自己的应用稍作改动,就能跑起来。 + +我们已经生活在大数据的时代,纵向扩展已经无法满足日益增长的业务需求,任何系统都必须具有水平扩展的能力,集群成为大数据以及 database 系统的不可缺失功能。TDengine 团队不仅实现了集群功能,而且将这一重要核心功能开源。怎么部署、管理和维护 TDengine 集群,请参考[集群管理](./cluster)一章。 + +TDengine 采用 SQL 作为其查询语言,大大降低学习成本、降低迁移成本,但同时针对时序数据场景,又做了一些扩展,以支持插值、降采样、时间加权平均等操作。[SQL 手册](./taos-sql)一章详细描述了 SQL 语法、详细列出了各种支持的命令和函数。 + +如果你是系统管理员,关心安装、升级、容错灾备、关心数据导入、导出,配置参数,怎么监测 TDengine 是否健康运行,怎么提升系统运行的性能,那么请仔细参考[运维指南](./operation)一章。 + +如果你对 TDengine 外围工具,REST API, 各种编程语言的连接器想做更多详细了解,请看[参考指南](./reference)一章。 + +如果你对 TDengine 内部的架构设计很有兴趣,欢迎仔细阅读[技术内幕](./tdinternal)一章,里面对集群的设计、数据分区、分片、写入、读出、查询、聚合查询的流程都做了详细的介绍。如果你想研读 TDengine 代码甚至贡献代码,请一定仔细读完这一章。 + +最后,作为一个开源软件,欢迎大家的参与。如果发现文档的任何错误,描述不清晰的地方,都请在每个页面的最下方,点击“编辑本文档“直接进行修改。 + +Together, we make a difference! diff --git a/docs-cn/02-intro.md b/docs-cn/02-intro.md new file mode 100644 index 0000000000..2a56c5e9e6 --- /dev/null +++ b/docs-cn/02-intro.md @@ -0,0 +1,125 @@ +--- +title: 产品简介 +toc_max_heading_level: 2 +--- + +TDengine 是一款高性能、分布式、支持 SQL 的时序数据库 (Database),其核心代码,包括集群功能全部开源(开源协议,AGPL v3.0)。TDengine 能被广泛运用于物联网、工业互联网、车联网、IT 运维、金融等领域。除核心的时序数据库 (Database) 功能外,TDengine 还提供[缓存](/develop/cache/)、[数据订阅](/develop/subscribe)、[流式计算](/develop/continuous-query)等大数据平台所需要的系列功能,最大程度减少研发和运维的复杂度。 + +本章节介绍TDengine的主要功能、竞争优势、适用场景、与其他数据库的对比测试等等,让大家对TDengine有个整体的了解。 + +## 主要功能 + +TDengine的主要功能如下: + +1. 高速数据写入,除 [SQL 写入](/develop/insert-data/sql-writing)外,还支持 [Schemaless 写入](/reference/schemaless/),支持 [InfluxDB LINE 协议](/develop/insert-data/influxdb-line),[OpenTSDB Telnet](/develop/insert-data/opentsdb-telnet), [OpenTSDB JSON ](/develop/insert-data/opentsdb-json)等协议写入; +2. 第三方数据采集工具 [Telegraf](/third-party/telegraf),[Prometheus](/third-party/prometheus),[StatsD](/third-party/statsd),[collectd](/third-party/collectd),[icinga2](/third-party/icinga2), [TCollector](/third-party/tcollector), [EMQ](/third-party/emq-broker), [HiveMQ](/third-party/hive-mq-broker) 等都可以进行配置后,不用任何代码,即可将数据写入; +3. 支持[各种查询](/develop/query-data),包括聚合查询、嵌套查询、降采样查询、插值等 +4. 支持[用户自定义函数](/develop/udf) +5. 支持[缓存](/develop/cache),将每张表的最后一条记录缓存起来,这样无需 Redis +6. 支持[连续查询](/develop/continuous-query)(Continuous Query) +7. 支持[数据订阅](/develop/subscribe),而且可以指定过滤条件 +8. 支持[集群](/cluster/),可以通过多节点进行水平扩展,并通过多副本实现高可靠 +9. 提供[命令行程序](/reference/taos-shell),便于管理集群,检查系统状态,做即席查询 +10. 提供多种数据的[导入](/operation/import)、[导出](/operation/export) +11. 支持对[TDengine 集群本身的监控](/operation/monitor) +12. 提供 [C/C++](/reference/connector/cpp), [Java](/reference/connector/java), [Python](/reference/connector/python), [Go](/reference/connector/go), [Rust](/reference/connector/rust), [Node.js](/reference/connector/node) 等多种编程语言的[连接器](/reference/connector/) +13. 支持 [REST 接口](/reference/rest-api/) +14. 支持与[ Grafana 无缝集成](/third-party/grafana) +15. 支持与 Google Data Studio 无缝集成 + +更多细小的功能,请阅读整个文档。 + +## 竞争优势 + +由于 TDengine 充分利用了[时序数据特点](https://www.taosdata.com/blog/2019/07/09/105.html),比如结构化、无需事务、很少删除或更新、写多读少等等,设计了全新的针对时序数据的存储引擎和计算引擎,因此与其他时序数据库相比,TDengine 有以下特点: + +- **[高性能](https://www.taosdata.com/fast)**:通过创新的存储引擎设计,无论是数据写入还是查询,TDengine 的性能比通用数据库快 10 倍以上,也远超其他时序数据库,而且存储空间也大为节省。 + +- **[分布式](https://www.taosdata.com/scalable)**:通过原生分布式的设计,TDengine 提供了水平扩展的能力,只需要增加节点就能获得更强的数据处理能力,同时通过多副本机制保证了系统的高可用。 + +- **[支持 SQL](https://www.taosdata.com/sql-support)**:TDengine 采用 SQL 作为数据查询语言,减少学习和迁移成本,同时提供 SQL 扩展来处理时序数据特有的分析,而且支持方便灵活的 schemaless 数据写入。 + +- **All in One**:将数据库、消息队列、缓存、流式计算等功能融合一起,应用无需再集成 Kafka/Redis/HBase/Spark 等软件,大幅降低应用开发和维护成本。 + +- **零管理**:安装、集群几秒搞定,无任何依赖,不用分库分表,系统运行状态监测能与 Grafana 或其他运维工具无缝集成。 + +- **零学习成本**:采用 SQL 查询语言,支持 C/C++、Python、Java、Go、Rust、Node.js、C#、Lua(社区贡献)、PHP(社区贡献) 等多种编程语言,与 MySQL 相似,零学习成本。 + +- **无缝集成**:不用一行代码,即可与 Telegraf、Grafana、Prometheus、EMQX、HiveMQ、StatsD、collectd、icinga、TCollector、Matlab、R 等第三方工具无缝集成。 + +- **互动 Console**: 通过命令行 console,不用编程,执行 SQL 语句就能做即席查询、各种数据库的操作、管理以及集群的维护. + +采用 TDengine,可将典型的物联网、车联网、工业互联网大数据平台的总拥有成本大幅降低。表现在几个方面: + +1. 由于其超强性能,它能将系统需要的计算资源和存储资源大幅降低 +2. 因为采用 SQL 接口,能与众多第三放软件无缝集成,学习迁移成本大幅下降 +3. 因为其 All In One 的特性,系统复杂度降低,能降研发成本 +4. 因为运维维护简单,运营维护成本能大幅降低 + +## 技术生态 + +在整个时序大数据平台中,TDengine 在其中扮演的角色如下: + +
+ +![TDengine技术生态图](eco_system.png) + +
+
图 1. TDengine技术生态图
+ +上图中,左侧是各种数据采集或消息队列,包括 OPC-UA、MQTT、Telegraf、也包括 Kafka, 他们的数据将被源源不断的写入到 TDengine。右侧则是可视化、BI 工具、组态软件、应用程序。下侧则是 TDengine 自身提供的命令行程序 (CLI) 以及可视化管理管理。 + +## 总体适用场景 + +作为一个高性能、分布式、支持 SQL 的时序数据库 (Database),TDengine 的典型适用场景包括但不限于 IoT、工业互联网、车联网、IT 运维、能源、金融证券等领域。需要指出的是,TDengine 是针对时序数据场景设计的专用数据库和专用大数据处理工具,因充分利用了时序大数据的特点,它无法用来处理网络爬虫、微博、微信、电商、ERP、CRM 等通用型数据。本文对适用场景做更多详细的分析。 + +### 数据源特点和需求 + +从数据源角度,设计人员可以从下面几个角度分析 TDengine 在目标应用系统里面的适用性。 + +| 数据源特点和需求 | 不适用 | 可能适用 | 非常适用 | 简单说明 | +| ---------------------------- | ------ | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- | +| 总体数据量巨大 | | | √ | TDengine 在容量方面提供出色的水平扩展功能,并且具备匹配高压缩的存储结构,达到业界最优的存储效率。 | +| 数据输入速度偶尔或者持续巨大 | | | √ | TDengine 的性能大大超过同类产品,可以在同样的硬件环境下持续处理大量的输入数据,并且提供很容易在用户环境里面运行的性能评估工具。 | +| 数据源数目巨大 | | | √ | TDengine 设计中包含专门针对大量数据源的优化,包括数据的写入和查询,尤其适合高效处理海量(千万或者更多量级)的数据源。 | + +### 系统架构要求 + +| 系统架构要求 | 不适用 | 可能适用 | 非常适用 | 简单说明 | +| ---------------------- | ------ | -------- | -------- | ----------------------------------------------------------------------------------------------------- | +| 要求简单可靠的系统架构 | | | √ | TDengine 的系统架构非常简单可靠,自带消息队列,缓存,流式计算,监控等功能,无需集成额外的第三方产品。 | +| 要求容错和高可靠 | | | √ | TDengine 的集群功能,自动提供容错灾备等高可靠功能。 | +| 标准化规范 | | | √ | TDengine 使用标准的 SQL 语言提供主要功能,遵守标准化规范。 | + +### 系统功能需求 + +| 系统功能需求 | 不适用 | 可能适用 | 非常适用 | 简单说明 | +| -------------------------- | ------ | -------- | -------- | --------------------------------------------------------------------------------------------------------------------- | +| 要求完整的内置数据处理算法 | | √ | | TDengine 的实现了通用的数据处理算法,但是还没有做到妥善处理各行各业的所有要求,因此特殊类型的处理还需要应用层面处理。 | +| 需要大量的交叉查询处理 | | √ | | 这种类型的处理更多应该用关系型数据系统处理,或者应该考虑 TDengine 和关系型数据系统配合实现系统功能。 | + +### 系统性能需求 + +| 系统性能需求 | 不适用 | 可能适用 | 非常适用 | 简单说明 | +| ---------------------- | ------ | -------- | -------- | ------------------------------------------------------------------------------------------------------ | +| 要求较大的总体处理能力 | | | √ | TDengine 的集群功能可以轻松地让多服务器配合达成处理能力的提升。 | +| 要求高速处理数据 | | | √ | TDengine 的专门为 IoT 优化的存储和数据处理的设计,一般可以让系统得到超出同类产品多倍数的处理速度提升。 | +| 要求快速处理小粒度数据 | | | √ | 这方面 TDengine 性能可以完全对标关系型和 NoSQL 型数据处理系统。 | + +### 系统维护需求 + +| 系统维护需求 | 不适用 | 可能适用 | 非常适用 | 简单说明 | +| ---------------------- | ------ | -------- | -------- | --------------------------------------------------------------------------------------------------------------------- | +| 要求系统可靠运行 | | | √ | TDengine 的系统架构非常稳定可靠,日常维护也简单便捷,对维护人员的要求简洁明了,最大程度上杜绝人为错误和事故。 | +| 要求运维学习成本可控 | | | √ | 同上。 | +| 要求市场有大量人才储备 | √ | | | TDengine 作为新一代产品,目前人才市场里面有经验的人员还有限。但是学习成本低,我们作为厂家也提供运维的培训和辅助服务。 | + +## 与其他数据库的对比测试 + +- [用 InfluxDB 开源的性能测试工具对比 InfluxDB 和 TDengine](https://www.taosdata.com/blog/2020/01/13/1105.html) +- [TDengine 与 OpenTSDB 对比测试](https://www.taosdata.com/blog/2019/08/21/621.html) +- [TDengine 与 Cassandra 对比测试](https://www.taosdata.com/blog/2019/08/14/573.html) +- [TDengine 与 InfluxDB 对比测试](https://www.taosdata.com/blog/2019/07/19/419.html) +- [TDengine VS InfluxDB ,写入性能大 PK !](https://www.taosdata.com/2021/11/05/3248.html) +- [TDengine 和 InfluxDB 查询性能对比测试报告](https://www.taosdata.com/2022/02/22/5969.html) +- [TDengine 与 InfluxDB、OpenTSDB、Cassandra、MySQL、ClickHouse 等数据库的对比测试报告](https://www.taosdata.com/downloads/TDengine_Testing_Report_cn.pdf) diff --git a/docs-cn/04-concept/_category_.yml b/docs-cn/04-concept/_category_.yml new file mode 100644 index 0000000000..aad75dce21 --- /dev/null +++ b/docs-cn/04-concept/_category_.yml @@ -0,0 +1 @@ +label: 基本概念 \ No newline at end of file diff --git a/docs-cn/04-concept/index.md b/docs-cn/04-concept/index.md new file mode 100644 index 0000000000..ca25595260 --- /dev/null +++ b/docs-cn/04-concept/index.md @@ -0,0 +1,173 @@ +--- +title: 数据模型和基本概念 +--- + +为了便于解释基本概念,便于撰写示例程序,整个 TDengine 文档以智能电表作为典型时序数据场景。假设每个智能电表采集电流、电压、相位三个量,有多个智能电表,每个电表有位置 location 和分组 group ID 的静态属性. 其采集的数据类似如下的表格: + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Device IDTime StampCollected MetricsTags
Device IDTime StampcurrentvoltagephaselocationgroupId
d1001153854868500010.32190.31Beijing.Chaoyang2
d1002153854868400010.22200.23Beijing.Chaoyang3
d1003153854868650011.52210.35Beijing.Haidian3
d1004153854868550013.42230.29Beijing.Haidian2
d1001153854869500012.62180.33Beijing.Chaoyang2
d1004153854869660011.82210.28Beijing.Haidian2
d1002153854869665010.32180.25Beijing.Chaoyang3
d1001153854869680012.32210.31Beijing.Chaoyang2
+表 1:智能电表数据示例 +
+ +每一条记录都有设备 ID,时间戳,采集的物理量以及每个设备相关的静态标签。每个设备是受外界的触发,或按照设定的周期采集数据。采集的数据点是时序的,是一个数据流。 + +## 采集量 (Metric) + +采集量是指传感器、设备或其他类型采集点采集的物理量,比如电流、电压、温度、压力、GPS 位置等,是随时间变化的,数据类型可以是整型、浮点型、布尔型,也可是字符串。随着时间的推移,存储的采集量的数据量越来越大。 + +## 标签 (Label/Tag) + +标签是指传感器、设备或其他类型采集点的静态属性,不是随时间变化的,比如设备型号、颜色、设备的所在地等,数据类型可以是任何类型。虽然是静态的,但 TDengine 容许用户修改、删除或增加标签值。与采集量不一样的是,随时间的推移,存储的标签的数据量不会有什么变化。 + +## 数据采集点 (Data Collection Point) + +数据采集点是指按照预设时间周期或受事件触发采集物理量的硬件或软件。一个数据采集点可以采集一个或多个采集量,**但这些采集量都是同一时刻采集的,具有相同的时间戳**。对于复杂的设备,往往有多个数据采集点,每个数据采集点采集的周期都可能不一样,而且完全独立,不同步。比如对于一台汽车,有数据采集点专门采集 GPS 位置,有数据采集点专门采集发动机状态,有数据采集点专门采集车内的环境,这样一台汽车就有三个数据采集点。 + +## 表 (Table) + +因为采集量一般是结构化数据,同时为降低学习门槛,TDengine 采用传统的关系型数据库模型管理数据。用户需要先创建库,然后创建表,之后才能插入或查询数据。 + +为充分利用其数据的时序性和其他数据特点,TDengine 采取**一个数据采集点一张表**的策略,要求对每个数据采集点单独建表(比如有一千万个智能电表,就需创建一千万张表,上述表格中的 d1001,d1002,d1003,d1004 都需单独建表),用来存储这个数据采集点所采集的时序数据。这种设计有几大优点: + +1. 由于不同数据采集点产生数据的过程完全独立,每个数据采集点的数据源是唯一的,一张表也就只有一个写入者,这样就可采用无锁方式来写,写入速度就能大幅提升。 +2. 对于一个数据采集点而言,其产生的数据是按照时间排序的,因此写的操作可用追加的方式实现,进一步大幅提高数据写入速度。 +3. 一个数据采集点的数据是以块为单位连续存储的。如果读取一个时间段的数据,它能大幅减少随机读取操作,成数量级的提升读取和查询速度。 +4. 一个数据块内部,采用列式存储,对于不同数据类型,采用不同压缩算法,而且由于一个数据采集点的采集量的变化是缓慢的,压缩率更高。 + +如果采用传统的方式,将多个数据采集点的数据写入一张表,由于网络延时不可控,不同数据采集点的数据到达服务器的时序是无法保证的,写入操作是要有锁保护的,而且一个数据采集点的数据是难以保证连续存储在一起的。**采用一个数据采集点一张表的方式,能最大程度的保证单个数据采集点的插入和查询的性能是最优的。** + +TDengine 建议用数据采集点的名字(如上表中的 D1001)来做表名。每个数据采集点可能同时采集多个采集量(如上表中的 current,voltage,phase),每个采集量对应一张表中的一列,数据类型可以是整型、浮点型、字符串等。除此之外,表的第一列必须是时间戳,即数据类型为 timestamp。对采集量,TDengine 将自动按照时间戳建立索引,但对采集量本身不建任何索引。数据用列式存储方式保存。 + +对于复杂的设备,比如汽车,它有多个数据采集点,那么就需要为一台汽车建立多张表。 + +## 超级表 (STable) + +由于一个数据采集点一张表,导致表的数量巨增,难以管理,而且应用经常需要做采集点之间的聚合操作,聚合的操作也变得复杂起来。为解决这个问题,TDengine 引入超级表(Super Table,简称为 STable)的概念。 + +超级表是指某一特定类型的数据采集点的集合。同一类型的数据采集点,其表的结构是完全一样的,但每个表(数据采集点)的静态属性(标签)是不一样的。描述一个超级表(某一特定类型的数据采集点的集合),除需要定义采集量的表结构之外,还需要定义其标签的 schema,标签的数据类型可以是整数、浮点数、字符串,标签可以有多个,可以事后增加、删除或修改。如果整个系统有 N 个不同类型的数据采集点,就需要建立 N 个超级表。 + +在 TDengine 的设计里,**表用来代表一个具体的数据采集点,超级表用来代表一组相同类型的数据采集点集合**。 + +## 子表 (Subtable) + +当为某个具体数据采集点创建表时,用户可以使用超级表的定义做模板,同时指定该具体采集点(表)的具体标签值来创建该表。**通过超级表创建的表称之为子表**。正常的表与子表的差异在于: + +1. 子表就是表,因此所有正常表的SQL操作都可以在子表上执行。 +2. 子表在正常表的基础上有扩展,它是带有静态标签的,而且这些标签可以事后增加、删除、修改,而正常的表没有。 +3. 子表一定属于一张超级表,但普通表不属于任何超级表 +4. 普通表无法转为子表,子表也无法转为普通表。 + +超级表与与基于超级表建立的子表之间的关系表现在: + +1. 一张超级表包含有多张子表,这些子表具有相同的采集量 schema,但带有不同的标签值。 +2. 不能通过子表调整数据或标签的模式,对于超级表的数据模式修改立即对所有的子表生效。 +3. 超级表只定义一个模板,自身不存储任何数据或标签信息。因此,不能向一个超级表写入数据,只能将数据写入子表中。 + +查询既可以在表上进行,也可以在超级表上进行。针对超级表的查询,TDengine 将把所有子表中的数据视为一个整体数据集进行处理,会先把满足标签过滤条件的表从超级表中找出来,然后再扫描这些表的时序数据,进行聚合操作,这样需要扫描的数据集会大幅减少,从而显著提高查询的性能。本质上,TDengine 通过对超级表查询的支持,实现了多个同类数据采集点的高效聚合。 + +TDengine系统建议给一个数据采集点建表,需要通过超级表建表,而不是建普通表。 + +## 库 (database) + +库是指一组表的集合。TDengine 容许一个运行实例有多个库,而且每个库可以配置不同的存储策略。不同类型的数据采集点往往具有不同的数据特征,包括数据采集频率的高低,数据保留时间的长短,副本的数目,数据块的大小,是否允许更新数据等等。为了在各种场景下 TDengine 都能最大效率的工作,TDengine 建议将不同数据特征的超级表创建在不同的库里。 + +一个库里,可以有一到多个超级表,但一个超级表只属于一个库。一个超级表所拥有的子表全部存在一个库里。 + +## FQDN & End Point + +FQDN (fully qualified domain name, 完全限定域名)是 Internet 上特定计算机或主机的完整域名。FQDN 由两部分组成:主机名和域名。例如,假设邮件服务器的 FQDN 可能是 mail.tdengine.com。主机名是 mail,主机位于域名 tdengine.com 中。DNS(Domain Name System),负责将 FQDN 翻译成 IP,是互联网应用的寻址方式。对于没有 DNS 的系统,可以通过配置 hosts 文件来解决。 + +TDengine 集群的每个节点是由 End Point 来唯一标识的,End Point 是由 FQDN 外加 Port 组成,比如 h1.tdengine.com:6030。这样当 IP 发生变化的时候,我们依然可以使用 FQDN 来动态找到节点,不需要更改集群的任何配置。而且采用 FQDN,便于内网和外网对同一个集群的统一访问。 + +TDengine 不建议采用直接的 IP 地址访问集群,不利于管理。不了解 FQDN 概念,请看博文[《一篇文章说清楚 TDengine 的 FQDN》](https://www.taosdata.com/blog/2020/09/11/1824.html)。 diff --git a/docs-cn/05-get-started/_apt_get_install.mdx b/docs-cn/05-get-started/_apt_get_install.mdx new file mode 100644 index 0000000000..b1bc4a1351 --- /dev/null +++ b/docs-cn/05-get-started/_apt_get_install.mdx @@ -0,0 +1,26 @@ +可以使用 apt-get 工具从官方仓库安装。 + +**安装包仓库** + +``` +wget -qO - http://repos.taosdata.com/tdengine.key | sudo apt-key add - +echo "deb [arch=amd64] http://repos.taosdata.com/tdengine-stable stable main" | sudo tee /etc/apt/sources.list.d/tdengine-stable.list +``` + +如果安装 Beta 版需要安装包仓库 + +``` +echo "deb [arch=amd64] http://repos.taosdata.com/tdengine-beta beta main" | sudo tee /etc/apt/sources.list.d/tdengine-beta.list +``` + +**使用 apt-get 命令安装** + +``` +sudo apt-get update +apt-cache policy tdengine +sudo apt-get install tdengine +``` + +:::tip +apt-get 方式只适用于 Debian 或 Ubuntu 系统 +:::: diff --git a/docs-cn/05-get-started/_category_.yml b/docs-cn/05-get-started/_category_.yml new file mode 100644 index 0000000000..b2348fade6 --- /dev/null +++ b/docs-cn/05-get-started/_category_.yml @@ -0,0 +1 @@ +label: 立即开始 diff --git a/docs-cn/05-get-started/_pkg_install.mdx b/docs-cn/05-get-started/_pkg_install.mdx new file mode 100644 index 0000000000..83c987af8b --- /dev/null +++ b/docs-cn/05-get-started/_pkg_install.mdx @@ -0,0 +1,17 @@ +import PkgList from "/components/PkgList"; + +TDengine 的安装非常简单,从下载到安装成功仅仅只要几秒钟。 + +为方便使用,从 2.4.0.10 开始,标准的服务端安装包包含了 taos、taosd、taosAdapter、taosdump、taosBenchmark、TDinsight 安装脚本和示例代码;如果您只需要用到服务端程序和客户端连接的 C/C++ 语言支持,也可以仅下载 lite 版本的安装包。 + +在安装包格式上,我们提供 tar.gz, rpm 和 deb 格式,为企业客户提供 tar.gz 格式安装包,以方便在特定操作系统上使用。需要注意的是,rpm 和 deb 包不含 taosdump、taosBenchmark 和 TDinsight 安装脚本,这些工具需要通过安装 taosTool 包获得。 + +发布版本包括稳定版和 Beta 版,Beta 版含有更多新功能。正式上线或测试建议安装稳定版。您可以根据需要选择下载: + + + +具体的安装方法,请参见[安装包的安装和卸载](/operation/pkg-install)。 + +下载其他组件、最新 Beta 版及之前版本的安装包,请点击[这里](https://www.taosdata.com/all-downloads) + +查看 Release Notes, 请点击[这里](https://github.com/taosdata/TDengine/releases) diff --git a/docs-cn/05-get-started/index.md b/docs-cn/05-get-started/index.md new file mode 100644 index 0000000000..458df90916 --- /dev/null +++ b/docs-cn/05-get-started/index.md @@ -0,0 +1,173 @@ +--- +title: 立即开始 +description: '从 Docker,安装包或使用 apt-get 快速安装 TDengine, 通过命令行程序TAOS CLI和工具 taosdemo 快速体验 TDengine 功能' +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import PkgInstall from "./\_pkg_install.mdx"; +import AptGetInstall from "./\_apt_get_install.mdx"; + +## 安装 + +TDengine 完整的软件包包括服务端(taosd)、用于与第三方系统对接并提供 RESTful 接口的 taosAdapter、应用驱动(taosc)、命令行程序 (CLI,taos) 和一些工具软件,目前 2.X 版服务端 taosd 和 taosAdapter 仅在 Linux 系统上安装和运行,后续将支持 Windows、macOS 等系统。应用驱动 taosc 与 TDengine CLI 可以在 Windows 或 Linux 上安装和运行。TDengine 除了提供多种语言的连接器之外,还通过 [taosAdapter](/reference/taosadapter) 提供 [RESTful 接口](/reference/rest-api)。但在 2.4 之前的版本中没有 taosAdapter,RESTful 接口是由 taosd 内置的 HTTP 服务提供的。 + +TDengine 支持 X64/ARM64/MIPS64/Alpha64 硬件平台,后续将支持 ARM32、RISC-V 等 CPU 架构。 + + + +如果已经安装了 docker, 只需执行下面的命令。 + +```shell +docker run -d -p 6030-6049:6030-6049 -p 6030-6049:6030-6049/udp tdengine/tdengine +``` + +确定该容器已经启动并且在正常运行 + +```shell +docker ps +``` + +进入该容器并执行 bash + +```shell +docker exec -it bash +``` + +然后就可以执行相关的 Linux 命令操作和访问 TDengine + +详细操作方法请参照 [通过 Docker 快速体验 TDengine](/train-faq/docker)。 + +:::info +从 2.4.0.10 开始,除 taosd 以外,Docker 镜像还包含:taos、taosAdapter、taosdump、taosBenchmark、TDinsight 安装脚本和示例代码。启动 Docker 容器时,将同时启动 taosAdapter 和 taosd,实现对 RESTful 的支持。 + +::: + + + + + + + + + + +如果您希望对 TDengine 贡献代码或对内部实现感兴趣,请参考我们的 [TDengine GitHub 主页](https://github.com/taosdata/TDengine) 下载源码构建和安装. + +下载其他组件、最新 Beta 版及之前版本的安装包,请点击[这里](https://www.taosdata.com/cn/all-downloads/)。 + + + + +## 启动 + +安装后,请使用 `systemctl` 命令来启动 TDengine 的服务进程。 + +```bash +systemctl start taosd +``` + +检查服务是否正常工作: + +```bash +systemctl status taosd +``` + +如果 TDengine 服务正常工作,那么您可以通过 TDengine 的命令行程序 `taos` 来访问并体验 TDengine。 + +:::info + +- systemctl 命令需要 _root_ 权限来运行,如果您非 _root_ 用户,请在命令前添加 sudo 。 +- 为更好的获得产品反馈,改善产品,TDengine 会采集基本的使用信息,但您可以修改系统配置文件 taos.cfg 里的配置参数 telemetryReporting,将其设为 0,就可将其关闭。 +- TDengine 采用 FQDN(一般就是 hostname)作为节点的 ID,为保证正常运行,需要给运行 taosd 的服务器配置好 FQDN,在 TDengine CLI 或应用运行的机器配置好 DNS 服务或 hosts 文件,保证 FQDN 能够解析。 +- `systemctl stop taosd` 指令在执行后并不会马上停止 TDengine 服务,而是会等待系统中必要的落盘工作正常完成。在数据量很大的情况下,这可能会消耗较长时间。 + +TDengine 支持在使用 [`systemd`](https://en.wikipedia.org/wiki/Systemd) 做进程服务管理的 Linux 系统上安装,用 `which systemctl` 命令来检测系统中是否存在 `systemd` 包: + +```bash +which systemctl +``` + +如果系统中不支持 `systemd`,也可以用手动运行 `/usr/local/taos/bin/taosd` 方式启动 TDengine 服务。 + +:::note + +## TDengine 命令行 (CLI) + +为便于检查 TDengine 的状态,执行数据库 (Database) 的各种即席(Ad Hoc)查询,TDengine 提供一命令行应用程序(以下简称为 TDengine CLI) taos。要进入 TDengine 命令行,您只要在安装有 TDengine 的 Linux 终端执行 `taos` 即可。 + +```bash +taos +``` + +如果连接服务成功,将会打印出欢迎消息和版本信息。如果失败,则会打印错误消息出来(请参考 [FAQ](/train-faq/faq) 来解决终端连接服务端失败的问题)。 TDengine CLI 的提示符号如下: + +```cmd +taos> +``` + +在 TDengine CLI 中,用户可以通过 SQL 命令来创建/删除数据库、表等,并进行数据库(database)插入查询操作。在终端中运行的 SQL 语句需要以分号结束来运行。示例: + +```sql +create database demo; +use demo; +create table t (ts timestamp, speed int); +insert into t values ('2019-07-15 00:00:00', 10); +insert into t values ('2019-07-15 01:00:00', 20); +select * from t; + ts | speed | +======================================== + 2019-07-15 00:00:00.000 | 10 | + 2019-07-15 01:00:00.000 | 20 | +Query OK, 2 row(s) in set (0.003128s) +``` + +除执行 SQL 语句外,系统管理员还可以从 TDengine CLI 进行检查系统运行状态、添加删除用户账号等操作。TAOS CLI 连同应用驱动也可以独立安装在 Linux 或 Windows 机器上运行,更多细节请参考 [这里](../reference/taos-shell/) + +## 使用 taosBenchmark 体验写入速度 + +启动 TDengine 的服务,在 Linux 终端执行 `taosBenchmark` (曾命名为 `taosdemo`): + +```bash +taosBenchmark +``` + +该命令将在数据库 test 下面自动创建一张超级表 meters,该超级表下有 1 万张表,表名为 "d0" 到 "d9999",每张表有 1 万条记录,每条记录有 (ts, current, voltage, phase) 四个字段,时间戳从 "2017-07-14 10:40:00 000" 到 "2017-07-14 10:40:09 999",每张表带有标签 location 和 groupId,groupId 被设置为 1 到 10, location 被设置为 "beijing" 或者 "shanghai"。 + +这条命令很快完成 1 亿条记录的插入。具体时间取决于硬件性能,即使在一台普通的 PC 服务器往往也仅需十几秒。 + +taosBenchmark 命令本身带有很多选项,配置表的数目、记录条数等等,您可以设置不同参数进行体验,请执行 `taosBenchmark --help` 详细列出。taosBenchmark 详细使用方法请参照 [如何使用 taosBenchmark 对 TDengine 进行性能测试](https://www.taosdata.com/2021/10/09/3111.html)。 + +## 使用 TDengine CLI 体验查询速度 + +使用上述 taosBenchmark 插入数据后,可以在 TDengine CLI 输入查询命令,体验查询速度。 + +查询超级表下记录总条数: + +```sql +taos> select count(*) from test.meters; +``` + +查询 1 亿条记录的平均值、最大值、最小值等: + +```sql +taos> select avg(current), max(voltage), min(phase) from test.meters; +``` + +查询 location="beijing" 的记录总条数: + +```sql +taos> select count(*) from test.meters where location="beijing"; +``` + +查询 groupId=10 的所有记录的平均值、最大值、最小值等: + +```sql +taos> select avg(current), max(voltage), min(phase) from test.meters where groupId=10; +``` + +对表 d10 按 10s 进行平均值、最大值和最小值聚合统计: + +```sql +taos> select avg(current), max(voltage), min(phase) from test.d10 interval(10s); +``` diff --git a/docs-cn/07-develop/01-connect/_category_.yml b/docs-cn/07-develop/01-connect/_category_.yml new file mode 100644 index 0000000000..f75d563ac9 --- /dev/null +++ b/docs-cn/07-develop/01-connect/_category_.yml @@ -0,0 +1 @@ +label: 建立连接 diff --git a/docs-cn/07-develop/01-connect/_connect_c.mdx b/docs-cn/07-develop/01-connect/_connect_c.mdx new file mode 100644 index 0000000000..9cd8669561 --- /dev/null +++ b/docs-cn/07-develop/01-connect/_connect_c.mdx @@ -0,0 +1,3 @@ +```c title="原生连接" +{{#include docs-examples/c/connect_example.c}} +``` diff --git a/docs-cn/07-develop/01-connect/_connect_cs.mdx b/docs-cn/07-develop/01-connect/_connect_cs.mdx new file mode 100644 index 0000000000..821820e8fe --- /dev/null +++ b/docs-cn/07-develop/01-connect/_connect_cs.mdx @@ -0,0 +1,8 @@ +```csharp title="原生连接" +{{#include docs-examples/csharp/ConnectExample.cs}} +``` + +:::info +C# 连接器目前只支持原生连接。 + +::: diff --git a/docs-cn/07-develop/01-connect/_connect_go.mdx b/docs-cn/07-develop/01-connect/_connect_go.mdx new file mode 100644 index 0000000000..478768caaa --- /dev/null +++ b/docs-cn/07-develop/01-connect/_connect_go.mdx @@ -0,0 +1,17 @@ +#### 使用数据库访问统一接口 + +```go title="原生连接" +{{#include docs-examples/go/connect/cgoexample/main.go}} +``` + +```go title="REST 连接" +{{#include docs-examples/go/connect/restexample/main.go}} +``` + +#### 使用高级封装 + +也可以使用 driver-go 的 af 包建立连接。这个模块封装了 TDengine 的高级功能, 如:参数绑定、订阅等。 + +```go title="使用 af 包建立原生连接" +{{#include docs-examples/go/connect/afconn/main.go}} +``` diff --git a/docs-cn/07-develop/01-connect/_connect_java.mdx b/docs-cn/07-develop/01-connect/_connect_java.mdx new file mode 100644 index 0000000000..635f39ceb2 --- /dev/null +++ b/docs-cn/07-develop/01-connect/_connect_java.mdx @@ -0,0 +1,15 @@ +```java title="原生连接" +{{#include docs-examples/java/src/main/java/com/taos/example/JNIConnectExample.java}} +``` + +```java title="REST 连接" +{{#include docs-examples/java/src/main/java/com/taos/example/RESTConnectExample.java:main}} +``` + +使用 REST 连接时,如果查询数据量比较大,还可开启批量拉取功能。 + +```java title="开启批量拉取功能" {4} +{{#include docs-examples/java/src/main/java/com/taos/example/WSConnectExample.java:main}} +``` + +更多连接参数配置,参考[Java 连接器](/reference/connector/java) diff --git a/docs-cn/07-develop/01-connect/_connect_node.mdx b/docs-cn/07-develop/01-connect/_connect_node.mdx new file mode 100644 index 0000000000..199a6e3faa --- /dev/null +++ b/docs-cn/07-develop/01-connect/_connect_node.mdx @@ -0,0 +1,7 @@ +```js title="原生连接" +{{#include docs-examples/node/nativeexample/connect.js}} +``` + +```js title="REST 连接" +{{#include docs-examples/node/restexample/connect.js}} +``` diff --git a/docs-cn/07-develop/01-connect/_connect_php.mdx b/docs-cn/07-develop/01-connect/_connect_php.mdx new file mode 100644 index 0000000000..2431df2a72 --- /dev/null +++ b/docs-cn/07-develop/01-connect/_connect_php.mdx @@ -0,0 +1,3 @@ +```php title="原生连接" +{{#include docs-examples/php/connect.php}} +``` diff --git a/docs-cn/07-develop/01-connect/_connect_python.mdx b/docs-cn/07-develop/01-connect/_connect_python.mdx new file mode 100644 index 0000000000..c0043c752e --- /dev/null +++ b/docs-cn/07-develop/01-connect/_connect_python.mdx @@ -0,0 +1,3 @@ +```python title="原生连接" +{{#include docs-examples/python/connect_example.py}} +``` diff --git a/docs-cn/07-develop/01-connect/_connect_r.mdx b/docs-cn/07-develop/01-connect/_connect_r.mdx new file mode 100644 index 0000000000..8aab6121a6 --- /dev/null +++ b/docs-cn/07-develop/01-connect/_connect_r.mdx @@ -0,0 +1,3 @@ +```r title="原生连接" +{{#include docs-examples/R/connect_native.r:demo}} +``` diff --git a/docs-cn/07-develop/01-connect/_connect_rust.mdx b/docs-cn/07-develop/01-connect/_connect_rust.mdx new file mode 100644 index 0000000000..9e64724c17 --- /dev/null +++ b/docs-cn/07-develop/01-connect/_connect_rust.mdx @@ -0,0 +1,8 @@ +```rust title="原生连接/REST 连接" +{{#include docs-examples/rust/nativeexample/examples/connect.rs}} +``` + +:::note +对于 Rust 连接器, 连接方式的不同只体现在使用的特性不同。如果启用了 "rest" 特性,那么只有 RESTful 的实现会被编译进来。 + +::: diff --git a/docs-cn/07-develop/01-connect/index.md b/docs-cn/07-develop/01-connect/index.md new file mode 100644 index 0000000000..ebdefc77b9 --- /dev/null +++ b/docs-cn/07-develop/01-connect/index.md @@ -0,0 +1,284 @@ +--- +title: 建立连接 +description: "本节介绍如何使用连接器建立与 TDengine 的连接,给出连接器安装、连接的简单说明。" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import ConnJava from "./_connect_java.mdx"; +import ConnGo from "./_connect_go.mdx"; +import ConnRust from "./_connect_rust.mdx"; +import ConnNode from "./_connect_node.mdx"; +import ConnPythonNative from "./_connect_python.mdx"; +import ConnCSNative from "./_connect_cs.mdx"; +import ConnC from "./_connect_c.mdx"; +import ConnR from "./_connect_r.mdx"; +import ConnPHP from "./_connect_php.mdx"; +import InstallOnWindows from "../../14-reference/03-connector/_linux_install.mdx"; +import InstallOnLinux from "../../14-reference/03-connector/_windows_install.mdx"; +import VerifyLinux from "../../14-reference/03-connector/_verify_linux.mdx"; +import VerifyWindows from "../../14-reference/03-connector/_verify_windows.mdx"; + +TDengine 提供了丰富的应用程序开发接口,为了便于用户快速开发自己的应用,TDengine 支持了多种编程语言的连接器,其中官方连接器包括支持 C/C++、Java、Python、Go、Node.js、C#、Rust、Lua(社区贡献)和 PHP (社区贡献)的连接器。这些连接器支持使用原生接口(taosc)和 REST 接口(部分语言暂不支持)连接 TDengine 集群。社区开发者也贡献了多个非官方连接器,例如 ADO.NET 连接器、Lua 连接器和 PHP 连接器。 + +## 连接器建立连接的方式 + +连接器建立连接的方式,TDengine 提供两种: + +1. 通过 taosAdapter 组件提供的 REST API 建立与 taosd 的连接,这种连接方式下文中简称“REST 连接” +2. 通过客户端驱动程序 taosc 直接与服务端程序 taosd 建立连接,这种连接方式下文中简称“原生连接”。 + +无论使用何种方式建立连接,连接器都提供了相同或相似的 API 操作数据库,都可以执行 SQL 语句,只是初始化连接的方式稍有不同,用户在使用上不会感到什么差别。 + +关键不同点在于: + +1. 使用 REST 连接,用户无需安装客户端驱动程序 taosc,具有跨平台易用的优势,但性能要下降 30%左右。 +2. 使用原生连接可以体验 TDengine 的全部功能,如[参数绑定接口](/reference/connector/cpp#参数绑定-api)、[订阅](reference/connector/cpp#数据订阅接口)等等。 + +## 安装客户端驱动 taosc + +如果选择原生连接,而且应用程序不在 TDengine 同一台服务器上运行,你需要先安装客户端驱动,否则可以跳过此一步。为避免客户端驱动和服务端不兼容,请使用一致的版本。 + +### 安装步骤 + + + + + + + + + + +### 安装验证 + +以上安装和配置完成后,并确认 TDengine 服务已经正常启动运行,此时可以执行安装包里带有的 TDengine 命令行程序 taos 进行登录。 + + + + + + + + + + +## 安装连接器 + + + + +如果使用 maven 管理项目,只需在 pom.xml 中加入以下依赖。 + +```xml + + com.taosdata.jdbc + taos-jdbcdriver + 2.0.38 + +``` + + + + +使用 `pip` 从 PyPI 安装: + +``` +pip install taospy +``` + +从 Git URL 安装: + +``` +pip install git+https://github.com/taosdata/taos-connector-python.git +``` + + + + +编辑 `go.mod` 添加 `driver-go` 依赖即可。 + +```go-mod title=go.mod +module goexample + +go 1.17 + +require github.com/taosdata/driver-go/v2 develop +``` + +:::note +driver-go 使用 cgo 封装了 taosc 的 API。cgo 需要使用 gcc 编译 C 的源码。因此需要确保你的系统上有 gcc。 + +::: + + + + +编辑 `Cargo.toml` 添加 `libtaos` 依赖即可。 + +```toml title=Cargo.toml +[dependencies] +libtaos = { version = "0.4.2"} +``` + +:::info +Rust 连接器通过不同的特性区分不同的连接方式。如果要建立 REST 连接,需要开启 `rest` 特性: + +```toml +libtaos = { version = "*", features = ["rest"] } +``` + +::: + + + + +Node.js 连接器通过不同的包提供不同的连接方式。 + +1. 安装 Node.js 原生连接器 + + ``` + npm i td2.0-connector + ``` + +:::note +推荐 Node 版本大于等于 `node-v12.8.0` 小于 `node-v13.0.0` +::: + +2. 安装 Node.js REST 连接器 + + ``` + npm i td2.0-rest-connector + ``` + + + + +编辑项目配置文件中添加 [TDengine.Connector](https://www.nuget.org/packages/TDengine.Connector/) 的引用即可: + +```xml title=csharp.csproj {12} + + + + Exe + net6.0 + enable + enable + TDengineExample.AsyncQueryExample + + + + + + + +``` + +也可通过 dotnet 命令添加: + +``` +dotnet add package TDengine.Connector +``` + +:::note +以下示例代码,均基于 dotnet6.0,如果使用其它版本,可能需要做适当调整。 + +::: + + + + +1. 下载 [taos-jdbcdriver-version-dist.jar](https://repo1.maven.org/maven2/com/taosdata/jdbc/taos-jdbcdriver/2.0.38/)。 +2. 安装 R 的依赖包`RJDBC`: + +```R +install.packages("RJDBC") +``` + + + + +如果已经安装了 TDengine 服务端软件或 TDengine 客户端驱动 taosc, 那么已经安装了 C 连接器,无需额外操作。 +
+ +
+ + +**下载代码并解压:** + +```shell +curl -L -o php-tdengine.tar.gz https://github.com/Yurunsoft/php-tdengine/archive/refs/tags/v1.0.2.tar.gz \ +&& mkdir php-tdengine \ +&& tar -xzf php-tdengine.tar.gz -C php-tdengine --strip-components=1 +``` + +> 版本 `v1.0.0` 可替换为任意更新的版本,可在 Release 中查看最新版本。 + +**非 Swoole 环境:** + +```shell +phpize && ./configure && make -j && make install +``` + +**手动指定 TDengine 目录:** + +```shell +phpize && ./configure --with-tdengine-dir=/usr/local/Cellar/tdengine/2.4.0.0 && make -j && make install +``` + +> `--with-tdengine-dir=` 后跟上 TDengine 目录。 +> 适用于默认找不到的情况,或者 macOS 系统用户。 + +**Swoole 环境:** + +```shell +phpize && ./configure --enable-swoole && make -j && make install +``` + +**启用扩展:** + +方法一:在 `php.ini` 中加入 `extension=tdengine` + +方法二:运行带参数 `php -d extension=tdengine test.php` + + +
+ +## 建立连接 + +在执行这一步之前,请确保有一个正在运行的,且可以访问到的 TDengine,而且服务端的 FQDN 配置正确。以下示例代码,都假设 TDengine 安装在本机,且 FQDN(默认 localhost) 和 serverPort(默认 6030) 都使用默认配置。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +:::tip +如果建立连接失败,大部分情况下是 FQDN 或防火墙的配置不正确,详细的排查方法请看[《常见问题及反馈》](https://docs.taosdata.com/train-faq/faq)中的“遇到错误 Unable to establish connection, 我怎么办?” + +::: diff --git a/docs-cn/07-develop/02-model/_category_.yml b/docs-cn/07-develop/02-model/_category_.yml new file mode 100644 index 0000000000..e5dae7c27c --- /dev/null +++ b/docs-cn/07-develop/02-model/_category_.yml @@ -0,0 +1,2 @@ +label: 数据建模 + diff --git a/docs-cn/07-develop/02-model/index.mdx b/docs-cn/07-develop/02-model/index.mdx new file mode 100644 index 0000000000..a060e3c84b --- /dev/null +++ b/docs-cn/07-develop/02-model/index.mdx @@ -0,0 +1,86 @@ +--- +title: TDengine 数据建模 +--- + +TDengine 采用类关系型数据模型,需要建库、建表。因此对于一个具体的应用场景,需要考虑库、超级表和普通表的设计。本节不讨论细致的语法规则,只介绍概念。 + +关于数据建模请参考[视频教程](https://www.taosdata.com/blog/2020/11/11/1945.html)。 + +## 创建库 + +不同类型的数据采集点往往具有不同的数据特征,包括数据采集频率的高低,数据保留时间的长短,副本的数目,数据块的大小,是否允许更新数据等等。为了在各种场景下 TDengine 都能最大效率的工作,TDengine 建议将不同数据特征的表创建在不同的库里,因为每个库可以配置不同的存储策略。创建一个库时,除 SQL 标准的选项外,还可以指定保留时长、副本数、内存块个数、时间精度、文件块里最大最小记录条数、是否压缩、一个数据文件覆盖的天数等多种参数。比如: + +```sql +CREATE DATABASE power KEEP 365 DAYS 10 BLOCKS 6 UPDATE 1; +``` + +上述语句将创建一个名为 power 的库,这个库的数据将保留 365 天(超过 365 天将被自动删除),每 10 天一个数据文件,内存块数为 6,允许更新数据。详细的语法及参数请见 [数据库管理](/taos-sql/database) 章节。 + +创建库之后,需要使用 SQL 命令 `USE` 将当前库切换过来,例如: + +```sql +USE power; +``` + +将当前连接里操作的库换为 power,否则对具体表操作前,需要使用“库名.表名”来指定库的名字。 + +:::note + +- 任何一张表或超级表必须属于某个库,在创建表之前,必须先创建库。 +- 处于两个不同库的表是不能进行 JOIN 操作的。 +- 创建并插入记录、查询历史记录的时候,均需要指定时间戳。 + +::: + +## 创建超级表 + +一个物联网系统,往往存在多种类型的设备,比如对于电网,存在智能电表、变压器、母线、开关等等。为便于多表之间的聚合,使用 TDengine, 需要对每个类型的数据采集点创建一个超级表。以[表 1](/tdinternal/arch#model_table1) 中的智能电表为例,可以使用如下的 SQL 命令创建超级表: + +```sql +CREATE STABLE meters (ts timestamp, current float, voltage int, phase float) TAGS (location binary(64), groupId int); +``` + +:::note +这一指令中的 STABLE 关键字,在 2.0.15 之前的版本中需写作 TABLE 。 +::: + +与创建普通表一样,创建超级表时,需要提供表名(示例中为 meters),表结构 Schema,即数据列的定义。第一列必须为时间戳(示例中为 ts),其他列为采集的物理量(示例中为 current, voltage, phase),数据类型可以为整型、浮点型、字符串等。除此之外,还需要提供标签的 schema (示例中为 location, groupId),标签的数据类型可以为整型、浮点型、字符串等。采集点的静态属性往往可以作为标签,比如采集点的地理位置、设备型号、设备组 ID、管理员 ID 等等。标签的 schema 可以事后增加、删除、修改。具体定义以及细节请见 [TAOS SQL 的超级表管理](/taos-sql/stable) 章节。 + +每一种类型的数据采集点需要建立一个超级表,因此一个物联网系统,往往会有多个超级表。对于电网,我们就需要对智能电表、变压器、母线、开关等都建立一个超级表。在物联网中,一个设备就可能有多个数据采集点(比如一台风力发电的风机,有的采集点采集电流、电压等电参数,有的采集点采集温度、湿度、风向等环境参数),这个时候,对这一类型的设备,需要建立多张超级表。 + +一张超级表最多容许 4096 列 (在 2.1.7.0 版本之前,列数限制为 1024 列),如果一个采集点采集的物理量个数超过 4096,需要建多张超级表来处理。一个系统可以有多个 DB,一个 DB 里可以有一到多个超级表。 + +## 创建表 + +TDengine 对每个数据采集点需要独立建表。与标准的关系型数据库一样,一张表有表名,Schema,但除此之外,还可以带有一到多个标签。创建时,需要使用超级表做模板,同时指定标签的具体值。以[表 1](/tdinternal/arch#model_table1)中的智能电表为例,可以使用如下的 SQL 命令建表: + +```sql +CREATE TABLE d1001 USING meters TAGS ("Beijing.Chaoyang", 2); +``` + +其中 d1001 是表名,meters 是超级表的表名,后面紧跟标签 Location 的具体标签值 ”Beijing.Chaoyang",标签 groupId 的具体标签值 2。虽然在创建表时,需要指定标签值,但可以事后修改。详细细则请见 [TAOS SQL 的表管理](/taos-sql/table) 章节。 + +:::warning +目前 TDengine 没有从技术层面限制使用一个 database (db1) 的超级表作为模板建立另一个 database (db2) 的子表,后续会禁止这种用法,不建议使用这种方法建表。 + +::: + +TDengine 建议将数据采集点的全局唯一 ID 作为表名(比如设备序列号)。但对于有的场景,并没有唯一的 ID,可以将多个 ID 组合成一个唯一的 ID。不建议将具有唯一性的 ID 作为标签值。 + +### 自动建表 + +在某些特殊场景中,用户在写数据时并不确定某个数据采集点的表是否存在,此时可在写入数据时使用自动建表语法来创建不存在的表,若该表已存在则不会建立新表且后面的 USING 语句被忽略。比如: + +```sql +INSERT INTO d1001 USING meters TAGS ("Beijng.Chaoyang", 2) VALUES (now, 10.2, 219, 0.32); +``` + +上述 SQL 语句将记录`(now, 10.2, 219, 0.32)`插入表 d1001。如果表 d1001 还未创建,则使用超级表 meters 做模板自动创建,同时打上标签值 `"Beijing.Chaoyang", 2`。 + +关于自动建表的详细语法请参见 [插入记录时自动建表](/taos-sql/insert#插入记录时自动建表) 章节。 + +## 多列模型 vs 单列模型 + +TDengine 支持多列模型,只要物理量是一个数据采集点同时采集的(时间戳一致),这些量就可以作为不同列放在一张超级表里。但还有一种极限的设计,单列模型,每个采集的物理量都单独建表,因此每种类型的物理量都单独建立一超级表。比如电流、电压、相位,就建三张超级表。 + +TDengine 建议尽可能采用多列模型,因为插入效率以及存储效率更高。但对于有些场景,一个采集点的采集量的种类经常变化,这个时候,如果采用多列模型,就需要频繁修改超级表的结构定义,让应用变的复杂,这个时候,采用单列模型会显得更简单。 diff --git a/docs-cn/07-develop/03-insert-data/01-sql-writing.mdx b/docs-cn/07-develop/03-insert-data/01-sql-writing.mdx new file mode 100644 index 0000000000..e63ffce6dd --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/01-sql-writing.mdx @@ -0,0 +1,137 @@ +--- +title: SQL 写入 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import JavaSQL from "./_java_sql.mdx"; +import JavaStmt from "./_java_stmt.mdx"; +import PySQL from "./_py_sql.mdx"; +import PyStmt from "./_py_stmt.mdx"; +import GoSQL from "./_go_sql.mdx"; +import GoStmt from "./_go_stmt.mdx"; +import RustSQL from "./_rust_sql.mdx"; +import RustStmt from "./_rust_stmt.mdx"; +import NodeSQL from "./_js_sql.mdx"; +import NodeStmt from "./_js_stmt.mdx"; +import CsSQL from "./_cs_sql.mdx"; +import CsStmt from "./_cs_stmt.mdx"; +import CSQL from "./_c_sql.mdx"; +import CStmt from "./_c_stmt.mdx"; +import PhpSQL from "./_php_sql.mdx"; +import PhpStmt from "./_php_stmt.mdx"; + +## SQL 写入简介 + +应用通过连接器执行 INSERT 语句来插入数据,用户还可以通过 TAOS Shell,手动输入 INSERT 语句插入数据。 + +### 一次写入一条 +下面这条 INSERT 就将一条记录写入到表 d1001 中: + +```sql +INSERT INTO d1001 VALUES (1538548685000, 10.3, 219, 0.31); +``` + +### 一次写入多条 + +TDengine 支持一次写入多条记录,比如下面这条命令就将两条记录写入到表 d1001 中: + +```sql +INSERT INTO d1001 VALUES (1538548684000, 10.2, 220, 0.23) (1538548696650, 10.3, 218, 0.25); +``` + +### 一次写入多表 + +TDengine 也支持一次向多个表写入数据,比如下面这条命令就向 d1001 写入两条记录,向 d1002 写入一条记录: + +```sql +INSERT INTO d1001 VALUES (1538548685000, 10.3, 219, 0.31) (1538548695000, 12.6, 218, 0.33) d1002 VALUES (1538548696800, 12.3, 221, 0.31); +``` + +详细的 SQL INSERT 语法规则参考 [TAOS SQL 的数据写入](/taos-sql/insert)。 + +:::info + +- 要提高写入效率,需要批量写入。一批写入的记录条数越多,插入效率就越高。但一条记录不能超过 16K,一条 SQL 语句总长度不能超过 1M 。 +- TDengine 支持多线程同时写入,要进一步提高写入速度,一个客户端需要打开 20 个以上的线程同时写。但线程数达到一定数量后,无法再提高,甚至还会下降,因为线程频繁切换,带来额外开销。 + +::: + +:::warning + +- 对同一张表,如果新插入记录的时间戳已经存在,默认情形下(UPDATE=0)新记录将被直接抛弃,也就是说,在一张表里,时间戳必须是唯一的。如果应用自动生成记录,很有可能生成的时间戳是一样的,这样,成功插入的记录条数会小于应用插入的记录条数。如果在创建数据库时使用了 UPDATE 1 选项,插入相同时间戳的新记录将覆盖原有记录。 +- 写入的数据的时间戳必须大于当前时间减去配置参数 keep 的时间。如果 keep 配置为 3650 天,那么无法写入比 3650 天还早的数据。写入数据的时间戳也不能大于当前时间加配置参数 days。如果 days 为 2,那么无法写入比当前时间还晚 2 天的数据。 + +::: + +## 示例程序 + +### 普通 SQL 写入 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +:::note + +1. 无论 RESTful 方式建立连接还是本地驱动方式建立连接,以上示例代码都能正常工作。 +2. 唯一需要注意的是:由于 RESTful 接口无状态, 不能使用 `use db` 语句来切换数据库, 所以在上面示例中使用了`dbName.tbName`指定表名。 + +::: + +### 参数绑定写入 + +TDengine 也提供了支持参数绑定的 Prepare API,与 MySQL 类似,这些 API 目前也仅支持用问号 `?` 来代表待绑定的参数。从 2.1.1.0 和 2.1.2.0 版本开始,TDengine 大幅改进了参数绑定接口对数据写入(INSERT)场景的支持。这样在通过参数绑定接口写入数据时,就避免了 SQL 语法解析的资源消耗,从而在绝大多数情况下显著提升写入性能。 + +需要注意的是,只有使用原生连接的连接器,才能使用参数绑定功能。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs-cn/07-develop/03-insert-data/02-influxdb-line.mdx b/docs-cn/07-develop/03-insert-data/02-influxdb-line.mdx new file mode 100644 index 0000000000..dedd7f0e70 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/02-influxdb-line.mdx @@ -0,0 +1,70 @@ +--- +sidebar_label: InfluxDB 行协议 +title: InfluxDB 行协议 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import JavaLine from "./_java_line.mdx"; +import PyLine from "./_py_line.mdx"; +import GoLine from "./_go_line.mdx"; +import RustLine from "./_rust_line.mdx"; +import NodeLine from "./_js_line.mdx"; +import CsLine from "./_cs_line.mdx"; +import CLine from "./_c_line.mdx"; + +## 协议介绍 + +InfluxDB Line 协议采用一行字符串来表示一行数据。分为四部分: + +``` +measurement,tag_set field_set timestamp +``` + +- measurement 将作为超级表名。它与 tag_set 之间使用一个英文逗号来分隔。 +- tag_set 将作为标签数据,其格式形如 `=,=`,也即可以使用英文逗号来分隔多个标签数据。它与 field_set 之间使用一个半角空格来分隔。 +- field_set 将作为普通列数据,其格式形如 `=,=`,同样是使用英文逗号来分隔多个普通列的数据。它与 timestamp 之间使用一个半角空格来分隔。 +- timestamp 即本行数据对应的主键时间戳。 + +例如: + +``` +meters,location=Beijing.Haidian,groupid=2 current=13.4,voltage=223,phase=0.29 1648432611249500 +``` + +:::note + +- tag_set 中的所有的数据自动转化为 nchar 数据类型; +- field_set 中的每个数据项都需要对自身的数据类型进行描述, 比如 1.2f32 代表 float 类型的数值 1.2, 如果不带类型后缀会被当作 double 处理; +- timestamp 支持多种时间精度。写入数据的时候需要用参数指定时间精度,支持从小时到纳秒的 6 种时间精度。 + +::: + +要了解更多可参考:[InfluxDB Line 协议官方文档](https://docs.influxdata.com/influxdb/v2.0/reference/syntax/line-protocol/) 和 [TDengine 无模式写入参考指南](/reference/schemaless/#无模式写入行协议) + + +## 示例代码 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs-cn/07-develop/03-insert-data/03-opentsdb-telnet.mdx b/docs-cn/07-develop/03-insert-data/03-opentsdb-telnet.mdx new file mode 100644 index 0000000000..dfbe6efda6 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/03-opentsdb-telnet.mdx @@ -0,0 +1,84 @@ +--- +sidebar_label: OpenTSDB 行协议 +title: OpenTSDB 行协议 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import JavaTelnet from "./_java_opts_telnet.mdx"; +import PyTelnet from "./_py_opts_telnet.mdx"; +import GoTelnet from "./_go_opts_telnet.mdx"; +import RustTelnet from "./_rust_opts_telnet.mdx"; +import NodeTelnet from "./_js_opts_telnet.mdx"; +import CsTelnet from "./_cs_opts_telnet.mdx"; +import CTelnet from "./_c_opts_telnet.mdx"; + +## 协议介绍 + +OpenTSDB 行协议同样采用一行字符串来表示一行数据。OpenTSDB 采用的是单列模型,因此一行只能包含一个普通数据列。标签列依然可以有多个。分为四部分,具体格式约定如下: + +```txt + =[ =] +``` + +- metric 将作为超级表名。 +- timestamp 本行数据对应的时间戳。根据时间戳的长度自动识别时间精度。支持秒和毫秒两种时间精度 +- value 度量值,必须为一个数值。对应的列名也是 “value”。 +- 最后一部分是标签集, 用空格分隔不同标签, 所有标签自动转化为 nchar 数据类型; + +例如: + +```txt +meters.current 1648432611250 11.3 location=Beijing.Haidian groupid=3 +``` + +参考[OpenTSDB Telnet API文档](http://opentsdb.net/docs/build/html/api_telnet/put.html)。 + +## 示例代码 + + + + + + + + + + + + + + + + + + + + + + + + + +以上示例代码会自动创建 2 个超级表, 每个超级表有 4 条数据。 + +```cmd +taos> use test; +Database changed. + +taos> show stables; + name | created_time | columns | tags | tables | +============================================================================================ + meters.current | 2022-03-30 17:04:10.877 | 2 | 2 | 2 | + meters.voltage | 2022-03-30 17:04:10.882 | 2 | 2 | 2 | +Query OK, 2 row(s) in set (0.002544s) + +taos> select tbname, * from `meters.current`; + tbname | ts | value | groupid | location | +================================================================================================================================== + t_0e7bcfa21a02331c06764f275... | 2022-03-28 09:56:51.249 | 10.800000000 | 3 | Beijing.Haidian | + t_0e7bcfa21a02331c06764f275... | 2022-03-28 09:56:51.250 | 11.300000000 | 3 | Beijing.Haidian | + t_7e7b26dd860280242c6492a16... | 2022-03-28 09:56:51.249 | 10.300000000 | 2 | Beijing.Chaoyang | + t_7e7b26dd860280242c6492a16... | 2022-03-28 09:56:51.250 | 12.600000000 | 2 | Beijing.Chaoyang | +Query OK, 4 row(s) in set (0.005399s) +``` diff --git a/docs-cn/07-develop/03-insert-data/04-opentsdb-json.mdx b/docs-cn/07-develop/03-insert-data/04-opentsdb-json.mdx new file mode 100644 index 0000000000..5d445997d0 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/04-opentsdb-json.mdx @@ -0,0 +1,99 @@ +--- +sidebar_label: OpenTSDB JSON 格式协议 +title: OpenTSDB JSON 格式协议 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import JavaJson from "./_java_opts_json.mdx"; +import PyJson from "./_py_opts_json.mdx"; +import GoJson from "./_go_opts_json.mdx"; +import RustJson from "./_rust_opts_json.mdx"; +import NodeJson from "./_js_opts_json.mdx"; +import CsJson from "./_cs_opts_json.mdx"; +import CJson from "./_c_opts_json.mdx"; + +## 协议介绍 + +OpenTSDB JSON 格式协议采用一个 JSON 字符串表示一行或多行数据。例如: + +```json +[ + { + "metric": "sys.cpu.nice", + "timestamp": 1346846400, + "value": 18, + "tags": { + "host": "web01", + "dc": "lga" + } + }, + { + "metric": "sys.cpu.nice", + "timestamp": 1346846400, + "value": 9, + "tags": { + "host": "web02", + "dc": "lga" + } + } +] +``` + +与 OpenTSDB 行协议类似, metric 将作为超级表名, timestamp 表示时间戳,value 表示度量值, tags 表示标签集。 + + +参考[OpenTSDB HTTP API文档](http://opentsdb.net/docs/build/html/api_http/put.html)。 + +:::note +- 对于 JSON 格式协议,TDengine 并不会自动把所有标签转成 nchar 类型, 字符串将将转为 nchar 类型, 数值将同样转换为 double 类型。 +- TDengine 只接收 JSON **数组格式**的字符串,即使一行数据也需要转换成数组形式。 + +::: + +## 示例代码 + + + + + + + + + + + + + + + + + + + + + + + + + +以上示例代码会自动创建 2 个超级表, 每个超级表有 2 条数据。 + +```cmd +taos> use test; +Database changed. + +taos> show stables; + name | created_time | columns | tags | tables | +============================================================================================ + meters.current | 2022-03-29 16:05:25.193 | 2 | 2 | 1 | + meters.voltage | 2022-03-29 16:05:25.200 | 2 | 2 | 1 | +Query OK, 2 row(s) in set (0.001954s) + +taos> select * from `meters.current`; + ts | value | groupid | location | +=================================================================================================================== + 2022-03-28 09:56:51.249 | 10.300000000 | 2.000000000 | Beijing.Chaoyang | + 2022-03-28 09:56:51.250 | 12.600000000 | 2.000000000 | Beijing.Chaoyang | +Query OK, 2 row(s) in set (0.004076s) +``` diff --git a/docs-cn/07-develop/03-insert-data/_c_line.mdx b/docs-cn/07-develop/03-insert-data/_c_line.mdx new file mode 100644 index 0000000000..5ef2e9af77 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_c_line.mdx @@ -0,0 +1,3 @@ +```c +{{#include docs-examples/c/line_example.c:main}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/03-insert-data/_c_opts_json.mdx b/docs-cn/07-develop/03-insert-data/_c_opts_json.mdx new file mode 100644 index 0000000000..22ad2e0122 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_c_opts_json.mdx @@ -0,0 +1,3 @@ +```c +{{#include docs-examples/c/json_protocol_example.c:main}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/03-insert-data/_c_opts_telnet.mdx b/docs-cn/07-develop/03-insert-data/_c_opts_telnet.mdx new file mode 100644 index 0000000000..508d7bc98a --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_c_opts_telnet.mdx @@ -0,0 +1,3 @@ +```c +{{#include docs-examples/c/telnet_line_example.c:main}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/03-insert-data/_c_sql.mdx b/docs-cn/07-develop/03-insert-data/_c_sql.mdx new file mode 100644 index 0000000000..f4153fd2c4 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_c_sql.mdx @@ -0,0 +1,3 @@ +```c +{{#include docs-examples/c/insert_example.c}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/03-insert-data/_c_stmt.mdx b/docs-cn/07-develop/03-insert-data/_c_stmt.mdx new file mode 100644 index 0000000000..01ac067519 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_c_stmt.mdx @@ -0,0 +1,6 @@ +```c title=一次绑定一行 +{{#include docs-examples/c/stmt_example.c}} +``` +```c title=一次绑定多行 72:117 +{{#include docs-examples/c/multi_bind_example.c}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/03-insert-data/_category_.yml b/docs-cn/07-develop/03-insert-data/_category_.yml new file mode 100644 index 0000000000..430b3e4209 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_category_.yml @@ -0,0 +1 @@ +label: 写入数据 \ No newline at end of file diff --git a/docs-cn/07-develop/03-insert-data/_cs_line.mdx b/docs-cn/07-develop/03-insert-data/_cs_line.mdx new file mode 100644 index 0000000000..9c275ee3d7 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_cs_line.mdx @@ -0,0 +1,3 @@ +```csharp +{{#include docs-examples/csharp/InfluxDBLineExample.cs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_cs_opts_json.mdx b/docs-cn/07-develop/03-insert-data/_cs_opts_json.mdx new file mode 100644 index 0000000000..3d538b8506 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_cs_opts_json.mdx @@ -0,0 +1,3 @@ +```csharp +{{#include docs-examples/csharp/OptsJsonExample.cs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_cs_opts_telnet.mdx b/docs-cn/07-develop/03-insert-data/_cs_opts_telnet.mdx new file mode 100644 index 0000000000..c53bf3d723 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_cs_opts_telnet.mdx @@ -0,0 +1,3 @@ +```csharp +{{#include docs-examples/csharp/OptsTelnetExample.cs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_cs_sql.mdx b/docs-cn/07-develop/03-insert-data/_cs_sql.mdx new file mode 100644 index 0000000000..c7688bfbe7 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_cs_sql.mdx @@ -0,0 +1,3 @@ +```csharp +{{#include docs-examples/csharp/SQLInsertExample.cs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_cs_stmt.mdx b/docs-cn/07-develop/03-insert-data/_cs_stmt.mdx new file mode 100644 index 0000000000..97c3b910ff --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_cs_stmt.mdx @@ -0,0 +1,3 @@ +```csharp +{{#include docs-examples/csharp/StmtInsertExample.cs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_go_line.mdx b/docs-cn/07-develop/03-insert-data/_go_line.mdx new file mode 100644 index 0000000000..cd225945b7 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_go_line.mdx @@ -0,0 +1,3 @@ +```go +{{#include docs-examples/go/insert/line/main.go}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_go_opts_json.mdx b/docs-cn/07-develop/03-insert-data/_go_opts_json.mdx new file mode 100644 index 0000000000..0c0d3e5b63 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_go_opts_json.mdx @@ -0,0 +1,3 @@ +```go +{{#include docs-examples/go/insert/json/main.go}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_go_opts_telnet.mdx b/docs-cn/07-develop/03-insert-data/_go_opts_telnet.mdx new file mode 100644 index 0000000000..d5ca40cc14 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_go_opts_telnet.mdx @@ -0,0 +1,3 @@ +```go +{{#include docs-examples/go/insert/telnet/main.go}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_go_sql.mdx b/docs-cn/07-develop/03-insert-data/_go_sql.mdx new file mode 100644 index 0000000000..613a65add1 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_go_sql.mdx @@ -0,0 +1,3 @@ +```go +{{#include docs-examples/go/insert/sql/main.go}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_go_stmt.mdx b/docs-cn/07-develop/03-insert-data/_go_stmt.mdx new file mode 100644 index 0000000000..7bb6792d6d --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_go_stmt.mdx @@ -0,0 +1,8 @@ +```go +{{#include docs-examples/go/insert/stmt/main.go}} +``` + +:::tip +driver-go 的模块 `github.com/taosdata/driver-go/v2/wrapper` 是 C 接口的底层封装。使用这个模块也可以实现参数绑定写入。 + +::: diff --git a/docs-cn/07-develop/03-insert-data/_java_line.mdx b/docs-cn/07-develop/03-insert-data/_java_line.mdx new file mode 100644 index 0000000000..2e59a5d470 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_java_line.mdx @@ -0,0 +1,3 @@ +```java +{{#include docs-examples/java/src/main/java/com/taos/example/LineProtocolExample.java}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_java_opts_json.mdx b/docs-cn/07-develop/03-insert-data/_java_opts_json.mdx new file mode 100644 index 0000000000..826a1a07d9 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_java_opts_json.mdx @@ -0,0 +1,3 @@ +```java +{{#include docs-examples/java/src/main/java/com/taos/example/JSONProtocolExample.java}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_java_opts_telnet.mdx b/docs-cn/07-develop/03-insert-data/_java_opts_telnet.mdx new file mode 100644 index 0000000000..954dcc1a48 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_java_opts_telnet.mdx @@ -0,0 +1,3 @@ +```java +{{#include docs-examples/java/src/main/java/com/taos/example/TelnetLineProtocolExample.java}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_java_sql.mdx b/docs-cn/07-develop/03-insert-data/_java_sql.mdx new file mode 100644 index 0000000000..a863378def --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_java_sql.mdx @@ -0,0 +1,3 @@ +```java +{{#include docs-examples/java/src/main/java/com/taos/example/RestInsertExample.java:insert}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/03-insert-data/_java_stmt.mdx b/docs-cn/07-develop/03-insert-data/_java_stmt.mdx new file mode 100644 index 0000000000..54443e535f --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_java_stmt.mdx @@ -0,0 +1,3 @@ +```java +{{#include docs-examples/java/src/main/java/com/taos/example/StmtInsertExample.java}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_js_line.mdx b/docs-cn/07-develop/03-insert-data/_js_line.mdx new file mode 100644 index 0000000000..172c9bc17b --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_js_line.mdx @@ -0,0 +1,3 @@ +```js +{{#include docs-examples/node/nativeexample/influxdb_line_example.js}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_js_opts_json.mdx b/docs-cn/07-develop/03-insert-data/_js_opts_json.mdx new file mode 100644 index 0000000000..20ac9ec91e --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_js_opts_json.mdx @@ -0,0 +1,3 @@ +```js +{{#include docs-examples/node/nativeexample/opentsdb_json_example.js}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_js_opts_telnet.mdx b/docs-cn/07-develop/03-insert-data/_js_opts_telnet.mdx new file mode 100644 index 0000000000..c3c8c40bd6 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_js_opts_telnet.mdx @@ -0,0 +1,3 @@ +```js +{{#include docs-examples/node/nativeexample/opentsdb_telnet_example.js}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_js_sql.mdx b/docs-cn/07-develop/03-insert-data/_js_sql.mdx new file mode 100644 index 0000000000..f5e17c7689 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_js_sql.mdx @@ -0,0 +1,3 @@ +```js +{{#include docs-examples/node/nativeexample/insert_example.js}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_js_stmt.mdx b/docs-cn/07-develop/03-insert-data/_js_stmt.mdx new file mode 100644 index 0000000000..17a6c9785c --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_js_stmt.mdx @@ -0,0 +1,12 @@ +```js title=一次绑定一行 +{{#include docs-examples/node/nativeexample/param_bind_example.js}} +``` + +```js title=一次绑定多行 +{{#include docs-examples/node/nativeexample/multi_bind_example.js:insertData}} +``` + +:::info +一次绑定一行效率不如一次绑定多行,但支持非 INSERT 语句。一次绑定多行效率更高,但仅支持 INSERT 语句。 + +::: diff --git a/docs-cn/07-develop/03-insert-data/_php_sql.mdx b/docs-cn/07-develop/03-insert-data/_php_sql.mdx new file mode 100644 index 0000000000..42d6a54847 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_php_sql.mdx @@ -0,0 +1,3 @@ +```php +{{#include docs-examples/php/insert.php}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_php_stmt.mdx b/docs-cn/07-develop/03-insert-data/_php_stmt.mdx new file mode 100644 index 0000000000..c1ba4ed3b1 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_php_stmt.mdx @@ -0,0 +1,3 @@ +```php +{{#include docs-examples/php/insert_stmt.php}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_py_line.mdx b/docs-cn/07-develop/03-insert-data/_py_line.mdx new file mode 100644 index 0000000000..d3bb1ebb34 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_py_line.mdx @@ -0,0 +1,3 @@ +```py +{{#include docs-examples/python/line_protocol_example.py}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_py_opts_json.mdx b/docs-cn/07-develop/03-insert-data/_py_opts_json.mdx new file mode 100644 index 0000000000..cfbfe13ccf --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_py_opts_json.mdx @@ -0,0 +1,3 @@ +```py +{{#include docs-examples/python/json_protocol_example.py}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_py_opts_telnet.mdx b/docs-cn/07-develop/03-insert-data/_py_opts_telnet.mdx new file mode 100644 index 0000000000..14bc65a7a3 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_py_opts_telnet.mdx @@ -0,0 +1,3 @@ +```py +{{#include docs-examples/python/telnet_line_protocol_example.py}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_py_sql.mdx b/docs-cn/07-develop/03-insert-data/_py_sql.mdx new file mode 100644 index 0000000000..c0e15b8ec1 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_py_sql.mdx @@ -0,0 +1,3 @@ +```py +{{#include docs-examples/python/native_insert_example.py}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_py_stmt.mdx b/docs-cn/07-develop/03-insert-data/_py_stmt.mdx new file mode 100644 index 0000000000..8241ea86bc --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_py_stmt.mdx @@ -0,0 +1,12 @@ +```py title=一次绑定一行 +{{#include docs-examples/python/bind_param_example.py}} +``` + +```py title=一次绑定多行 +{{#include docs-examples/python/multi_bind_example.py:bind_batch}} +``` + +:::info +一次绑定一行效率不如一次绑定多行,但支持非 INSERT 语句。一次绑定多行效率更高,但仅支持 INSERT 语句。 + +::: \ No newline at end of file diff --git a/docs-cn/07-develop/03-insert-data/_rust_line.mdx b/docs-cn/07-develop/03-insert-data/_rust_line.mdx new file mode 100644 index 0000000000..696ddb7b85 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_rust_line.mdx @@ -0,0 +1,3 @@ +```rust +{{#include docs-examples/rust/schemalessexample/examples/influxdb_line_example.rs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_rust_opts_json.mdx b/docs-cn/07-develop/03-insert-data/_rust_opts_json.mdx new file mode 100644 index 0000000000..97d9052dac --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_rust_opts_json.mdx @@ -0,0 +1,3 @@ +```rust +{{#include docs-examples/rust/schemalessexample/examples/opentsdb_json_example.rs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_rust_opts_telnet.mdx b/docs-cn/07-develop/03-insert-data/_rust_opts_telnet.mdx new file mode 100644 index 0000000000..14021f43d8 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_rust_opts_telnet.mdx @@ -0,0 +1,3 @@ +```rust +{{#include docs-examples/rust/schemalessexample/examples/opentsdb_telnet_example.rs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_rust_sql.mdx b/docs-cn/07-develop/03-insert-data/_rust_sql.mdx new file mode 100644 index 0000000000..8e8013e4ad --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_rust_sql.mdx @@ -0,0 +1,3 @@ +```rust +{{#include docs-examples/rust/restexample/examples/insert_example.rs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/_rust_stmt.mdx b/docs-cn/07-develop/03-insert-data/_rust_stmt.mdx new file mode 100644 index 0000000000..590a7a0e71 --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/_rust_stmt.mdx @@ -0,0 +1,3 @@ +```rust +{{#include docs-examples/rust/nativeexample/examples/stmt_example.rs}} +``` diff --git a/docs-cn/07-develop/03-insert-data/index.md b/docs-cn/07-develop/03-insert-data/index.md new file mode 100644 index 0000000000..55a28e4a8b --- /dev/null +++ b/docs-cn/07-develop/03-insert-data/index.md @@ -0,0 +1,12 @@ +--- +title: 写入数据 +--- + +TDengine 支持多种写入协议,包括 SQL,InfluxDB Line 协议, OpenTSDB Telnet 协议,OpenTSDB JSON 格式协议。数据可以单条插入,也可以批量插入,可以插入一个数据采集点的数据,也可以同时插入多个数据采集点的数据。同时,TDengine 支持多线程插入,支持时间乱序数据插入,也支持历史数据插入。InfluxDB Line 协议、OpenTSDB Telnet 协议和 OpenTSDB JSON 格式协议是 TDengine 支持的三种无模式写入协议。使用无模式方式写入无需提前创建超级表和子表,并且引擎能自适用数据对表结构做调整。 + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + + +``` \ No newline at end of file diff --git a/docs-cn/07-develop/04-query-data/_c.mdx b/docs-cn/07-develop/04-query-data/_c.mdx new file mode 100644 index 0000000000..76c9067e2f --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_c.mdx @@ -0,0 +1,3 @@ +```c +{{#include docs-examples/c/query_example.c}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/04-query-data/_c_async.mdx b/docs-cn/07-develop/04-query-data/_c_async.mdx new file mode 100644 index 0000000000..09f3d3b3ff --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_c_async.mdx @@ -0,0 +1,3 @@ +```c +{{#include docs-examples/c/async_query_example.c:demo}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/04-query-data/_category_.yml b/docs-cn/07-develop/04-query-data/_category_.yml new file mode 100644 index 0000000000..69273cb31c --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_category_.yml @@ -0,0 +1 @@ +label: 查询数据 diff --git a/docs-cn/07-develop/04-query-data/_cs.mdx b/docs-cn/07-develop/04-query-data/_cs.mdx new file mode 100644 index 0000000000..2ab52feb56 --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_cs.mdx @@ -0,0 +1,3 @@ +```csharp +{{#include docs-examples/csharp/QueryExample.cs}} +``` diff --git a/docs-cn/07-develop/04-query-data/_cs_async.mdx b/docs-cn/07-develop/04-query-data/_cs_async.mdx new file mode 100644 index 0000000000..f868994b30 --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_cs_async.mdx @@ -0,0 +1,3 @@ +```csharp +{{#include docs-examples/csharp/AsyncQueryExample.cs}} +``` diff --git a/docs-cn/07-develop/04-query-data/_go.mdx b/docs-cn/07-develop/04-query-data/_go.mdx new file mode 100644 index 0000000000..417c12315c --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_go.mdx @@ -0,0 +1,3 @@ +```go +{{#include docs-examples/go/query/sync/main.go}} +``` diff --git a/docs-cn/07-develop/04-query-data/_go_async.mdx b/docs-cn/07-develop/04-query-data/_go_async.mdx new file mode 100644 index 0000000000..72fff411b9 --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_go_async.mdx @@ -0,0 +1,3 @@ +```go +{{#include docs-examples/go/query/async/main.go}} +``` diff --git a/docs-cn/07-develop/04-query-data/_java.mdx b/docs-cn/07-develop/04-query-data/_java.mdx new file mode 100644 index 0000000000..519b926614 --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_java.mdx @@ -0,0 +1,3 @@ +```java +{{#include docs-examples/java/src/main/java/com/taos/example/RestQueryExample.java}} +``` diff --git a/docs-cn/07-develop/04-query-data/_js.mdx b/docs-cn/07-develop/04-query-data/_js.mdx new file mode 100644 index 0000000000..c5e4c4f3fc --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_js.mdx @@ -0,0 +1,3 @@ +```js +{{#include docs-examples/node/nativeexample/query_example.js}} +``` diff --git a/docs-cn/07-develop/04-query-data/_js_async.mdx b/docs-cn/07-develop/04-query-data/_js_async.mdx new file mode 100644 index 0000000000..c65d54ed12 --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_js_async.mdx @@ -0,0 +1,3 @@ +```js +{{#include docs-examples/node/nativeexample/async_query_example.js}} +``` diff --git a/docs-cn/07-develop/04-query-data/_php.mdx b/docs-cn/07-develop/04-query-data/_php.mdx new file mode 100644 index 0000000000..6264bd99f5 --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_php.mdx @@ -0,0 +1,3 @@ +```go +{{#include docs-examples/php/query.php}} +``` diff --git a/docs-cn/07-develop/04-query-data/_py.mdx b/docs-cn/07-develop/04-query-data/_py.mdx new file mode 100644 index 0000000000..6a1bacdd3e --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_py.mdx @@ -0,0 +1,11 @@ +通过迭代逐行获取查询结果。 + +```py +{{#include docs-examples/python/query_example.py:iter}} +``` + +一次获取所有查询结果,并把每一行转化为一个字典返回。 + +```py +{{#include docs-examples/python/query_example.py:fetch_all}} +``` diff --git a/docs-cn/07-develop/04-query-data/_py_async.mdx b/docs-cn/07-develop/04-query-data/_py_async.mdx new file mode 100644 index 0000000000..2399a50df6 --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_py_async.mdx @@ -0,0 +1,8 @@ +```py +{{#include docs-examples/python/async_query_example.py}} +``` + +:::note +这个示例程序,目前在 Windows 系统上还无法运行 + +::: diff --git a/docs-cn/07-develop/04-query-data/_rust.mdx b/docs-cn/07-develop/04-query-data/_rust.mdx new file mode 100644 index 0000000000..742d70fd02 --- /dev/null +++ b/docs-cn/07-develop/04-query-data/_rust.mdx @@ -0,0 +1,3 @@ +```rust +{{#include docs-examples/rust/restexample/examples/query_example.rs}} +``` diff --git a/docs-cn/07-develop/04-query-data/index.mdx b/docs-cn/07-develop/04-query-data/index.mdx new file mode 100644 index 0000000000..b0a6bad3ea --- /dev/null +++ b/docs-cn/07-develop/04-query-data/index.mdx @@ -0,0 +1,181 @@ +--- +title: 查询数据 +description: "主要查询功能,通过连接器执行同步查询和异步查询" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import JavaQuery from "./_java.mdx"; +import PyQuery from "./_py.mdx"; +import GoQuery from "./_go.mdx"; +import RustQuery from "./_rust.mdx"; +import NodeQuery from "./_js.mdx"; +import CsQuery from "./_cs.mdx"; +import CQuery from "./_c.mdx"; +import PhpQuery from "./_php.mdx"; +import PyAsync from "./_py_async.mdx"; +import NodeAsync from "./_js_async.mdx"; +import CsAsync from "./_cs_async.mdx"; +import CAsync from "./_c_async.mdx"; + +## 主要查询功能 + +TDengine 采用 SQL 作为查询语言。应用程序可以通过 REST API 或连接器发送 SQL 语句,用户还可以通过 TDengine 命令行工具 taos 手动执行 SQL 即席查询(Ad-Hoc Query)。TDengine 支持如下查询功能: + +- 单列、多列数据查询 +- 标签和数值的多种过滤条件:>, <, =, <\>, like 等 +- 聚合结果的分组(Group by)、排序(Order by)、约束输出(Limit/Offset) +- 数值列及聚合结果的四则运算 +- 时间戳对齐的连接查询(Join Query: 隐式连接)操作 +- 多种聚合/计算函数: count, max, min, avg, sum, twa, stddev, leastsquares, top, bottom, first, last, percentile, apercentile, last_row, spread, diff 等 + +例如:在命令行工具 taos 中,从表 d1001 中查询出 voltage > 215 的记录,按时间降序排列,仅仅输出 2 条。 + +```sql +taos> select * from d1001 where voltage > 215 order by ts desc limit 2; + ts | current | voltage | phase | +====================================================================================== + 2018-10-03 14:38:16.800 | 12.30000 | 221 | 0.31000 | + 2018-10-03 14:38:15.000 | 12.60000 | 218 | 0.33000 | +Query OK, 2 row(s) in set (0.001100s) +``` + +为满足物联网场景的需求,TDengine 支持几个特殊的函数,比如 twa(时间加权平均),spread (最大值与最小值的差),last_row(最后一条记录)等,更多与物联网场景相关的函数将添加进来。TDengine 还支持连续查询。 + +具体的查询语法请看 [TAOS SQL 的数据查询](/taos-sql/select) 章节。 + +## 多表聚合查询 + +物联网场景中,往往同一个类型的数据采集点有多个。TDengine 采用超级表(STable)的概念来描述某一个类型的数据采集点,一张普通的表来描述一个具体的数据采集点。同时 TDengine 使用标签来描述数据采集点的静态属性,一个具体的数据采集点有具体的标签值。通过指定标签的过滤条件,TDengine 提供了一高效的方法将超级表(某一类型的数据采集点)所属的子表进行聚合查询。对普通表的聚合函数以及绝大部分操作都适用于超级表,语法完全一样。 + +### 示例一 + +在 TAOS Shell,查找北京所有智能电表采集的电压平均值,并按照 location 分组。 + +``` +taos> SELECT AVG(voltage) FROM meters GROUP BY location; + avg(voltage) | location | +============================================================= + 222.000000000 | Beijing.Haidian | + 219.200000000 | Beijing.Chaoyang | +Query OK, 2 row(s) in set (0.002136s) +``` + +### 示例二 + +在 TAOS shell, 查找 groupId 为 2 的所有智能电表过去 24 小时的记录条数,电流的最大值。 + +``` +taos> SELECT count(*), max(current) FROM meters where groupId = 2 and ts > now - 24h; + cunt(*) | max(current) | +================================== + 5 | 13.4 | +Query OK, 1 row(s) in set (0.002136s) +``` + +TDengine 仅容许对属于同一个超级表的表之间进行聚合查询,不同超级表之间的聚合查询不支持。在 [TAOS SQL 的数据查询](/taos-sql/select) 一章,查询类操作都会注明是否支持超级表。 + +## 降采样查询、插值 + +物联网场景里,经常需要通过降采样(down sampling)将采集的数据按时间段进行聚合。TDengine 提供了一个简便的关键词 interval 让按照时间窗口的查询操作变得极为简单。比如,将智能电表 d1001 采集的电流值每 10 秒钟求和 + +``` +taos> SELECT sum(current) FROM d1001 INTERVAL(10s); + ts | sum(current) | +====================================================== + 2018-10-03 14:38:00.000 | 10.300000191 | + 2018-10-03 14:38:10.000 | 24.900000572 | +Query OK, 2 row(s) in set (0.000883s) +``` + +降采样操作也适用于超级表,比如:将北京所有智能电表采集的电流值每秒钟求和 + +``` +taos> SELECT SUM(current) FROM meters where location like "Beijing%" INTERVAL(1s); + ts | sum(current) | +====================================================== + 2018-10-03 14:38:04.000 | 10.199999809 | + 2018-10-03 14:38:05.000 | 32.900000572 | + 2018-10-03 14:38:06.000 | 11.500000000 | + 2018-10-03 14:38:15.000 | 12.600000381 | + 2018-10-03 14:38:16.000 | 36.000000000 | +Query OK, 5 row(s) in set (0.001538s) +``` + +降采样操作也支持时间偏移,比如:将所有智能电表采集的电流值每秒钟求和,但要求每个时间窗口从 500 毫秒开始 + +``` +taos> SELECT SUM(current) FROM meters INTERVAL(1s, 500a); + ts | sum(current) | +====================================================== + 2018-10-03 14:38:04.500 | 11.189999809 | + 2018-10-03 14:38:05.500 | 31.900000572 | + 2018-10-03 14:38:06.500 | 11.600000000 | + 2018-10-03 14:38:15.500 | 12.300000381 | + 2018-10-03 14:38:16.500 | 35.000000000 | +Query OK, 5 row(s) in set (0.001521s) +``` + +物联网场景里,每个数据采集点采集数据的时间是难同步的,但很多分析算法(比如 FFT)需要把采集的数据严格按照时间等间隔的对齐,在很多系统里,需要应用自己写程序来处理,但使用 TDengine 的降采样操作就轻松解决。 + +如果一个时间间隔里,没有采集的数据,TDengine 还提供插值计算的功能。 + +语法规则细节请见 [TAOS SQL 的按时间窗口切分聚合](/taos-sql/interval) 章节。 + +## 示例代码 + +### 查询数据 + +在 [SQL 写入](/develop/insert-data/sql-writing) 一章,我们创建了 power 数据库,并向 meters 表写入了一些数据,以下示例代码展示如何查询这个表的数据。 + + + + + + + + + + + + + + + + + + + + + + + + + + + + +:::note + +1. 无论是使用 REST 连接还是原生连接的连接器,以上示例代码都能正常工作。 +2. 唯一需要注意的是:由于 REST 接口无状态, 不能使用 `use db` 语句来切换数据库。 + +::: + +### 异步查询 + +除同步查询 API 之外,TDengine 还提供性能更高的异步调用 API 处理数据插入、查询操作。在软硬件环境相同的情况下,异步 API 处理数据插入的速度比同步 API 快 2-4 倍。异步 API 采用非阻塞式的调用方式,在系统真正完成某个具体数据库操作前,立即返回。调用的线程可以去处理其他工作,从而可以提升整个应用的性能。异步 API 在网络延迟严重的情况下,优点尤为突出。 + +需要注意的是,只有使用原生连接的连接器,才能使用异步查询功能。 + + + + + + + + + + + + diff --git a/docs-cn/07-develop/05-continuous-query.mdx b/docs-cn/07-develop/05-continuous-query.mdx new file mode 100644 index 0000000000..2fd1b3cc75 --- /dev/null +++ b/docs-cn/07-develop/05-continuous-query.mdx @@ -0,0 +1,84 @@ +--- +sidebar_label: 连续查询 +description: "连续查询是一个按照预设频率自动执行的查询功能,提供按照时间窗口的聚合查询能力,是一种简化的时间驱动流式计算。" +title: "连续查询(Continuous Query)" +--- + +连续查询是 TDengine 定期自动执行的查询,采用滑动窗口的方式进行计算,是一种简化的时间驱动的流式计算。针对库中的表或超级表,TDengine 可提供定期自动执行的连续查询,用户可让 TDengine 推送查询的结果,也可以将结果再写回到 TDengine 中。每次执行的查询是一个时间窗口,时间窗口随着时间流动向前滑动。在定义连续查询的时候需要指定时间窗口(time window, 参数 interval)大小和每次前向增量时间(forward sliding times, 参数 sliding)。 + +TDengine 的连续查询采用时间驱动模式,可以直接使用 TAOS SQL 进行定义,不需要额外的操作。使用连续查询,可以方便快捷地按照时间窗口生成结果,从而对原始采集数据进行降采样(down sampling)。用户通过 TAOS SQL 定义连续查询以后,TDengine 自动在最后的一个完整的时间周期末端拉起查询,并将计算获得的结果推送给用户或者写回 TDengine。 + +TDengine 提供的连续查询与普通流计算中的时间窗口计算具有以下区别: + +- 不同于流计算的实时反馈计算结果,连续查询只在时间窗口关闭以后才开始计算。例如时间周期是 1 天,那么当天的结果只会在 23:59:59 以后才会生成。 +- 如果有历史记录写入到已经计算完成的时间区间,连续查询并不会重新进行计算,也不会重新将结果推送给用户。对于写回 TDengine 的模式,也不会更新已经存在的计算结果。 +- 使用连续查询推送结果的模式,服务端并不缓存客户端计算状态,也不提供 Exactly-Once 的语义保证。如果用户的应用端崩溃,再次拉起的连续查询将只会从再次拉起的时间开始重新计算最近的一个完整的时间窗口。如果使用写回模式,TDengine 可确保数据写回的有效性和连续性。 + +## 连续查询语法 + +```sql +[CREATE TABLE AS] SELECT select_expr [, select_expr ...] + FROM {tb_name_list} + [WHERE where_condition] + [INTERVAL(interval_val [, interval_offset]) [SLIDING sliding_val]] + +``` + +INTERVAL: 连续查询作用的时间窗口 + +SLIDING: 连续查询的时间窗口向前滑动的时间间隔 + +## 使用连续查询 + +下面以智能电表场景为例介绍连续查询的具体使用方法。假设我们通过下列 SQL 语句创建了超级表和子表: + +```sql +create table meters (ts timestamp, current float, voltage int, phase float) tags (location binary(64), groupId int); +create table D1001 using meters tags ("Beijing.Chaoyang", 2); +create table D1002 using meters tags ("Beijing.Haidian", 2); +... +``` + +可以通过下面这条 SQL 语句以一分钟为时间窗口、30 秒为前向增量统计这些电表的平均电压。 + +```sql +select avg(voltage) from meters interval(1m) sliding(30s); +``` + +每次执行这条语句,都会重新计算所有数据。 如果需要每隔 30 秒执行一次来增量计算最近一分钟的数据,可以把上面的语句改进成下面的样子,每次使用不同的 `startTime` 并定期执行: + +```sql +select avg(voltage) from meters where ts > {startTime} interval(1m) sliding(30s); +``` + +这样做没有问题,但 TDengine 提供了更简单的方法,只要在最初的查询语句前面加上 `create table {tableName} as` 就可以了,例如: + +```sql +create table avg_vol as select avg(voltage) from meters interval(1m) sliding(30s); +``` + +会自动创建一个名为 `avg_vol` 的新表,然后每隔 30 秒,TDengine 会增量执行 `as` 后面的 SQL 语句,并将查询结果写入这个表中,用户程序后续只要从 `avg_vol` 中查询数据即可。例如: + +```sql +taos> select * from avg_vol; + ts | avg_voltage_ | +=================================================== + 2020-07-29 13:37:30.000 | 222.0000000 | + 2020-07-29 13:38:00.000 | 221.3500000 | + 2020-07-29 13:38:30.000 | 220.1700000 | + 2020-07-29 13:39:00.000 | 223.0800000 | +``` + +需要注意,查询时间窗口的最小值是 10 毫秒,没有时间窗口范围的上限。 + +此外,TDengine 还支持用户指定连续查询的起止时间。如果不输入开始时间,连续查询将从第一条原始数据所在的时间窗口开始;如果没有输入结束时间,连续查询将永久运行;如果用户指定了结束时间,连续查询在系统时间达到指定的时间以后停止运行。比如使用下面的 SQL 创建的连续查询将运行一小时,之后会自动停止。 + +```sql +create table avg_vol as select avg(voltage) from meters where ts > now and ts <= now + 1h interval(1m) sliding(30s); +``` + +需要说明的是,上面例子中的 `now` 是指创建连续查询的时间,而不是查询执行的时间,否则,查询就无法自动停止了。另外,为了尽量避免原始数据延迟写入导致的问题,TDengine 中连续查询的计算有一定的延迟。也就是说,一个时间窗口过去后,TDengine 并不会立即计算这个窗口的数据,所以要稍等一会(一般不会超过 1 分钟)才能查到计算结果。 + +## 管理连续查询 + +用户可在控制台中通过 `show streams` 命令来查看系统中全部运行的连续查询,并可以通过 `kill stream` 命令杀掉对应的连续查询。后续版本会提供更细粒度和便捷的连续查询管理命令。 diff --git a/docs-cn/07-develop/06-subscribe.mdx b/docs-cn/07-develop/06-subscribe.mdx new file mode 100644 index 0000000000..d471c114e8 --- /dev/null +++ b/docs-cn/07-develop/06-subscribe.mdx @@ -0,0 +1,253 @@ +--- +sidebar_label: 数据订阅 +description: "轻量级的数据订阅与推送服务。连续写入到 TDengine 中的时序数据能够被自动推送到订阅客户端。" +title: 数据订阅 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import Java from "./_sub_java.mdx"; +import Python from "./_sub_python.mdx"; +import Go from "./_sub_go.mdx"; +import Rust from "./_sub_rust.mdx"; +import Node from "./_sub_node.mdx"; +import CSharp from "./_sub_cs.mdx"; +import CDemo from "./_sub_c.mdx"; + +基于数据天然的时间序列特性,TDengine 的数据写入(insert)与消息系统的数据发布(pub)逻辑上一致,均可视为系统中插入一条带时间戳的新记录。同时,TDengine 在内部严格按照数据时间序列单调递增的方式保存数据。本质上来说,TDengine 中每一张表均可视为一个标准的消息队列。 + +TDengine 内嵌支持轻量级的消息订阅与推送服务。使用系统提供的 API,用户可使用普通查询语句订阅数据库中的一张或多张表。订阅的逻辑和操作状态的维护均是由客户端完成,客户端定时轮询服务器是否有新的记录到达,有新的记录到达就会将结果反馈到客户。 + +TDengine 的订阅与推送服务的状态是由客户端维持,TDengine 服务端并不维持。因此如果应用重启,从哪个时间点开始获取最新数据,由应用决定。 + +TDengine 的 API 中,与订阅相关的主要有以下三个: + +```c +taos_subscribe +taos_consume +taos_unsubscribe +``` + +这些 API 的文档请见 [C/C++ Connector](/reference/connector/cpp),下面仍以智能电表场景为例介绍一下它们的具体用法(超级表和子表结构请参考上一节“连续查询”),完整的示例代码可以在 [这里](https://github.com/taosdata/TDengine/blob/master/examples/c/subscribe.c) 找到。 + +如果我们希望当某个电表的电流超过一定限制(比如 10A)后能得到通知并进行一些处理, 有两种方法:一是分别对每张子表进行查询,每次查询后记录最后一条数据的时间戳,后续只查询这个时间戳之后的数据: + +```sql +select * from D1001 where ts > {last_timestamp1} and current > 10; +select * from D1002 where ts > {last_timestamp2} and current > 10; +... +``` + +这确实可行,但随着电表数量的增加,查询数量也会增加,客户端和服务端的性能都会受到影响,当电表数增长到一定的程度,系统就无法承受了。 + +另一种方法是对超级表进行查询。这样,无论有多少电表,都只需一次查询: + +```sql +select * from meters where ts > {last_timestamp} and current > 10; +``` + +但是,如何选择 `last_timestamp` 就成了一个新的问题。因为,一方面数据的产生时间(也就是数据时间戳)和数据入库的时间一般并不相同,有时偏差还很大;另一方面,不同电表的数据到达 TDengine 的时间也会有差异。所以,如果我们在查询中使用最慢的那台电表的数据的时间戳作为 `last_timestamp`,就可能重复读入其它电表的数据;如果使用最快的电表的时间戳,其它电表的数据就可能被漏掉。 + +TDengine 的订阅功能为上面这个问题提供了一个彻底的解决方案。 + +首先是使用 `taos_subscribe` 创建订阅: + +```c +TAOS_SUB* tsub = NULL; +if (async) { +  // create an asynchronized subscription, the callback function will be called every 1s +  tsub = taos_subscribe(taos, restart, topic, sql, subscribe_callback, &blockFetch, 1000); +} else { +  // create an synchronized subscription, need to call 'taos_consume' manually +  tsub = taos_subscribe(taos, restart, topic, sql, NULL, NULL, 0); +} +``` + +TDengine 中的订阅既可以是同步的,也可以是异步的,上面的代码会根据从命令行获取的参数 `async` 的值来决定使用哪种方式。这里,同步的意思是用户程序要直接调用 `taos_consume` 来拉取数据,而异步则由 API 在内部的另一个线程中调用 `taos_consume`,然后把拉取到的数据交给回调函数 `subscribe_callback`去处理。(注意,`subscribe_callback` 中不宜做较为耗时的操作,否则有可能导致客户端阻塞等不可控的问题。) + +参数 `taos` 是一个已经建立好的数据库连接,在同步模式下无特殊要求。但在异步模式下,需要注意它不会被其它线程使用,否则可能导致不可预计的错误,因为回调函数在 API 的内部线程中被调用,而 TDengine 的部分 API 不是线程安全的。 + +参数 `sql` 是查询语句,可以在其中使用 where 子句指定过滤条件。在我们的例子中,如果只想订阅电流超过 10A 时的数据,可以这样写: + +```sql +select * from meters where current > 10; +``` + +注意,这里没有指定起始时间,所以会读到所有时间的数据。如果只想从一天前的数据开始订阅,而不需要更早的历史数据,可以再加上一个时间条件: + +```sql +select * from meters where ts > now - 1d and current > 10; +``` + +订阅的 `topic` 实际上是它的名字,因为订阅功能是在客户端 API 中实现的,所以没必要保证它全局唯一,但需要它在一台客户端机器上唯一。 + +如果名为 `topic` 的订阅不存在,参数 `restart` 没有意义;但如果用户程序创建这个订阅后退出,当它再次启动并重新使用这个 `topic` 时,`restart` 就会被用于决定是从头开始读取数据,还是接续上次的位置进行读取。本例中,如果 `restart` 是 **true**(非零值),用户程序肯定会读到所有数据。但如果这个订阅之前就存在了,并且已经读取了一部分数据,且 `restart` 是 **false**(**0**),用户程序就不会读到之前已经读取的数据了。 + +`taos_subscribe`的最后一个参数是以毫秒为单位的轮询周期。在同步模式下,如果前后两次调用 `taos_consume` 的时间间隔小于此时间,`taos_consume` 会阻塞,直到间隔超过此时间。异步模式下,这个时间是两次调用回调函数的最小时间间隔。 + +`taos_subscribe` 的倒数第二个参数用于用户程序向回调函数传递附加参数,订阅 API 不对其做任何处理,只原样传递给回调函数。此参数在同步模式下无意义。 + +订阅创建以后,就可以消费其数据了,同步模式下,示例代码是下面的 else 部分: + +```c +if (async) { +  getchar(); +} else while(1) { +  TAOS_RES* res = taos_consume(tsub); +  if (res == NULL) { +    printf("failed to consume data."); +    break; +  } else { +    print_result(res, blockFetch); +    getchar(); +  } +} +``` + +这里是一个 **while** 循环,用户每按一次回车键就调用一次 `taos_consume`,而 `taos_consume` 的返回值是查询到的结果集,与 `taos_use_result` 完全相同,例子中使用这个结果集的代码是函数 `print_result`: + +```c +void print_result(TAOS_RES* res, int blockFetch) { +  TAOS_ROW row = NULL; +  int num_fields = taos_num_fields(res); +  TAOS_FIELD* fields = taos_fetch_fields(res); +  int nRows = 0; +  if (blockFetch) { +    nRows = taos_fetch_block(res, &row); +    for (int i = 0; i < nRows; i++) { +      char temp[256]; +      taos_print_row(temp, row + i, fields, num_fields); +      puts(temp); +    } +  } else { +    while ((row = taos_fetch_row(res))) { +      char temp[256]; +      taos_print_row(temp, row, fields, num_fields); +      puts(temp); +      nRows++; +    } +  } +  printf("%d rows consumed.\n", nRows); +} +``` + +其中的 `taos_print_row` 用于处理订阅到数据,在我们的例子中,它会打印出所有符合条件的记录。而异步模式下,消费订阅到的数据则显得更为简单: + +```c +void subscribe_callback(TAOS_SUB* tsub, TAOS_RES *res, void* param, int code) { +  print_result(res, *(int*)param); +} +``` + +当要结束一次数据订阅时,需要调用 `taos_unsubscribe`: + +```c +taos_unsubscribe(tsub, keep); +``` + +其第二个参数,用于决定是否在客户端保留订阅的进度信息。如果这个参数是**false**(**0**),那无论下次调用 `taos_subscribe` 时的 `restart` 参数是什么,订阅都只能重新开始。另外,进度信息的保存位置是 _{DataDir}/subscribe/_ 这个目录下,每个订阅有一个与其 `topic` 同名的文件,删掉某个文件,同样会导致下次创建其对应的订阅时只能重新开始。 + +代码介绍完毕,我们来看一下实际的运行效果。假设: + +- 示例代码已经下载到本地 +- TDengine 也已经在同一台机器上安装好 +- 示例所需的数据库、超级表、子表已经全部创建好 + +则可以在示例代码所在目录执行以下命令来编译并启动示例程序: + +```bash +make +./subscribe -sql='select * from meters where current > 10;' +``` + +示例程序启动后,打开另一个终端窗口,启动 TDengine CLI 向 **D1001** 插入一条电流为 12A 的数据: + +```sql +$ taos +> use test; +> insert into D1001 values(now, 12, 220, 1); +``` + +这时,因为电流超过了 10A,您应该可以看到示例程序将它输出到了屏幕上。您可以继续插入一些数据观察示例程序的输出。 + +## 示例程序 + +下面的示例程序展示是如何使用连接器订阅所有电流超过 10A 的记录。 + +### 准备数据 + +``` +# create database "power" +taos> create database power; +# use "power" as the database in following operations +taos> use power; +# create super table "meters" +taos> create table meters(ts timestamp, current float, voltage int, phase int) tags(location binary(64), groupId int); +# create tabes using the schema defined by super table "meters" +taos> create table d1001 using meters tags ("Beijing.Chaoyang", 2); +taos> create table d1002 using meters tags ("Beijing.Haidian", 2); +# insert some rows +taos> insert into d1001 values("2020-08-15 12:00:00.000", 12, 220, 1),("2020-08-15 12:10:00.000", 12.3, 220, 2),("2020-08-15 12:20:00.000", 12.2, 220, 1); +taos> insert into d1002 values("2020-08-15 12:00:00.000", 9.9, 220, 1),("2020-08-15 12:10:00.000", 10.3, 220, 1),("2020-08-15 12:20:00.000", 11.2, 220, 1); +# filter out the rows in which current is bigger than 10A +taos> select * from meters where current > 10; + ts | current | voltage | phase | location | groupid | +=========================================================================================================== + 2020-08-15 12:10:00.000 | 10.30000 | 220 | 1 | Beijing.Haidian | 2 | + 2020-08-15 12:20:00.000 | 11.20000 | 220 | 1 | Beijing.Haidian | 2 | + 2020-08-15 12:00:00.000 | 12.00000 | 220 | 1 | Beijing.Chaoyang | 2 | + 2020-08-15 12:10:00.000 | 12.30000 | 220 | 2 | Beijing.Chaoyang | 2 | + 2020-08-15 12:20:00.000 | 12.20000 | 220 | 1 | Beijing.Chaoyang | 2 | +Query OK, 5 row(s) in set (0.004896s) +``` +### 示例代码 + + + + + + + + + {/* + + */} + + + + {/* + + + + + */} + + + + + +### 运行示例程序 + +示例程序会先消费符合查询条件的所有历史数据: + +```bash +ts: 1597464000000 current: 12.0 voltage: 220 phase: 1 location: Beijing.Chaoyang groupid : 2 +ts: 1597464600000 current: 12.3 voltage: 220 phase: 2 location: Beijing.Chaoyang groupid : 2 +ts: 1597465200000 current: 12.2 voltage: 220 phase: 1 location: Beijing.Chaoyang groupid : 2 +ts: 1597464600000 current: 10.3 voltage: 220 phase: 1 location: Beijing.Haidian groupid : 2 +ts: 1597465200000 current: 11.2 voltage: 220 phase: 1 location: Beijing.Haidian groupid : 2 +``` + +接着,使用 TDengine CLI 向表中新增一条数据: + +``` +# taos +taos> use power; +taos> insert into d1001 values(now, 12.4, 220, 1); +``` + +因为这条数据的电流大于 10A,示例程序会将其消费: + +``` +ts: 1651146662805 current: 12.4 voltage: 220 phase: 1 location: Beijing.Chaoyang groupid: 2 +``` diff --git a/docs-cn/07-develop/07-cache.md b/docs-cn/07-develop/07-cache.md new file mode 100644 index 0000000000..fd31335310 --- /dev/null +++ b/docs-cn/07-develop/07-cache.md @@ -0,0 +1,21 @@ +--- +sidebar_label: 缓存 +title: 缓存 +description: "提供写驱动的缓存管理机制,将每个表最近写入的一条记录持续保存在缓存中,可以提供高性能的最近状态查询。" +--- + +TDengine 采用时间驱动缓存管理策略(First-In-First-Out,FIFO),又称为写驱动的缓存管理机制。这种策略有别于读驱动的数据缓存模式(Least-Recent-Used,LRU),直接将最近写入的数据保存在系统的缓存中。当缓存达到临界值的时候,将最早的数据批量写入磁盘。一般意义上来说,对于物联网数据的使用,用户最为关心最近产生的数据,即当前状态。TDengine 充分利用了这一特性,将最近到达的(当前状态)数据保存在缓存中。 + +TDengine 通过查询函数向用户提供毫秒级的数据获取能力。直接将最近到达的数据保存在缓存中,可以更加快速地响应用户针对最近一条或一批数据的查询分析,整体上提供更快的数据库查询响应能力。从这个意义上来说,可通过设置合适的配置参数将 TDengine 作为数据缓存来使用,而不需要再部署额外的缓存系统,可有效地简化系统架构,降低运维的成本。需要注意的是,TDengine 重启以后系统的缓存将被清空,之前缓存的数据均会被批量写入磁盘,缓存的数据将不会像专门的 key-value 缓存系统再将之前缓存的数据重新加载到缓存中。 + +TDengine 分配固定大小的内存空间作为缓存空间,缓存空间可根据应用的需求和硬件资源配置。通过适当的设置缓存空间,TDengine 可以提供极高性能的写入和查询的支持。TDengine 中每个虚拟节点(virtual node)创建时分配独立的缓存池。每个虚拟节点管理自己的缓存池,不同虚拟节点间不共享缓存池。每个虚拟节点内部所属的全部表共享该虚拟节点的缓存池。 + +TDengine 将内存池按块划分进行管理,数据在内存块里是以行(row)的形式存储。一个 vnode 的内存池是在 vnode 创建时按块分配好,而且每个内存块按照先进先出的原则进行管理。在创建内存池时,块的大小由系统配置参数 cache 决定;每个 vnode 中内存块的数目则由配置参数 blocks 决定。因此对于一个 vnode,总的内存大小为:`cache * blocks`。一个 cache block 需要保证每张表能存储至少几十条以上记录,才会有效率。 + +你可以通过函数 last_row() 快速获取一张表或一张超级表的最后一条记录,这样很便于在大屏显示各设备的实时状态或采集值。例如: + +```sql +select last_row(voltage) from meters where location='Beijing.Chaoyang'; +``` + +该 SQL 语句将获取所有位于北京朝阳区的电表最后记录的电压值。 diff --git a/docs-cn/07-develop/08-udf.md b/docs-cn/07-develop/08-udf.md new file mode 100644 index 0000000000..09681650db --- /dev/null +++ b/docs-cn/07-develop/08-udf.md @@ -0,0 +1,208 @@ +--- +sidebar_label: 用户定义函数 +title: UDF(用户定义函数) +description: "支持用户编码的聚合函数和标量函数,在查询中嵌入并使用用户定义函数,拓展查询的能力和功能。" +--- + +在有些应用场景中,应用逻辑需要的查询无法直接使用系统内置的函数来表示。利用 UDF 功能,TDengine 可以插入用户编写的处理代码并在查询中使用它们,就能够很方便地解决特殊应用场景中的使用需求。 UDF 通常以数据表中的一列数据做为输入,同时支持以嵌套子查询的结果作为输入。 + +从 2.2.0.0 版本开始,TDengine 支持通过 C/C++ 语言进行 UDF 定义。接下来结合示例讲解 UDF 的使用方法。 + +用户可以通过 UDF 实现两类函数: 标量函数 和 聚合函数。 + +## 用 C/C++ 语言来定义 UDF + +### 标量函数 + +用户可以按照下列函数模板定义自己的标量计算函数 + + `void udfNormalFunc(char* data, short itype, short ibytes, int numOfRows, long long* ts, char* dataOutput, char* interBuf, char* tsOutput, int* numOfOutput, short otype, short obytes, SUdfInit* buf)` + + 其中 udfNormalFunc 是函数名的占位符,以上述模板实现的函数对行数据块进行标量计算,其参数项是固定的,用于按照约束完成与引擎之间的数据交换。 + +- udfNormalFunc 中各参数的具体含义是: + - data:输入数据。 + - itype:输入数据的类型。这里采用的是短整型表示法,与各种数据类型对应的值可以参见 [column_meta 中的列类型说明](/reference/rest-api/)。例如 4 用于表示 INT 型。 + - iBytes:输入数据中每个值会占用的字节数。 + - numOfRows:输入数据的总行数。 + - ts:主键时间戳在输入中的列数据(只读)。 + - dataOutput:输出数据的缓冲区,缓冲区大小为用户指定的输出类型大小 \* numOfRows。 + - interBuf:中间计算结果的缓冲区,大小为用户在创建 UDF 时指定的 BUFSIZE 大小。通常用于计算中间结果与最终结果不一致时使用,由引擎负责分配与释放。 + - tsOutput:主键时间戳在输出时的列数据,如果非空可用于输出结果对应的时间戳。 + - numOfOutput:输出结果的个数(行数)。 + - oType:输出数据的类型。取值含义与 itype 参数一致。 + - oBytes:输出数据中每个值占用的字节数。 + - buf:用于在 UDF 与引擎间的状态控制信息传递块。 + + [add_one.c](https://github.com/taosdata/TDengine/blob/develop/tests/script/sh/add_one.c) 是结构最简单的 UDF 实现,也即上面定义的 udfNormalFunc 函数的一个具体实现。其功能为:对传入的一个数据列(可能因 WHERE 子句进行了筛选)中的每一项,都输出 +1 之后的值,并且要求输入的列数据类型为 INT。 + +### 聚合函数 + +用户可以按照如下函数模板定义自己的聚合函数。 + +`void abs_max_merge(char* data, int32_t numOfRows, char* dataOutput, int32_t* numOfOutput, SUdfInit* buf)` + +其中 udfMergeFunc 是函数名的占位符,以上述模板实现的函数用于对计算中间结果进行聚合,只有针对超级表的聚合查询才需要调用该函数。其中各参数的具体含义是: + + - data:udfNormalFunc 的输出数据数组,如果使用了 interBuf 那么 data 就是 interBuf 的数组。 + - numOfRows:data 中数据的行数。 + - dataOutput:输出数据的缓冲区,大小等于一条最终结果的大小。如果此时输出还不是最终结果,可以选择输出到 interBuf 中即 data 中。 + - numOfOutput:输出结果的个数(行数)。 + - buf:用于在 UDF 与引擎间的状态控制信息传递块。 + +[abs_max.c](https://github.com/taosdata/TDengine/blob/develop/tests/script/sh/abs_max.c) 实现的是一个聚合函数,功能是对一组数据按绝对值取最大值。 + +其计算过程为:与所在查询语句相关的数据会被分为多个行数据块,对每个行数据块调用 udfNormalFunc(在本例的实现代码中,实际函数名是 `abs_max`)来生成每个子表的中间结果,再将子表的中间结果调用 udfMergeFunc(本例中,其实际的函数名是 `abs_max_merge`)进行聚合,生成超级表的最终聚合结果或中间结果。聚合查询最后还会通过 udfFinalizeFunc(本例中,其实际的函数名是 `abs_max_finalize`)再把超级表的中间结果处理为最终结果,最终结果只能含 0 或 1 条结果数据。 + +其他典型场景,如协方差的计算,也可通过定义聚合 UDF 的方式实现。 + +### 最终计算 + +用户可以按下面的函数模板实现自己的函数对计算结果进行最终计算,通常用于有 interBuf 使用的场景。 + +`void abs_max_finalize(char* dataOutput, char* interBuf, int* numOfOutput, SUdfInit* buf)` + +其中 udfFinalizeFunc 是函数名的占位符 ,其中各参数的具体含义是: + - dataOutput:输出数据的缓冲区。 + - interBuf:中间结算结果缓冲区,可作为输入。 + - numOfOutput:输出数据的个数,对聚合函数来说只能是 0 或者 1。 + - buf:用于在 UDF 与引擎间的状态控制信息传递块。 + +## UDF 实现方式的规则总结 + +三类 UDF 函数: udfNormalFunc、udfMergeFunc、udfFinalizeFunc ,其函数名约定使用相同的前缀,此前缀即 udfNormalFunc 的实际函数名,也即 udfNormalFunc 函数不需要在实际函数名后添加后缀;而udfMergeFunc 的函数名要加上后缀 `_merge`、udfFinalizeFunc 的函数名要加上后缀 `_finalize`,这是 UDF 实现规则的一部分,系统会按照这些函数名后缀来调用相应功能。 + +根据 UDF 函数类型的不同,用户所要实现的功能函数也不同: + +- 标量函数:UDF 中需实现 udfNormalFunc。 +- 聚合函数:UDF 中需实现 udfNormalFunc、udfMergeFunc(对超级表查询)、udfFinalizeFunc。 + +:::note +如果对应的函数不需要具体的功能,也需要实现一个空函数。 + +::: + +## 编译 UDF + +用户定义函数的 C 语言源代码无法直接被 TDengine 系统使用,而是需要先编译为 动态链接库,之后才能载入 TDengine 系统。 + +例如,按照上一章节描述的规则准备好了用户定义函数的源代码 add_one.c,以 Linux 为例可以执行如下指令编译得到动态链接库文件: + +```bash +gcc -g -O0 -fPIC -shared add_one.c -o add_one.so +``` + +这样就准备好了动态链接库 add_one.so 文件,可以供后文创建 UDF 时使用了。为了保证可靠的系统运行,编译器 GCC 推荐使用 7.5 及以上版本。 + +## 在系统中管理和使用 UDF + +### 创建 UDF + +用户可以通过 SQL 指令在系统中加载客户端所在主机上的 UDF 函数库(不能通过 RESTful 接口或 HTTP 管理界面来进行这一过程)。一旦创建成功,则当前 TDengine 集群的所有用户都可以在 SQL 指令中使用这些函数。UDF 存储在系统的 MNode 节点上,因此即使重启 TDengine 系统,已经创建的 UDF 也仍然可用。 + +在创建 UDF 时,需要区分标量函数和聚合函数。如果创建时声明了错误的函数类别,则可能导致通过 SQL 指令调用函数时出错。此外, UDF 支持输入与输出类型不一致,用户需要保证输入数据类型与 UDF 程序匹配,UDF 输出数据类型与 OUTPUTTYPE 匹配。 + +- 创建标量函数 +```sql +CREATE FUNCTION ids(X) AS ids(Y) OUTPUTTYPE typename(Z) [ BUFSIZE B ]; +``` + + - ids(X):标量函数未来在 SQL 指令中被调用时的函数名,必须与函数实现中 udfNormalFunc 的实际名称一致; + - ids(Y):包含 UDF 函数实现的动态链接库的库文件绝对路径(指的是库文件在当前客户端所在主机上的保存路径,通常是指向一个 .so 文件),这个路径需要用英文单引号或英文双引号括起来; + - typename(Z):此函数计算结果的数据类型,与上文中 udfNormalFunc 的 itype 参数不同,这里不是使用数字表示法,而是直接写类型名称即可; + - B:中间计算结果的缓冲区大小,单位是字节,最小 0,最大 512,如果不使用可以不设置。 + + 例如,如下语句可以把 add_one.so 创建为系统中可用的 UDF: + + ```sql + CREATE FUNCTION add_one AS "/home/taos/udf_example/add_one.so" OUTPUTTYPE INT; + ``` + +- 创建聚合函数: +```sql +CREATE AGGREGATE FUNCTION ids(X) AS ids(Y) OUTPUTTYPE typename(Z) [ BUFSIZE B ]; +``` + + - ids(X):聚合函数未来在 SQL 指令中被调用时的函数名,必须与函数实现中 udfNormalFunc 的实际名称一致; + - ids(Y):包含 UDF 函数实现的动态链接库的库文件绝对路径(指的是库文件在当前客户端所在主机上的保存路径,通常是指向一个 .so 文件),这个路径需要用英文单引号或英文双引号括起来; + - typename(Z):此函数计算结果的数据类型,与上文中 udfNormalFunc 的 itype 参数不同,这里不是使用数字表示法,而是直接写类型名称即可; + - B:中间计算结果的缓冲区大小,单位是字节,最小 0,最大 512,如果不使用可以不设置。 + + 关于中间计算结果的使用,可以参考示例程序[demo.c](https://github.com/taosdata/TDengine/blob/develop/tests/script/sh/demo.c) + + 例如,如下语句可以把 demo.so 创建为系统中可用的 UDF: + + ```sql + CREATE AGGREGATE FUNCTION demo AS "/home/taos/udf_example/demo.so" OUTPUTTYPE DOUBLE bufsize 14; + ``` + +### 管理 UDF + +- 删除指定名称的用户定义函数: +``` +DROP FUNCTION ids(X); +``` + +- ids(X):此参数的含义与 CREATE 指令中的 ids(X) 参数一致,也即要删除的函数的名字,例如 +```sql +DROP FUNCTION add_one; +``` +- 显示系统中当前可用的所有 UDF: +```sql +SHOW FUNCTIONS; +``` + +### 调用 UDF + +在 SQL 指令中,可以直接以在系统中创建 UDF 时赋予的函数名来调用用户定义函数。例如: +```sql +SELECT X(c) FROM table/stable; +``` + +表示对名为 c 的数据列调用名为 X 的用户定义函数。SQL 指令中用户定义函数可以配合 WHERE 等查询特性来使用。 + +## UDF 的一些使用限制 + +在当前版本下,使用 UDF 存在如下这些限制: + +1. 在创建和调用 UDF 时,服务端和客户端都只支持 Linux 操作系统; +2. UDF 不能与系统内建的 SQL 函数混合使用,暂不支持在一条 SQL 语句中使用多个不同名的 UDF ; +3. UDF 只支持以单个数据列作为输入; +4. UDF 只要创建成功,就会被持久化存储到 MNode 节点中; +5. 无法通过 RESTful 接口来创建 UDF; +6. UDF 在 SQL 中定义的函数名,必须与 .so 库文件实现中的接口函数名前缀保持一致,也即必须是 udfNormalFunc 的名称,而且不可与 TDengine 中已有的内建 SQL 函数重名。 + +## 示例代码 + +### 标量函数示例 [add_one](https://github.com/taosdata/TDengine/blob/develop/tests/script/sh/add_one.c) + +
+add_one.c + +```c +{{#include tests/script/sh/add_one.c}} +``` + +
+ +### 向量函数示例 [abs_max](https://github.com/taosdata/TDengine/blob/develop/tests/script/sh/abs_max.c) + +
+abs_max.c + +```c +{{#include tests/script/sh/abs_max.c}} +``` + +
+ +### 使用中间计算结果示例 [demo](https://github.com/taosdata/TDengine/blob/develop/tests/script/sh/demo.c) + +
+demo.c + +```c +{{#include tests/script/sh/demo.c}} +``` + +
diff --git a/docs-cn/07-develop/_category_.yml b/docs-cn/07-develop/_category_.yml new file mode 100644 index 0000000000..509a9405c4 --- /dev/null +++ b/docs-cn/07-develop/_category_.yml @@ -0,0 +1 @@ +label: 开发指南 \ No newline at end of file diff --git a/docs-cn/07-develop/_sub_c.mdx b/docs-cn/07-develop/_sub_c.mdx new file mode 100644 index 0000000000..95fef0042d --- /dev/null +++ b/docs-cn/07-develop/_sub_c.mdx @@ -0,0 +1,3 @@ +```c +{{#include docs-examples/c/subscribe_demo.c}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/_sub_cs.mdx b/docs-cn/07-develop/_sub_cs.mdx new file mode 100644 index 0000000000..80934aa4d0 --- /dev/null +++ b/docs-cn/07-develop/_sub_cs.mdx @@ -0,0 +1,3 @@ +```csharp +{{#include docs-examples/csharp/SubscribeDemo.cs}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/_sub_go.mdx b/docs-cn/07-develop/_sub_go.mdx new file mode 100644 index 0000000000..cd908fc12c --- /dev/null +++ b/docs-cn/07-develop/_sub_go.mdx @@ -0,0 +1,3 @@ +```go +{{#include docs-examples/go/sub/main.go}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/_sub_java.mdx b/docs-cn/07-develop/_sub_java.mdx new file mode 100644 index 0000000000..1ee0cb1a21 --- /dev/null +++ b/docs-cn/07-develop/_sub_java.mdx @@ -0,0 +1,7 @@ +```java +{{#include docs-examples/java/src/main/java/com/taos/example/SubscribeDemo.java}} +``` +:::note +目前 Java 接口没有提供异步订阅模式,但用户程序可以通过创建 `TimerTask` 等方式达到同样的效果。 + +::: \ No newline at end of file diff --git a/docs-cn/07-develop/_sub_node.mdx b/docs-cn/07-develop/_sub_node.mdx new file mode 100644 index 0000000000..c93ad627ce --- /dev/null +++ b/docs-cn/07-develop/_sub_node.mdx @@ -0,0 +1,3 @@ +```js +{{#include docs-examples/node/nativeexample/subscribe_demo.js}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/_sub_python.mdx b/docs-cn/07-develop/_sub_python.mdx new file mode 100644 index 0000000000..b817deeba6 --- /dev/null +++ b/docs-cn/07-develop/_sub_python.mdx @@ -0,0 +1,3 @@ +```py +{{#include docs-examples/python/subscribe_demo.py}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/_sub_rust.mdx b/docs-cn/07-develop/_sub_rust.mdx new file mode 100644 index 0000000000..4750cf7a3b --- /dev/null +++ b/docs-cn/07-develop/_sub_rust.mdx @@ -0,0 +1,3 @@ +```rs +{{#include docs-examples/rust/nativeexample/examples/subscribe_demo.rs}} +``` \ No newline at end of file diff --git a/docs-cn/07-develop/index.md b/docs-cn/07-develop/index.md new file mode 100644 index 0000000000..0393a87ab2 --- /dev/null +++ b/docs-cn/07-develop/index.md @@ -0,0 +1,24 @@ +--- +title: 开发指南 +--- + +开发一个应用,如果你准备采用TDengine作为时序数据处理的工具,那么有如下几个事情要做: +1. 确定应用到TDengine的链接方式。无论你使用何种编程语言,你总可以使用REST接口, 但也可以使用每种编程语言独有的连接器方便的进行链接。 +2. 根据自己的应用场景,确定数据模型。根据数据特征,决定建立一个还是多个库;分清静态标签、采集量,建立正确的超级表,建立子表。 +3. 决定插入数据的方式。TDengine支持使用标准的SQL写入,但同时也支持schemaless模式写入,这样不用手工建表,可以将数据直接写入。 +4. 根据业务要求,看需要撰写哪些SQL查询语句。 +5. 如果你要基于时序数据做实时的统计分析,包括各种监测看板,那么建议你采用TDengine的连续查询功能,而不用上线Spark, Flink等复杂的流式计算系统。 +6. 如果你的应用有模块需要消费插入的数据,希望有新的数据插入时,就能获取通知,那么建议你采用TDengine提供的数据订阅功能,而无需专门部署Kafka或其他消息队列软件。 +7. 在很多场景下(如车辆管理),应用需要获取每个数据采集点的最新状态,那么建议你采用TDengine的cache功能,而不用单独部署Redis等缓存软件。 +8. 如果你发现TDengine的函数无法满足你的要求,那么你可以使用用户自定义函数来解决问题。 + +本部分内容就是按照上述的顺序组织的。为便于理解,TDengine为每个功能为每个支持的编程语言都提供了示例代码。如果你希望深入了解SQL的使用,需要查看[SQL手册](/taos-sql/)。如果想更深入地了解各连接器的使用,请阅读[连接器参考指南](/reference/connector/)。如果还希望想将TDengine与第三方系统集成起来,比如Grafana, 请参考[第三方工具](/third-party/)。 + +如果在开发过程中遇到任何问题,请点击每个页面下方的["反馈问题"](https://github.com/taosdata/TDengine/issues/new/choose), 在GitHub上直接递交issue。 + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + + +``` diff --git a/docs-cn/10-cluster/01-deploy.md b/docs-cn/10-cluster/01-deploy.md new file mode 100644 index 0000000000..cee140c0ec --- /dev/null +++ b/docs-cn/10-cluster/01-deploy.md @@ -0,0 +1,138 @@ +--- +title: 集群部署 +--- + +## 准备工作 + +### 第零步 + +规划集群所有物理节点的 FQDN,将规划好的 FQDN 分别添加到每个物理节点的 /etc/hosts;修改每个物理节点的 /etc/hosts,将所有集群物理节点的 IP 与 FQDN 的对应添加好。【如部署了 DNS,请联系网络管理员在 DNS 上做好相关配置】 + +### 第一步 + +如果搭建集群的物理节点中,存有之前的测试数据、装过 1.X 的版本,或者装过其他版本的 TDengine,请先将其删除,并清空所有数据(如果需要保留原有数据,请联系涛思交付团队进行旧版本升级、数据迁移),具体步骤请参考博客[《TDengine 多种安装包的安装和卸载》](https://www.taosdata.com/blog/2019/08/09/566.html)。 + +:::note +因为 FQDN 的信息会写进文件,如果之前没有配置或者更改 FQDN,且启动了 TDengine。请一定在确保数据无用或者备份的前提下,清理一下之前的数据(rm -rf /var/lib/taos/\*); +::: + +:::note +客户端所在服务器也需要配置,确保它可以正确解析每个节点的 FQDN 配置,不管是通过 DNS 服务,还是修改 hosts 文件。 +::: + +### 第二步 + +建议关闭所有物理节点的防火墙,至少保证端口:6030 - 6042 的 TCP 和 UDP 端口都是开放的。强烈建议先关闭防火墙,集群搭建完毕之后,再来配置端口; + +### 第三步 + +在所有物理节点安装 TDengine,且版本必须是一致的,但不要启动 taosd。安装时,提示输入是否要加入一个已经存在的 TDengine 集群时,第一个物理节点直接回车创建新集群,后续物理节点则输入该集群任何一个在线的物理节点的 FQDN:端口号(默认 6030); + +### 第四步 + +检查所有数据节点,以及应用程序所在物理节点的网络设置: + +每个物理节点上执行命令 `hostname -f`,查看和确认所有节点的 hostname 是不相同的(应用驱动所在节点无需做此项检查); + +每个物理节点上执行 ping host,其中 host 是其他物理节点的 hostname,看能否 ping 通其它物理节点;如果不能 ping 通,需要检查网络设置,或 /etc/hosts 文件(Windows 系统默认路径为 C:\Windows\system32\drivers\etc\hosts),或 DNS 的配置。如果无法 ping 通,是无法组成集群的; + +从应用运行的物理节点,ping taosd 运行的数据节点,如果无法 ping 通,应用是无法连接 taosd 的,请检查应用所在物理节点的 DNS 设置或 hosts 文件; + +每个数据节点的 End Point 就是输出的 hostname 外加端口号,比如 h1.taosdata.com:6030。 + +### 第五步 + +修改 TDengine 的配置文件(所有节点的文件 /etc/taos/taos.cfg 都需要修改)。假设准备启动的第一个数据节点 End Point 为 h1.taosdata.com:6030,其与集群配置相关参数如下: + +```c +// firstEp 是每个数据节点首次启动后连接的第一个数据节点 +firstEp h1.taosdata.com:6030 + +// 必须配置为本数据节点的 FQDN,如果本机只有一个 hostname,可注释掉本项 +fqdn h1.taosdata.com + +// 配置本数据节点的端口号,缺省是 6030 +serverPort 6030 + +// 副本数为偶数的时候,需要配置,请参考《Arbitrator 的使用》的部分 +arbitrator ha.taosdata.com:6042 +``` + +一定要修改的参数是 firstEp 和 fqdn。在每个数据节点,firstEp 需全部配置成一样,但 fqdn 一定要配置成其所在数据节点的值。其他参数可不做任何修改,除非你很清楚为什么要修改。 + +加入到集群中的数据节点 dnode,涉及集群相关的下表 9 项参数必须完全相同,否则不能成功加入到集群中。 + +| **#** | **配置参数名称** | **含义** | +| ----- | ------------------ | ------------------------------------------- | +| 1 | numOfMnodes | 系统中管理节点个数 | +| 2 | mnodeEqualVnodeNum | 一个 mnode 等同于 vnode 消耗的个数 | +| 3 | offlineThreshold | dnode 离线阈值,超过该时间将导致 Dnode 离线 | +| 4 | statusInterval | dnode 向 mnode 报告状态时长 | +| 5 | arbitrator | 系统中裁决器的 End Point | +| 6 | timezone | 时区 | +| 7 | balance | 是否启动负载均衡 | +| 8 | maxTablesPerVnode | 每个 vnode 中能够创建的最大表个数 | +| 9 | maxVgroupsPerDb | 每个 DB 中能够使用的最大 vgroup 个数 | + +:::note +在 2.0.19.0 及更早的版本中,除以上 9 项参数外,dnode 加入集群时,还会要求 locale 和 charset 参数的取值也一致。 + +::: + +## 启动集群 + +### 启动第一个数据节点 + +按照《立即开始》里的步骤,启动第一个数据节点,例如 h1.taosdata.com,然后执行 taos,启动 taos shell,从 shell 里执行命令“SHOW DNODES”,如下所示: + +``` +Welcome to the TDengine shell from Linux, Client Version:2.0.0.0 + + +Copyright (c) 2017 by TAOS Data, Inc. All rights reserved. + +taos> show dnodes; + id | end_point | vnodes | cores | status | role | create_time | +===================================================================================== + 1 | h1.taos.com:6030 | 0 | 2 | ready | any | 2020-07-31 03:49:29.202 | +Query OK, 1 row(s) in set (0.006385s) + +taos> +``` + +上述命令里,可以看到刚启动的数据节点的 End Point 是:h1.taos.com:6030,就是这个新集群的 firstEp。 + +### 启动后续数据节点 + +将后续的数据节点添加到现有集群,具体有以下几步: + +按照《立即开始》一章的方法在每个物理节点启动 taosd;(注意:每个物理节点都需要在 taos.cfg 文件中将 firstEp 参数配置为新集群首个节点的 End Point——在本例中是 h1.taos.com:6030) + +在第一个数据节点,使用 CLI 程序 taos,登录进 TDengine 系统,执行命令: + +```sql +CREATE DNODE "h2.taos.com:6030"; +``` + +将新数据节点的 End Point(准备工作中第四步获知的)添加进集群的 EP 列表。“fqdn:port”需要用双引号引起来,否则出错。请注意将示例的“h2.taos.com:6030” 替换为这个新数据节点的 End Point。 + +然后执行命令 + +```sql +SHOW DNODES; +``` + +查看新节点是否被成功加入。如果该被加入的数据节点处于离线状态,请做两个检查: + +查看该数据节点的 taosd 是否正常工作,如果没有正常运行,需要先检查为什么? +查看该数据节点 taosd 日志文件 taosdlog.0 里前面几行日志(一般在 /var/log/taos 目录),看日志里输出的该数据节点 fqdn 以及端口号是否为刚添加的 End Point。如果不一致,需要将正确的 End Point 添加进去。 +按照上述步骤可以源源不断的将新的数据节点加入到集群。 + +:::tip + +任何已经加入集群在线的数据节点,都可以作为后续待加入节点的 firstEp。 +firstEp 这个参数仅仅在该数据节点首次加入集群时有作用,加入集群后,该数据节点会保存最新的 mnode 的 End Point 列表,不再依赖这个参数。 +接下来,配置文件中的 firstEp 参数就主要在客户端连接的时候使用了,例如 taos shell 如果不加参数,会默认连接由 firstEp 指定的节点。 +两个没有配置 firstEp 参数的数据节点 dnode 启动后,会独立运行起来。这个时候,无法将其中一个数据节点加入到另外一个数据节点,形成集群。无法将两个独立的集群合并成为新的集群。 + +::: diff --git a/docs-cn/10-cluster/02-cluster-mgmt.md b/docs-cn/10-cluster/02-cluster-mgmt.md new file mode 100644 index 0000000000..6ab8ec091b --- /dev/null +++ b/docs-cn/10-cluster/02-cluster-mgmt.md @@ -0,0 +1,216 @@ +--- +title: 数据节点管理 +--- + +上面已经介绍如何从零开始搭建集群。集群组建完成后,可以随时查看集群中当前的数据节点的状态,还可以添加新的数据节点进行扩容,删除数据节点,甚至手动进行数据节点之间的负载均衡操作。 + +:::note + +以下所有执行命令的操作需要先登陆进 TDengine 系统,必要时请使用 root 权限。 + +::: + +## 查看数据节点 + +启动 TDengine CLI 程序 taos,然后执行: + +```sql +SHOW DNODES; +``` + +它将列出集群中所有的 dnode,每个 dnode 的 ID,end_point(fqdn:port),状态(ready,offline 等),vnode 数目,还未使用的 vnode 数目等信息。在添加或删除一个数据节点后,可以使用该命令查看。 + +输出如下(具体内容仅供参考,取决于实际的集群配置) + +``` +taos> show dnodes; + id | end_point | vnodes | cores | status | role | create_time | offline reason | +====================================================================================================================================== + 1 | localhost:6030 | 9 | 8 | ready | any | 2022-04-15 08:27:09.359 | | +Query OK, 1 row(s) in set (0.008298s) +``` + +## 查看虚拟节点组 + +为充分利用多核技术,并提供 scalability,数据需要分片处理。因此 TDengine 会将一个 DB 的数据切分成多份,存放在多个 vnode 里。这些 vnode 可能分布在多个数据节点 dnode 里,这样就实现了水平扩展。一个 vnode 仅仅属于一个 DB,但一个 DB 可以有多个 vnode。vnode 所在的数据节点是 mnode 根据当前系统资源的情况,自动进行分配的,无需任何人工干预。 + +启动 CLI 程序 taos,然后执行: + +```sql +USE SOME_DATABASE; +SHOW VGROUPS; +``` + +输出如下(具体内容仅供参考,取决于实际的集群配置) + +``` +taos> show dnodes; + id | end_point | vnodes | cores | status | role | create_time | offline reason | +====================================================================================================================================== + 1 | localhost:6030 | 9 | 8 | ready | any | 2022-04-15 08:27:09.359 | | +Query OK, 1 row(s) in set (0.008298s) + +taos> use db; +Database changed. + +taos> show vgroups; + vgId | tables | status | onlines | v1_dnode | v1_status | compacting | +========================================================================================== + 14 | 38000 | ready | 1 | 1 | master | 0 | + 15 | 38000 | ready | 1 | 1 | master | 0 | + 16 | 38000 | ready | 1 | 1 | master | 0 | + 17 | 38000 | ready | 1 | 1 | master | 0 | + 18 | 37001 | ready | 1 | 1 | master | 0 | + 19 | 37000 | ready | 1 | 1 | master | 0 | + 20 | 37000 | ready | 1 | 1 | master | 0 | + 21 | 37000 | ready | 1 | 1 | master | 0 | +Query OK, 8 row(s) in set (0.001154s) +``` + +## 添加数据节点 + +启动 CLI 程序 taos,然后执行: + +```sql +CREATE DNODE "fqdn:port"; +``` + +将新数据节点的 End Point 添加进集群的 EP 列表。“fqdn:port“需要用双引号引起来,否则出错。一个数据节点对外服务的 fqdn 和 port 可以通过配置文件 taos.cfg 进行配置,缺省是自动获取。【强烈不建议用自动获取方式来配置 FQDN,可能导致生成的数据节点的 End Point 不是所期望的】 + +示例如下: +``` +taos> create dnode "localhost:7030"; +Query OK, 0 of 0 row(s) in database (0.008203s) + +taos> show dnodes; + id | end_point | vnodes | cores | status | role | create_time | offline reason | +====================================================================================================================================== + 1 | localhost:6030 | 9 | 8 | ready | any | 2022-04-15 08:27:09.359 | | + 2 | localhost:7030 | 0 | 0 | offline | any | 2022-04-19 08:11:42.158 | status not received | +Query OK, 2 row(s) in set (0.001017s) +``` + +在上面的示例中可以看到新创建的 dnode 的状态为 offline,待该 dnode 被启动并连接上配置文件中指定的 firstEp后再次查看,得到如下结果(示例) + +``` +taos> show dnodes; + id | end_point | vnodes | cores | status | role | create_time | offline reason | +====================================================================================================================================== + 1 | localhost:6030 | 3 | 8 | ready | any | 2022-04-15 08:27:09.359 | | + 2 | localhost:7030 | 6 | 8 | ready | any | 2022-04-19 08:14:59.165 | | +Query OK, 2 row(s) in set (0.001316s) +``` +从中可以看到两个 dnode 状态都为 ready + + +## 删除数据节点 + +启动 CLI 程序 taos,然后执行: + +```sql +DROP DNODE "fqdn:port"; +``` +或者 +```sql +DROP DNODE dnodeId; +``` + +通过 “fqdn:port” 或 dnodeID 来指定一个具体的节点都是可以的。其中 fqdn 是被删除的节点的 FQDN,port 是其对外服务器的端口号;dnodeID 可以通过 SHOW DNODES 获得。 + +示例如下: +``` +taos> show dnodes; + id | end_point | vnodes | cores | status | role | create_time | offline reason | +====================================================================================================================================== + 1 | localhost:6030 | 9 | 8 | ready | any | 2022-04-15 08:27:09.359 | | + 2 | localhost:7030 | 0 | 0 | offline | any | 2022-04-19 08:11:42.158 | status not received | +Query OK, 2 row(s) in set (0.001017s) + +taos> drop dnode 2; +Query OK, 0 of 0 row(s) in database (0.000518s) + +taos> show dnodes; + id | end_point | vnodes | cores | status | role | create_time | offline reason | +====================================================================================================================================== + 1 | localhost:6030 | 9 | 8 | ready | any | 2022-04-15 08:27:09.359 | | +Query OK, 1 row(s) in set (0.001137s) +``` + +上面的示例中,初次执行 `show dnodes` 列出了两个 dnode, 执行 `drop dnode 2` 删除其中 ID 为 2 的 dnode 之后再次执行 `show dnodes`,可以看到只剩下 ID 为 1 的 dnode 。 + +:::warning + +数据节点一旦被 drop 之后,不能重新加入集群。需要将此节点重新部署(清空数据文件夹)。集群在完成 `drop dnode` 操作之前,会将该 dnode 的数据迁移走。 +请注意 `drop dnode` 和 停止 taosd 进程是两个不同的概念,不要混淆:因为删除 dnode 之前要执行迁移数据的操作,因此被删除的 dnode 必须保持在线状态。待删除操作结束之后,才能停止 taosd 进程。 +一个数据节点被 drop 之后,其他节点都会感知到这个 dnodeID 的删除操作,任何集群中的节点都不会再接收此 dnodeID 的请求。 +dnodeID 是集群自动分配的,不得人工指定。它在生成时是递增的,不会重复。 + +::: + +## 手动迁移数据节点 + +手动将某个 vnode 迁移到指定的 dnode。 + +启动 CLI 程序 taos,然后执行: + +```sql +ALTER DNODE BALANCE "VNODE:-DNODE:"; +``` + +其中:source-dnodeId 是源 dnodeId,也就是待迁移的 vnode 所在的 dnodeID;vgId 可以通过 SHOW VGROUPS 获得,列表的第一列;dest-dnodeId 是目标 dnodeId。 + +首先执行 `show vgroups` 查看 vgroup 的分布情况 +``` +taos> show vgroups; + vgId | tables | status | onlines | v1_dnode | v1_status | compacting | +========================================================================================== + 14 | 38000 | ready | 1 | 3 | master | 0 | + 15 | 38000 | ready | 1 | 3 | master | 0 | + 16 | 38000 | ready | 1 | 3 | master | 0 | + 17 | 38000 | ready | 1 | 3 | master | 0 | + 18 | 37001 | ready | 1 | 3 | master | 0 | + 19 | 37000 | ready | 1 | 1 | master | 0 | + 20 | 37000 | ready | 1 | 1 | master | 0 | + 21 | 37000 | ready | 1 | 1 | master | 0 | +Query OK, 8 row(s) in set (0.001314s) +``` + +从中可以看到在 dnode 3 中有5个 vgroup,而 dnode 1 有 3 个 vgroup,假定我们想将其中 vgId 为18 的 vgroup 从 dnode 3 迁移到 dnode 1 + +``` +taos> alter dnode 3 balance "vnode:18-dnode:1"; + +DB error: Balance already enabled (0.00755 +``` + +上面的结果表明目前所在数据库已经启动了 balance 选项,所以无法进行手动迁移。 + +停止整个集群,将两个 dnode 的配置文件中的 balance 都设置为 0 (默认为1)之后,重新启动集群,再次执行 ` alter dnode` 和 `show vgroups` 命令如下 +``` +taos> alter dnode 3 balance "vnode:18-dnode:1"; +Query OK, 0 row(s) in set (0.000575s) + +taos> show vgroups; + vgId | tables | status | onlines | v1_dnode | v1_status | v2_dnode | v2_status | compacting | +================================================================================================================= + 14 | 38000 | ready | 1 | 3 | master | 0 | NULL | 0 | + 15 | 38000 | ready | 1 | 3 | master | 0 | NULL | 0 | + 16 | 38000 | ready | 1 | 3 | master | 0 | NULL | 0 | + 17 | 38000 | ready | 1 | 3 | master | 0 | NULL | 0 | + 18 | 37001 | ready | 2 | 1 | slave | 3 | master | 0 | + 19 | 37000 | ready | 1 | 1 | master | 0 | NULL | 0 | + 20 | 37000 | ready | 1 | 1 | master | 0 | NULL | 0 | + 21 | 37000 | ready | 1 | 1 | master | 0 | NULL | 0 | +Query OK, 8 row(s) in set (0.001242s) +``` + +从上面的输出可以看到 vgId 为 18 的 vnode 被从 dnode 3 迁移到了 dnode 1。 + +:::warning + +只有在集群的自动负载均衡选项关闭时(balance 设置为 0),才允许手动迁移。 +只有处于正常工作状态的 vnode 才能被迁移:master/slave;当处于 offline/unsynced/syncing 状态时,是不能迁移的。 +迁移前,务必核实目标 dnode 的资源足够:CPU、内存、硬盘。 + +::: + diff --git a/docs-cn/10-cluster/03-ha-and-lb.md b/docs-cn/10-cluster/03-ha-and-lb.md new file mode 100644 index 0000000000..3d15feb11c --- /dev/null +++ b/docs-cn/10-cluster/03-ha-and-lb.md @@ -0,0 +1,87 @@ +--- +title: 高可用与负载均衡 +--- + +## Vnode 的高可用性 + +TDengine 通过多副本的机制来提供系统的高可用性,包括 vnode 和 mnode 的高可用性。 + +vnode 的副本数是与 DB 关联的,一个集群里可以有多个 DB,根据运营的需求,每个 DB 可以配置不同的副本数。创建数据库时,通过参数 replica 指定副本数(缺省为 1)。如果副本数为 1,系统的可靠性无法保证,只要数据所在的节点宕机,就将无法提供服务。集群的节点数必须大于等于副本数,否则创建表时将返回错误“more dnodes are needed”。比如下面的命令将创建副本数为 3 的数据库 demo: + +```sql +CREATE DATABASE demo replica 3; +``` + +一个 DB 里的数据会被切片分到多个 vnode group,vnode group 里的 vnode 数目就是 DB 的副本数,同一个 vnode group 里各 vnode 的数据是完全一致的。为保证高可用性,vnode group 里的 vnode 一定要分布在不同的数据节点 dnode 里(实际部署时,需要在不同的物理机上),只要一个 vnode group 里超过半数的 vnode 处于工作状态,这个 vnode group 就能正常的对外服务。 + +一个数据节点 dnode 里可能有多个 DB 的数据,因此一个 dnode 离线时,可能会影响到多个 DB。如果一个 vnode group 里的一半或一半以上的 vnode 不工作,那么该 vnode group 就无法对外服务,无法插入或读取数据,这样会影响到它所属的 DB 的一部分表的读写操作。 + +因为 vnode 的引入,无法简单地给出结论:“集群中过半数据节点 dnode 工作,集群就应该工作”。但是对于简单的情形,很好下结论。比如副本数为 3,只有三个 dnode,那如果仅有一个节点不工作,整个集群还是可以正常工作的,但如果有两个数据节点不工作,那整个集群就无法正常工作了。 + +## Mnode 的高可用性 + +TDengine 集群是由 mnode(taosd 的一个模块,管理节点)负责管理的,为保证 mnode 的高可用,可以配置多个 mnode 副本,副本数由系统配置参数 numOfMnodes 决定,有效范围为 1-3。为保证元数据的强一致性,mnode 副本之间是通过同步的方式进行数据复制的。 + +一个集群有多个数据节点 dnode,但一个 dnode 至多运行一个 mnode 实例。多个 dnode 情况下,哪个 dnode 可以作为 mnode 呢?这是完全由系统根据整个系统资源情况,自动指定的。用户可通过 CLI 程序 taos,在 TDengine 的 console 里,执行如下命令: + +```sql +SHOW MNODES; +``` + +来查看 mnode 列表,该列表将列出 mnode 所处的 dnode 的 End Point 和角色(master,slave,unsynced 或 offline)。当集群中第一个数据节点启动时,该数据节点一定会运行一个 mnode 实例,否则该数据节点 dnode 无法正常工作,因为一个系统是必须有至少一个 mnode 的。如果 numOfMnodes 配置为 2,启动第二个 dnode 时,该 dnode 也将运行一个 mnode 实例。 + +为保证 mnode 服务的高可用性,numOfMnodes 必须设置为 2 或更大。因为 mnode 保存的元数据必须是强一致的,如果 numOfMnodes 大于 2,复制参数 quorum 自动设为 2,也就是说,至少要保证有两个副本写入数据成功,才通知客户端应用写入成功。 + +:::note +一个 TDengine 高可用系统,无论是 vnode 还是 mnode,都必须配置多个副本。 + +::: + +## 负载均衡 + +有三种情况,将触发负载均衡,而且都无需人工干预。 + +当一个新数据节点添加进集群时,系统将自动触发负载均衡,一些节点上的数据将被自动转移到新数据节点上,无需任何人工干预。 +当一个数据节点从集群中移除时,系统将自动把该数据节点上的数据转移到其他数据节点,无需任何人工干预。 +如果一个数据节点过热(数据量过大),系统将自动进行负载均衡,将该数据节点的一些 vnode 自动挪到其他节点。 +当上述三种情况发生时,系统将启动各个数据节点的负载计算,从而决定如何挪动。 + +:::tip +负载均衡由参数 balance 控制,它决定是否启动自动负载均衡,0 表示禁用,1 表示启用自动负载均衡。 + +::: + +## 数据节点离线处理 + +如果一个数据节点离线,TDengine 集群将自动检测到。有如下两种情况: + +该数据节点离线超过一定时间(taos.cfg 里配置参数 offlineThreshold 控制时长),系统将自动把该数据节点删除,产生系统报警信息,触发负载均衡流程。如果该被删除的数据节点重新上线时,它将无法加入集群,需要系统管理员重新将其添加进集群才会开始工作。 + +离线后,在 offlineThreshold 的时长内重新上线,系统将自动启动数据恢复流程,等数据完全恢复后,该节点将开始正常工作。 + +:::note +如果一个虚拟节点组(包括 mnode 组)里所归属的每个数据节点都处于离线或 unsynced 状态,必须等该虚拟节点组里的所有数据节点都上线、都能交换状态信息后,才能选出 Master,该虚拟节点组才能对外提供服务。比如整个集群有 3 个数据节点,副本数为 3,如果 3 个数据节点都宕机,然后 2 个数据节点重启,是无法工作的,只有等 3 个数据节点都重启成功,才能对外服务。 + +::: + +## Arbitrator 的使用 + +如果副本数为偶数,当一个 vnode group 里一半或超过一半的 vnode 不工作时,是无法从中选出 master 的。同理,一半或超过一半的 mnode 不工作时,是无法选出 mnode 的 master 的,因为存在“split brain”问题。 + +为解决这个问题,TDengine 引入了 Arbitrator 的概念。Arbitrator 模拟一个 vnode 或 mnode 在工作,但只简单的负责网络连接,不处理任何数据插入或访问。只要包含 Arbitrator 在内,超过半数的 vnode 或 mnode 工作,那么该 vnode group 或 mnode 组就可以正常的提供数据插入或查询服务。比如对于副本数为 2 的情形,如果一个节点 A 离线,但另外一个节点 B 正常,而且能连接到 Arbitrator,那么节点 B 就能正常工作。 + +总之,在目前版本下,TDengine 建议在双副本环境要配置 Arbitrator,以提升系统的可用性。 + +Arbitrator 的执行程序名为 tarbitrator。该程序对系统资源几乎没有要求,只需要保证有网络连接,找任何一台 Linux 服务器运行它即可。以下简要描述安装配置的步骤: + +请点击 安装包下载,在 TDengine Arbitrator Linux 一节中,选择合适的版本下载并安装。 +该应用的命令行参数 -p 可以指定其对外服务的端口号,缺省是 6042。 + +修改每个 taosd 实例的配置文件,在 taos.cfg 里将参数 arbitrator 设置为 tarbitrator 程序所对应的 End Point。(如果该参数配置了,当副本数为偶数时,系统将自动连接配置的 Arbitrator。如果副本数为奇数,即使配置了 Arbitrator,系统也不会去建立连接。) + +在配置文件中配置了的 Arbitrator,会出现在 SHOW DNODES 指令的返回结果中,对应的 role 列的值会是“arb”。 +查看集群 Arbitrator 的状态【2.0.14.0 以后支持】 + +```sql +SHOW DNODES; +``` diff --git a/docs-cn/10-cluster/_category_.yml b/docs-cn/10-cluster/_category_.yml new file mode 100644 index 0000000000..3cee5ce4cd --- /dev/null +++ b/docs-cn/10-cluster/_category_.yml @@ -0,0 +1 @@ +label: 集群管理 diff --git a/docs-cn/10-cluster/index.md b/docs-cn/10-cluster/index.md new file mode 100644 index 0000000000..ef2a7253c9 --- /dev/null +++ b/docs-cn/10-cluster/index.md @@ -0,0 +1,14 @@ +--- +title: 集群管理 +--- + +TDengine 支持集群,提供水平扩展的能力。如果需要获得更高的处理能力,只需要多增加节点即可。TDengine 采用虚拟节点技术,将一个节点虚拟化为多个虚拟节点,以实现负载均衡。同时,TDengine可以将多个节点上的虚拟节点组成虚拟节点组,通过多副本机制,以保证供系统的高可用。TDengine的集群功能完全开源。 + +本章节主要介绍集群的部署、维护,以及如何实现高可用和负载均衡。 + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + + +``` diff --git a/docs-cn/12-taos-sql/01-data-type.md b/docs-cn/12-taos-sql/01-data-type.md new file mode 100644 index 0000000000..be5c9a8cb4 --- /dev/null +++ b/docs-cn/12-taos-sql/01-data-type.md @@ -0,0 +1,50 @@ +--- +sidebar_label: 支持的数据类型 +title: 支持的数据类型 +description: "TDengine 支持的数据类型: 时间戳、浮点型、JSON 类型等" +--- + +使用 TDengine,最重要的是时间戳。创建并插入记录、查询历史记录的时候,均需要指定时间戳。时间戳有如下规则: + +- 时间格式为 `YYYY-MM-DD HH:mm:ss.MS`,默认时间分辨率为毫秒。比如:`2017-08-12 18:25:58.128` +- 内部函数 now 是客户端的当前时间 +- 插入记录时,如果时间戳为 now,插入数据时使用提交这条记录的客户端的当前时间 +- Epoch Time:时间戳也可以是一个长整数,表示从格林威治时间 1970-01-01 00:00:00.000 (UTC/GMT) 开始的毫秒数(相应地,如果所在 Database 的时间精度设置为“微秒”,则长整型格式的时间戳含义也就对应于从格林威治时间 1970-01-01 00:00:00.000 (UTC/GMT) 开始的微秒数;纳秒精度逻辑类似。) +- 时间可以加减,比如 now-2h,表明查询时刻向前推 2 个小时(最近 2 小时)。数字后面的时间单位可以是 b(纳秒)、u(微秒)、a(毫秒)、s(秒)、m(分)、h(小时)、d(天)、w(周)。 比如 `select * from t1 where ts > now-2w and ts <= now-1w`,表示查询两周前整整一周的数据。在指定降采样操作(down sampling)的时间窗口(interval)时,时间单位还可以使用 n (自然月) 和 y (自然年)。 + +TDengine 缺省的时间戳精度是毫秒,但通过在 `CREATE DATABASE` 时传递的 PRECISION 参数也可以支持微秒和纳秒。(从 2.1.5.0 版本开始支持纳秒精度) + +```sql +CREATE DATABASE db_name PRECISION 'ns'; +``` + +在 TDengine 中,普通表的数据模型中可使用以下 10 种数据类型。 + +| # | **类型** | **Bytes** | **说明** | +| --- | :-------: | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1 | TIMESTAMP | 8 | 时间戳。缺省精度毫秒,可支持微秒和纳秒。从格林威治时间 1970-01-01 00:00:00.000 (UTC/GMT) 开始,计时不能早于该时间。(从 2.0.18.0 版本开始,已经去除了这一时间范围限制)(从 2.1.5.0 版本开始支持纳秒精度) | +| 2 | INT | 4 | 整型,范围 [-2^31+1, 2^31-1], -2^31 用作 NULL | +| 3 | BIGINT | 8 | 长整型,范围 [-2^63+1, 2^63-1], -2^63 用作 NULL | +| 4 | FLOAT | 4 | 浮点型,有效位数 6-7,范围 [-3.4E38, 3.4E38] | +| 5 | DOUBLE | 8 | 双精度浮点型,有效位数 15-16,范围 [-1.7E308, 1.7E308] | +| 6 | BINARY | 自定义 | 记录单字节字符串,建议只用于处理 ASCII 可见字符,中文等多字节字符需使用 nchar。理论上,最长可以有 16374 字节。binary 仅支持字符串输入,字符串两端需使用单引号引用。使用时须指定大小,如 binary(20) 定义了最长为 20 个单字节字符的字符串,每个字符占 1 byte 的存储空间,总共固定占用 20 bytes 的空间,此时如果用户字符串超出 20 字节将会报错。对于字符串内的单引号,可以用转义字符反斜线加单引号来表示,即 `\’`。 | +| 7 | SMALLINT | 2 | 短整型, 范围 [-32767, 32767], -32768 用作 NULL | +| 8 | TINYINT | 1 | 单字节整型,范围 [-127, 127], -128 用作 NULL | +| 9 | BOOL | 1 | 布尔型,{true, false} | +| 10 | NCHAR | 自定义 | 记录包含多字节字符在内的字符串,如中文字符。每个 nchar 字符占用 4 bytes 的存储空间。字符串两端使用单引号引用,字符串内的单引号需用转义字符 `\’`。nchar 使用时须指定字符串大小,类型为 nchar(10) 的列表示此列的字符串最多存储 10 个 nchar 字符,会固定占用 40 bytes 的空间。如果用户字符串长度超出声明长度,将会报错。 | +| 11 | JSON | | json 数据类型, 只有 tag 可以是 json 格式 | + +:::tip +TDengine 对 SQL 语句中的英文字符不区分大小写,自动转化为小写执行。因此用户大小写敏感的字符串及密码,需要使用单引号将字符串引起来。 + +::: + +:::note +虽然 BINARY 类型在底层存储上支持字节型的二进制字符,但不同编程语言对二进制数据的处理方式并不保证一致,因此建议在 BINARY 类型中只存储 ASCII 可见字符,而避免存储不可见字符。多字节的数据,例如中文字符,则需要使用 NCHAR 类型进行保存。如果强行使用 BINARY 类型保存中文字符,虽然有时也能正常读写,但并不带有字符集信息,很容易出现数据乱码甚至数据损坏等情况。 + +::: + +:::note +SQL 语句中的数值类型将依据是否存在小数点,或使用科学计数法表示,来判断数值类型是否为整型或者浮点型,因此在使用时要注意相应类型越界的情况。例如,9999999999999999999 会认为超过长整型的上边界而溢出,而 9999999999999999999.0 会被认为是有效的浮点数。 + +::: diff --git a/docs-cn/12-taos-sql/02-database.md b/docs-cn/12-taos-sql/02-database.md new file mode 100644 index 0000000000..1454d1d344 --- /dev/null +++ b/docs-cn/12-taos-sql/02-database.md @@ -0,0 +1,128 @@ +--- +sidebar_label: 数据库管理 +title: 数据库管理 +description: "创建、删除数据库,查看、修改数据库参数" +--- + +## 创建数据库 + +``` +CREATE DATABASE [IF NOT EXISTS] db_name [KEEP keep] [DAYS days] [UPDATE 1]; +``` + +:::info +1. KEEP 是该数据库的数据保留多长天数,缺省是 3650 天(10 年),数据库会自动删除超过时限的数据; +2. UPDATE 标志数据库支持更新相同时间戳数据;(从 2.1.7.0 版本开始此参数支持设为 2,表示允许部分列更新,也即更新数据行时未被设置的列会保留原值。)(从 2.0.8.0 版本开始支持此参数。注意此参数不能通过 `ALTER DATABASE` 指令进行修改。) + 1. UPDATE 设为 0 时,表示不允许更新数据,后发送的相同时间戳的数据会被直接丢弃; + 2. UPDATE 设为 1 时,表示更新全部列数据,即如果更新一个数据行,其中某些列没有提供取值,那么这些列会被设为 NULL; + 3. UPDATE 设为 2 时,表示支持更新部分列数据,即如果更新一个数据行,其中某些列没有提供取值,那么这些列会保持原有数据行中的对应值; + 4. 更多关于 UPDATE 参数的用法,请参考[FAQ](/train-faq/faq)。 +3. 数据库名最大长度为 33; +4. 一条 SQL 语句的最大长度为 65480 个字符; +5. 创建数据库时可用的参数有: + - cache: [Description](/reference/config/#cache) + - blocks: [Description](/reference/config/#blocks) + - days: [Description](/reference/config/#days) + - keep: [Description](/reference/config/#keep) + - minRows: [Description](/reference/config/#minrows) + - maxRows: [Description](/reference/config/#maxrows) + - wal: [Description](/reference/config/#wallevel) + - fsync: [Description](/reference/config/#fsync) + - update: [Description](/reference/config/#update) + - cacheLast: [Description](/reference/config/#cachelast) + - replica: [Description](/reference/config/#replica) + - quorum: [Description](/reference/config/#quorum) + - maxVgroupsPerDb: [Description](/reference/config/#maxvgroupsperdb) + - comp: [Description](/reference/config/#comp) + - precision: [Description](/reference/config/#precision) +6. 请注意上面列出的所有参数都可以配置在配置文件 `taosd.cfg` 中作为创建数据库时使用的默认配置, `create database` 的参数中明确指定的会覆盖配置文件中的设置。 + +::: + +### 创建数据库示例 + +创建时间精度为纳秒的数据库, 保留 1 年数据: + +```sql +CREATE DATABASE test PRECISION 'ns' KEEP 365; +``` + +## 显示系统当前参数 + +``` +SHOW VARIABLES; +``` + +## 使用数据库 + +``` +USE db_name; +``` + +使用/切换数据库(在 REST 连接方式下无效)。 + +## 删除数据库 + +``` +DROP DATABASE [IF EXISTS] db_name; +``` + +删除数据库。指定 Database 所包含的全部数据表将被删除,谨慎使用! + +## 修改数据库参数 + +``` +ALTER DATABASE db_name COMP 2; +``` + +COMP 参数是指修改数据库文件压缩标志位,缺省值为 2,取值范围为 [0, 2]。0 表示不压缩,1 表示一阶段压缩,2 表示两阶段压缩。 + +``` +ALTER DATABASE db_name REPLICA 2; +``` + +REPLICA 参数是指修改数据库副本数,取值范围 [1, 3]。在集群中使用,副本数必须小于或等于 DNODE 的数目。 + +``` +ALTER DATABASE db_name KEEP 365; +``` + +KEEP 参数是指修改数据文件保存的天数,缺省值为 3650,取值范围 [days, 365000],必须大于或等于 days 参数值。 + +``` +ALTER DATABASE db_name QUORUM 2; +``` + +QUORUM 参数是指数据写入成功所需要的确认数,取值范围 [1, 2]。对于异步复制,quorum 设为 1,具有 master 角色的虚拟节点自己确认即可。对于同步复制,quorum 设为 2。原则上,Quorum >= 1 并且 Quorum <= replica(副本数),这个参数在启动一个同步模块实例时需要提供。 + +``` +ALTER DATABASE db_name BLOCKS 100; +``` + +BLOCKS 参数是每个 VNODE (TSDB) 中有多少 cache 大小的内存块,因此一个 VNODE 的用的内存大小粗略为(cache \* blocks)。取值范围 [3, 1000]。 + +``` +ALTER DATABASE db_name CACHELAST 0; +``` + +CACHELAST 参数控制是否在内存中缓存子表的最近数据。缺省值为 0,取值范围 [0, 1, 2, 3]。其中 0 表示不缓存,1 表示缓存子表最近一行数据,2 表示缓存子表每一列的最近的非 NULL 值,3 表示同时打开缓存最近行和列功能。(从 2.0.11.0 版本开始支持参数值 [0, 1],从 2.1.2.0 版本开始支持参数值 [0, 1, 2, 3]。) +说明:缓存最近行,将显著改善 LAST_ROW 函数的性能表现;缓存每列的最近非 NULL 值,将显著改善无特殊影响(WHERE、ORDER BY、GROUP BY、INTERVAL)下的 LAST 函数的性能表现。 + +:::tip +以上所有参数修改后都可以用 show databases 来确认是否修改成功。另外,从 2.1.3.0 版本开始,修改这些参数后无需重启服务器即可生效。 +::: + +## 显示系统所有数据库 + +``` +SHOW DATABASES; +``` + +## 显示一个数据库的创建语句 + +``` +SHOW CREATE DATABASE db_name; +``` + +常用于数据库迁移。对一个已经存在的数据库,返回其创建语句;在另一个集群中执行该语句,就能得到一个设置完全相同的 Database。 + diff --git a/docs-cn/12-taos-sql/03-table.md b/docs-cn/12-taos-sql/03-table.md new file mode 100644 index 0000000000..675c157b3d --- /dev/null +++ b/docs-cn/12-taos-sql/03-table.md @@ -0,0 +1,123 @@ +--- +title: 表管理 +--- + +## 创建数据表 + +``` +CREATE TABLE [IF NOT EXISTS] tb_name (timestamp_field_name TIMESTAMP, field1_name data_type1 [, field2_name data_type2 ...]); +``` + +:::info 说明 + +1. 表的第一个字段必须是 TIMESTAMP,并且系统自动将其设为主键; +2. 表名最大长度为 192; +3. 表的每行长度不能超过 16k 个字符;(注意:每个 BINARY/NCHAR 类型的列还会额外占用 2 个字节的存储位置) +4. 子表名只能由字母、数字和下划线组成,且不能以数字开头,不区分大小写 +5. 使用数据类型 binary 或 nchar,需指定其最长的字节数,如 binary(20),表示 20 字节; +6. 为了兼容支持更多形式的表名,TDengine 引入新的转义符 "\`",可以让表名与关键词不冲突,同时不受限于上述表名称合法性约束检查。但是同样具有长度限制要求。使用转义字符以后,不再对转义字符中的内容进行大小写统一。 + 例如:\`aBc\` 和 \`abc\` 是不同的表名,但是 abc 和 aBc 是相同的表名。 + 需要注意的是转义字符中的内容必须是可打印字符。 + 上述的操作逻辑和约束要求与 MySQL 数据的操作一致。 + 从 2.3.0.0 版本开始支持这种方式。 + +::: + +### 以超级表为模板创建数据表 + +``` +CREATE TABLE [IF NOT EXISTS] tb_name USING stb_name TAGS (tag_value1, ...); +``` + +以指定的超级表为模板,指定 TAGS 的值来创建数据表。 + +### 以超级表为模板创建数据表,并指定具体的 TAGS 列 + +``` +CREATE TABLE [IF NOT EXISTS] tb_name USING stb_name (tag_name1, ...) TAGS (tag_value1, ...); +``` + +以指定的超级表为模板,指定一部分 TAGS 列的值来创建数据表(没被指定的 TAGS 列会设为空值)。 +说明:从 2.0.17.0 版本开始支持这种方式。在之前的版本中,不允许指定 TAGS 列,而必须显式给出所有 TAGS 列的取值。 + +### 批量创建数据表 + +``` +CREATE TABLE [IF NOT EXISTS] tb_name1 USING stb_name TAGS (tag_value1, ...) [IF NOT EXISTS] tb_name2 USING stb_name TAGS (tag_value2, ...) ...; +``` + +以更快的速度批量创建大量数据表(服务器端 2.0.14 及以上版本)。 + +:::info + +1.批量建表方式要求数据表必须以超级表为模板。 2.在不超出 SQL 语句长度限制的前提下,单条语句中的建表数量建议控制在 1000 ~ 3000 之间,将会获得比较理想的建表速度。 + +::: + +## 删除数据表 + +``` +DROP TABLE [IF EXISTS] tb_name; +``` + +## 显示当前数据库下的所有数据表信息 + +``` +SHOW TABLES [LIKE tb_name_wildchar]; +``` + +显示当前数据库下的所有数据表信息。 + +## 显示一个数据表的创建语句 + +``` +SHOW CREATE TABLE tb_name; +``` + +常用于数据库迁移。对一个已经存在的数据表,返回其创建语句;在另一个集群中执行该语句,就能得到一个结构完全相同的数据表。 + +## 获取表的结构信息 + +``` +DESCRIBE tb_name; +``` + +## 修改表定义 + +### 表增加列 + +``` +ALTER TABLE tb_name ADD COLUMN field_name data_type; +``` + +:::info + +1. 列的最大个数为 1024,最小个数为 2;(从 2.1.7.0 版本开始,改为最多允许 4096 列) +2. 列名最大长度为 64。 + +::: + +### 表删除列 + +``` +ALTER TABLE tb_name DROP COLUMN field_name; +``` + +如果表是通过超级表创建,更改表结构的操作只能对超级表进行。同时针对超级表的结构更改对所有通过该结构创建的表生效。对于不是通过超级表创建的表,可以直接修改表结构。 + +### 表修改列宽 + +``` +ALTER TABLE tb_name MODIFY COLUMN field_name data_type(length); +``` + +如果数据列的类型是可变长格式(BINARY 或 NCHAR),那么可以使用此指令修改其宽度(只能改大,不能改小)。(2.1.3.0 版本新增) +如果表是通过超级表创建,更改表结构的操作只能对超级表进行。同时针对超级表的结构更改对所有通过该结构创建的表生效。对于不是通过超级表创建的表,可以直接修改表结构。 + +### 修改子表标签值 + +``` +ALTER TABLE tb_name SET TAG tag_name=new_tag_value; +``` + +如果表是通过超级表创建,可以使用此指令修改其标签值 diff --git a/docs-cn/12-taos-sql/04-stable.md b/docs-cn/12-taos-sql/04-stable.md new file mode 100644 index 0000000000..a3c227317c --- /dev/null +++ b/docs-cn/12-taos-sql/04-stable.md @@ -0,0 +1,125 @@ +--- +sidebar_label: 超级表管理 +title: 超级表 STable 管理 +--- + +:::note + +在 2.0.15.0 及以后的版本中开始支持 STABLE 保留字。也即,在本节后文的指令说明中,CREATE、DROP、ALTER 三个指令在 2.0.15.0 之前的版本中 STABLE 保留字需写作 TABLE。 + +::: + +## 创建超级表 + +``` +CREATE STABLE [IF NOT EXISTS] stb_name (timestamp_field_name TIMESTAMP, field1_name data_type1 [, field2_name data_type2 ...]) TAGS (tag1_name tag_type1, tag2_name tag_type2 [, tag3_name tag_type3]); +``` + +创建 STable,与创建表的 SQL 语法相似,但需要指定 TAGS 字段的名称和类型。 + +:::info + +1. TAGS 列的数据类型不能是 timestamp 类型;(从 2.1.3.0 版本开始,TAGS 列中支持使用 timestamp 类型,但需注意在 TAGS 中的 timestamp 列写入数据时需要提供给定值,而暂不支持四则运算,例如 `NOW + 10s` 这类表达式) +2. TAGS 列名不能与其他列名相同; +3. TAGS 列名不能为预留关键字(参见:[参数限制与保留关键字](/taos-sql/keywords/) 章节); +4. TAGS 最多允许 128 个,至少 1 个,总长度不超过 16 KB。 + +::: + +## 删除超级表 + +``` +DROP STABLE [IF EXISTS] stb_name; +``` + +删除 STable 会自动删除通过 STable 创建的子表。 + +## 显示当前数据库下的所有超级表信息 + +``` +SHOW STABLES [LIKE tb_name_wildcard]; +``` + +查看数据库内全部 STable,及其相关信息,包括 STable 的名称、创建时间、列数量、标签(TAG)数量、通过该 STable 建表的数量。 + +## 显示一个超级表的创建语句 + +``` +SHOW CREATE STABLE stb_name; +``` + +常用于数据库迁移。对一个已经存在的超级表,返回其创建语句;在另一个集群中执行该语句,就能得到一个结构完全相同的超级表。 + +## 获取超级表的结构信息 + +``` +DESCRIBE stb_name; +``` + +## 修改超级表普通列 + +### 超级表增加列 + +``` +ALTER STABLE stb_name ADD COLUMN field_name data_type; +``` + +### 超级表删除列 + +``` +ALTER STABLE stb_name DROP COLUMN field_name; +``` + +### 超级表修改列宽 + +``` +ALTER STABLE stb_name MODIFY COLUMN field_name data_type(length); +``` + +如果数据列的类型是可变长格式(BINARY 或 NCHAR),那么可以使用此指令修改其宽度(只能改大,不能改小)。(2.1.3.0 版本新增) + +## 修改超级表标签列 + +### 添加标签 + +``` +ALTER STABLE stb_name ADD TAG new_tag_name tag_type; +``` + +为 STable 增加一个新的标签,并指定新标签的类型。标签总数不能超过 128 个,总长度不超过 16k 个字符。 + +### 删除标签 + +``` +ALTER STABLE stb_name DROP TAG tag_name; +``` + +删除超级表的一个标签,从超级表删除某个标签后,该超级表下的所有子表也会自动删除该标签。 + +### 修改标签名 + +``` +ALTER STABLE stb_name CHANGE TAG old_tag_name new_tag_name; +``` + +修改超级表的标签名,从超级表修改某个标签名后,该超级表下的所有子表也会自动更新该标签名。 + +### 修改标签列宽度 + +``` +ALTER STABLE stb_name MODIFY TAG tag_name data_type(length); +``` + +如果标签的类型是可变长格式(BINARY 或 NCHAR),那么可以使用此指令修改其宽度(只能改大,不能改小)。(2.1.3.0 版本新增) + +### 超级表查询 +使用 SELECT 语句可以完成在超级表上的投影及聚合两类查询,在 WHERE 语句中可以对标签及列进行筛选及过滤。 + +如果在超级表查询语句中不加 ORDER BY, 返回顺序是先返回一个子表的所有数据,然后再返回下个子表的所有数据,所以返回的数据是无序的。如果增加了 ORDER BY 语句,会严格按 ORDER BY 语句指定的顺序返回的。 + + + +:::note +除了更新标签的值的操作是针对子表进行,其他所有的标签操作(添加标签、删除标签等)均只能作用于 STable,不能对单个子表操作。对 STable 添加标签以后,依托于该 STable 建立的所有表将自动增加了一个标签,所有新增标签的默认值都是 NULL。 + +::: diff --git a/docs-cn/12-taos-sql/05-insert.md b/docs-cn/12-taos-sql/05-insert.md new file mode 100644 index 0000000000..e542e442b7 --- /dev/null +++ b/docs-cn/12-taos-sql/05-insert.md @@ -0,0 +1,149 @@ +--- +sidebar_label: 数据写入 +title: 数据写入 +--- + +## 写入语法 + +``` +INSERT INTO + tb_name + [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)] + [(field1_name, ...)] + VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path + [tb2_name + [USING stb_name [(tag1_name, ...)] TAGS (tag1_value, ...)] + [(field1_name, ...)] + VALUES (field1_value, ...) [(field1_value2, ...) ...] | FILE csv_file_path + ...]; +``` + +## 插入一条或多条记录 + +指定已经创建好的数据子表的表名,并通过 VALUES 关键字提供一行或多行数据,即可向数据库写入这些数据。例如,执行如下语句可以写入一行记录: + +``` +INSERT INTO d1001 VALUES (NOW, 10.2, 219, 0.32); +``` + +或者,可以通过如下语句写入两行记录: + +``` +INSERT INTO d1001 VALUES ('2021-07-13 14:06:32.272', 10.2, 219, 0.32) (1626164208000, 10.15, 217, 0.33); +``` + +:::note + +1. 在第二个例子中,两行记录的首列时间戳使用了不同格式的写法。其中字符串格式的时间戳写法不受所在 DATABASE 的时间精度设置影响;而长整形格式的时间戳写法会受到所在 DATABASE 的时间精度设置影响——例子中的时间戳在毫秒精度下可以写作 1626164208000,而如果是在微秒精度设置下就需要写为 1626164208000000,纳秒精度设置下需要写为 1626164208000000000。 +2. 在使用“插入多条记录”方式写入数据时,不能把第一列的时间戳取值都设为 NOW,否则会导致语句中的多条记录使用相同的时间戳,于是就可能出现相互覆盖以致这些数据行无法全部被正确保存。其原因在于,NOW 函数在执行中会被解析为所在 SQL 语句的实际执行时间,出现在同一语句中的多个 NOW 标记也就会被替换为完全相同的时间戳取值。 +3. 允许插入的最老记录的时间戳,是相对于当前服务器时间,减去配置的 keep 值(数据保留的天数);允许插入的最新记录的时间戳,是相对于当前服务器时间,加上配置的 days 值(数据文件存储数据的时间跨度,单位为天)。keep 和 days 都是可以在创建数据库时指定的,缺省值分别是 3650 天和 10 天。 + +::: + +## 插入记录,数据对应到指定的列 + +向数据子表中插入记录时,无论插入一行还是多行,都可以让数据对应到指定的列。对于 SQL 语句中没有出现的列,数据库将自动填充为 NULL。主键(时间戳)不能为 NULL。例如: + +``` +INSERT INTO d1001 (ts, current, phase) VALUES ('2021-07-13 14:06:33.196', 10.27, 0.31); +``` + +:::info +如果不指定列,也即使用全列模式——那么在 VALUES 部分提供的数据,必须为数据表的每个列都显式地提供数据。全列模式写入速度会远快于指定列,因此建议尽可能采用全列写入方式,此时空列可以填入 NULL。 + +::: + +## 向多个表插入记录 + +可以在一条语句中,分别向多个表插入一条或多条记录,并且也可以在插入过程中指定列。例如: + +``` +INSERT INTO d1001 VALUES ('2021-07-13 14:06:34.630', 10.2, 219, 0.32) ('2021-07-13 14:06:35.779', 10.15, 217, 0.33) + d1002 (ts, current, phase) VALUES ('2021-07-13 14:06:34.255', 10.27, 0.31); +``` + +## 插入记录时自动建表 + +如果用户在写数据时并不确定某个表是否存在,此时可以在写入数据时使用自动建表语法来创建不存在的表,若该表已存在则不会建立新表。自动建表时,要求必须以超级表为模板,并写明数据表的 TAGS 取值。例如: + +``` +INSERT INTO d21001 USING meters TAGS ('Beijing.Chaoyang', 2) VALUES ('2021-07-13 14:06:32.272', 10.2, 219, 0.32); +``` + +也可以在自动建表时,只是指定部分 TAGS 列的取值,未被指定的 TAGS 列将置为 NULL。例如: + +``` +INSERT INTO d21001 USING meters (groupId) TAGS (2) VALUES ('2021-07-13 14:06:33.196', 10.15, 217, 0.33); +``` + +自动建表语法也支持在一条语句中向多个表插入记录。例如: + +``` +INSERT INTO d21001 USING meters TAGS ('Beijing.Chaoyang', 2) VALUES ('2021-07-13 14:06:34.630', 10.2, 219, 0.32) ('2021-07-13 14:06:35.779', 10.15, 217, 0.33) + d21002 USING meters (groupId) TAGS (2) VALUES ('2021-07-13 14:06:34.255', 10.15, 217, 0.33) + d21003 USING meters (groupId) TAGS (2) (ts, current, phase) VALUES ('2021-07-13 14:06:34.255', 10.27, 0.31); +``` + +:::info +在 2.0.20.5 版本之前,在使用自动建表语法并指定列时,子表的列名必须紧跟在子表名称后面,而不能如例子里那样放在 TAGS 和 VALUES 之间。从 2.0.20.5 版本开始,两种写法都可以,但不能在一条 SQL 语句中混用,否则会报语法错误。 +::: + +## 插入来自文件的数据记录 + +除了使用 VALUES 关键字插入一行或多行数据外,也可以把要写入的数据放在 CSV 文件中(英文逗号分隔、英文单引号括住每个值)供 SQL 指令读取。其中 CSV 文件无需表头。例如,如果 /tmp/csvfile.csv 文件的内容为: + +``` +'2021-07-13 14:07:34.630', '10.2', '219', '0.32' +'2021-07-13 14:07:35.779', '10.15', '217', '0.33' +``` + +那么通过如下指令可以把这个文件中的数据写入子表中: + +``` +INSERT INTO d1001 FILE '/tmp/csvfile.csv'; +``` + +## 插入来自文件的数据记录,并自动建表 + +从 2.1.5.0 版本开始,支持在插入来自 CSV 文件的数据时,以超级表为模板来自动创建不存在的数据表。例如: + +``` +INSERT INTO d21001 USING meters TAGS ('Beijing.Chaoyang', 2) FILE '/tmp/csvfile.csv'; +``` + +也可以在一条语句中向多个表以自动建表的方式插入记录。例如: + +``` +INSERT INTO d21001 USING meters TAGS ('Beijing.Chaoyang', 2) FILE '/tmp/csvfile_21001.csv' + d21002 USING meters (groupId) TAGS (2) FILE '/tmp/csvfile_21002.csv'; +``` + +## 历史记录写入 + +可使用 IMPORT 或者 INSERT 命令,IMPORT 的语法,功能与 INSERT 完全一样。 + +针对 insert 类型的 SQL 语句,我们采用的流式解析策略,在发现后面的错误之前,前面正确的部分 SQL 仍会执行。下面的 SQL 中,INSERT 语句是无效的,但是 d1001 仍会被创建。 + +``` +taos> CREATE TABLE meters(ts TIMESTAMP, current FLOAT, voltage INT, phase FLOAT) TAGS(location BINARY(30), groupId INT); +Query OK, 0 row(s) affected (0.008245s) + +taos> SHOW STABLES; + name | created_time | columns | tags | tables | +============================================================================================ + meters | 2020-08-06 17:50:27.831 | 4 | 2 | 0 | +Query OK, 1 row(s) in set (0.001029s) + +taos> SHOW TABLES; +Query OK, 0 row(s) in set (0.000946s) + +taos> INSERT INTO d1001 USING meters TAGS('Beijing.Chaoyang', 2) VALUES('a'); + +DB error: invalid SQL: 'a' (invalid timestamp) (0.039494s) + +taos> SHOW TABLES; + table_name | created_time | columns | stable_name | +====================================================================================================== + d1001 | 2020-08-06 17:52:02.097 | 4 | meters | +Query OK, 1 row(s) in set (0.001091s) +``` diff --git a/docs-cn/12-taos-sql/06-select.md b/docs-cn/12-taos-sql/06-select.md new file mode 100644 index 0000000000..3a860119cf --- /dev/null +++ b/docs-cn/12-taos-sql/06-select.md @@ -0,0 +1,462 @@ +--- +sidebar_label: 数据查询 +title: 数据查询 +--- + +## 查询语法 + +``` +SELECT select_expr [, select_expr ...] + FROM {tb_name_list} + [WHERE where_condition] + [SESSION(ts_col, tol_val)] + [STATE_WINDOW(col)] + [INTERVAL(interval_val [, interval_offset]) [SLIDING sliding_val]] + [FILL(fill_mod_and_val)] + [GROUP BY col_list] + [ORDER BY col_list { DESC | ASC }] + [SLIMIT limit_val [SOFFSET offset_val]] + [LIMIT limit_val [OFFSET offset_val]] + [>> export_file]; +``` + +## 通配符 + +通配符 \* 可以用于代指全部列。对于普通表,结果中只有普通列。 + +``` +taos> SELECT * FROM d1001; + ts | current | voltage | phase | +====================================================================================== + 2018-10-03 14:38:05.000 | 10.30000 | 219 | 0.31000 | + 2018-10-03 14:38:15.000 | 12.60000 | 218 | 0.33000 | + 2018-10-03 14:38:16.800 | 12.30000 | 221 | 0.31000 | +Query OK, 3 row(s) in set (0.001165s) +``` + +在针对超级表,通配符包含 _标签列_ 。 + +``` +taos> SELECT * FROM meters; + ts | current | voltage | phase | location | groupid | +===================================================================================================================================== + 2018-10-03 14:38:05.500 | 11.80000 | 221 | 0.28000 | Beijing.Haidian | 2 | + 2018-10-03 14:38:16.600 | 13.40000 | 223 | 0.29000 | Beijing.Haidian | 2 | + 2018-10-03 14:38:05.000 | 10.80000 | 223 | 0.29000 | Beijing.Haidian | 3 | + 2018-10-03 14:38:06.500 | 11.50000 | 221 | 0.35000 | Beijing.Haidian | 3 | + 2018-10-03 14:38:04.000 | 10.20000 | 220 | 0.23000 | Beijing.Chaoyang | 3 | + 2018-10-03 14:38:16.650 | 10.30000 | 218 | 0.25000 | Beijing.Chaoyang | 3 | + 2018-10-03 14:38:05.000 | 10.30000 | 219 | 0.31000 | Beijing.Chaoyang | 2 | + 2018-10-03 14:38:15.000 | 12.60000 | 218 | 0.33000 | Beijing.Chaoyang | 2 | + 2018-10-03 14:38:16.800 | 12.30000 | 221 | 0.31000 | Beijing.Chaoyang | 2 | +Query OK, 9 row(s) in set (0.002022s) +``` + +通配符支持表名前缀,以下两个 SQL 语句均为返回全部的列: + +``` +SELECT * FROM d1001; +SELECT d1001.* FROM d1001; +``` + +在 JOIN 查询中,带前缀的\*和不带前缀\*返回的结果有差别, \*返回全部表的所有列数据(不包含标签),带前缀的通配符,则只返回该表的列数据。 + +``` +taos> SELECT * FROM d1001, d1003 WHERE d1001.ts=d1003.ts; + ts | current | voltage | phase | ts | current | voltage | phase | +================================================================================================================================== + 2018-10-03 14:38:05.000 | 10.30000| 219 | 0.31000 | 2018-10-03 14:38:05.000 | 10.80000| 223 | 0.29000 | +Query OK, 1 row(s) in set (0.017385s) +``` + +``` +taos> SELECT d1001.* FROM d1001,d1003 WHERE d1001.ts = d1003.ts; + ts | current | voltage | phase | +====================================================================================== + 2018-10-03 14:38:05.000 | 10.30000 | 219 | 0.31000 | +Query OK, 1 row(s) in set (0.020443s) +``` + +在使用 SQL 函数来进行查询的过程中,部分 SQL 函数支持通配符操作。其中的区别在于: +`count(*)`函数只返回一列。`first`、`last`、`last_row`函数则是返回全部列。 + +``` +taos> SELECT COUNT(*) FROM d1001; + count(*) | +======================== + 3 | +Query OK, 1 row(s) in set (0.001035s) +``` + +``` +taos> SELECT FIRST(*) FROM d1001; + first(ts) | first(current) | first(voltage) | first(phase) | +========================================================================================= + 2018-10-03 14:38:05.000 | 10.30000 | 219 | 0.31000 | +Query OK, 1 row(s) in set (0.000849s) +``` + +## 标签列 + +从 2.0.14 版本开始,支持在普通表的查询中指定 _标签列_,且标签列的值会与普通列的数据一起返回。 + +``` +taos> SELECT location, groupid, current FROM d1001 LIMIT 2; + location | groupid | current | +====================================================================== + Beijing.Chaoyang | 2 | 10.30000 | + Beijing.Chaoyang | 2 | 12.60000 | +Query OK, 2 row(s) in set (0.003112s) +``` + +注意:普通表的通配符 \* 中并不包含 _标签列_。 + +## 获取标签列或普通列的去重取值 + +从 2.0.15.0 版本开始,支持在超级表查询标签列时,指定 DISTINCT 关键字,这样将返回指定标签列的所有不重复取值。注意,在 2.1.6.0 版本之前,DISTINCT 只支持处理单个标签列,而从 2.1.6.0 版本开始,DISTINCT 可以对多个标签列进行处理,输出这些标签列取值不重复的组合。 + +```sql +SELECT DISTINCT tag_name [, tag_name ...] FROM stb_name; +``` + +从 2.1.7.0 版本开始,DISTINCT 也支持对数据子表或普通表进行处理,也即支持获取单个普通列的不重复取值,或多个普通列取值的不重复组合。 + +```sql +SELECT DISTINCT col_name [, col_name ...] FROM tb_name; +``` + +:::info + +1. cfg 文件中的配置参数 maxNumOfDistinctRes 将对 DISTINCT 能够输出的数据行数进行限制。其最小值是 100000,最大值是 100000000,默认值是 10000000。如果实际计算结果超出了这个限制,那么会仅输出这个数量范围内的部分。 +2. 由于浮点数天然的精度机制原因,在特定情况下,对 FLOAT 和 DOUBLE 列使用 DISTINCT 并不能保证输出值的完全唯一性。 +3. 在当前版本下,DISTINCT 不能在嵌套查询的子查询中使用,也不能与聚合函数、GROUP BY、或 JOIN 在同一条语句中混用。 + +::: + +## 结果集列名 + +`SELECT`子句中,如果不指定返回结果集合的列名,结果集列名称默认使用`SELECT`子句中的表达式名称作为列名称。此外,用户可使用`AS`来重命名返回结果集合中列的名称。例如: + +``` +taos> SELECT ts, ts AS primary_key_ts FROM d1001; + ts | primary_key_ts | +==================================================== + 2018-10-03 14:38:05.000 | 2018-10-03 14:38:05.000 | + 2018-10-03 14:38:15.000 | 2018-10-03 14:38:15.000 | + 2018-10-03 14:38:16.800 | 2018-10-03 14:38:16.800 | +Query OK, 3 row(s) in set (0.001191s) +``` + +但是针对`first(*)`、`last(*)`、`last_row(*)`不支持针对单列的重命名。 + +## 隐式结果列 + +`Select_exprs`可以是表所属列的列名,也可以是基于列的函数表达式或计算式,数量的上限 256 个。当用户使用了`interval`或`group by tags`的子句以后,在最后返回结果中会强制返回时间戳列(第一列)和 group by 子句中的标签列。后续的版本中可以支持关闭 group by 子句中隐式列的输出,列输出完全由 select 子句控制。 + +## 表(超级表)列表 + +FROM 关键字后面可以是若干个表(超级表)列表,也可以是子查询的结果。 +如果没有指定用户的当前数据库,可以在表名称之前使用数据库的名称来指定表所属的数据库。例如:`power.d1001` 方式来跨库使用表。 + +``` +SELECT * FROM power.d1001; +------------------------------ +USE power; +SELECT * FROM d1001; +``` + +## 特殊功能 + +部分特殊的查询功能可以不使用 FROM 子句执行。获取当前所在的数据库 database(): + +``` +taos> SELECT DATABASE(); + database() | +================================= + power | +Query OK, 1 row(s) in set (0.000079s) +``` + +如果登录的时候没有指定默认数据库,且没有使用`USE`命令切换数据,则返回 NULL。 + +``` +taos> SELECT DATABASE(); + database() | +================================= + NULL | +Query OK, 1 row(s) in set (0.000184s) +``` + +获取服务器和客户端版本号: + +``` +taos> SELECT CLIENT_VERSION(); + client_version() | +=================== + 2.0.0.0 | +Query OK, 1 row(s) in set (0.000070s) + +taos> SELECT SERVER_VERSION(); + server_version() | +=================== + 2.0.0.0 | +Query OK, 1 row(s) in set (0.000077s) +``` + +服务器状态检测语句。如果服务器正常,返回一个数字(例如 1)。如果服务器异常,返回 error code。该 SQL 语法能兼容连接池对于 TDengine 状态的检查及第三方工具对于数据库服务器状态的检查。并可以避免出现使用了错误的心跳检测 SQL 语句导致的连接池连接丢失的问题。 + +``` +taos> SELECT SERVER_STATUS(); + server_status() | +================== + 1 | +Query OK, 1 row(s) in set (0.000074s) + +taos> SELECT SERVER_STATUS() AS status; + status | +============== + 1 | +Query OK, 1 row(s) in set (0.000081s) +``` + +## \_block_dist 函数 + +**功能说明**: 用于获得指定的(超级)表的数据块分布信息 + +```txt title="语法" +SELECT _block_dist() FROM { tb_name | stb_name } +``` + +**返回结果类型**:字符串。 + +**适用数据类型**:不能输入任何参数。 + +**嵌套子查询支持**:不支持子查询或嵌套查询。 + +**返回结果**: + +- 返回 FROM 子句中输入的表或超级表的数据块分布情况。不支持查询条件。 +- 返回的结果是该表或超级表的数据块所包含的行数的数据分布直方图。 + +```txt title="返回结果" +summary: +5th=[392], 10th=[392], 20th=[392], 30th=[392], 40th=[792], 50th=[792] 60th=[792], 70th=[792], 80th=[792], 90th=[792], 95th=[792], 99th=[792] Min=[392(Rows)] Max=[800(Rows)] Avg=[666(Rows)] Stddev=[2.17] Rows=[2000], Blocks=[3], Size=[5.440(Kb)] Comp=[0.23] RowsInMem=[0] SeekHeaderTime=[1(us)] +``` + +**上述信息的说明如下**: + +- 查询的(超级)表所包含的存储在文件中的数据块(data block)中所包含的数据行的数量分布直方图信息:5%, 10%, 20%, 30%, 40%, 50%, 60%, 70%, 80%, 90%, 95%, 99% 的数值; +- 所有数据块中,包含行数最少的数据块所包含的行数量, 其中的 Min 指标 392 行。 +- 所有数据块中,包含行数最多的数据块所包含的行数量, 其中的 Max 指标 800 行。 +- 所有数据块行数的算数平均值 666 行(其中的 Avg 项)。 +- 所有数据块中行数分布的均方差为 2.17 ( stddev )。 +- 数据块包含的行的总数为 2000 行(Rows)。 +- 数据块总数是 3 个数据块 (Blocks)。 +- 数据块占用磁盘空间大小 5.44 Kb (size)。 +- 压缩后的数据块的大小除以原始数据的所获得的压缩比例: 23%(Comp),及压缩后的数据规模是原始数据规模的 23%。 +- 内存中存在的数据行数是 0,表示内存中没有数据缓存。 +- 获取数据块信息的过程中读取头文件的时间开销 1 微秒(SeekHeaderTime)。 + +**支持版本**:指定计算算法的功能从 2.1.0.x 版本开始,2.1.0.0 之前的版本不支持指定使用算法的功能。 + +## TAOS SQL 中特殊关键词 + +- `TBNAME`: 在超级表查询中可视为一个特殊的标签,代表查询涉及的子表名 +- `_c0`: 表示表(超级表)的第一列 + +## 小技巧 + +获取一个超级表所有的子表名及相关的标签信息: + +``` +SELECT TBNAME, location FROM meters; +``` + +统计超级表下辖子表数量: + +``` +SELECT COUNT(TBNAME) FROM meters; +``` + +以上两个查询均只支持在 WHERE 条件子句中添加针对标签(TAGS)的过滤条件。例如: + +``` +taos> SELECT TBNAME, location FROM meters; + tbname | location | +================================================================== + d1004 | Beijing.Haidian | + d1003 | Beijing.Haidian | + d1002 | Beijing.Chaoyang | + d1001 | Beijing.Chaoyang | +Query OK, 4 row(s) in set (0.000881s) + +taos> SELECT COUNT(tbname) FROM meters WHERE groupId > 2; + count(tbname) | +======================== + 2 | +Query OK, 1 row(s) in set (0.001091s) +``` + +- 可以使用 \* 返回所有列,或指定列名。可以对数字列进行四则运算,可以给输出的列取列名。 + - 暂不支持含列名的四则运算表达式用于条件过滤算子(例如,不支持 `where a*2>6;`,但可以写 `where a>6/2;`)。 + - 暂不支持含列名的四则运算表达式作为 SQL 函数的应用对象(例如,不支持 `select min(2*a) from t;`,但可以写 `select 2*min(a) from t;`)。 +- WHERE 语句可以使用各种逻辑判断来过滤数字值,或使用通配符来过滤字符串。 +- 输出结果缺省按首列时间戳升序排序,但可以指定按降序排序( \_c0 指首列时间戳)。使用 ORDER BY 对其他字段进行排序,排序结果顺序不确定。 +- 参数 LIMIT 控制输出条数,OFFSET 指定从第几条开始输出。LIMIT/OFFSET 对结果集的执行顺序在 ORDER BY 之后。且 `LIMIT 5 OFFSET 2` 可以简写为 `LIMIT 2, 5`。 + - 在有 GROUP BY 子句的情况下,LIMIT 参数控制的是每个分组中至多允许输出的条数。 +- 参数 SLIMIT 控制由 GROUP BY 指令划分的分组中,至多允许输出几个分组的数据。且 `SLIMIT 5 SOFFSET 2` 可以简写为 `SLIMIT 2, 5`。 +- 通过 “>>” 输出结果可以导出到指定文件。 + +## 条件过滤操作 + +| **Operation** | **Note** | **Applicable Data Types** | +| ------------- | ------------------------ | ----------------------------------------- | +| > | larger than | all types except bool | +| < | smaller than | all types except bool | +| >= | larger than or equal to | all types except bool | +| <= | smaller than or equal to | all types except bool | +| = | equal to | all types | +| <\> | not equal to | all types | +| is [not] null | is null or is not null | all types | +| between and | within a certain range | all types except bool | +| in | match any value in a set | all types except first column `timestamp` | +| like | match a wildcard string | **`binary`** **`nchar`** | +| match/nmatch | filter regex | **`binary`** **`nchar`** | + +**使用说明**: + +- <\> 算子也可以写为 != ,请注意,这个算子不能用于数据表第一列的 timestamp 字段。 +- like 算子使用通配符字符串进行匹配检查。 + - 在通配符字符串中:'%'(百分号)匹配 0 到任意个字符;'\_'(下划线)匹配单个任意 ASCII 字符。 + - 如果希望匹配字符串中原本就带有的 \_(下划线)字符,那么可以在通配符字符串中写作 `\_`,也即加一个反斜线来进行转义。(从 2.2.0.0 版本开始支持) + - 通配符字符串最长不能超过 20 字节。(从 2.1.6.1 版本开始,通配符字符串的长度放宽到了 100 字节,并可以通过 taos.cfg 中的 maxWildCardsLength 参数来配置这一长度限制。但不建议使用太长的通配符字符串,将有可能严重影响 LIKE 操作的执行性能。) +- 同时进行多个字段的范围过滤,需要使用关键词 AND 来连接不同的查询条件,暂不支持 OR 连接的不同列之间的查询过滤条件。 + - 从 2.3.0.0 版本开始,已支持完整的同一列和/或不同列间的 AND/OR 运算。 +- 针对单一字段的过滤,如果是时间过滤条件,则一条语句中只支持设定一个;但针对其他的(普通)列或标签列,则可以使用 `OR` 关键字进行组合条件的查询过滤。例如: `((value > 20 AND value < 30) OR (value < 12))`。 + - 从 2.3.0.0 版本开始,允许使用多个时间过滤条件,但首列时间戳的过滤运算结果只能包含一个区间。 +- 从 2.0.17.0 版本开始,条件过滤开始支持 BETWEEN AND 语法,例如 `WHERE col2 BETWEEN 1.5 AND 3.25` 表示查询条件为“1.5 ≤ col2 ≤ 3.25”。 +- 从 2.1.4.0 版本开始,条件过滤开始支持 IN 算子,例如 `WHERE city IN ('Beijing', 'Shanghai')`。说明:BOOL 类型写作 `{true, false}` 或 `{0, 1}` 均可,但不能写作 0、1 之外的整数;FLOAT 和 DOUBLE 类型会受到浮点数精度影响,集合内的值在精度范围内认为和数据行的值完全相等才能匹配成功;TIMESTAMP 类型支持非主键的列。 +- 从 2.3.0.0 版本开始,条件过滤开始支持正则表达式,关键字 match/nmatch,不区分大小写。 + +## 正则表达式过滤 + +### 语法 + +```txt +WHERE (column|tbname) **match/MATCH/nmatch/NMATCH** _regex_ +``` + +### 正则表达式规范 + +确保使用的正则表达式符合 POSIX 的规范,具体规范内容可参见[Regular Expressions](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html) + +### 使用限制 + +只能针对表名(即 tbname 筛选)、binary/nchar 类型标签值进行正则表达式过滤,不支持普通列的过滤。 + +正则匹配字符串长度不能超过 128 字节。可以通过参数 _maxRegexStringLen_ 设置和调整最大允许的正则匹配字符串,该参数是客户端配置参数,需要重启才能生效。 + +## JOIN 子句 + +从 2.2.0.0 版本开始,TDengine 对内连接(INNER JOIN)中的自然连接(Natural join)操作实现了完整的支持。也即支持“普通表与普通表之间”、“超级表与超级表之间”、“子查询与子查询之间”进行自然连接。自然连接与内连接的主要区别是,自然连接要求参与连接的字段在不同的表/超级表中必须是同名字段。也即,TDengine 在连接关系的表达中,要求必须使用同名数据列/标签列的相等关系。 + +在普通表与普通表之间的 JOIN 操作中,只能使用主键时间戳之间的相等关系。例如: + +```sql +SELECT * +FROM temp_tb_1 t1, pressure_tb_1 t2 +WHERE t1.ts = t2.ts +``` + +在超级表与超级表之间的 JOIN 操作中,除了主键时间戳一致的条件外,还要求引入能实现一一对应的标签列的相等关系。例如: + +```sql +SELECT * +FROM temp_stable t1, temp_stable t2 +WHERE t1.ts = t2.ts AND t1.deviceid = t2.deviceid AND t1.status=0; +``` + +类似地,也可以对多个子查询的查询结果进行 JOIN 操作。 + +:::note + +JOIN语句存在如下限制要求: + +- 参与一条语句中 JOIN 操作的表/超级表最多可以有 10 个。 +- 在包含 JOIN 操作的查询语句中不支持 FILL。 +- 暂不支持参与 JOIN 操作的表之间聚合后的四则运算。 +- 不支持只对其中一部分表做 GROUP BY。 +- JOIN 查询的不同表的过滤条件之间不能为 OR。 +- JOIN 查询要求连接条件不能是普通列,只能针对标签和主时间字段列(第一列)。 + +::: + +## 嵌套查询 + +“嵌套查询”又称为“子查询”,也即在一条 SQL 语句中,“内层查询”的计算结果可以作为“外层查询”的计算对象来使用。 + +从 2.2.0.0 版本开始,TDengine 的查询引擎开始支持在 FROM 子句中使用非关联子查询(“非关联”的意思是,子查询不会用到父查询中的参数)。也即在普通 SELECT 语句的 tb_name_list 位置,用一个独立的 SELECT 语句来代替(这一 SELECT 语句被包含在英文圆括号内),于是完整的嵌套查询 SQL 语句形如: + +``` +SELECT ... FROM (SELECT ... FROM ...) ...; +``` + +:::info + +- 目前仅支持一层嵌套,也即不能在子查询中再嵌入子查询。 +- 内层查询的返回结果将作为“虚拟表”供外层查询使用,此虚拟表可以使用 AS 语法做重命名,以便于外层查询中方便引用。 +- 目前不能在“连续查询”功能中使用子查询。 +- 在内层和外层查询中,都支持普通的表间/超级表间 JOIN。内层查询的计算结果也可以再参与数据子表的 JOIN 操作。 +- 目前内层查询、外层查询均不支持 UNION 操作。 +- 内层查询支持的功能特性与非嵌套的查询语句能力是一致的。 + - 内层查询的 ORDER BY 子句一般没有意义,建议避免这样的写法以免无谓的资源消耗。 +- 与非嵌套的查询语句相比,外层查询所能支持的功能特性存在如下限制: + - 计算函数部分: + - 如果内层查询的结果数据未提供时间戳,那么计算过程依赖时间戳的函数在外层会无法正常工作。例如:TOP, BOTTOM, FIRST, LAST, DIFF。 + - 计算过程需要两遍扫描的函数,在外层查询中无法正常工作。例如:此类函数包括:STDDEV, PERCENTILE。 + - 外层查询中不支持 IN 算子,但在内层中可以使用。 + - 外层查询不支持 GROUP BY。 + +::: + +## UNION ALL 子句 + +```txt title=语法 +SELECT ... +UNION ALL SELECT ... +[UNION ALL SELECT ...] +``` + +TDengine 支持 UNION ALL 操作符。也就是说,如果多个 SELECT 子句返回结果集的结构完全相同(列名、列类型、列数、顺序),那么可以通过 UNION ALL 把这些结果集合并到一起。目前只支持 UNION ALL 模式,也即在结果集的合并过程中是不去重的。在同一个 sql 语句中,UNION ALL 最多支持 100 个。 + +### SQL 示例 + +对于下面的例子,表 tb1 用以下语句创建: + +``` +CREATE TABLE tb1 (ts TIMESTAMP, col1 INT, col2 FLOAT, col3 BINARY(50)); +``` + +查询 tb1 刚过去的一个小时的所有记录: + +``` +SELECT * FROM tb1 WHERE ts >= NOW - 1h; +``` + +查询表 tb1 从 2018-06-01 08:00:00.000 到 2018-06-02 08:00:00.000 时间范围,并且 col3 的字符串是'nny'结尾的记录,结果按照时间戳降序: + +``` +SELECT * FROM tb1 WHERE ts > '2018-06-01 08:00:00.000' AND ts <= '2018-06-02 08:00:00.000' AND col3 LIKE '%nny' ORDER BY ts DESC; +``` + +查询 col1 与 col2 的和,并取名 complex, 时间大于 2018-06-01 08:00:00.000, col2 大于 1.2,结果输出仅仅 10 条记录,从第 5 条开始: + +``` +SELECT (col1 + col2) AS 'complex' FROM tb1 WHERE ts > '2018-06-01 08:00:00.000' AND col2 > 1.2 LIMIT 10 OFFSET 5; +``` + +查询过去 10 分钟的记录,col2 的值大于 3.14,并且将结果输出到文件 `/home/testoutput.csv`: + +``` +SELECT COUNT(*) FROM tb1 WHERE ts >= NOW - 10m AND col2 > 3.14 >> /home/testoutput.csv; +``` diff --git a/docs-cn/12-taos-sql/07-function.md b/docs-cn/12-taos-sql/07-function.md new file mode 100644 index 0000000000..f6e564419d --- /dev/null +++ b/docs-cn/12-taos-sql/07-function.md @@ -0,0 +1,1794 @@ +--- +sidebar_label: SQL 函数 +title: SQL 函数 +--- + +## 聚合函数 + +TDengine 支持针对数据的聚合查询。提供支持的聚合和选择函数如下: + +### COUNT + +``` +SELECT COUNT([*|field_name]) FROM tb_name [WHERE clause]; +``` + +**功能说明**:统计表/超级表中记录行数或某列的非空值个数。 + +**返回数据类型**:长整型 INT64。 + +**应用字段**:应用全部字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 可以使用星号(\*)来替代具体的字段,使用星号(\*)返回全部记录数量。 +- 针对同一表的(不包含 NULL 值)字段查询结果均相同。 +- 如果统计对象是具体的列,则返回该列中非 NULL 值的记录数量。 + +**示例**: + +``` +taos> SELECT COUNT(*), COUNT(voltage) FROM meters; + count(*) | count(voltage) | +================================================ + 9 | 9 | +Query OK, 1 row(s) in set (0.004475s) + +taos> SELECT COUNT(*), COUNT(voltage) FROM d1001; + count(*) | count(voltage) | +================================================ + 3 | 3 | +Query OK, 1 row(s) in set (0.001075s) +``` + +### AVG + +``` +SELECT AVG(field_name) FROM tb_name [WHERE clause]; +``` + +**功能说明**:统计表/超级表中某列的平均值。 + +**返回数据类型**:双精度浮点数 Double。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 字段。 + +**适用于**:表、超级表。 + +**示例**: + +``` +taos> SELECT AVG(current), AVG(voltage), AVG(phase) FROM meters; + avg(current) | avg(voltage) | avg(phase) | +==================================================================================== + 11.466666751 | 220.444444444 | 0.293333333 | +Query OK, 1 row(s) in set (0.004135s) + +taos> SELECT AVG(current), AVG(voltage), AVG(phase) FROM d1001; + avg(current) | avg(voltage) | avg(phase) | +==================================================================================== + 11.733333588 | 219.333333333 | 0.316666673 | +Query OK, 1 row(s) in set (0.000943s) +``` + +### TWA + +``` +SELECT TWA(field_name) FROM tb_name WHERE clause; +``` + +**功能说明**:时间加权平均函数。统计表中某列在一段时间内的时间加权平均。 + +**返回数据类型**:双精度浮点数 Double。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 从 2.1.3.0 版本开始,TWA 函数可以在由 GROUP BY 划分出单独时间线的情况下用于超级表(也即 GROUP BY tbname)。 + +### IRATE + +``` +SELECT IRATE(field_name) FROM tb_name WHERE clause; +``` + +**功能说明**:计算瞬时增长率。使用时间区间中最后两个样本数据来计算瞬时增长速率;如果这两个值呈递减关系,那么只取最后一个数用于计算,而不是使用二者差值。 + +**返回数据类型**:双精度浮点数 Double。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 从 2.1.3.0 版本开始此函数可用,IRATE 可以在由 GROUP BY 划分出单独时间线的情况下用于超级表(也即 GROUP BY tbname)。 + +### SUM + +``` +SELECT SUM(field_name) FROM tb_name [WHERE clause]; +``` + +**功能说明**:统计表/超级表中某列的和。 + +**返回数据类型**:双精度浮点数 Double 和长整型 INT64。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**示例**: + +``` +taos> SELECT SUM(current), SUM(voltage), SUM(phase) FROM meters; + sum(current) | sum(voltage) | sum(phase) | +================================================================================ + 103.200000763 | 1984 | 2.640000001 | +Query OK, 1 row(s) in set (0.001702s) + +taos> SELECT SUM(current), SUM(voltage), SUM(phase) FROM d1001; + sum(current) | sum(voltage) | sum(phase) | +================================================================================ + 35.200000763 | 658 | 0.950000018 | +Query OK, 1 row(s) in set (0.000980s) +``` + +### STDDEV + +``` +SELECT STDDEV(field_name) FROM tb_name [WHERE clause]; +``` + +**功能说明**:统计表中某列的均方差。 + +**返回数据类型**:双精度浮点数 Double。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表(从 2.0.15.1 版本开始) + +**示例**: + +``` +taos> SELECT STDDEV(current) FROM d1001; + stddev(current) | +============================ + 1.020892909 | +Query OK, 1 row(s) in set (0.000915s) +``` + +### LEASTSQUARES + +``` +SELECT LEASTSQUARES(field_name, start_val, step_val) FROM tb_name [WHERE clause]; +``` + +**功能说明**:统计表中某列的值是主键(时间戳)的拟合直线方程。start_val 是自变量初始值,step_val 是自变量的步长值。 + +**返回数据类型**:字符串表达式(斜率, 截距)。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表。 + +**示例**: + +``` +taos> SELECT LEASTSQUARES(current, 1, 1) FROM d1001; + leastsquares(current, 1, 1) | +===================================================== +{slop:1.000000, intercept:9.733334} | +Query OK, 1 row(s) in set (0.000921s) +``` + +### MODE + +``` +SELECT MODE(field_name) FROM tb_name [WHERE clause]; +``` + +**功能说明**:返回出现频率最高的值,若存在多个频率相同的最高值,输出空。不能匹配标签、时间戳输出。 + +**返回数据类型**:同应用的字段。 + +**应用字段**:适合于除时间主列外的任何类型字段。 + +**使用说明**:由于返回数据量未知,考虑到内存因素,为了函数可以正常返回结果,建议不重复的数据量在 10 万级别,否则会报错。 + +**支持的版本**:2.6.0.0 及以后的版本。 + +**示例**: + +``` +taos> select voltage from d002; + voltage | +======================== + 1 | + 1 | + 2 | + 19 | +Query OK, 4 row(s) in set (0.003545s) + +taos> select mode(voltage) from d002; + mode(voltage) | +======================== + 1 | +Query OK, 1 row(s) in set (0.019393s) +``` + +### HYPERLOGLOG + +``` +SELECT HYPERLOGLOG(field_name) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**: + - 采用 hyperloglog 算法,返回某列的基数。该算法在数据量很大的情况下,可以明显降低内存的占用,但是求出来的基数是个估算值,标准误差(标准误差是多次实验,每次的平均数的标准差,不是与真实结果的误差)为 0.81%。 + - 在数据量较少的时候该算法不是很准确,可以使用 select count(data) from (select unique(col) as data from table) 的方法。 + +**返回结果类型**:整形。 + +**应用字段**:适合于任何类型字段。 + +**支持的版本**:2.6.0.0 及以后的版本。 + +**示例**: + +``` +taos> select dbig from shll; + dbig | +======================== + 1 | + 1 | + 1 | + NULL | + 2 | + 19 | + NULL | + 9 | +Query OK, 8 row(s) in set (0.003755s) + +taos> select hyperloglog(dbig) from shll; + hyperloglog(dbig)| +======================== + 4 | +Query OK, 1 row(s) in set (0.008388s) +``` + +## 选择函数 + +在使用所有的选择函数的时候,可以同时指定输出 ts 列或标签列(包括 tbname),这样就可以方便地知道被选出的值是源于哪个数据行的。 + +### MIN + +``` +SELECT MIN(field_name) FROM {tb_name | stb_name} [WHERE clause]; +``` + +**功能说明**:统计表/超级表中某列的值最小值。 + +**返回数据类型**:同应用的字段。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**示例**: + +``` +taos> SELECT MIN(current), MIN(voltage) FROM meters; + min(current) | min(voltage) | +====================================== + 10.20000 | 218 | +Query OK, 1 row(s) in set (0.001765s) + +taos> SELECT MIN(current), MIN(voltage) FROM d1001; + min(current) | min(voltage) | +====================================== + 10.30000 | 218 | +Query OK, 1 row(s) in set (0.000950s) +``` + +### MAX + +``` +SELECT MAX(field_name) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:统计表/超级表中某列的值最大值。 + +**返回数据类型**:同应用的字段。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**示例**: + +``` +taos> SELECT MAX(current), MAX(voltage) FROM meters; + max(current) | max(voltage) | +====================================== + 13.40000 | 223 | +Query OK, 1 row(s) in set (0.001123s) + +taos> SELECT MAX(current), MAX(voltage) FROM d1001; + max(current) | max(voltage) | +====================================== + 12.60000 | 221 | +Query OK, 1 row(s) in set (0.000987s) +``` + +### FIRST + +``` +SELECT FIRST(field_name) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:统计表/超级表中某列的值最先写入的非 NULL 值。 + +**返回数据类型**:同应用的字段。 + +**应用字段**:所有字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 如果要返回各个列的首个(时间戳最小)非 NULL 值,可以使用 FIRST(\*); +- 如果结果集中的某列全部为 NULL 值,则该列的返回结果也是 NULL; +- 如果结果集中所有列全部为 NULL 值,则不返回结果。 + +**示例**: + +``` +taos> SELECT FIRST(*) FROM meters; + first(ts) | first(current) | first(voltage) | first(phase) | +========================================================================================= +2018-10-03 14:38:04.000 | 10.20000 | 220 | 0.23000 | +Query OK, 1 row(s) in set (0.004767s) + +taos> SELECT FIRST(current) FROM d1002; + first(current) | +======================= + 10.20000 | +Query OK, 1 row(s) in set (0.001023s) +``` + +### LAST + +``` +SELECT LAST(field_name) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:统计表/超级表中某列的值最后写入的非 NULL 值。 + +**返回数据类型**:同应用的字段。 + +**应用字段**:所有字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 如果要返回各个列的最后(时间戳最大)一个非 NULL 值,可以使用 LAST(\*); +- 如果结果集中的某列全部为 NULL 值,则该列的返回结果也是 NULL;如果结果集中所有列全部为 NULL 值,则不返回结果。 +- 在用于超级表时,时间戳完全一样且同为最大的数据行可能有多个,那么会从中随机返回一条,而并不保证多次运行所挑选的数据行必然一致。 + + +**示例**: + +``` +taos> SELECT LAST(*) FROM meters; + last(ts) | last(current) | last(voltage) | last(phase) | +======================================================================================== +2018-10-03 14:38:16.800 | 12.30000 | 221 | 0.31000 | +Query OK, 1 row(s) in set (0.001452s) + +taos> SELECT LAST(current) FROM d1002; + last(current) | +======================= + 10.30000 | +Query OK, 1 row(s) in set (0.000843s) +``` + +### TOP + +``` +SELECT TOP(field_name, K) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**: 统计表/超级表中某列的值最大 _k_ 个非 NULL 值。如果多条数据取值一样,全部取用又会超出 k 条限制时,系统会从相同值中随机选取符合要求的数量返回。 + +**返回数据类型**:同应用的字段。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- *k*值取值范围 1≤*k*≤100; +- 系统同时返回该记录关联的时间戳列; +- 限制:TOP 函数不支持 FILL 子句。 + +**示例**: + +``` +taos> SELECT TOP(current, 3) FROM meters; + ts | top(current, 3) | +================================================= +2018-10-03 14:38:15.000 | 12.60000 | +2018-10-03 14:38:16.600 | 13.40000 | +2018-10-03 14:38:16.800 | 12.30000 | +Query OK, 3 row(s) in set (0.001548s) + +taos> SELECT TOP(current, 2) FROM d1001; + ts | top(current, 2) | +================================================= +2018-10-03 14:38:15.000 | 12.60000 | +2018-10-03 14:38:16.800 | 12.30000 | +Query OK, 2 row(s) in set (0.000810s) +``` + +### BOTTOM + +``` +SELECT BOTTOM(field_name, K) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:统计表/超级表中某列的值最小 _k_ 个非 NULL 值。如果多条数据取值一样,全部取用又会超出 k 条限制时,系统会从相同值中随机选取符合要求的数量返回。 + +**返回数据类型**:同应用的字段。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- *k*值取值范围 1≤*k*≤100; +- 系统同时返回该记录关联的时间戳列; +- 限制:BOTTOM 函数不支持 FILL 子句。 + +**示例**: + +``` +taos> SELECT BOTTOM(voltage, 2) FROM meters; + ts | bottom(voltage, 2) | +=============================================== +2018-10-03 14:38:15.000 | 218 | +2018-10-03 14:38:16.650 | 218 | +Query OK, 2 row(s) in set (0.001332s) + +taos> SELECT BOTTOM(current, 2) FROM d1001; + ts | bottom(current, 2) | +================================================= +2018-10-03 14:38:05.000 | 10.30000 | +2018-10-03 14:38:16.800 | 12.30000 | +Query OK, 2 row(s) in set (0.000793s) +``` + +### PERCENTILE + +``` +SELECT PERCENTILE(field_name, P) FROM { tb_name } [WHERE clause]; +``` + +**功能说明**:统计表中某列的值百分比分位数。 + +**返回数据类型**: 双精度浮点数 Double。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表。 + +**使用说明**:*P*值取值范围 0≤*P*≤100,为 0 的时候等同于 MIN,为 100 的时候等同于 MAX。 + +**示例**: + +``` +taos> SELECT PERCENTILE(current, 20) FROM d1001; +percentile(current, 20) | +============================ + 11.100000191 | +Query OK, 1 row(s) in set (0.000787s) +``` + +### APERCENTILE + +``` +SELECT APERCENTILE(field_name, P[, algo_type]) +FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:统计表/超级表中指定列的值百分比分位数,与 PERCENTILE 函数相似,但是返回近似结果。 + +**返回数据类型**: 双精度浮点数 Double。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**使用说明** + +- **P**值有效取值范围 0≤P≤100,为 0 的时候等同于 MIN,为 100 的时候等同于 MAX; +- **algo_type**的有效输入:**default** 和 **t-digest** +- 用于指定计算近似分位数的算法。可不提供第三个参数的输入,此时将使用 default 的算法进行计算,即 apercentile(column_name, 50, "default") 与 apercentile(column_name, 50) 等价。 +- 当使用“t-digest”参数的时候,将使用 t-digest 方式采样计算近似分位数。但该参数指定计算算法的功能从 2.2.0.x 版本开始支持,2.2.0.0 之前的版本不支持指定使用算法的功能。 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +``` +taos> SELECT APERCENTILE(current, 20) FROM d1001; +apercentile(current, 20) | +============================ + 10.300000191 | +Query OK, 1 row(s) in set (0.000645s) + +taos> select apercentile (count, 80, 'default') from stb1; + apercentile (c0, 80, 'default') | +================================== + 601920857.210056424 | +Query OK, 1 row(s) in set (0.012363s) + +taos> select apercentile (count, 80, 't-digest') from stb1; + apercentile (c0, 80, 't-digest') | +=================================== + 605869120.966666579 | +Query OK, 1 row(s) in set (0.011639s) +``` + +### LAST_ROW + +``` +SELECT LAST_ROW(field_name) FROM { tb_name | stb_name }; +``` + +**功能说明**:返回表/超级表的最后一条记录。 + +**返回数据类型**:同应用的字段。 + +**应用字段**:所有字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 在用于超级表时,时间戳完全一样且同为最大的数据行可能有多个,那么会从中随机返回一条,而并不保证多次运行所挑选的数据行必然一致。 +- 不能与 INTERVAL 一起使用。 + +**示例**: + +``` + taos> SELECT LAST_ROW(current) FROM meters; + last_row(current) | + ======================= + 12.30000 | + Query OK, 1 row(s) in set (0.001238s) + + taos> SELECT LAST_ROW(current) FROM d1002; + last_row(current) | + ======================= + 10.30000 | + Query OK, 1 row(s) in set (0.001042s) +``` + +### INTERP [2.3.1 及之后的版本] + +``` +SELECT INTERP(field_name) FROM { tb_name | stb_name } [WHERE where_condition] [ RANGE(timestamp1,timestamp2) ] [EVERY(interval)] [FILL ({ VALUE | PREV | NULL | LINEAR | NEXT})]; +``` + +**功能说明**:返回表/超级表的指定时间截面指定列的记录值(插值)。 + +**返回数据类型**:同字段类型。 + +**应用字段**:数值型字段。 + +**适用于**:表、超级表、嵌套查询。 + + +**使用说明** + +- INTERP 用于在指定时间断面获取指定列的记录值,如果该时间断面不存在符合条件的行数据,那么会根据 FILL 参数的设定进行插值。 +- INTERP 的输入数据为指定列的数据,可以通过条件语句(where 子句)来对原始列数据进行过滤,如果没有指定过滤条件则输入为全部数据。 +- INTERP 的输出时间范围根据 RANGE(timestamp1,timestamp2)字段来指定,需满足 timestamp1<=timestamp2。其中 timestamp1(必选值)为输出时间范围的起始值,即如果 timestamp1 时刻符合插值条件则 timestamp1 为输出的第一条记录,timestamp2(必选值)为输出时间范围的结束值,即输出的最后一条记录的 timestamp 不能大于 timestamp2。如果没有指定 RANGE,那么满足过滤条件的输入数据中第一条记录的 timestamp 即为 timestamp1,最后一条记录的 timestamp 即为 timestamp2,同样也满足 timestamp1 <= timestamp2。 +- INTERP 根据 EVERY 字段来确定输出时间范围内的结果条数,即从 timestamp1 开始每隔固定长度的时间(EVERY 值)进行插值。如果没有指定 EVERY,则默认窗口大小为无穷大,即从 timestamp1 开始只有一个窗口。 +- INTERP 根据 FILL 字段来决定在每个符合输出条件的时刻如何进行插值,如果没有 FILL 字段则默认不插值,即输出为原始记录值或不输出(原始记录不存在)。 +- INTERP 只能在一个时间序列内进行插值,因此当作用于超级表时必须跟 group by tbname 一起使用,当作用嵌套查询外层时内层子查询不能含 GROUP BY 信息。 +- INTERP 的插值结果不受 ORDER BY timestamp 的影响,ORDER BY timestamp 只影响输出结果的排序。 + +**SQL示例(基于文档中广泛使用的电表 schema )**: + +- 单点线性插值 + +``` + taos> SELECT INTERP(current) FROM t1 RANGE('2017-7-14 18:40:00','2017-7-14 18:40:00') FILL(LINEAR); +``` + +- 在2017-07-14 18:00:00到2017-07-14 19:00:00间每隔5秒钟进行取值(不插值) + +``` + taos> SELECT INTERP(current) FROM t1 RANGE('2017-7-14 18:00:00','2017-7-14 19:00:00') EVERY(5s); +``` + +- 在2017-07-14 18:00:00到2017-07-14 19:00:00间每隔5秒钟进行线性插值 + +``` + taos> SELECT INTERP(current) FROM t1 RANGE('2017-7-14 18:00:00','2017-7-14 19:00:00') EVERY(5s) FILL(LINEAR); +``` + +- 在所有时间范围内每隔 5 秒钟进行向后插值 + +``` + taos> SELECT INTERP(current) FROM t1 EVERY(5s) FILL(NEXT); +``` + +- 根据 2017-07-14 17:00:00 到 2017-07-14 20:00:00 间的数据进行从 2017-07-14 18:00:00 到 2017-07-14 19:00:00 间每隔 5 秒钟进行线性插值 + +``` + taos> SELECT INTERP(current) FROM t1 where ts >= '2017-07-14 17:00:00' and ts <= '2017-07-14 20:00:00' RANGE('2017-7-14 18:00:00','2017-7-14 19:00:00') EVERY(5s) FILL(LINEAR); +``` + +### INTERP [2.3.1 之前的版本] + +``` +SELECT INTERP(field_name) FROM { tb_name | stb_name } WHERE ts='timestamp' [FILL ({ VALUE | PREV | NULL | LINEAR | NEXT})]; +``` + +**功能说明**:返回表/超级表的指定时间截面、指定字段的记录。 + +**返回数据类型**:同字段类型。 + +**应用字段**:数值型字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 从 2.0.15.0 及以后版本可用 +- INTERP 必须指定时间断面,如果该时间断面不存在直接对应的数据,那么会根据 FILL 参数的设定进行插值。此外,条件语句里面可附带筛选条件,例如标签、tbname。 +- INTERP 查询要求查询的时间区间必须位于数据集合(表)的所有记录的时间范围之内。如果给定的时间戳位于时间范围之外,即使有插值指令,仍然不返回结果。 +- 单个 INTERP 函数查询只能够针对一个时间点进行查询,如果需要返回等时间间隔的断面数据,可以通过 INTERP 配合 EVERY 的方式来进行查询处理(而不是使用 INTERVAL),其含义是每隔固定长度的时间进行插值 + +**示例**: + +``` + taos> SELECT INTERP(*) FROM meters WHERE ts='2017-7-14 18:40:00.004'; + interp(ts) | interp(current) | interp(voltage) | interp(phase) | + ========================================================================================== + 2017-07-14 18:40:00.004 | 9.84020 | 216 | 0.32222 | + Query OK, 1 row(s) in set (0.002652s) +``` + +如果给定的时间戳无对应的数据,在不指定插值生成策略的情况下,不会返回结果,如果指定了插值策略,会根据插值策略返回结果。 + +``` + taos> SELECT INTERP(*) FROM meters WHERE tbname IN ('d636') AND ts='2017-7-14 18:40:00.005'; + Query OK, 0 row(s) in set (0.004022s) + + taos> SELECT INTERP(*) FROM meters WHERE tbname IN ('d636') AND ts='2017-7-14 18:40:00.005' FILL(PREV); + interp(ts) | interp(current) | interp(voltage) | interp(phase) | + ========================================================================================== + 2017-07-14 18:40:00.005 | 9.88150 | 217 | 0.32500 | + Query OK, 1 row(s) in set (0.003056s) +``` + +如下所示代码表示在时间区间 `['2017-7-14 18:40:00', '2017-7-14 18:40:00.014']` 中每隔 5 毫秒 进行一次断面计算。 + +``` + taos> SELECT INTERP(current) FROM d636 WHERE ts>='2017-7-14 18:40:00' AND ts<='2017-7-14 18:40:00.014' EVERY(5a); + ts | interp(current) | + ================================================= + 2017-07-14 18:40:00.000 | 10.04179 | + 2017-07-14 18:40:00.010 | 10.16123 | + Query OK, 2 row(s) in set (0.003487s) +``` + +### TAIL + +``` +SELECT TAIL(field_name, k, offset_val) FROM {tb_name | stb_name} [WHERE clause]; +``` + +**功能说明**:返回跳过最后 offset_value 个,然后取连续 k 个记录,不忽略 NULL 值。offset_val 可以不输入。此时返回最后的 k 个记录。当有 offset_val 输入的情况下,该函数功能等效于 `order by ts desc LIMIT k OFFSET offset_val`。 + +**参数范围**:k: [1,100] offset_val: [0,100]。 + +**返回结果数据类型**:同应用的字段。 + +**应用字段**:适合于除时间主列外的任何类型字段。 + +**支持版本**:2.6.0.0 及之后的版本。 + +**示例**: + +``` +taos> select ts,dbig from tail2; + ts | dbig | +================================================== +2021-10-15 00:31:33.000 | 1 | +2021-10-17 00:31:31.000 | NULL | +2021-12-24 00:31:34.000 | 2 | +2022-01-01 08:00:05.000 | 19 | +2022-01-01 08:00:06.000 | NULL | +2022-01-01 08:00:07.000 | 9 | +Query OK, 6 row(s) in set (0.001952s) + +taos> select tail(dbig,2,2) from tail2; +ts | tail(dbig,2,2) | +================================================== +2021-12-24 00:31:34.000 | 2 | +2022-01-01 08:00:05.000 | 19 | +Query OK, 2 row(s) in set (0.002307s) +``` + +### UNIQUE + +``` +SELECT UNIQUE(field_name) FROM {tb_name | stb_name} [WHERE clause]; +``` + +**功能说明**:返回该列的数值首次出现的值。该函数功能与 distinct 相似,但是可以匹配标签和时间戳信息。可以针对除时间列以外的字段进行查询,可以匹配标签和时间戳,其中的标签和时间戳是第一次出现时刻的标签和时间戳。 + +**返回结果数据类型**:同应用的字段。 + +**应用字段**:适合于除时间类型以外的字段。 + +**支持版本**:2.6.0.0 及之后的版本。 + +**使用说明**: + +- 该函数可以应用在普通表和超级表上。不能和窗口操作一起使用,例如 interval/state_window/session_window 。 +- 由于返回数据量未知,考虑到内存因素,为了函数可以正常返回结果,建议不重复的数据量在 10 万级别,否则会报错。 + +**示例**: + +``` +taos> select ts,voltage from unique1; + ts | voltage | +================================================== +2021-10-17 00:31:31.000 | 1 | +2022-01-24 00:31:31.000 | 1 | +2021-10-17 00:31:31.000 | 1 | +2021-12-24 00:31:31.000 | 2 | +2022-01-01 08:00:01.000 | 19 | +2021-10-17 00:31:31.000 | NULL | +2022-01-01 08:00:02.000 | NULL | +2022-01-01 08:00:03.000 | 9 | +Query OK, 8 row(s) in set (0.003018s) + +taos> select unique(voltage) from unique1; +ts | unique(voltage) | +================================================== +2021-10-17 00:31:31.000 | 1 | +2021-10-17 00:31:31.000 | NULL | +2021-12-24 00:31:31.000 | 2 | +2022-01-01 08:00:01.000 | 19 | +2022-01-01 08:00:03.000 | 9 | +Query OK, 5 row(s) in set (0.108458s) +``` + +## 计算函数 + +### DIFF + + ```sql + SELECT {DIFF(field_name, ignore_negative) | DIFF(field_name)} FROM tb_name [WHERE clause]; + ``` + +**功能说明**:统计表中某列的值与前一行对应值的差。 ignore_negative 取值为 0|1 , 可以不填,默认值为 0. 不忽略负值。ignore_negative 为 1 时表示忽略负数。 + +**返回结果数据类型**:同应用字段。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 输出结果行数是范围内总行数减一,第一行没有结果输出。 +- 从 2.1.3.0 版本开始,DIFF 函数可以在由 GROUP BY 划分出单独时间线的情况下用于超级表(也即 GROUP BY tbname)。 +- 从 2.6.0 开始,DIFF 函数支持 ignore_negative 参数 + +**示例**: + + ```sql + taos> SELECT DIFF(current) FROM d1001; + ts | diff(current) | + ================================================= + 2018-10-03 14:38:15.000 | 2.30000 | + 2018-10-03 14:38:16.800 | -0.30000 | + Query OK, 2 row(s) in set (0.001162s) + ``` + +### DERIVATIVE + +``` +SELECT DERIVATIVE(field_name, time_interval, ignore_negative) FROM tb_name [WHERE clause]; +``` + +**功能说明**:统计表中某列数值的单位变化率。其中单位时间区间的长度可以通过 time_interval 参数指定,最小可以是 1 秒(1s);ignore_negative 参数的值可以是 0 或 1,为 1 时表示忽略负值。 + +**返回数据类型**:双精度浮点数。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表 + +**使用说明**: + +- 从 2.1.3.0 及以后版本可用;输出结果行数是范围内总行数减一,第一行没有结果输出。 +- DERIVATIVE 函数可以在由 GROUP BY 划分出单独时间线的情况下用于超级表(也即 GROUP BY tbname)。 + +**示例**: + +``` +taos> select derivative(current, 10m, 0) from t1; + ts | derivative(current, 10m, 0) | +======================================================== + 2021-08-20 10:11:22.790 | 0.500000000 | + 2021-08-20 11:11:22.791 | 0.166666620 | + 2021-08-20 12:11:22.791 | 0.000000000 | + 2021-08-20 13:11:22.792 | 0.166666620 | + 2021-08-20 14:11:22.792 | -0.666666667 | +Query OK, 5 row(s) in set (0.004883s) +``` + +### SPREAD + +``` +SELECT SPREAD(field_name) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:统计表/超级表中某列的最大值和最小值之差。 + +**返回数据类型**:双精度浮点数。 + +**应用字段**:不能应用在 binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**使用说明**:可用于 TIMESTAMP 字段,此时表示记录的时间覆盖范围。 + +**示例**: + +``` +taos> SELECT SPREAD(voltage) FROM meters; + spread(voltage) | +============================ + 5.000000000 | +Query OK, 1 row(s) in set (0.001792s) + +taos> SELECT SPREAD(voltage) FROM d1001; + spread(voltage) | +============================ + 3.000000000 | +Query OK, 1 row(s) in set (0.000836s) +``` + +### CEIL + +``` +SELECT CEIL(field_name) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:获得指定列的向上取整数的结果。 + +**返回结果类型**:与指定列的原始数据类型一致。例如,如果指定列的原始数据类型为 Float,那么返回的数据类型也为 Float;如果指定列的原始数据类型为 Double,那么返回的数据类型也为 Double。 + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列,无论 tag 列的类型是什么类型。 + +**适用于**: 普通表、超级表。 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 支持 +、-、\*、/ 运算,如 ceil(col1) + ceil(col2)。 +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 + +### FLOOR + +``` +SELECT FLOOR(field_name) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:获得指定列的向下取整数的结果。 + 其他使用说明参见 CEIL 函数描述。 + +### ROUND + +``` +SELECT ROUND(field_name) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:获得指定列的四舍五入的结果。 + 其他使用说明参见 CEIL 函数描述。 + +### CSUM + +```sql + SELECT CSUM(field_name) FROM { tb_name | stb_name } [WHERE clause] +``` + + **功能说明**:累加和(Cumulative sum),输出行与输入行数相同。 + + **返回结果类型**: 输入列如果是整数类型返回值为长整型 (int64_t),浮点数返回值为双精度浮点数(Double)。无符号整数类型返回值为无符号长整型(uint64_t)。 返回结果中同时带有每行记录对应的时间戳。 + + **适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在标签之上。 + + **嵌套子查询支持**: 适用于内层查询和外层查询。 + + **使用说明**: + + - 不支持 +、-、*、/ 运算,如 csum(col1) + csum(col2)。 + - 只能与聚合(Aggregation)函数一起使用。 该函数可以应用在普通表和超级表上。 + - 使用在超级表上的时候,需要搭配 Group by tbname使用,将结果强制规约到单个时间线。 + +**支持版本**: 从2.3.0.x开始支持 + +### MAVG + +```sql + SELECT MAVG(field_name, K) FROM { tb_name | stb_name } [WHERE clause] +``` + + **功能说明**: 计算连续 k 个值的移动平均数(moving average)。如果输入行数小于 k,则无结果输出。参数 k 的合法输入范围是 1≤ k ≤ 1000。 + + **返回结果类型**: 返回双精度浮点数类型。 + + **适用数据类型**: 不能应用在 timestamp、binary、nchar、bool 类型上;在超级表查询中使用时,不能应用在标签之上。 + + **嵌套子查询支持**: 适用于内层查询和外层查询。 + + **使用说明**: + + - 不支持 +、-、*、/ 运算,如 mavg(col1, k1) + mavg(col2, k1); + - 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用; + - 该函数可以应用在普通表和超级表上;使用在超级表上的时候,需要搭配 Group by tbname使用,将结果强制规约到单个时间线。 + +**支持版本**: 从2.3.0.x开始支持 + +### SAMPLE + +```sql + SELECT SAMPLE(field_name, K) FROM { tb_name | stb_name } [WHERE clause] +``` + + **功能说明**: 获取数据的 k 个采样值。参数 k 的合法输入范围是 1≤ k ≤ 1000。 + + **返回结果类型**: 同原始数据类型, 返回结果中带有该行记录的时间戳。 + + **适用数据类型**: 在超级表查询中使用时,不能应用在标签之上。 + + **嵌套子查询支持**: 适用于内层查询和外层查询。 + + **使用说明**: + + - 不能参与表达式计算;该函数可以应用在普通表和超级表上; + - 使用在超级表上的时候,需要搭配 Group by tbname 使用,将结果强制规约到单个时间线。 + +**支持版本**: 从2.3.0.x开始支持 + +### ASIN + +```sql + SELECT ASIN(field_name) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列的反正弦结果 + +**返回结果类型**:DOUBLE。如果输入值为 NULL,输出值也为 NULL + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### ACOS + +```sql + SELECT ACOS(field_name) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列的反余弦结果 + +**返回结果类型**:DOUBLE。如果输入值为 NULL,输出值也为 NULL + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### ATAN + +```sql + SELECT ATAN(field_name) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列的反正切结果 + +**返回结果类型**:DOUBLE。如果输入值为 NULL,输出值也为 NULL + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### SIN + +```sql + SELECT SIN(field_name) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列的正弦结果 + +**返回结果类型**:DOUBLE。如果输入值为 NULL,输出值也为 NULL + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### COS + +```sql + SELECT COS(field_name) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列的余弦结果 + +**返回结果类型**:DOUBLE。如果输入值为 NULL,输出值也为 NULL + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### TAN + +```sql + SELECT TAN(field_name) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列的正切结果 + +**返回结果类型**:DOUBLE。如果输入值为 NULL,输出值也为 NULL + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### POW + +```sql + SELECT POW(field_name, power) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列的指数为 power 的幂 + +**返回结果类型**:DOUBLE。如果输入值为 NULL,输出值也为 NULL + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### LOG + +```sql + SELECT LOG(field_name, base) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列对于底数 base 的对数 + +**返回结果类型**:DOUBLE。如果输入值为 NULL,输出值也为 NULL + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### ABS + +```sql + SELECT ABS(field_name) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列的绝对值 + +**返回结果类型**:如果输入值为整数,输出值是 UBIGINT 类型。如果输入值是 FLOAT/DOUBLE 数据类型,输出值是 DOUBLE 数据类型。 + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### SQRT + +```sql + SELECT SQRT(field_name) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:获得指定列的平方根 + +**返回结果类型**:DOUBLE。如果输入值为 NULL,输出值也为 NULL + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上;在超级表查询中使用时,不能应用在 tag 列 + +**嵌套子查询支持**:适用于内层查询和外层查询。 + +**使用说明**: + +- 只能与普通列,选择(Selection)、投影(Projection)函数一起使用,不能与聚合(Aggregation)函数一起使用。 +- 该函数可以应用在普通表和超级表上。 +- 版本2.6.0.x后支持 + +### CAST + +```sql + SELECT CAST(expression AS type_name) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:数据类型转换函数,输入参数 expression 支持普通列、常量、标量函数及它们之间的四则运算,不支持 tag 列,只适用于 select 子句中。 + +**返回结果类型**:CAST 中指定的类型(type_name)。 + +**适用数据类型**: + +- 输入参数 expression 的类型可以是除 JSON 外目前所有类型字段(BOOL/TINYINT/SMALLINT/INT/BIGINT/FLOAT/DOUBLE/BINARY(M)/TIMESTAMP/NCHAR(M)/TINYINT UNSIGNED/SMALLINT UNSIGNED/INT UNSIGNED/BIGINT UNSIGNED); +- 输出目标类型只支持 BIGINT/BINARY(N)/TIMESTAMP/NCHAR(N)/BIGINT UNSIGNED。 + +**使用说明**: + +- 对于不能支持的类型转换会直接报错。 +- 如果输入值为NULL则输出值也为NULL。 +- 对于类型支持但某些值无法正确转换的情况对应的转换后的值以转换函数输出为准。目前可能遇到的几种情况: + 1)BINARY/NCHAR转BIGINT/BIGINT UNSIGNED时可能出现的无效字符情况,例如"a"可能转为0。 + 2)有符号数或TIMESTAMP转BIGINT UNSIGNED可能遇到的溢出问题。 + 3)BIGINT UNSIGNED转BIGINT可能遇到的溢出问题。 + 4)FLOAT/DOUBLE转BIGINT/BIGINT UNSIGNED可能遇到的溢出问题。 +- 版本2.6.0.x后支持 + +### CONCAT + +```sql + SELECT CONCAT(str1|column1, str2|column2, ...) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:字符串连接函数。 + +**返回结果类型**:同输入参数类型,BINARY 或者 NCHAR。 + +**适用数据类型**:输入参数或者全部是 BINARY 格式的字符串或者列,或者全部是 NCHAR 格式的字符串或者列。不能应用在 TAG 列。 + +**使用说明**: + +- 如果输入值为NULL,输出值为NULL。 +- 该函数最小参数个数为2个,最大参数个数为8个。 +- 该函数可以应用在普通表和超级表上。 +- 该函数适用于内层查询和外层查询。 +- 版本2.6.0.x后支持 + +### CONCAT_WS + +``` + SELECT CONCAT_WS(separator, str1|column1, str2|column2, ...) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:带分隔符的字符串连接函数。 + +**返回结果类型**:同输入参数类型,BINARY 或者 NCHAR。 + +**适用数据类型**:输入参数或者全部是 BINARY 格式的字符串或者列,或者全部是 NCHAR 格式的字符串或者列。不能应用在 TAG 列。 + +**使用说明**: + +- 如果separator值为NULL,输出值为NULL。如果separator值不为NULL,其他输入为NULL,输出为空串 +- 该函数最小参数个数为3个,最大参数个数为9个。 +- 该函数可以应用在普通表和超级表上。 +- 该函数适用于内层查询和外层查询。 +- 版本2.6.0.x后支持 + +### LENGTH + +``` + SELECT LENGTH(str|column) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:以字节计数的字符串长度。 + +**返回结果类型**:INT。 + +**适用数据类型**:输入参数是 BINARY 类型或者 NCHAR 类型的字符串或者列。不能应用在 TAG 列。 + +**使用说明** + +- 如果输入值为NULL,输出值为NULL。 +- 该函数可以应用在普通表和超级表上。 +- 函数适用于内层查询和外层查询。 +- 版本2.6.0.x后支持 + +### CHAR_LENGTH + +``` + SELECT CHAR_LENGTH(str|column) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:以字符计数的字符串长度。 + +**返回结果类型**:INT。 + +**适用数据类型**:输入参数是 BINARY 类型或者 NCHAR 类型的字符串或者列。不能应用在 TAG 列。 + +**使用说明** + +- 如果输入值为NULL,输出值为NULL。 +- 该函数可以应用在普通表和超级表上。 +- 该函数适用于内层查询和外层查询。 +- 版本2.6.0.x后支持 + +### LOWER + +``` + SELECT LOWER(str|column) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:将字符串参数值转换为全小写字母。 + +**返回结果类型**:同输入类型。 + +**适用数据类型**:输入参数是 BINARY 类型或者 NCHAR 类型的字符串或者列。不能应用在 TAG 列。 + +**使用说明**: + +- 如果输入值为NULL,输出值为NULL。 +- 该函数可以应用在普通表和超级表上。 +- 该函数适用于内层查询和外层查询。 +- 版本2.6.0.x后支持 + +### UPPER + +``` + SELECT UPPER(str|column) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:将字符串参数值转换为全大写字母。 + +**返回结果类型**:同输入类型。 + +**适用数据类型**:输入参数是 BINARY 类型或者 NCHAR 类型的字符串或者列。不能应用在 TAG 列。 + +**使用说明**: + +- 如果输入值为NULL,输出值为NULL。 +- 该函数可以应用在普通表和超级表上。 +- 该函数适用于内层查询和外层查询。 +- 版本2.6.0.x后支持 + +### LTRIM + +``` + SELECT LTRIM(str|column) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:返回清除左边空格后的字符串。 + +**返回结果类型**:同输入类型。 + +**适用数据类型**:输入参数是 BINARY 类型或者 NCHAR 类型的字符串或者列。不能应用在 TAG 列。 + +**使用说明**: + +- 如果输入值为NULL,输出值为NULL。 +- 该函数可以应用在普通表和超级表上。 +- 该函数适用于内层查询和外层查询。 +- 版本2.6.0.x后支持 + +### RTRIM + +``` + SELECT RTRIM(str|column) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:返回清除右边空格后的字符串。 + +**返回结果类型**:同输入类型。 + +**适用数据类型**:输入参数是 BINARY 类型或者 NCHAR 类型的字符串或者列。不能应用在 TAG 列。 + +**使用说明**: + +- 如果输入值为NULL,输出值为NULL。 +- 该函数可以应用在普通表和超级表上。 +- 该函数适用于内层查询和外层查询。 +- 版本2.6.0.x后支持 + +### SUBSTR + +``` + SELECT SUBSTR(str,pos[,len]) FROM { tb_name | stb_name } [WHERE clause] +``` + +**功能说明**:从源字符串 str 中的指定位置 pos 开始取一个长度为 len 的子串并返回。 + +**返回结果类型**:同输入类型。 + +**适用数据类型**:输入参数是 BINARY 类型或者 NCHAR 类型的字符串或者列。不能应用在 TAG 列。 + +**使用说明**: + +- 如果输入值为NULL,输出值为NULL。 +- 输入参数pos可以为正数,也可以为负数。如果pos是正数,表示开始位置从字符串开头正数计算。如果pos为负数,表示开始位置从字符串结尾倒数计算。如果输入参数len被忽略,返回的子串包含从pos开始的整个字串。 +- 该函数可以应用在普通表和超级表上。 +- 该函数适用于内层查询和外层查询。 +- 版本2.6.0.x后支持 + +### 四则运算 + +``` +SELECT field_name [+|-|*|/|%][Value|field_name] FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:统计表/超级表中某列或多列间的值加、减、乘、除、取余计算结果。 + +**返回数据类型**:双精度浮点数。 + +**应用字段**:不能应用在 timestamp、binary、nchar、bool 类型字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 支持两列或多列之间进行计算,可使用括号控制计算优先级; +- NULL 字段不参与计算,如果参与计算的某行中包含 NULL,该行的计算结果为 NULL。 + +``` +taos> SELECT current + voltage * phase FROM d1001; +(current+(voltage*phase)) | +============================ + 78.190000713 | + 84.540003240 | + 80.810000718 | +Query OK, 3 row(s) in set (0.001046s) +``` + +### STATECOUNT + +``` +SELECT STATECOUNT(field_name, oper, val) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:返回满足某个条件的连续记录的个数,结果作为新的一列追加在每行后面。条件根据参数计算,如果条件为 true 则加 1,条件为 false 则重置为-1,如果数据为 NULL,跳过该条数据。 + +**参数范围**: + +- oper : LT (小于)、GT(大于)、LE(小于等于)、GE(大于等于)、NE(不等于)、EQ(等于),不区分大小写。 +- val : 数值型 + +**返回结果类型**:整形。 + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上。 + +**嵌套子查询支持**:不支持应用在子查询上。 + +**支持的版本**:2.6 开始的版本。 + +**使用说明**: + +- 该函数可以应用在普通表上,在由 GROUP BY 划分出单独时间线的情况下用于超级表(也即 GROUP BY tbname) +- 不能和窗口操作一起使用,例如 interval/state_window/session_window。 + +**示例**: + +``` +taos> select ts,dbig from statef2; + ts | dbig | +======================================================== +2021-10-15 00:31:33.000000000 | 1 | +2021-10-17 00:31:31.000000000 | NULL | +2021-12-24 00:31:34.000000000 | 2 | +2022-01-01 08:00:05.000000000 | 19 | +2022-01-01 08:00:06.000000000 | NULL | +2022-01-01 08:00:07.000000000 | 9 | +Query OK, 6 row(s) in set (0.002977s) + +taos> select stateCount(dbig,GT,2) from statef2; +ts | dbig | statecount(dbig,gt,2) | +================================================================================ +2021-10-15 00:31:33.000000000 | 1 | -1 | +2021-10-17 00:31:31.000000000 | NULL | NULL | +2021-12-24 00:31:34.000000000 | 2 | -1 | +2022-01-01 08:00:05.000000000 | 19 | 1 | +2022-01-01 08:00:06.000000000 | NULL | NULL | +2022-01-01 08:00:07.000000000 | 9 | 2 | +Query OK, 6 row(s) in set (0.002791s) +``` + +### STATEDURATION + +```sql +SELECT stateDuration(field_name, oper, val, unit) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:返回满足某个条件的连续记录的时间长度,结果作为新的一列追加在每行后面。条件根据参数计算,如果条件为 true 则加上两个记录之间的时间长度(第一个满足条件的记录时间长度记为 0),条件为 false 则重置为-1,如果数据为 NULL,跳过该条数据。 + +**参数范围**: + +- oper : LT (小于)、GT(大于)、LE(小于等于)、GE(大于等于)、NE(不等于)、EQ(等于),不区分大小写。 +- val : 数值型 +- unit : 时间长度的单位,范围[1s、1m、1h ],不足一个单位舍去。默认为 1s。 + +**返回结果类型**:整形。 + +**适用数据类型**:不能应用在 timestamp、binary、nchar、bool 类型字段上。 + +**嵌套子查询支持**:不支持应用在子查询上。 + +**支持的版本**:2.6 开始的版本。 + +**使用说明**: + +- 该函数可以应用在普通表上,在由 GROUP BY 划分出单独时间线的情况下用于超级表(也即 GROUP BY tbname) +- 不能和窗口操作一起使用,例如 interval/state_window/session_window。 + +**示例**: + +``` +taos> select ts,dbig from statef2; + ts | dbig | +======================================================== +2021-10-15 00:31:33.000000000 | 1 | +2021-10-17 00:31:31.000000000 | NULL | +2021-12-24 00:31:34.000000000 | 2 | +2022-01-01 08:00:05.000000000 | 19 | +2022-01-01 08:00:06.000000000 | NULL | +2022-01-01 08:00:07.000000000 | 9 | +Query OK, 6 row(s) in set (0.002407s) + +taos> select stateDuration(dbig,GT,2) from statef2; +ts | dbig | stateduration(dbig,gt,2) | +=================================================================================== +2021-10-15 00:31:33.000000000 | 1 | -1 | +2021-10-17 00:31:31.000000000 | NULL | NULL | +2021-12-24 00:31:34.000000000 | 2 | -1 | +2022-01-01 08:00:05.000000000 | 19 | 0 | +2022-01-01 08:00:06.000000000 | NULL | NULL | +2022-01-01 08:00:07.000000000 | 9 | 2 | +Query OK, 6 row(s) in set (0.002613s) +``` + +## 时间函数 + +从 2.6.0.0 版本开始,TDengine 查询引擎支持以下时间相关函数: + +### NOW + +```sql +SELECT NOW() FROM { tb_name | stb_name } [WHERE clause]; +SELECT select_expr FROM { tb_name | stb_name } WHERE ts_col cond_operatior NOW(); +INSERT INTO tb_name VALUES (NOW(), ...); +``` + +**功能说明**:返回客户端当前系统时间。 + +**返回结果数据类型**:TIMESTAMP 时间戳类型。 + +**应用字段**:在 WHERE 或 INSERT 语句中使用时只能作用于 TIMESTAMP 类型的字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 支持时间加减操作,如 NOW() + 1s, 支持的时间单位如下: + b(纳秒)、u(微秒)、a(毫秒)、s(秒)、m(分)、h(小时)、d(天)、w(周)。 +- 返回的时间戳精度与当前 DATABASE 设置的时间精度一致。 + +**示例**: + +```sql +taos> SELECT NOW() FROM meters; + now() | +========================== + 2022-02-02 02:02:02.456 | +Query OK, 1 row(s) in set (0.002093s) + +taos> SELECT NOW() + 1h FROM meters; + now() + 1h | +========================== + 2022-02-02 03:02:02.456 | +Query OK, 1 row(s) in set (0.002093s) + +taos> SELECT COUNT(voltage) FROM d1001 WHERE ts < NOW(); + count(voltage) | +============================= + 5 | +Query OK, 5 row(s) in set (0.004475s) + +taos> INSERT INTO d1001 VALUES (NOW(), 10.2, 219, 0.32); +Query OK, 1 of 1 row(s) in database (0.002210s) +``` + +### TODAY + +```sql +SELECT TODAY() FROM { tb_name | stb_name } [WHERE clause]; +SELECT select_expr FROM { tb_name | stb_name } WHERE ts_col cond_operatior TODAY()]; +INSERT INTO tb_name VALUES (TODAY(), ...); +``` + +**功能说明**:返回客户端当日零时的系统时间。 + +**返回结果数据类型**:TIMESTAMP 时间戳类型。 + +**应用字段**:在 WHERE 或 INSERT 语句中使用时只能作用于 TIMESTAMP 类型的字段。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 支持时间加减操作,如 TODAY() + 1s, 支持的时间单位如下: + b(纳秒),u(微秒),a(毫秒),s(秒),m(分),h(小时),d(天),w(周)。 +- 返回的时间戳精度与当前 DATABASE 设置的时间精度一致。 + +**示例**: + +```sql +taos> SELECT TODAY() FROM meters; + today() | +========================== + 2022-02-02 00:00:00.000 | +Query OK, 1 row(s) in set (0.002093s) + +taos> SELECT TODAY() + 1h FROM meters; + today() + 1h | +========================== + 2022-02-02 01:00:00.000 | +Query OK, 1 row(s) in set (0.002093s) + +taos> SELECT COUNT(voltage) FROM d1001 WHERE ts < TODAY(); + count(voltage) | +============================= + 5 | +Query OK, 5 row(s) in set (0.004475s) + +taos> INSERT INTO d1001 VALUES (TODAY(), 10.2, 219, 0.32); +Query OK, 1 of 1 row(s) in database (0.002210s) +``` + +### TIMEZONE + +```sql +SELECT TIMEZONE() FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:返回客户端当前时区信息。 + +**返回结果数据类型**:BINARY 类型。 + +**应用字段**:无 + +**适用于**:表、超级表。 + +**示例**: + +```sql +taos> SELECT TIMEZONE() FROM meters; + timezone() | +================================= + UTC (UTC, +0000) | +Query OK, 1 row(s) in set (0.002093s) +``` + +### TO_ISO8601 + +```sql +SELECT TO_ISO8601(ts_val | ts_col) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:将 UNIX 时间戳转换成为 ISO8601 标准的日期时间格式,并附加客户端时区信息。 + +**返回结果数据类型**:BINARY 类型。 + +**应用字段**:UNIX 时间戳常量或是 TIMESTAMP 类型的列 + +**适用于**:表、超级表。 + +**使用说明**: + +- 如果输入是 UNIX 时间戳常量,返回格式精度由时间戳的位数决定; +- 如果输入是 TIMSTAMP 类型的列,返回格式的时间戳精度与当前 DATABASE 设置的时间精度一致。 + +**示例**: + +```sql +taos> SELECT TO_ISO8601(1643738400) FROM meters; + to_iso8601(1643738400) | +============================== + 2022-02-02T02:00:00+0800 | + +taos> SELECT TO_ISO8601(ts) FROM meters; + to_iso8601(ts) | +============================== + 2022-02-02T02:00:00+0800 | + 2022-02-02T02:00:00+0800 | + 2022-02-02T02:00:00+0800 | +``` + +### TO_UNIXTIMESTAMP + +```sql +SELECT TO_UNIXTIMESTAMP(datetime_string | ts_col) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:将日期时间格式的字符串转换成为 UNIX 时间戳。 + +**返回结果数据类型**:长整型 INT64。 + +**应用字段**:字符串常量或是 BINARY/NCHAR 类型的列。 + +**适用于**:表、超级表。 + +**使用说明**: + +- 输入的日期时间字符串须符合 ISO8601/RFC3339 标准,无法转换的字符串格式将返回 0。 +- 返回的时间戳精度与当前 DATABASE 设置的时间精度一致。 + +**示例**: + +```sql +taos> SELECT TO_UNIXTIMESTAMP("2022-02-02T02:00:00.000Z") FROM meters; +to_unixtimestamp("2022-02-02T02:00:00.000Z") | +============================================== + 1643767200000 | + +taos> SELECT TO_UNIXTIMESTAMP(col_binary) FROM meters; + to_unixtimestamp(col_binary) | +======================================== + 1643767200000 | + 1643767200000 | + 1643767200000 | +``` + +### TIMETRUNCATE + +```sql +SELECT TIMETRUNCATE(ts_val | datetime_string | ts_col, time_unit) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:将时间戳按照指定时间单位 time_unit 进行截断。 + +**返回结果数据类型**:TIMESTAMP 时间戳类型。 + +**应用字段**:UNIX 时间戳,日期时间格式的字符串,或者 TIMESTAMP 类型的列。 + +**适用于**:表、超级表。 + +**使用说明**: +- 支持的时间单位 time_unit 如下: + 1u(微秒),1a(毫秒),1s(秒),1m(分),1h(小时),1d(天)。 +- 返回的时间戳精度与当前 DATABASE 设置的时间精度一致。 + +**示例**: + +```sql +taos> SELECT TIMETRUNCATE(1643738522000, 1h) FROM meters; + timetruncate(1643738522000, 1h) | +=================================== + 2022-02-02 02:00:00.000 | +Query OK, 1 row(s) in set (0.001499s) + +taos> SELECT TIMETRUNCATE("2022-02-02 02:02:02", 1h) FROM meters; + timetruncate("2022-02-02 02:02:02", 1h) | +=========================================== + 2022-02-02 02:00:00.000 | +Query OK, 1 row(s) in set (0.003903s) + +taos> SELECT TIMETRUNCATE(ts, 1h) FROM meters; + timetruncate(ts, 1h) | +========================== + 2022-02-02 02:00:00.000 | + 2022-02-02 02:00:00.000 | + 2022-02-02 02:00:00.000 | +Query OK, 3 row(s) in set (0.003903s) +``` + +### TIMEDIFF + +```sql +SELECT TIMEDIFF(ts_val1 | datetime_string1 | ts_col1, ts_val2 | datetime_string2 | ts_col2 [, time_unit]) FROM { tb_name | stb_name } [WHERE clause]; +``` + +**功能说明**:计算两个时间戳之间的差值,并近似到时间单位 time_unit 指定的精度。 + +**返回结果数据类型**:长整型 INT64。 + +**应用字段**:UNIX 时间戳,日期时间格式的字符串,或者 TIMESTAMP 类型的列。 + +**适用于**:表、超级表。 + +**使用说明**: +- 支持的时间单位 time_unit 如下: + 1u(微秒),1a(毫秒),1s(秒),1m(分),1h(小时),1d(天)。 +- 如果时间单位 time_unit 未指定, 返回的时间差值精度与当前 DATABASE 设置的时间精度一致。 + +**示例**: + +```sql +taos> SELECT TIMEDIFF(1643738400000, 1643742000000) FROM meters; + timediff(1643738400000, 1643742000000) | +========================================= + 3600000 | +Query OK, 1 row(s) in set (0.002553s) +taos> SELECT TIMEDIFF(1643738400000, 1643742000000, 1h) FROM meters; + timediff(1643738400000, 1643742000000, 1h) | +============================================= + 1 | +Query OK, 1 row(s) in set (0.003726s) + +taos> SELECT TIMEDIFF("2022-02-02 03:00:00", "2022-02-02 02:00:00", 1h) FROM meters; + timediff("2022-02-02 03:00:00", "2022-02-02 02:00:00", 1h) | +============================================================= + 1 | +Query OK, 1 row(s) in set (0.001937s) + +taos> SELECT TIMEDIFF(ts_col1, ts_col2, 1h) FROM meters; + timediff(ts_col1, ts_col2, 1h) | +=================================== + 1 | +Query OK, 1 row(s) in set (0.001937s) +``` diff --git a/docs-cn/12-taos-sql/08-interval.md b/docs-cn/12-taos-sql/08-interval.md new file mode 100644 index 0000000000..d62e11b0db --- /dev/null +++ b/docs-cn/12-taos-sql/08-interval.md @@ -0,0 +1,113 @@ +--- +sidebar_label: 按窗口切分聚合 +title: 按窗口切分聚合 +--- + + +TDengine 支持按时间段窗口切分方式进行聚合结果查询,比如温度传感器每秒采集一次数据,但需查询每隔 10 分钟的温度平均值。这种场景下可以使用窗口子句来获得需要的查询结果。 +窗口子句用于针对查询的数据集合进行按照窗口切分成为查询子集并进行聚合,窗口包含时间窗口(time window)、状态窗口(status window)、会话窗口(session window)三种窗口。其中时间窗口又可划分为滑动时间窗口和翻转时间窗口。 + +## 时间窗口 + +INTERVAL 子句用于产生相等时间周期的窗口,SLIDING 用以指定窗口向前滑动的时间。每次执行的查询是一个时间窗口,时间窗口随着时间流动向前滑动。在定义连续查询的时候需要指定时间窗口(time window )大小和每次前向增量时间(forward sliding times)。如图,[t0s, t0e] ,[t1s , t1e], [t2s, t2e] 是分别是执行三次连续查询的时间窗口范围,窗口的前向滑动的时间范围 sliding time 标识 。查询过滤、聚合等操作按照每个时间窗口为独立的单位执行。当 SLIDING 与 INTERVAL 相等的时候,滑动窗口即为翻转窗口。 + +![时间窗口示意图](/img/sql/timewindow-1.png) + +INTERVAL 和 SLIDING 子句需要配合聚合和选择函数来使用。以下 SQL 语句非法: + +``` +SELECT * FROM temp_tb_1 INTERVAL(1m); +``` + +SLIDING 的向前滑动的时间不能超过一个窗口的时间范围。以下语句非法: + +``` +SELECT COUNT(*) FROM temp_tb_1 INTERVAL(1m) SLIDING(2m); +``` + +当 SLIDING 与 INTERVAL 取值相等的时候,滑动窗口即为翻转窗口。 +_ 聚合时间段的窗口宽度由关键词 INTERVAL 指定,最短时间间隔 10 毫秒(10a);并且支持偏移 offset(偏移必须小于间隔),也即时间窗口划分与“UTC 时刻 0”相比的偏移量。SLIDING 语句用于指定聚合时间段的前向增量,也即每次窗口向前滑动的时长。 +_ 从 2.1.5.0 版本开始,INTERVAL 语句允许的最短时间间隔调整为 1 微秒(1u),当然如果所查询的 DATABASE 的时间精度设置为毫秒级,那么允许的最短时间间隔为 1 毫秒(1a)。 \* **注意**:用到 INTERVAL 语句时,除非极特殊的情况,都要求把客户端和服务端的 taos.cfg 配置文件中的 timezone 参数配置为相同的取值,以避免时间处理函数频繁进行跨时区转换而导致的严重性能影响。 + +## 状态窗口 + +使用整数(布尔值)或字符串来标识产生记录时候设备的状态量。产生的记录如果具有相同的状态量数值则归属于同一个状态窗口,数值改变后该窗口关闭。如下图所示,根据状态量确定的状态窗口分别是[2019-04-28 14:22:07,2019-04-28 14:22:10]和[2019-04-28 14:22:11,2019-04-28 14:22:12]两个。(状态窗口暂不支持对超级表使用) + +![时间窗口示意图](/img/sql/timewindow-3.png) + +使用 STATE_WINDOW 来确定状态窗口划分的列。例如: + +``` +SELECT COUNT(*), FIRST(ts), status FROM temp_tb_1 STATE_WINDOW(status); +``` + +## 会话窗口 + +会话窗口根据记录的时间戳主键的值来确定是否属于同一个会话。如下图所示,如果设置时间戳的连续的间隔小于等于 12 秒,则以下 6 条记录构成 2 个会话窗口,分别是:[2019-04-28 14:22:10,2019-04-28 14:22:30]和[2019-04-28 14:23:10,2019-04-28 14:23:30]。因为 2019-04-28 14:22:30 与 2019-04-28 14:23:10 之间的时间间隔是 40 秒,超过了连续时间间隔(12 秒)。 + +![时间窗口示意图](/img/sql/timewindow-2.png) + +在 tol_value 时间间隔范围内的结果都认为归属于同一个窗口,如果连续的两条记录的时间超过 tol_val,则自动开启下一个窗口。(会话窗口暂不支持对超级表使用) + +``` + +SELECT COUNT(*), FIRST(ts) FROM temp_tb_1 SESSION(ts, tol_val); +``` + +这种类型的查询语法如下: + +``` +SELECT function_list FROM tb_name + [WHERE where_condition] + [SESSION(ts_col, tol_val)] + [STATE_WINDOW(col)] + [INTERVAL(interval [, offset]) [SLIDING sliding]] + [FILL({NONE | VALUE | PREV | NULL | LINEAR | NEXT})] + +SELECT function_list FROM stb_name + [WHERE where_condition] + [INTERVAL(interval [, offset]) [SLIDING sliding]] + [FILL({NONE | VALUE | PREV | NULL | LINEAR | NEXT})] + [GROUP BY tags] +``` + +- 在聚合查询中,function_list 位置允许使用聚合和选择函数,并要求每个函数仅输出单个结果(例如:COUNT、AVG、SUM、STDDEV、LEASTSQUARES、PERCENTILE、MIN、MAX、FIRST、LAST),而不能使用具有多行输出结果的函数(例如:DIFF 以及四则运算)。 +- 此外 LAST_ROW 查询也不能与窗口聚合同时出现。 +- 标量函数(如:CEIL/FLOOR 等)也不能使用在窗口聚合查询中。 +- + +- WHERE 语句可以指定查询的起止时间和其他过滤条件。 +- FILL 语句指定某一窗口区间数据缺失的情况下的填充模式。填充模式包括以下几种: + 1. 不进行填充:NONE(默认填充模式)。 + 2. VALUE 填充:固定值填充,此时需要指定填充的数值。例如:FILL(VALUE, 1.23)。 + 3. PREV 填充:使用前一个非 NULL 值填充数据。例如:FILL(PREV)。 + 4. NULL 填充:使用 NULL 填充数据。例如:FILL(NULL)。 + 5. LINEAR 填充:根据前后距离最近的非 NULL 值做线性插值填充。例如:FILL(LINEAR)。 + 6. NEXT 填充:使用下一个非 NULL 值填充数据。例如:FILL(NEXT)。 + +:::info + +1. 使用 FILL 语句的时候可能生成大量的填充输出,务必指定查询的时间区间。针对每次查询,系统可返回不超过 1 千万条具有插值的结果。 +2. 在时间维度聚合中,返回的结果中时间序列严格单调递增。 +3. 如果查询对象是超级表,则聚合函数会作用于该超级表下满足值过滤条件的所有表的数据。如果查询中没有使用 GROUP BY 语句,则返回的结果按照时间序列严格单调递增;如果查询中使用了 GROUP BY 语句分组,则返回结果中每个 GROUP 内不按照时间序列严格单调递增。 + +::: + +时间聚合也常被用于连续查询场景,可以参考文档 [连续查询(Continuous Query)](/develop/continuous-query)。 + +## 示例 + +智能电表的建表语句如下: + +``` +CREATE TABLE meters (ts TIMESTAMP, current FLOAT, voltage INT, phase FLOAT) TAGS (location BINARY(64), groupId INT); +``` + +针对智能电表采集的数据,以 10 分钟为一个阶段,计算过去 24 小时的电流数据的平均值、最大值、电流的中位数。如果没有计算值,用前一个非 NULL 值填充。使用的查询语句如下: + +``` +SELECT AVG(current), MAX(current), APERCENTILE(current, 50) FROM meters + WHERE ts>=NOW-1d and ts<=now + INTERVAL(10m) + FILL(PREV); +``` diff --git a/docs-cn/12-taos-sql/09-limit.md b/docs-cn/12-taos-sql/09-limit.md new file mode 100644 index 0000000000..3c86a38621 --- /dev/null +++ b/docs-cn/12-taos-sql/09-limit.md @@ -0,0 +1,54 @@ +--- +sidebar_label: 边界限制 +title: 边界限制 +--- + +## 一般限制 + +- 数据库名最大长度为 32。 +- 表名最大长度为 192,不包括数据库名前缀和分隔符 +- 每行数据最大长度 16k 个字符, 从 2.1.7.0 版本开始,每行数据最大长度 48k 个字符(注意:数据行内每个 BINARY/NCHAR 类型的列还会额外占用 2 个字节的存储位置)。 +- 列名最大长度为 64,最多允许 4096 列,最少需要 2 列,第一列必须是时间戳。注:从 2.1.7.0 版本(不含)以前最多允许 4096 列 +- 标签名最大长度为 64,最多允许 128 个,至少要有 1 个标签,一个表中标签值的总长度不超过 16k 个字符。 +- SQL 语句最大长度 1048576 个字符,也可通过客户端配置参数 maxSQLLength 修改,取值范围 65480 ~ 1048576。 +- SELECT 语句的查询结果,最多允许返回 4096 列(语句中的函数调用可能也会占用一些列空间),超限时需要显式指定较少的返回数据列,以避免语句执行报错。注: 2.1.7.0 版本(不含)之前为最多允许 1024 列 +- 库的数目,超级表的数目、表的数目,系统不做限制,仅受系统资源限制。 + +## GROUP BY 的限制 + +TAOS SQL 支持对标签、TBNAME 进行 GROUP BY 操作,也支持普通列进行 GROUP BY,前提是:仅限一列且该列的唯一值小于 10 万个。注意:group by 不支持 float,double 类型。 + +## IS NOT NULL 的限制 + +IS NOT NULL 与不为空的表达式适用范围。 + +IS NOT NULL 支持所有类型的列。不为空的表达式为 <\>"",仅对非数值类型的列适用。 + +## ORDER BY 的限制 + +- 非超级表只能有一个 order by. +- 超级表最多两个 order by, 并且第二个必须为 ts. +- order by tag,必须和 group by tag 一起,并且是同一个 tag。 tbname 和 tag 一样逻辑。 只适用于超级表 +- order by 普通列,必须和 group by 一起或者和 top/bottom 一起,并且是同一个普通列。 适用于超级表和普通表。如果同时存在 group by 和 top/bottom 一起,order by 优先必须和 group by 同一列。 +- order by ts. 适用于超级表和普通表。 +- order by ts 同时含有 group by 时 针对 group 内部用 ts 排序 + +## 表(列)名合法性说明 + +### TDengine 中的表(列)名命名规则如下: +只能由字母、数字、下划线构成,数字不能在首位,长度不能超过 192 字节,不区分大小写。这里表名称不包括数据库名的前缀和分隔符。 + +### 转义后表(列)名规则: +为了兼容支持更多形式的表(列)名,TDengine 引入新的转义符 "`",可以避免表名与关键词的冲突,同时不受限于上述表名合法性约束检查,转义符不计入表名的长度。 +转义后的表(列)名同样受到长度限制要求,且长度计算的时候不计算转义符。使用转义字符以后,不再对转义字符中的内容进行大小写统一。 + +例如: +\`aBc\` 和 \`abc\` 是不同的表(列)名,但是 abc 和 aBc 是相同的表(列)名。 + +:::note +转义字符中的内容必须是可打印字符。 + +::: + +### 支持版本 +支持转义符的功能从 2.3.0.1 版本开始。 \ No newline at end of file diff --git a/docs-cn/12-taos-sql/10-json.md b/docs-cn/12-taos-sql/10-json.md new file mode 100644 index 0000000000..4a4a8cca73 --- /dev/null +++ b/docs-cn/12-taos-sql/10-json.md @@ -0,0 +1,91 @@ +--- +sidebar_label: JSON 类型使用说明 +title: JSON 类型使用说明 +--- + + +## 语法说明 + +1. 创建 json 类型 tag + + ``` + create stable s1 (ts timestamp, v1 int) tags (info json) + + create table s1_1 using s1 tags ('{"k1": "v1"}') + ``` + +2. json 取值操作符 -> + + ``` + select * from s1 where info->'k1' = 'v1' + + select info->'k1' from s1 + ``` + +3. json key 是否存在操作符 contains + + ``` + select * from s1 where info contains 'k2' + + select * from s1 where info contains 'k1' + ``` + +## 支持的操作 + +1. 在 where 条件中时,支持函数 match/nmatch/between and/like/and/or/is null/is no null,不支持 in + + ``` + select * from s1 where info->'k1' match 'v*'; + + select * from s1 where info->'k1' like 'v%' and info contains 'k2'; + + select * from s1 where info is null; + + select * from s1 where info->'k1' is not null + ``` + +2. 支持 json tag 放在 group by、order by、join 子句、union all 以及子查询中,比如 group by json->'key' + +3. 支持 distinct 操作. + + ``` + select distinct info->'k1' from s1 + ``` + +4. 标签操作 + + 支持修改 json 标签值(全量覆盖) + + 支持修改 json 标签名 + + 不支持添加 json 标签、删除 json 标签、修改 json 标签列宽 + +## 其他约束条件 + +1. 只有标签列可以使用 json 类型,如果用 json 标签,标签列只能有一个。 + +2. 长度限制:json 中 key 的长度不能超过 256,并且 key 必须为可打印 ascii 字符;json 字符串总长度不超过 4096 个字节。 + +3. json 格式限制: + + 1. json 输入字符串可以为空("","\t"," "或 null)或 object,不能为非空的字符串,布尔型和数组。 + 2. object 可为{},如果 object 为{},则整个 json 串记为空。key 可为"",若 key 为"",则 json 串中忽略该 k-v 对。 + 3. value 可以为数字(int/double)或字符串或 bool 或 null,暂不可以为数组。不允许嵌套。 + 4. 若 json 字符串中出现两个相同的 key,则第一个生效。 + 5. json 字符串里暂不支持转义。 + +4. 当查询 json 中不存在的 key 时,返回 NULL + +5. 当 json tag 作为子查询结果时,不再支持上层查询继续对子查询中的 json 串做解析查询。 + + 比如暂不支持 + + ``` + select jtag->'key' from (select jtag from stable) + ``` + + 不支持 + + ``` + select jtag->'key' from (select jtag from stable) where jtag->'key'>0 + ``` diff --git a/docs-cn/12-taos-sql/11-escape.md b/docs-cn/12-taos-sql/11-escape.md new file mode 100644 index 0000000000..756e5c8159 --- /dev/null +++ b/docs-cn/12-taos-sql/11-escape.md @@ -0,0 +1,30 @@ +--- +title: 转义字符说明 +--- + +## 转义字符表 + +| 字符序列 | **代表的字符** | +| :------: | -------------- | +| `\'` | 单引号' | +| `\"` | 双引号" | +| \n | 换行符 | +| \r | 回车符 | +| \t | tab 符 | +| `\\` | 斜杠\ | +| `\%` | % 规则见下 | +| `\_` | \_ 规则见下 | + +:::note +转义符的功能从 2.4.0.4 版本开始 + +::: + +## 转义字符使用规则 + +1. 标识符里有转义字符(数据库名、表名、列名) + 1. 普通标识符: 直接提示错误的标识符,因为标识符规定必须是数字、字母和下划线,并且不能以数字开头。 + 2. 反引号``标识符: 保持原样,不转义 +2. 数据里有转义字符 + 1. 遇到上面定义的转义字符会转义(%和\_见下面说明),如果没有匹配的转义字符会忽略掉转义符\。 + 2. 对于%和\_,因为在 like 里这两个字符是通配符,所以在模式匹配 like 里用`\%`%和`\_`表示字符里本身的%和\_,如果在 like 模式匹配上下文之外使用`\%`或`\_`,则它们的计算结果为字符串`\%`和`\_`,而不是%和\_。 diff --git a/docs-cn/12-taos-sql/12-keywords/_category_.yml b/docs-cn/12-taos-sql/12-keywords/_category_.yml new file mode 100644 index 0000000000..67738650a4 --- /dev/null +++ b/docs-cn/12-taos-sql/12-keywords/_category_.yml @@ -0,0 +1 @@ +label: 参数限制与保留关键字 \ No newline at end of file diff --git a/docs-cn/12-taos-sql/12-keywords/index.md b/docs-cn/12-taos-sql/12-keywords/index.md new file mode 100644 index 0000000000..608d4e0809 --- /dev/null +++ b/docs-cn/12-taos-sql/12-keywords/index.md @@ -0,0 +1,87 @@ +--- +sidebar_label: 参数限制与保留关键字 +title: TDengine 参数限制与保留关键字 +--- + +## 名称命名规则 + +1. 合法字符:英文字符、数字和下划线 +2. 允许英文字符或下划线开头,不允许以数字开头 +3. 不区分大小写 +4. 转义后表(列)名规则: + 为了兼容支持更多形式的表(列)名,TDengine 引入新的转义符 "`"。可用让表名与关键词不冲突,同时不受限于上述表名称合法性约束检查。 + 转义后的表(列)名同样受到长度限制要求,且长度计算的时候不计算转义符。使用转义字符以后,不再对转义字符中的内容进行大小写统一。 + + 例如:\`aBc\` 和 \`abc\` 是不同的表(列)名,但是 abc 和 aBc 是相同的表(列)名。 + 需要注意的是转义字符中的内容必须是可打印字符。 + 支持转义符的功能从 2.3.0.1 版本开始。 + +## 密码合法字符集 + +`[a-zA-Z0-9!?$%^&*()_–+={[}]:;@~#|<,>.?/]` + +去掉了 `` ‘“`\ `` (单双引号、撇号、反斜杠、空格) + +- 数据库名:不能包含“.”以及特殊字符,不能超过 32 个字符 +- 表名:不能包含“.”以及特殊字符,与所属数据库名一起,不能超过 192 个字符,每行数据最大长度 16k 个字符 +- 表的列名:不能包含特殊字符,不能超过 64 个字符 +- 数据库名、表名、列名,都不能以数字开头,合法的可用字符集是“英文字符、数字和下划线” +- 表的列数:不能超过 1024 列,最少需要 2 列,第一列必须是时间戳(从 2.1.7.0 版本开始,改为最多支持 4096 列) +- 记录的最大长度:包括时间戳 8 byte,不能超过 16KB(每个 BINARY/NCHAR 类型的列还会额外占用 2 个 byte 的存储位置) +- 单条 SQL 语句默认最大字符串长度:1048576 byte,但可通过系统配置参数 maxSQLLength 修改,取值范围 65480 ~ 1048576 byte +- 数据库副本数:不能超过 3 +- 用户名:不能超过 23 个 byte +- 用户密码:不能超过 15 个 byte +- 标签(Tags)数量:不能超过 128 个,可以 0 个 +- 标签的总长度:不能超过 16K byte +- 记录条数:仅受存储空间限制 +- 表的个数:仅受节点个数限制 +- 库的个数:仅受节点个数限制 +- 单个库上虚拟节点个数:不能超过 64 个 +- 库的数目,超级表的数目、表的数目,系统不做限制,仅受系统资源限制 +- SELECT 语句的查询结果,最多允许返回 1024 列(语句中的函数调用可能也会占用一些列空间),超限时需要显式指定较少的返回数据列,以避免语句执行报错。(从 2.1.7.0 版本开始,改为最多允许 4096 列) + +## 保留关键字 + +目前 TDengine 有将近 200 个内部保留关键字,这些关键字无论大小写均不可以用作库名、表名、STable 名、数据列名及标签列名等。这些关键字列表如下: + +| 关键字列表 | | | | | +| ----------- | ---------- | --------- | ---------- | ------------ | +| ABORT | CREATE | IGNORE | NULL | STAR | +| ACCOUNT | CTIME | IMMEDIATE | OF | STATE | +| ACCOUNTS | DATABASE | IMPORT | OFFSET | STATEMENT | +| ADD | DATABASES | IN | OR | STATE_WINDOW | +| AFTER | DAYS | INITIALLY | ORDER | STORAGE | +| ALL | DBS | INSERT | PARTITIONS | STREAM | +| ALTER | DEFERRED | INSTEAD | PASS | STREAMS | +| AND | DELIMITERS | INT | PLUS | STRING | +| AS | DESC | INTEGER | PPS | SYNCDB | +| ASC | DESCRIBE | INTERVAL | PRECISION | TABLE | +| ATTACH | DETACH | INTO | PREV | TABLES | +| BEFORE | DISTINCT | IS | PRIVILEGE | TAG | +| BEGIN | DIVIDE | ISNULL | QTIME | TAGS | +| BETWEEN | DNODE | JOIN | QUERIES | TBNAME | +| BIGINT | DNODES | KEEP | QUERY | TIMES | +| BINARY | DOT | KEY | QUORUM | TIMESTAMP | +| BITAND | DOUBLE | KILL | RAISE | TINYINT | +| BITNOT | DROP | LE | REM | TOPIC | +| BITOR | EACH | LIKE | REPLACE | TOPICS | +| BLOCKS | END | LIMIT | REPLICA | TRIGGER | +| BOOL | EQ | LINEAR | RESET | TSERIES | +| BY | EXISTS | LOCAL | RESTRICT | UMINUS | +| CACHE | EXPLAIN | LP | ROW | UNION | +| CACHELAST | FAIL | LSHIFT | RP | UNSIGNED | +| CASCADE | FILE | LT | RSHIFT | UPDATE | +| CHANGE | FILL | MATCH | SCORES | UPLUS | +| CLUSTER | FLOAT | MAXROWS | SELECT | USE | +| COLON | FOR | MINROWS | SEMI | USER | +| COLUMN | FROM | MINUS | SESSION | USERS | +| COMMA | FSYNC | MNODES | SET | USING | +| COMP | GE | MODIFY | SHOW | VALUES | +| COMPACT | GLOB | MODULES | SLASH | VARIABLE | +| CONCAT | GRANTS | NCHAR | SLIDING | VARIABLES | +| CONFLICT | GROUP | NE | SLIMIT | VGROUPS | +| CONNECTION | GT | NONE | SMALLINT | VIEW | +| CONNECTIONS | HAVING | NOT | SOFFSET | VNODES | +| CONNS | ID | NOTNULL | STABLE | WAL | +| COPY | IF | NOW | STABLES | WHERE | diff --git a/docs-cn/12-taos-sql/_category_.yml b/docs-cn/12-taos-sql/_category_.yml new file mode 100644 index 0000000000..62290997ec --- /dev/null +++ b/docs-cn/12-taos-sql/_category_.yml @@ -0,0 +1 @@ +label: SQL 手册 diff --git a/docs-cn/12-taos-sql/index.md b/docs-cn/12-taos-sql/index.md new file mode 100644 index 0000000000..269bc1d2b5 --- /dev/null +++ b/docs-cn/12-taos-sql/index.md @@ -0,0 +1,40 @@ +--- +title: TAOS SQL +description: "TAOS SQL 支持的语法规则、主要查询功能、支持的 SQL 查询函数,以及常用技巧等内容" +--- + +本文档说明 TAOS SQL 支持的语法规则、主要查询功能、支持的 SQL 查询函数,以及常用技巧等内容。阅读本文档需要读者具有基本的 SQL 语言的基础。 + +TAOS SQL 是用户对 TDengine 进行数据写入和查询的主要工具。TAOS SQL 为了便于用户快速上手,在一定程度上提供与标准 SQL 类似的风格和模式。严格意义上,TAOS SQL 并不是也不试图提供标准的 SQL 语法。此外,由于 TDengine 针对的时序性结构化数据不提供删除功能,因此在 TAO SQL 中不提供数据删除的相关功能。 + +TAOS SQL 不支持关键字的缩写,例如 DESCRIBE 不能缩写为 DESC。 + +本章节 SQL 语法遵循如下约定: + +- <\> 里的内容是用户需要输入的,但不要输入 <\> 本身 +- \[ \] 表示内容为可选项,但不能输入 [] 本身 +- | 表示多选一,选择其中一个即可,但不能输入 | 本身 +- … 表示前面的项可重复多个 + +为更好地说明 SQL 语法的规则及其特点,本文假设存在一个数据集。以智能电表(meters)为例,假设每个智能电表采集电流、电压、相位三个量。其建模如下: + +``` +taos> DESCRIBE meters; + Field | Type | Length | Note | +================================================================================= + ts | TIMESTAMP | 8 | | + current | FLOAT | 4 | | + voltage | INT | 4 | | + phase | FLOAT | 4 | | + location | BINARY | 64 | TAG | + groupid | INT | 4 | TAG | +``` + +数据集包含 4 个智能电表的数据,按照 TDengine 的建模规则,对应 4 个子表,其名称分别是 d1001, d1002, d1003, d1004。 + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + + +``` \ No newline at end of file diff --git a/docs-cn/13-operation/01-pkg-install.md b/docs-cn/13-operation/01-pkg-install.md new file mode 100644 index 0000000000..92b04a42ec --- /dev/null +++ b/docs-cn/13-operation/01-pkg-install.md @@ -0,0 +1,283 @@ +--- +title: 安装和卸载 +description: 安装、卸载、启动、停止和升级 +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +TDengine 开源版本提供 deb 和 rpm 格式安装包,用户可以根据自己的运行环境选择合适的安装包。其中 deb 支持 Debian/Ubuntu 及衍生系统,rpm 支持 CentOS/RHEL/SUSE 及衍生系统。同时我们也为企业用户提供 tar.gz 格式安装包。 + +## 安装 + + + + +1、从官网下载获得 deb 安装包,例如 TDengine-server-2.4.0.7-Linux-x64.deb; +2、进入到 TDengine-server-2.4.0.7-Linux-x64.deb 安装包所在目录,执行如下的安装命令: + +``` +$ sudo dpkg -i TDengine-server-2.4.0.7-Linux-x64.deb +(Reading database ... 137504 files and directories currently installed.) +Preparing to unpack TDengine-server-2.4.0.7-Linux-x64.deb ... +TDengine is removed successfully! +Unpacking tdengine (2.4.0.7) over (2.4.0.7) ... +Setting up tdengine (2.4.0.7) ... +Start to install TDengine... + +System hostname is: ubuntu-1804 + +Enter FQDN:port (like h1.taosdata.com:6030) of an existing TDengine cluster node to join +OR leave it blank to build one: + +Enter your email address for priority support or enter empty to skip: +Created symlink /etc/systemd/system/multi-user.target.wants/taosd.service → /etc/systemd/system/taosd.service. + +To configure TDengine : edit /etc/taos/taos.cfg +To start TDengine : sudo systemctl start taosd +To access TDengine : taos -h ubuntu-1804 to login into TDengine server + + +TDengine is installed successfully! +``` + + + + + +1、从官网下载获得 rpm 安装包,例如 TDengine-server-2.4.0.7-Linux-x64.rpm; +2、进入到 TDengine-server-2.4.0.7-Linux-x64.rpm 安装包所在目录,执行如下的安装命令: + +``` +$ sudo rpm -ivh TDengine-server-2.4.0.7-Linux-x64.rpm +Preparing... ################################# [100%] +Updating / installing... + 1:tdengine-2.4.0.7-3 ################################# [100%] +Start to install TDengine... + +System hostname is: centos7 + +Enter FQDN:port (like h1.taosdata.com:6030) of an existing TDengine cluster node to join +OR leave it blank to build one: + +Enter your email address for priority support or enter empty to skip: + +Created symlink from /etc/systemd/system/multi-user.target.wants/taosd.service to /etc/systemd/system/taosd.service. + +To configure TDengine : edit /etc/taos/taos.cfg +To start TDengine : sudo systemctl start taosd +To access TDengine : taos -h centos7 to login into TDengine server + + +TDengine is installed successfully! +``` + + + + + +1、从官网下载获得 tar.gz 安装包,例如 TDengine-server-2.4.0.7-Linux-x64.tar.gz; +2、进入到 TDengine-server-2.4.0.7-Linux-x64.tar.gz 安装包所在目录,先解压文件后,进入子目录,执行其中的 install.sh 安装脚本: + +``` +$ tar xvzf TDengine-enterprise-server-2.4.0.7-Linux-x64.tar.gz +TDengine-enterprise-server-2.4.0.7/ +TDengine-enterprise-server-2.4.0.7/driver/ +TDengine-enterprise-server-2.4.0.7/driver/vercomp.txt +TDengine-enterprise-server-2.4.0.7/driver/libtaos.so.2.4.0.7 +TDengine-enterprise-server-2.4.0.7/install.sh +TDengine-enterprise-server-2.4.0.7/examples/ +... + +$ ll +total 43816 +drwxrwxr-x 3 ubuntu ubuntu 4096 Feb 22 09:31 ./ +drwxr-xr-x 20 ubuntu ubuntu 4096 Feb 22 09:30 ../ +drwxrwxr-x 4 ubuntu ubuntu 4096 Feb 22 09:30 TDengine-enterprise-server-2.4.0.7/ +-rw-rw-r-- 1 ubuntu ubuntu 44852544 Feb 22 09:31 TDengine-enterprise-server-2.4.0.7-Linux-x64.tar.gz + +$ cd TDengine-enterprise-server-2.4.0.7/ + + $ ll +total 40784 +drwxrwxr-x 4 ubuntu ubuntu 4096 Feb 22 09:30 ./ +drwxrwxr-x 3 ubuntu ubuntu 4096 Feb 22 09:31 ../ +drwxrwxr-x 2 ubuntu ubuntu 4096 Feb 22 09:30 driver/ +drwxrwxr-x 10 ubuntu ubuntu 4096 Feb 22 09:30 examples/ +-rwxrwxr-x 1 ubuntu ubuntu 33294 Feb 22 09:30 install.sh* +-rw-rw-r-- 1 ubuntu ubuntu 41704288 Feb 22 09:30 taos.tar.gz + +$ sudo ./install.sh + +Start to update TDengine... +Created symlink /etc/systemd/system/multi-user.target.wants/taosd.service → /etc/systemd/system/taosd.service. +Nginx for TDengine is updated successfully! + +To configure TDengine : edit /etc/taos/taos.cfg +To configure Taos Adapter (if has) : edit /etc/taos/taosadapter.toml +To start TDengine : sudo systemctl start taosd +To access TDengine : use taos -h ubuntu-1804 in shell OR from http://127.0.0.1:6060 + +TDengine is updated successfully! +Install taoskeeper as a standalone service +taoskeeper is installed, enable it by `systemctl enable taoskeeper` +``` + +:::info +install.sh 安装脚本在执行过程中,会通过命令行交互界面询问一些配置信息。如果希望采取无交互安装方式,那么可以用 -e no 参数来执行 install.sh 脚本。运行 `./install.sh -h` 指令可以查看所有参数的详细说明信息。 + +::: + + + + +:::note +当安装第一个节点时,出现 Enter FQDN:提示的时候,不需要输入任何内容。只有当安装第二个或以后更多的节点时,才需要输入已有集群中任何一个可用节点的 FQDN,支持该新节点加入集群。当然也可以不输入,而是在新节点启动前,配置到新节点的配置文件中。 + +::: + +## 卸载 + + + + +卸载命令如下: + +``` +$ sudo dpkg -r tdengine +(Reading database ... 137504 files and directories currently installed.) +Removing tdengine (2.4.0.7) ... +TDengine is removed successfully! + +``` + + + + + +卸载命令如下: + +``` +$ sudo rpm -e tdengine +TDengine is removed successfully! +``` + + + + + +卸载命令如下: + +``` +$ rmtaos +Nginx for TDengine is running, stopping it... +TDengine is removed successfully! + +taosKeeper is removed successfully! +``` + + + + +:::info +- TDengine 提供了多种安装包,但最好不要在一个系统上同时使用 tar.gz 安装包和 deb 或 rpm 安装包。否则会相互影响,导致在使用时出现问题。 + +- 对于 deb 包安装后,如果安装目录被手工误删了部分,出现卸载、或重新安装不能成功。此时,需要清除 TDengine 包的安装信息,执行如下命令: + + ``` + $ sudo rm -f /var/lib/dpkg/info/tdengine* + ``` + +然后再重新进行安装就可以了。 + +- 对于 rpm 包安装后,如果安装目录被手工误删了部分,出现卸载、或重新安装不能成功。此时,需要清除 TDengine 包的安装信息,执行如下命令: + + ``` + $ sudo rpm -e --noscripts tdengine + ``` + +然后再重新进行安装就可以了。 + +::: + +## 安装目录说明 + +TDengine 成功安装后,主安装目录是 /usr/local/taos,目录内容如下: + +``` +$ cd /usr/local/taos +$ ll +$ ll +total 28 +drwxr-xr-x 7 root root 4096 Feb 22 09:34 ./ +drwxr-xr-x 12 root root 4096 Feb 22 09:34 ../ +drwxr-xr-x 2 root root 4096 Feb 22 09:34 bin/ +drwxr-xr-x 2 root root 4096 Feb 22 09:34 cfg/ +lrwxrwxrwx 1 root root 13 Feb 22 09:34 data -> /var/lib/taos/ +drwxr-xr-x 2 root root 4096 Feb 22 09:34 driver/ +drwxr-xr-x 10 root root 4096 Feb 22 09:34 examples/ +drwxr-xr-x 2 root root 4096 Feb 22 09:34 include/ +lrwxrwxrwx 1 root root 13 Feb 22 09:34 log -> /var/log/taos/ +``` + +- 自动生成配置文件目录、数据库目录、日志目录。 +- 配置文件缺省目录:/etc/taos/taos.cfg, 软链接到 /usr/local/taos/cfg/taos.cfg; +- 数据库缺省目录:/var/lib/taos, 软链接到 /usr/local/taos/data; +- 日志缺省目录:/var/log/taos, 软链接到 /usr/local/taos/log; +- /usr/local/taos/bin 目录下的可执行文件,会软链接到 /usr/bin 目录下; +- /usr/local/taos/driver 目录下的动态库文件,会软链接到 /usr/lib 目录下; +- /usr/local/taos/include 目录下的头文件,会软链接到到 /usr/include 目录下; + +## 卸载和更新文件说明 + +卸载安装包的时候,将保留配置文件、数据库文件和日志文件,即 /etc/taos/taos.cfg 、 /var/lib/taos 、 /var/log/taos 。如果用户确认后不需保留,可以手工删除,但一定要慎重,因为删除后,数据将永久丢失,不可以恢复! + +如果是更新安装,当缺省配置文件( /etc/taos/taos.cfg )存在时,仍然使用已有的配置文件,安装包中携带的配置文件修改为 taos.cfg.orig 保存在 /usr/local/taos/cfg/ 目录,可以作为设置配置参数的参考样例;如果不存在配置文件,就使用安装包中自带的配置文件。 + +## 启动和停止 + +TDengine 使用 Linux 系统的 systemd/systemctl/service 来管理系统的启动和、停止、重启操作。TDengine 的服务进程是 taosd,默认情况下 TDengine 在系统启动后将自动启动。DBA 可以通过 systemd/systemctl/service 手动操作停止、启动、重新启动服务。 + +以 systemctl 为例,命令如下: + +- 启动服务进程:`systemctl start taosd` + +- 停止服务进程:`systemctl stop taosd` + +- 重启服务进程:`systemctl restart taosd` + +- 查看服务状态:`systemctl status taosd` + +注意:TDengine 在 2.4 版本之后包含一个独立组件 taosAdapter 需要使用 systemctl 命令管理 taosAdapter 服务的启动和停止。 + +如果服务进程处于活动状态,则 status 指令会显示如下的相关信息: + + ``` + Active: active (running) + ``` + +如果后台服务进程处于停止状态,则 status 指令会显示如下的相关信息: + + ``` + Active: inactive (dead) + ``` + +## 升级 +升级分为两个层面:升级安装包 和 升级运行中的实例。 + +升级安装包请遵循前述安装和卸载的步骤先卸载旧版本再安装新版本。 + +升级运行中的实例则要复杂得多,首先请注意版本号,TDengine 的版本号目前分为四段,如 2.4.0.14 和 2.4.0.16,只有前三段版本号一致(即只有第四段版本号不同)才能把一个运行中的实例进行升级。升级步骤如下: +- 停止数据写入 +- 确保所有数据落盘,即写入时序数据库 +- 停止 TDengine 集群 +- 卸载旧版本并安装新版本 +- 重新启动 TDengine 集群 +- 进行简单的查询操作确认旧数据没有丢失 +- 进行简单的写入操作确认 TDengine 集群可用 +- 重新恢复业务数据的写入 + +:::warning +TDengine 不保证低版本能够兼容高版本的数据,所以任何时候都不推荐降级 + +::: \ No newline at end of file diff --git a/docs-cn/13-operation/02-planning.mdx b/docs-cn/13-operation/02-planning.mdx new file mode 100644 index 0000000000..954ba7ca00 --- /dev/null +++ b/docs-cn/13-operation/02-planning.mdx @@ -0,0 +1,81 @@ +--- +sidebar_label: 容量规划 +title: 容量规划 +--- + +使用 TDengine 来搭建一个物联网大数据平台,计算资源、存储资源需要根据业务场景进行规划。下面分别讨论系统运行所需要的内存、CPU 以及硬盘空间。 + +## 内存需求 + +每个 Database 可以创建固定数目的 vgroup,默认与 CPU 核数相同,可通过 maxVgroupsPerDb 配置;vgroup 中的每个副本会是一个 vnode;每个 vnode 会占用固定大小的内存(大小与数据库的配置参数 blocks 和 cache 有关);每个 Table 会占用与标签总长度有关的内存;此外,系统会有一些固定的内存开销。因此,每个 DB 需要的系统内存可通过如下公式计算: + +``` +Database Memory Size = maxVgroupsPerDb * replica * (blocks * cache + 10MB) + numOfTables * (tagSizePerTable + 0.5KB) +``` + +示例:假设 maxVgroupPerDB 是缺省值 64,cache 是缺省大小 16M, blocks 是缺省值 6,并且一个 DB 中有 10 万张表,单副本,标签总长度是 256 字节,则这个 DB 总的内存需求为:64 \* 1 \* (16 \* 6 + 10) + 100000 \* (0.25 + 0.5) / 1000 = 6792M。 + +在实际的系统运维中,我们通常会更关心 TDengine 服务进程(taosd)会占用的内存量。 + +``` +taosd 内存总量 = vnode 内存 + mnode 内存 + 查询内存 +``` + +其中: + +1. “vnode 内存”指的是集群中所有的 Database 存储分摊到当前 taosd 节点上所占用的内存资源。可以按上文“Database Memory Size”计算公式估算每个 DB 的内存占用量进行加总,再按集群中总共的 TDengine 节点数做平均(如果设置为多副本,则还需要乘以对应的副本倍数)。 +2. “mnode 内存”指的是集群中管理节点所占用的资源。如果一个 taosd 节点上分布有 mnode 管理节点,则内存消耗还需要增加“0.2KB \* 集群中数据表总数”。 +3. “查询内存”指的是服务端处理查询请求时所需要占用的内存。单条查询语句至少会占用“0.2KB \* 查询涉及的数据表总数”的内存量。 + +注意:以上内存估算方法,主要讲解了系统的“必须内存需求”,而不是“内存总数上限”。在实际运行的生产环境中,由于操作系统缓存、资源管理调度等方面的原因,内存规划应当在估算结果的基础上保留一定冗余,以维持系统状态和系统性能的稳定性。并且,生产环境通常会配置系统资源的监控工具,以便及时发现硬件资源的紧缺情况。 + +最后,如果内存充裕,可以考虑加大 Blocks 的配置,这样更多数据将保存在内存里,提高写入和查询速度。 + +### 客户端内存需求 + +客户端应用采用 taosc 客户端驱动连接服务端,会有内存需求的开销。 + +客户端的内存开销主要由写入过程中的 SQL 语句、表的元数据信息缓存、以及结构性开销构成。系统最大容纳的表数量为 N(每个通过超级表创建的表的 meta data 开销约 256 字节),最大并行写入线程数量 T,最大 SQL 语句长度 S(通常是 1 Mbytes)。由此可以进行客户端内存开销的估算(单位 MBytes): + +``` +M = (T * S * 3 + (N / 4096) + 100) +``` + +举例如下:用户最大并发写入线程数 100,子表数总数 10,000,000,那么客户端的内存最低要求是: + +``` +100 * 3 + (10000000 / 4096) + 100 = 2741 (MBytes) +``` + +即配置 3 GBytes 内存是最低要求。 + +## CPU 需求 + +CPU 的需求取决于如下两方面: + +- **数据插入** TDengine 单核每秒能至少处理一万个插入请求。每个插入请求可以带多条记录,一次插入一条记录与插入 10 条记录,消耗的计算资源差别很小。因此每次插入,条数越大,插入效率越高。如果一个插入请求带 200 条以上记录,单核就能达到每秒插入 100 万条记录的速度。但对前端数据采集的要求越高,因为需要缓存记录,然后一批插入。 +- **查询需求** TDengine 提供高效的查询,但是每个场景的查询差异很大,查询频次变化也很大,难以给出客观数字。需要用户针对自己的场景,写一些查询语句,才能确定。 + +因此仅对数据插入而言,CPU 是可以估算出来的,但查询所耗的计算资源无法估算。在实际运营过程中,不建议 CPU 使用率超过 50%,超过后,需要增加新的节点,以获得更多计算资源。 + +## 存储需求 + +TDengine 相对于通用数据库,有超高的压缩比,在绝大多数场景下,TDengine 的压缩比不会低于 5 倍,有的场合,压缩比可达到 10 倍以上,取决于实际场景的数据特征。压缩前的原始数据大小可通过如下方式计算: + +``` +Raw DataSize = numOfTables * rowSizePerTable * rowsPerTable +``` + +示例:1000 万台智能电表,每台电表每 15 分钟采集一次数据,每次采集的数据 128 字节,那么一年的原始数据量是:10000000 \* 128 \* 24 \* 60 / 15 \* 365 = 44.8512T。TDengine 大概需要消耗 44.851 / 5 = 8.97024T 空间。 + +用户可以通过参数 keep,设置数据在磁盘中的最大保存时长。为进一步减少存储成本,TDengine 还提供多级存储,最冷的数据可以存放在最廉价的存储介质上,应用的访问不用做任何调整,只是读取速度降低了。 + +为提高速度,可以配置多块硬盘,这样可以并发写入或读取数据。需要提醒的是,TDengine 采取多副本的方式提供数据的高可靠,因此不再需要采用昂贵的磁盘阵列。 + +## 物理机或虚拟机台数 + +根据上面的内存、CPU、存储的预估,就可以知道整个系统需要多少核、多少内存、多少存储空间。如果数据副本数不为 1,总需求量需要再乘以副本数。 + +因为 TDengine 具有很好的水平扩展能力,根据总量,再根据单个物理机或虚拟机的资源,就可以轻松决定需要购置多少台物理机或虚拟机了。 + +**立即计算 CPU、内存、存储,请参见:[资源估算方法](https://www.taosdata.com/config/config.html)。** diff --git a/docs-cn/13-operation/03-tolerance.md b/docs-cn/13-operation/03-tolerance.md new file mode 100644 index 0000000000..2c46681962 --- /dev/null +++ b/docs-cn/13-operation/03-tolerance.md @@ -0,0 +1,28 @@ +--- +title: 容错和灾备 +--- + +## 容错 + +TDengine 支持**WAL**(Write Ahead Log)机制,实现数据的容错能力,保证数据的高可用。 + +TDengine 接收到应用的请求数据包时,先将请求的原始数据包写入数据库日志文件,等数据成功写入数据库数据文件后,再删除相应的 WAL。这样保证了 TDengine 能够在断电等因素导致的服务重启时从数据库日志文件中恢复数据,避免数据的丢失。 + +涉及的系统配置参数有两个: + +- walLevel:WAL 级别,0:不写 WAL; 1:写 WAL, 但不执行 fsync; 2:写 WAL, 而且执行 fsync。 +- fsync:当 walLevel 设置为 2 时,执行 fsync 的周期。设置为 0,表示每次写入,立即执行 fsync。 + +如果要 100%的保证数据不丢失,需要将 walLevel 设置为 2,fsync 设置为 0。这时写入速度将会下降。但如果应用侧启动的写数据的线程数达到一定的数量(超过 50),那么写入数据的性能也会很不错,只会比 fsync 设置为 3000 毫秒下降 30%左右。 + +## 灾备 + +TDengine 的集群通过多个副本的机制,来提供系统的高可用性,实现灾备能力。 + +TDengine 集群是由 mnode 负责管理的,为保证 mnode 的高可靠,可以配置多个 mnode 副本,副本数由系统配置参数 numOfMnodes 决定,为了支持高可靠,需要设置大于 1。为保证元数据的强一致性,mnode 副本之间通过同步方式进行数据复制,保证了元数据的强一致性。 + +TDengine 集群中的时序数据的副本数是与数据库关联的,一个集群里可以有多个数据库,每个数据库可以配置不同的副本数。创建数据库时,通过参数 replica 指定副本数。为了支持高可靠,需要设置副本数大于 1。 + +TDengine 集群的节点数必须大于等于副本数,否则创建表时将报错。 + +当 TDengine 集群中的节点部署在不同的物理机上,并设置多个副本数时,就实现了系统的高可靠性,无需再使用其他软件或工具。TDengine 企业版还可以将副本部署在不同机房,从而实现异地容灾。 diff --git a/docs-cn/13-operation/06-admin.md b/docs-cn/13-operation/06-admin.md new file mode 100644 index 0000000000..7934d31eaf --- /dev/null +++ b/docs-cn/13-operation/06-admin.md @@ -0,0 +1,42 @@ +--- +title: 用户管理 +--- + +系统管理员可以在 CLI 界面里添加、删除用户,也可以修改密码。CLI 里 SQL 语法如下: + +```sql +CREATE USER PASS <'password'>; +``` + +创建用户,并指定用户名和密码,密码需要用单引号引起来,单引号为英文半角 + +```sql +DROP USER ; +``` + +删除用户,限 root 用户使用 + +```sql +ALTER USER PASS <'password'>; +``` + +修改用户密码,为避免被转换为小写,密码需要用单引号引用,单引号为英文半角 + +```sql +ALTER USER PRIVILEGE ; +``` + +修改用户权限为:write 或 read,不需要添加单引号 + +说明:系统内共有 super/write/read 三种权限级别,但目前不允许通过 alter 指令把 super 权限赋予用户。 + +```sql +SHOW USERS; +``` + +显示所有用户 + +:::note +SQL 语法中,< >表示需要用户输入的部分,但请不要输入< >本身。 + +::: diff --git a/docs-cn/13-operation/07-import.md b/docs-cn/13-operation/07-import.md new file mode 100644 index 0000000000..7dee05720d --- /dev/null +++ b/docs-cn/13-operation/07-import.md @@ -0,0 +1,61 @@ +--- +title: 数据导入 +--- + +TDengine 提供多种方便的数据导入功能,一种按脚本文件导入,一种按数据文件导入,一种是 taosdump 工具导入本身导出的文件。 + +## 按脚本文件导入 + +TDengine 的 shell 支持 source filename 命令,用于批量运行文件中的 SQL 语句。用户可将建库、建表、写数据等 SQL 命令写在同一个文件中,每条命令单独一行,在 shell 中运行 source 命令,即可按顺序批量运行文件中的 SQL 语句。以‘#’开头的 SQL 语句被认为是注释,shell 将自动忽略。 + +## 按数据文件导入 + +TDengine 也支持在 shell 对已存在的表从 CSV 文件中进行数据导入。CSV 文件只属于一张表且 CSV 文件中的数据格式需与要导入表的结构相同,在导入的时候,其语法如下: + +```sql +insert into tb1 file 'path/data.csv'; +``` + +:::note +注意:如果 CSV 文件首行存在描述信息,请手动删除后再导入。如某列为空,填 NULL,无引号。\*\* + +::: + +例如,现在存在一个子表 d1001, 其表结构如下: + +```sql +taos> DESCRIBE d1001 + Field | Type | Length | Note | +================================================================================= + ts | TIMESTAMP | 8 | | + current | FLOAT | 4 | | + voltage | INT | 4 | | + phase | FLOAT | 4 | | + location | BINARY | 64 | TAG | + groupid | INT | 4 | TAG | +``` + +要导入的 data.csv 的格式如下: + +```csv +'2018-10-04 06:38:05.000',10.30000,219,0.31000 +'2018-10-05 06:38:15.000',12.60000,218,0.33000 +'2018-10-06 06:38:16.800',13.30000,221,0.32000 +'2018-10-07 06:38:05.000',13.30000,219,0.33000 +'2018-10-08 06:38:05.000',14.30000,219,0.34000 +'2018-10-09 06:38:05.000',15.30000,219,0.35000 +'2018-10-10 06:38:05.000',16.30000,219,0.31000 +'2018-10-11 06:38:05.000',17.30000,219,0.32000 +'2018-10-12 06:38:05.000',18.30000,219,0.31000 +``` + +那么可以用如下命令导入数据: + +```sql +taos> insert into d1001 file '~/data.csv'; +Query OK, 9 row(s) affected (0.004763s) +``` + +## taosdump 工具导入 + +TDengine 提供了方便的数据库导入导出工具 taosdump。用户可以将 taosdump 从一个系统导出的数据,导入到其他系统中。具体使用方法,请参见:[TDengine 数据备份工具: taosdump](/reference/taosdump)。 diff --git a/docs-cn/13-operation/08-export.md b/docs-cn/13-operation/08-export.md new file mode 100644 index 0000000000..042ecc7ba2 --- /dev/null +++ b/docs-cn/13-operation/08-export.md @@ -0,0 +1,20 @@ +--- +title: 数据导出 +--- + +为方便数据导出,TDengine 提供了两种导出方式,分别是按表导出和用 taosdump 导出。 + +## 按表导出 CSV 文件 + +如果用户需要导出一个表或一个 STable 中的数据,可在 taos shell 中运行: + +```sql +select * from >> data.csv; +``` + +这样,表 tb_name 中的数据就会按照 CSV 格式导出到文件 data.csv 中。 + +## 用 taosdump 导出数据 + +利用 taosdump,用户可以根据需要选择导出所有数据库、一个数据库或者数据库中的一张表,所有数据或一时间段的数据,甚至仅仅表的定义。具体使用方法,请参见: +[TDengine 数据备份工具: taosdump](/reference/taosdump)。 diff --git a/docs-cn/13-operation/09-status.md b/docs-cn/13-operation/09-status.md new file mode 100644 index 0000000000..e7ae78bace --- /dev/null +++ b/docs-cn/13-operation/09-status.md @@ -0,0 +1,53 @@ +--- +title: 系统连接、任务查询管理 +--- + +系统管理员可以从 CLI 查询系统的连接、正在进行的查询、流式计算,并且可以关闭连接、停止正在进行的查询和流式计算。 + +## 显示数据库的连接 + +```sql +SHOW CONNECTIONS; +``` + +其结果中的一列显示 ip:port, 为连接的 IP 地址和端口号。 + +## 强制关闭数据库连接 + +```sql +KILL CONNECTION ; +``` + +其中的 connection-id 是 SHOW CONNECTIONS 中显示的第一列的数字。 + +## 显示数据查询 + +```sql +SHOW QUERIES; +``` + +其中第一列显示的以冒号隔开的两个数字为 query-id,为发起该 query 应用连接的 connection-id 和查询次数。 + +## 强制关闭数据查询 + +```sql +KILL QUERY ; +``` + +其中 query-id 是 SHOW QUERIES 中显示的 connection-id:query-no 字串,如“105:2”,拷贝粘贴即可。 + +## 显示连续查询 + +```sql +SHOW STREAMS; +``` + +其中第一列显示的以冒号隔开的两个数字为 stream-id, 为启动该 stream 应用连接的 connection-id 和发起 stream 的次数。 + +## 强制关闭连续查询 + +```sql +KILL STREAM ; +``` + +其中的 stream-id 是 SHOW STREAMS 中显示的 connection-id:stream-no 字串,如 103:2,拷贝粘贴即可。 diff --git a/docs-cn/13-operation/10-monitor.md b/docs-cn/13-operation/10-monitor.md new file mode 100644 index 0000000000..e30be775fb --- /dev/null +++ b/docs-cn/13-operation/10-monitor.md @@ -0,0 +1,54 @@ +--- +title: 系统监控 +--- + +TDengine 启动后,会自动创建一个监测数据库 log,并自动将服务器的 CPU、内存、硬盘空间、带宽、请求数、磁盘读写速度、慢查询等信息定时写入该数据库。TDengine 还将重要的系统操作(比如登录、创建、删除数据库等)日志以及各种错误报警信息记录下来存放在 log 库里。系统管理员可以从 CLI 直接查看这个数据库,也可以在 WEB 通过图形化界面查看这些监测信息。 + +这些监测信息的采集缺省是打开的,但可以修改配置文件里的选项 monitor 将其关闭或打开。 + +## TDinsight - 使用监控数据库 + Grafana 对 TDengine 进行监控的解决方案 + +从 2.3.3.0 开始,监控数据库将提供更多的监控项,您可以从 [TDinsight Grafana Dashboard](https://grafana.com/grafana/dashboards/15167) 了解如何使用 TDinsight 方案对 TDengine 进行监控。 + +我们提供了一个自动化脚本 `TDinsight.sh` 对 TDinsight 进行部署。 + +下载 `TDinsight.sh`: + +```bash +wget https://github.com/taosdata/grafanaplugin/raw/master/dashboards/TDinsight.sh +chmod +x TDinsight.sh +``` + +准备: + +1. TDengine Server 信息: + + - TDengine RESTful 服务:对本地而言,可以是 `http://localhost:6041`,使用参数 `-a`。 + - TDengine 用户名和密码,使用 `-u` `-p` 参数设置。 + +2. Grafana 告警通知 + + - 使用已经存在的 Grafana Notification Channel `uid`,参数 `-E`。该参数可以使用 `curl -u admin:admin localhost:3000/api/alert-notifications |jq` 来获取。 + + ```bash + sudo ./TDinsight.sh -a http://localhost:6041 -u root -p taosdata -E + ``` + + - 使用 TDengine 数据源插件内置的阿里云短信告警通知,使用 `-s` 启用之,并设置如下参数: + + 1. 阿里云短信服务 Key ID,参数 `-I` + 2. 阿里云短信服务 Key Secret,参数 `K` + 3. 阿里云短信服务签名,参数 `-S` + 4. 短信通知模板号,参数 `-C` + 5. 短信通知模板输入参数,JSON 格式,参数 `-T`,如 `{"alarm_level":"%s","time":"%s","name":"%s","content":"%s"}` + 6. 逗号分隔的通知手机列表,参数 `-B` + + ```bash + sudo ./TDinsight.sh -a http://localhost:6041 -u root -p taosdata -s \ + -I XXXXXXX -K XXXXXXXX -S taosdata -C SMS_1111111 -B 18900000000 \ + -T '{"alarm_level":"%s","time":"%s","name":"%s","content":"%s"}' + ``` + +运行程序并重启 Grafana 服务,打开面板:`http://localhost:3000/d/tdinsight`。 + +更多使用场景和限制请参考[TDinsight](/reference/tdinsight/) 文档。 diff --git a/docs-cn/13-operation/11-optimize.md b/docs-cn/13-operation/11-optimize.md new file mode 100644 index 0000000000..1ca9e8c444 --- /dev/null +++ b/docs-cn/13-operation/11-optimize.md @@ -0,0 +1,100 @@ +--- +title: 性能优化 +--- + +因数据行 [update](/train-faq/faq/#update)、表删除、数据过期等原因,TDengine 的磁盘存储文件有可能出现数据碎片,影响查询操作的性能表现。从 2.1.3.0 版本开始,新增 SQL 指令 COMPACT 来启动碎片重整过程: + +```sql +COMPACT VNODES IN (vg_id1, vg_id2, ...) +``` + +COMPACT 命令对指定的一个或多个 VGroup 启动碎片重整,系统会通过任务队列尽快安排重整操作的具体执行。COMPACT 指令所需的 VGroup id,可以通过 `SHOW VGROUPS;` 指令的输出结果获取;而且在 `SHOW VGROUPS;` 中会有一个 compacting 列,值为 2 时表示对应的 VGroup 处于排队等待进行重整的状态,值为 1 时表示正在进行碎片重整,为 0 时则表示并没有处于重整状态(未要求进行重整或已经完成重整)。 + +需要注意的是,碎片重整操作会大幅消耗磁盘 I/O。因此在重整进行期间,有可能会影响节点的写入和查询性能,甚至在极端情况下导致短时间的阻写。 + +## 存储参数优化 + +不同应用场景的数据往往具有不同的数据特征,比如保留天数、副本数、采集频次、记录大小、采集点的数量、压缩等都可完全不同。为获得在存储上的最高效率,TDengine 提供如下存储相关的系统配置参数(既可以作为 create database 指令的参数,也可以写在 taos.cfg 配置文件中用来设定创建新数据库时所采用的默认值): + +| # | 配置参数名称 | 单位 | 含义 | **取值范围** | **缺省值** | +| --- | ------------ | ---- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- | ---------- | +| 1 | days | 天 | 一个数据文件存储数据的时间跨度 | 1-3650 | 10 | +| 2 | keep | 天 | (可通过 alter database 修改)数据库中数据保留的天数。 | 1-36500 | 3650 | +| 3 | cache | MB | 内存块的大小 | 1-128 | 16 | +| 4 | blocks | | (可通过 alter database 修改)每个 VNODE(TSDB)中有多少个 cache 大小的内存块。因此一个 VNODE 使用的内存大小粗略为(cache \* blocks)。 | 3-10000 | 6 | +| 5 | quorum | | (可通过 alter database 修改)多副本环境下指令执行的确认数要求 | 1-2 | 1 | +| 6 | minRows | | 文件块中记录的最小条数 | 10-1000 | 100 | +| 7 | maxRows | | 文件块中记录的最大条数 | 200-10000 | 4096 | +| 8 | comp | | (可通过 alter database 修改)文件压缩标志位 | 0:关闭,1:一阶段压缩,2:两阶段压缩 | 2 | +| 9 | walLevel | | (作为 database 的参数时名为 wal;在 taos.cfg 中作为参数时需要写作 walLevel)WAL 级别 | 1:写 WAL,但不执行 fsync;2:写 WAL, 而且执行 fsync | 1 | +| 10 | fsync | 毫秒 | 当 wal 设置为 2 时,执行 fsync 的周期。设置为 0,表示每次写入,立即执行 fsync。 | | 3000 | +| 11 | replica | | (可通过 alter database 修改)副本个数 | 1-3 | 1 | +| 12 | precision | | 时间戳精度标识(2.1.2.0 版本之前、2.0.20.7 版本之前在 taos.cfg 文件中不支持此参数。)(从 2.1.5.0 版本开始,新增对纳秒时间精度的支持) | ms 表示毫秒,us 表示微秒,ns 表示纳秒 | ms | +| 13 | update | | 是否允许数据更新(从 2.1.7.0 版本开始此参数支持 0 ~ 2 的取值范围,在此之前取值只能是 [0, 1];而 2.0.8.0 之前的版本在 SQL 指令中不支持此参数。) | 0:不允许;1:允许更新整行;2:允许部分列更新。 | 0 | +| 14 | cacheLast | | (可通过 alter database 修改)是否在内存中缓存子表的最近数据(从 2.1.2.0 版本开始此参数支持 0 ~ 3 的取值范围,在此之前取值只能是 [0, 1];而 2.0.11.0 之前的版本在 SQL 指令中不支持此参数。)(2.1.2.0 版本之前、2.0.20.7 版本之前在 taos.cfg 文件中不支持此参数。) | 0:关闭;1:缓存子表最近一行数据;2:缓存子表每一列的最近的非 NULL 值;3:同时打开缓存最近行和列功能 | 0 | + +对于一个应用场景,可能有多种数据特征的数据并存,最佳的设计是将具有相同数据特征的表放在一个库里,这样一个应用有多个库,而每个库可以配置不同的存储参数,从而保证系统有最优的性能。TDengine 允许应用在创建库时指定上述存储参数,如果指定,该参数就将覆盖对应的系统配置参数。举例,有下述 SQL: + +```sql + CREATE DATABASE demo DAYS 10 CACHE 32 BLOCKS 8 REPLICA 3 UPDATE 1; +``` + +该 SQL 创建了一个库 demo, 每个数据文件存储 10 天数据,内存块为 32 兆字节,每个 VNODE 占用 8 个内存块,副本数为 3,允许更新,而其他参数与系统配置完全一致。 + +一个数据库创建成功后,仅部分参数可以修改并实时生效,其余参数不能修改: + +| **参数名** | **能否修改** | **范围** | **修改语法示例** | +| ----------- | ------------ | ---------------------------------------------------------- | -------------------------------------- | +| name | | | | +| create time | | | | +| ntables | | | | +| vgroups | | | | +| replica | **YES** | 在线 dnode 数目为:
1:1-1;
2:1-2;
\>=3:1-3 | ALTER DATABASE REPLICA _n_ | +| quorum | **YES** | 1-2 | ALTER DATABASE QUORUM _n_ | +| days | | | | +| keep | **YES** | days-365000 | ALTER DATABASE KEEP _n_ | +| cache | | | | +| blocks | **YES** | 3-1000 | ALTER DATABASE BLOCKS _n_ | +| minrows | | | | +| maxrows | | | | +| wal | | | | +| fsync | | | | +| comp | **YES** | 0-2 | ALTER DATABASE COMP _n_ | +| precision | | | | +| status | | | | +| update | | | | +| cachelast | **YES** | 0 \| 1 \| 2 \| 3 | ALTER DATABASE CACHELAST _n_ | + +**说明:**在 2.1.3.0 版本之前,通过 ALTER DATABASE 语句修改这些参数后,需要重启服务器才能生效。 + +TDengine 集群中加入一个新的 dnode 时,涉及集群相关的一些参数必须与已有集群的配置相同,否则不能成功加入到集群中。会进行校验的参数如下: + +- numOfMnodes:系统中管理节点个数。默认值:3。(2.0 版本从 2.0.20.11 开始、2.1 及以上版本从 2.1.6.0 开始,numOfMnodes 默认值改为 1。) +- mnodeEqualVnodeNum: 一个 mnode 等同于 vnode 消耗的个数。默认值:4。 +- offlineThreshold: dnode 离线阈值,超过该时间将导致该 dnode 从集群中删除。单位为秒,默认值:86400\*10(即 10 天)。 +- statusInterval: dnode 向 mnode 报告状态时长。单位为秒,默认值:1。 +- maxTablesPerVnode: 每个 vnode 中能够创建的最大表个数。默认值:1000000。 +- maxVgroupsPerDb: 每个数据库中能够使用的最大 vgroup 个数。 +- arbitrator: 系统中裁决器的 endpoint,缺省为空。 +- timezone、locale、charset 的配置见客户端配置。(2.0.20.0 及以上的版本里,集群中加入新节点已不要求 locale 和 charset 参数取值一致) +- balance:是否启用负载均衡。0:否,1:是。默认值:1。 +- flowctrl:是否启用非阻塞流控。0:否,1:是。默认值:1。 +- slaveQuery:是否启用 slave vnode 参与查询。0:否,1:是。默认值:1。 +- adjustMaster:是否启用 vnode master 负载均衡。0:否,1:是。默认值:1。 + +为方便调试,可通过 SQL 语句临时调整每个 dnode 的日志配置,系统重启后会失效: + +```sql +ALTER DNODE +``` + +- dnode_id: 可以通过 SQL 语句"SHOW DNODES"命令获取 +- config: 要调整的日志参数,在如下列表中取值 + > resetlog 截断旧日志文件,创建一个新日志文件 + > debugFlag < 131 | 135 | 143 > 设置 debugFlag 为 131、135 或者 143 + +例如: + +``` +alter dnode 1 debugFlag 135; +``` diff --git a/docs-cn/13-operation/17-diagnose.md b/docs-cn/13-operation/17-diagnose.md new file mode 100644 index 0000000000..e2a2ef035a --- /dev/null +++ b/docs-cn/13-operation/17-diagnose.md @@ -0,0 +1,131 @@ +--- +title: 诊断及其他 +--- + +## 网络连接诊断 + +当出现客户端应用无法访问服务端时,需要确认客户端与服务端之间网络的各端口连通情况,以便有针对性地排除故障。 + +目前网络连接诊断支持在:Linux 与 Linux,Linux 与 Windows 之间进行诊断测试。 + +诊断步骤: + +1. 如拟诊断的端口范围与服务器 taosd 实例的端口范围相同,须先停掉 taosd 实例 +2. 服务端命令行输入:`taos -n server -P -l ` 以服务端身份启动对端口 port 为基准端口的监听 +3. 客户端命令行输入:`taos -n client -h -P -l ` 以客户端身份启动对指定的服务器、指定的端口发送测试包 + +-l : 测试网络包的大小(单位:字节)。最小值是 11、最大值是 64000,默认值为 1000。 +注:两端命令行中指定的测试包长度必须一致,否则测试显示失败。 + +服务端运行正常的话会输出以下信息: + +```bash +# taos -n server -P 6000 +12/21 14:50:13.522509 0x7f536f455200 UTL work as server, host:172.27.0.7 startPort:6000 endPort:6011 pkgLen:1000 + +12/21 14:50:13.522659 0x7f5352242700 UTL TCP server at port:6000 is listening +12/21 14:50:13.522727 0x7f5351240700 UTL TCP server at port:6001 is listening +... +... +... +12/21 14:50:13.523954 0x7f5342fed700 UTL TCP server at port:6011 is listening +12/21 14:50:13.523989 0x7f53437ee700 UTL UDP server at port:6010 is listening +12/21 14:50:13.524019 0x7f53427ec700 UTL UDP server at port:6011 is listening +12/21 14:50:22.192849 0x7f5352242700 UTL TCP: read:1000 bytes from 172.27.0.8 at 6000 +12/21 14:50:22.192993 0x7f5352242700 UTL TCP: write:1000 bytes to 172.27.0.8 at 6000 +12/21 14:50:22.237082 0x7f5351a41700 UTL UDP: recv:1000 bytes from 172.27.0.8 at 6000 +12/21 14:50:22.237203 0x7f5351a41700 UTL UDP: send:1000 bytes to 172.27.0.8 at 6000 +12/21 14:50:22.237450 0x7f5351240700 UTL TCP: read:1000 bytes from 172.27.0.8 at 6001 +12/21 14:50:22.237576 0x7f5351240700 UTL TCP: write:1000 bytes to 172.27.0.8 at 6001 +12/21 14:50:22.281038 0x7f5350a3f700 UTL UDP: recv:1000 bytes from 172.27.0.8 at 6001 +12/21 14:50:22.281141 0x7f5350a3f700 UTL UDP: send:1000 bytes to 172.27.0.8 at 6001 +... +... +... +12/21 14:50:22.677443 0x7f5342fed700 UTL TCP: read:1000 bytes from 172.27.0.8 at 6011 +12/21 14:50:22.677576 0x7f5342fed700 UTL TCP: write:1000 bytes to 172.27.0.8 at 6011 +12/21 14:50:22.721144 0x7f53427ec700 UTL UDP: recv:1000 bytes from 172.27.0.8 at 6011 +12/21 14:50:22.721261 0x7f53427ec700 UTL UDP: send:1000 bytes to 172.27.0.8 at 6011 +``` + +客户端运行正常会输出以下信息: + +```bash +# taos -n client -h 172.27.0.7 -P 6000 +12/21 14:50:22.192434 0x7fc95d859200 UTL work as client, host:172.27.0.7 startPort:6000 endPort:6011 pkgLen:1000 + +12/21 14:50:22.192472 0x7fc95d859200 UTL server ip:172.27.0.7 is resolved from host:172.27.0.7 +12/21 14:50:22.236869 0x7fc95d859200 UTL successed to test TCP port:6000 +12/21 14:50:22.237215 0x7fc95d859200 UTL successed to test UDP port:6000 +... +... +... +12/21 14:50:22.676891 0x7fc95d859200 UTL successed to test TCP port:6010 +12/21 14:50:22.677240 0x7fc95d859200 UTL successed to test UDP port:6010 +12/21 14:50:22.720893 0x7fc95d859200 UTL successed to test TCP port:6011 +12/21 14:50:22.721274 0x7fc95d859200 UTL successed to test UDP port:6011 +``` + +仔细阅读打印出来的错误信息,可以帮助管理员找到原因,以解决问题。 + +## 启动状态及 RPC 诊断 + +`taos -n startup -h ` + +判断 taosd 服务端是否成功启动,是数据库管理员经常遇到的一种情形。特别当若干台服务器组成集群时,判断每个服务端实例是否成功启动就会是一个重要问题。除检索 taosd 服务端日志文件进行问题定位、分析外,还可以通过 `taos -n startup -h ` 来诊断一个 taosd 进程的启动状态。 + +针对多台服务器组成的集群,当服务启动过程耗时较长时,可通过该命令行来诊断每台服务器的 taosd 实例的启动状态,以准确定位问题。 + +`taos -n rpc -h ` + +该命令用来诊断已经启动的 taosd 实例的端口是否可正常访问。如果 taosd 程序异常或者失去响应,可以通过 `taos -n rpc -h ` 来发起一个与指定 fqdn 的 rpc 通信,看看 taosd 是否能收到,以此来判定是网络问题还是 taosd 程序异常问题。 + +## sync 及 arbitrator 诊断 + +``` +taos -n sync -P 6040 -h +taos -n sync -P 6042 -h +``` + +用来诊断 sync 端口是否工作正常,判断服务端 sync 模块是否成功工作。另外,-P 6042 用来诊断 arbitrator 是否配置正常,判断指定服务器的 arbitrator 是否能正常工作。 + +## 网络速度诊断 + +`taos -n speed -h -P 6030 -N 10 -l 10000000 -S TCP` + +从 2.2.0.0 版本开始,taos 工具新提供了一个网络速度诊断的模式,可以对一个正在运行中的 taosd 实例或者 `taos -n server` 方式模拟的一个服务端实例,以非压缩传输的方式进行网络测速。这个模式下可供调整的参数如下: + +-n:设为“speed”时,表示对网络速度进行诊断。 +-h:所要连接的服务端的 FQDN 或 ip 地址。如果不设置这一项,会使用本机 taos.cfg 文件中 FQDN 参数的设置作为默认值。 +-P:所连接服务端的网络端口。默认值为 6030。 +-N:诊断过程中使用的网络包总数。最小值是 1、最大值是 10000,默认值为 100。 +-l:单个网络包的大小(单位:字节)。最小值是 1024、最大值是 1024 `*` 1024 `*` 1024,默认值为 1024。 +-S:网络封包的类型。可以是 TCP 或 UDP,默认值为 TCP。 + +## FQDN 解析速度诊断 + +`taos -n fqdn -h ` + +从 2.2.0.0 版本开始,taos 工具新提供了一个 FQDN 解析速度的诊断模式,可以对一个目标 FQDN 地址尝试解析,并记录解析过程中所消耗的时间。这个模式下可供调整的参数如下: + +-n:设为“fqdn”时,表示对 FQDN 解析进行诊断。 +-h:所要解析的目标 FQDN 地址。如果不设置这一项,会使用本机 taos.cfg 文件中 FQDN 参数的设置作为默认值。 + +## 服务端日志 + +taosd 服务端日志文件标志位 debugflag 默认为 131,在 debug 时往往需要将其提升到 135 或 143 。 + +一旦设定为 135 或 143,日志文件增长很快,特别是写入、查询请求量较大时,增长速度惊人。如合并保存日志,很容易把日志内的关键信息(如配置信息、错误信息等)冲掉。为此,服务端将重要信息日志与其他日志分开存放: + +- taosinfo 存放重要信息日志, 包括:INFO/ERROR/WARNING 级别的日志信息。不记录 DEBUG、TRACE 级别的日志。 +- taosdlog 服务器端生成的日志,记录 taosinfo 中全部信息外,还根据设置的日志输出级别,记录 DEBUG(日志级别 135)、TRACE(日志级别是 143)。 + +## 客户端日志 + +每个独立运行的客户端(一个进程)生成一个独立的客户端日志,其命名方式采用 taoslog+<序号> 的方式命名。文件标志位 debugflag 默认为 131,在 debug 时往往需要将其提升到 135 或 143 。 + +- taoslog 客户端(driver)生成的日志,默认记录客户端 INFO/ERROR/WARNING 级别日志,还根据设置的日志输出级别,记录 DEBUG(日志级别 135)、TRACE(日志级别是 143)。 + +其中,日志文件最大长度由 numOfLogLines 来进行配置,一个 taosd 实例最多保留两个文件。 + +taosd 服务端日志采用异步落盘写入机制,优点是可以避免硬盘写入压力太大,对性能造成很大影响。缺点是,在极端情况下,存在少量日志行数丢失的可能。 diff --git a/docs-cn/13-operation/_category_.yml b/docs-cn/13-operation/_category_.yml new file mode 100644 index 0000000000..315839970c --- /dev/null +++ b/docs-cn/13-operation/_category_.yml @@ -0,0 +1 @@ +label: 运维指南 diff --git a/docs-cn/13-operation/index.md b/docs-cn/13-operation/index.md new file mode 100644 index 0000000000..bc06fbdc13 --- /dev/null +++ b/docs-cn/13-operation/index.md @@ -0,0 +1,12 @@ +--- +title: 运维指南 +--- + +本章节主要为系统管理员写的,覆盖安装、下载、数据导入、导出、运行系统的监测、用户管理、连接管理等内容,同时介绍根据业务量,如何做容量规划,系统运行一段时间后,如何做系统优化。 + +```mdx-code-block +import DocCardList from '@theme/DocCardList'; +import {useCurrentSidebarCategory} from '@docusaurus/theme-common'; + + +``` diff --git a/docs-cn/14-reference/02-rest-api/02-rest-api.mdx b/docs-cn/14-reference/02-rest-api/02-rest-api.mdx new file mode 100644 index 0000000000..c7680ab3e9 --- /dev/null +++ b/docs-cn/14-reference/02-rest-api/02-rest-api.mdx @@ -0,0 +1,307 @@ +--- +title: REST API +--- + +为支持各种不同类型平台的开发,TDengine 提供符合 REST 设计标准的 API,即 REST API。为最大程度降低学习成本,不同于其他数据库 REST API 的设计方法,TDengine 直接通过 HTTP POST 请求 BODY 中包含的 SQL 语句来操作数据库,仅需要一个 URL。REST 连接器的使用参见[视频教程](https://www.taosdata.com/blog/2020/11/11/1965.html)。 + +:::note +与原生连接器的一个区别是,RESTful 接口是无状态的,因此 `USE db_name` 指令没有效果,所有对表名、超级表名的引用都需要指定数据库名前缀。从 2.2.0.0 版本开始,支持在 RESTful URL 中指定 db_name,这时如果 SQL 语句中没有指定数据库名前缀的话,会使用 URL 中指定的这个 db_name。从 2.4.0.0 版本开始,RESTful 默认由 taosAdapter 提供,要求必须在 URL 中指定 db_name。 +::: + +## 安装 + +RESTful 接口不依赖于任何 TDengine 的库,因此客户端不需要安装任何 TDengine 的库,只要客户端的开发语言支持 HTTP 协议即可。 + +## 验证 + +在已经安装 TDengine 服务器端的情况下,可以按照如下方式进行验证。 + +下面以 Ubuntu 环境中使用 curl 工具(确认已经安装)来验证 RESTful 接口的正常。 + +下面示例是列出所有的数据库,请把 h1.taosdata.com 和 6041(缺省值)替换为实际运行的 TDengine 服务 FQDN 和端口号: + +```html +curl -H 'Authorization: Basic cm9vdDp0YW9zZGF0YQ==' -d 'show databases;' h1.taosdata.com:6041/rest/sql +``` + +返回值结果如下表示验证通过: + +```json +{ + "status": "succ", + "head": [ + "name", + "created_time", + "ntables", + "vgroups", + "replica", + "quorum", + "days", + "keep1,keep2,keep(D)", + "cache(MB)", + "blocks", + "minrows", + "maxrows", + "wallevel", + "fsync", + "comp", + "precision", + "status" + ], + "data": [ + [ + "log", + "2020-09-02 17:23:00.039", + 4, + 1, + 1, + 1, + 10, + "30,30,30", + 1, + 3, + 100, + 4096, + 1, + 3000, + 2, + "us", + "ready" + ] + ], + "rows": 1 +} +``` + +## HTTP 请求格式 + +``` +http://:/rest/sql/[db_name] +``` + +参数说明: + +- fqnd: 集群中的任一台主机 FQDN 或 IP 地址 +- port: 配置文件中 httpPort 配置项,缺省为 6041 +- db_name: 可选参数,指定本次所执行的 SQL 语句的默认数据库库名。(从 2.2.0.0 版本开始支持) + +例如:`http://h1.taos.com:6041/rest/sql/test` 是指向地址为 `h1.taos.com:6041` 的 URL,并将默认使用的数据库库名设置为 `test`。 + +HTTP 请求的 Header 里需带有身份认证信息,TDengine 支持 Basic 认证与自定义认证两种机制,后续版本将提供标准安全的数字签名机制来做身份验证。 + +- 自定义身份认证信息如下所示(token 稍后介绍) + + ``` + Authorization: Taosd + ``` + +- Basic 身份认证信息如下所示 + + ``` + Authorization: Basic + ``` + +HTTP 请求的 BODY 里就是一个完整的 SQL 语句,SQL 语句中的数据表应提供数据库前缀,例如 db_name.tb_name。如果表名不带数据库前缀,又没有在 URL 中指定数据库名的话,系统会返回错误。因为 HTTP 模块只是一个简单的转发,没有当前 DB 的概念。 + +使用 `curl` 通过自定义身份认证方式来发起一个 HTTP Request,语法如下: + +```bash +curl -H 'Authorization: Basic ' -d '' :/rest/sql/[db_name] +``` + +或者 + +```bash +curl -u username:password -d '' :/rest/sql/[db_name] +``` + +其中,`TOKEN` 为 `{username}:{password}` 经过 Base64 编码之后的字符串,例如 `root:taosdata` 编码后为 `cm9vdDp0YW9zZGF0YQ==` + +## HTTP 返回格式 + +返回值为 JSON 格式,如下: + +```json +{ + "status": "succ", + "head": ["ts","current", …], + "column_meta": [["ts",9,8],["current",6,4], …], + "data": [ + ["2018-10-03 14:38:05.000", 10.3, …], + ["2018-10-03 14:38:15.000", 12.6, …] + ], + "rows": 2 +} +``` + +说明: + +- status: 告知操作结果是成功还是失败。 +- head: 表的定义,如果不返回结果集,则仅有一列 “affected_rows”。(从 2.0.17.0 版本开始,建议不要依赖 head 返回值来判断数据列类型,而推荐使用 column_meta。在后续版本中,有可能会从返回值中去掉 head 这一项。) +- column_meta: 从 2.0.17.0 版本开始,返回值中增加这一项来说明 data 里每一列的数据类型。具体每个列会用三个值来说明,分别为:列名、列类型、类型长度。例如`["current",6,4]`表示列名为“current”;列类型为 6,也即 float 类型;类型长度为 4,也即对应 4 个字节表示的 float。如果列类型为 binary 或 nchar,则类型长度表示该列最多可以保存的内容长度,而不是本次返回值中的具体数据长度。当列类型是 nchar 的时候,其类型长度表示可以保存的 unicode 字符数量,而不是 bytes。 +- data: 具体返回的数据,一行一行的呈现,如果不返回结果集,那么就仅有 [[affected_rows]]。data 中每一行的数据列顺序,与 column_meta 中描述数据列的顺序完全一致。 +- rows: 表明总共多少行数据。 + +column_meta 中的列类型说明: + +- 1:BOOL +- 2:TINYINT +- 3:SMALLINT +- 4:INT +- 5:BIGINT +- 6:FLOAT +- 7:DOUBLE +- 8:BINARY +- 9:TIMESTAMP +- 10:NCHAR + +## 自定义授权码 + +HTTP 请求中需要带有授权码 ``,用于身份识别。授权码通常由管理员提供,可简单的通过发送 `HTTP GET` 请求来获取授权码,操作如下: + +```bash +curl http://:/rest/login// +``` + +其中,`fqdn` 是 TDengine 数据库的 FQDN 或 IP 地址,`port` 是 TDengine 服务的端口号,`username` 为数据库用户名,`password` 为数据库密码,返回值为 JSON 格式,各字段含义如下: + +- status:请求结果的标志位 + +- code:返回值代码 + +- desc:授权码 + +获取授权码示例: + +```bash +curl http://192.168.0.1:6041/rest/login/root/taosdata +``` + +返回值: + +```json +{ + "status": "succ", + "code": 0, + "desc": "/KfeAzX/f9na8qdtNZmtONryp201ma04bEl8LcvLUd7a8qdtNZmtONryp201ma04" +} +``` + +## 使用示例 + +- 在 demo 库里查询表 d1001 的所有记录: + + ```bash + curl -H 'Authorization: Basic cm9vdDp0YW9zZGF0YQ==' -d 'select * from demo.d1001' 192.168.0.1:6041/rest/sql + ``` + + 返回值: + + ```json + { + "status": "succ", + "head": ["ts", "current", "voltage", "phase"], + "column_meta": [ + ["ts", 9, 8], + ["current", 6, 4], + ["voltage", 4, 4], + ["phase", 6, 4] + ], + "data": [ + ["2018-10-03 14:38:05.000", 10.3, 219, 0.31], + ["2018-10-03 14:38:15.000", 12.6, 218, 0.33] + ], + "rows": 2 + } + ``` + +- 创建库 demo: + + ```bash + curl -H 'Authorization: Basic cm9vdDp0YW9zZGF0YQ==' -d 'create database demo' 192.168.0.1:6041/rest/sql + ``` + + 返回值: + + ```json + { + "status": "succ", + "head": ["affected_rows"], + "column_meta": [["affected_rows", 4, 4]], + "data": [[1]], + "rows": 1 + } + ``` + +## 其他用法 + +### 结果集采用 Unix 时间戳 + +HTTP 请求 URL 采用 `/rest/sqlt` 时,返回结果集的时间戳将采用 Unix 时间戳格式表示,例如 + +```bash +curl -H 'Authorization: Basic cm9vdDp0YW9zZGF0YQ==' -d 'select * from demo.d1001' 192.168.0.1:6041/rest/sqlt +``` + +返回结果: + +```json +{ + "status": "succ", + "head": ["ts", "current", "voltage", "phase"], + "column_meta": [ + ["ts", 9, 8], + ["current", 6, 4], + ["voltage", 4, 4], + ["phase", 6, 4] + ], + "data": [ + [1538548685000, 10.3, 219, 0.31], + [1538548695000, 12.6, 218, 0.33] + ], + "rows": 2 +} +``` + +### 结果集采用 UTC 时间字符串 + +HTTP 请求 URL 采用 `/rest/sqlutc` 时,返回结果集的时间戳将采用 UTC 时间字符串表示,例如 + +```bash + curl -H 'Authorization: Basic cm9vdDp0YW9zZGF0YQ==' -d 'select * from demo.t1' 192.168.0.1:6041/rest/sqlutc +``` + +返回值: + +```json +{ + "status": "succ", + "head": ["ts", "current", "voltage", "phase"], + "column_meta": [ + ["ts", 9, 8], + ["current", 6, 4], + ["voltage", 4, 4], + ["phase", 6, 4] + ], + "data": [ + ["2018-10-03T14:38:05.000+0800", 10.3, 219, 0.31], + ["2018-10-03T14:38:15.000+0800", 12.6, 218, 0.33] + ], + "rows": 2 +} +``` + +## 重要配置项 + +下面仅列出一些与 RESTful 接口有关的配置参数,其他系统参数请看配置文件里的说明。 + +- 对外提供 RESTful 服务的端口号,默认绑定到 6041(实际取值是 serverPort + 11,因此可以通过修改 serverPort 参数的设置来修改)。 +- httpMaxThreads: 启动的线程数量,默认为 2(2.0.17.0 版本开始,默认值改为 CPU 核数的一半向下取整)。 +- restfulRowLimit: 返回结果集(JSON 格式)的最大条数,默认值为 10240。 +- httpEnableCompress: 是否支持压缩,默认不支持,目前 TDengine 仅支持 gzip 压缩格式。 +- httpDebugFlag: 日志开关,默认 131。131:仅错误和报警信息,135:调试信息,143:非常详细的调试信息。 +- httpDbNameMandatory: 是否必须在 RESTful URL 中指定默认的数据库名。默认为 0,即关闭此检查。如果设置为 1,那么每个 RESTful URL 中都必须设置一个默认数据库名,否则无论此时执行的 SQL 语句是否需要指定数据库,都会返回一个执行错误,拒绝执行此 SQL 语句。 + +:::note +如果使用 taosd 提供的 REST API, 那么以上配置需要写在 taosd 的配置文件 taos.cfg 中。如果使用 taosAdapter 提供的 REST API, 那么需要参考 taosAdapter [对应的配置方法](/reference/taosadapter/)。 +::: diff --git a/docs-cn/14-reference/02-rest-api/_category_.yml b/docs-cn/14-reference/02-rest-api/_category_.yml new file mode 100644 index 0000000000..57a20d8458 --- /dev/null +++ b/docs-cn/14-reference/02-rest-api/_category_.yml @@ -0,0 +1 @@ +label: REST API diff --git a/docs-cn/14-reference/03-connector/03-connector.mdx b/docs-cn/14-reference/03-connector/03-connector.mdx new file mode 100644 index 0000000000..c0e714f148 --- /dev/null +++ b/docs-cn/14-reference/03-connector/03-connector.mdx @@ -0,0 +1,117 @@ +--- +title: 连接器 +--- + +TDengine 提供了丰富的应用程序开发接口,为了便于用户快速开发自己的应用,TDengine 支持了多种编程语言的连接器,其中官方连接器包括支持 C/C++、Java、Python、Go、Node.js、C# 和 Rust 的连接器。这些连接器支持使用原生接口(taosc)和 REST 接口(部分语言暂不支持)连接 TDengine 集群。社区开发者也贡献了多个非官方连接器,例如 ADO.NET 连接器、Lua 连接器和 PHP 连接器。 + +![image-connector](/img/connector.png) + +## 支持的平台 + +目前 TDengine 的原生接口连接器可支持的平台包括:X64/X86/ARM64/ARM32/MIPS/Alpha 等硬件平台,以及 Linux/Win64/Win32 等开发环境。对照矩阵如下: + +| **CPU** | **OS** | **JDBC** | **Python** | **Go** | **Node.js** | **C#** | **Rust** | C/C++ | +| -------------- | --------- | -------- | ---------- | ------ | ----------- | ------ | -------- | ----- | +| **X86 64bit** | **Linux** | ● | ● | ● | ● | ● | ● | ● | +| **X86 64bit** | **Win64** | ● | ● | ● | ● | ● | ● | ● | +| **X86 64bit** | **Win32** | ● | ● | ● | ● | ○ | ○ | ● | +| **X86 32bit** | **Win32** | ○ | ○ | ○ | ○ | ○ | ○ | ● | +| **ARM64** | **Linux** | ● | ● | ● | ● | ○ | ○ | ● | +| **ARM32** | **Linux** | ● | ● | ● | ● | ○ | ○ | ● | +| **MIPS 龙芯** | **Linux** | ○ | ○ | ○ | ○ | ○ | ○ | ○ | +| **Alpha 申威** | **Linux** | ○ | ○ | -- | -- | -- | -- | ○ | +| **X86 海光** | **Linux** | ○ | ○ | ○ | -- | -- | -- | ○ | + +其中 ● 表示官方测试验证通过,○ 表示非官方测试验证通过,-- 表示未经验证。 + +使用 REST 连接由于不依赖客户端驱动可以支持更广泛的操作系统。 + +## 版本支持 + +TDengine 版本更新往往会增加新的功能特性,列表中的连接器版本为连接器最佳适配版本。 + +| **TDengine 版本** | **Java** | **Python** | **Go** | **C#** | **Node.js** | **Rust** | +| --------------------- | -------- | ---------- | ------------ | ------------- | --------------- | -------- | +| **2.4.0.14 及以上** | 2.0.38 | 当前版本 | develop 分支 | 1.0.2 - 1.0.6 | 2.0.10 - 2.0.12 | 当前版本 | +| **2.4.0.6 及以上** | 2.0.37 | 当前版本 | develop 分支 | 1.0.2 - 1.0.6 | 2.0.10 - 2.0.12 | 当前版本 | +| **2.4.0.4 - 2.4.0.5** | 2.0.37 | 当前版本 | develop 分支 | 1.0.2 - 1.0.6 | 2.0.10 - 2.0.12 | 当前版本 | +| **2.2.x.x ** | 2.0.36 | 当前版本 | master 分支 | n/a | 2.0.7 - 2.0.9 | 当前版本 | +| **2.0.x.x ** | 2.0.34 | 当前版本 | master 分支 | n/a | 2.0.1 - 2.0.6 | 当前版本 | + +## 功能特性 + +连接器对 TDengine 功能特性的支持对照如下: + +### 使用原生接口(taosc) + +| **功能特性** | **Java** | **Python** | **Go** | **C#** | **Node.js** | **Rust** | +| -------------- | -------- | ---------- | ------ | ------ | ----------- | -------- | +| **连接管理** | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| **普通查询** | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| **连续查询** | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| **参数绑定** | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| **订阅功能** | 支持 | 支持 | 支持 | 支持 | 支持 | 暂不支持 | +| **Schemaless** | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | +| **DataFrame** | 不支持 | 支持 | 不支持 | 不支持 | 不支持 | 不支持 | + +:::info +由于不同编程语言数据库框架规范不同,并不意味着所有 C/C++ 接口都需要对应封装支持。 +::: + +### 使用 REST 接口 + +| **功能特性** | **Java** | **Python** | **Go** | **C#(暂不支持)** | **Node.js** | **Rust** | +| ------------------------------ | -------- | ---------- | -------- | ------------------ | ----------- | -------- | +| **连接管理** | 支持 | 支持 | 支持 | N/A | 支持 | 支持 | +| **普通查询** | 支持 | 支持 | 支持 | N/A | 支持 | 支持 | +| **连续查询** | 支持 | 支持 | 支持 | N/A | 支持 | 支持 | +| **参数绑定** | 不支持 | 不支持 | 不支持 | N/A | 不支持 | 不支持 | +| **订阅功能** | 不支持 | 不支持 | 不支持 | N/A | 不支持 | 不支持 | +| **Schemaless** | 暂不支持 | 暂不支持 | 暂不支持 | N/A | 暂不支持 | 暂不支持 | +| **批量拉取(基于 WebSocket)** | 支持 | 暂不支持 | 暂不支持 | N/A | 暂不支持 | 暂不支持 | +| **DataFrame** | 不支持 | 支持 | 不支持 | N/A | 不支持 | 不支持 | + +:::warning + +- 无论选用何种编程语言的连接器,2.0 及以上版本的 TDengine 推荐数据库应用的每个线程都建立一个独立的连接,或基于线程建立连接池,以避免连接内的“USE statement”状态量在线程之间相互干扰(但连接的查询和写入操作都是线程安全的)。 + +::: + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; +import InstallOnWindows from "./_linux_install.mdx"; +import InstallOnLinux from "./_windows_install.mdx"; +import VerifyWindows from "./_verify_windows.mdx"; +import VerifyLinux from "./_verify_linux.mdx"; + +## 安装客户端驱动 + +:::info +只有在没有安装 TDengine 服务端软件的系统上使用原生接口连接器才需要安装客户端驱动。 + +::: + +### 安装步骤 + + + + + + + + + + +### 安装验证 + +以上安装和配置完成后,并确认 TDengine 服务已经正常启动运行,此时可以执行 TDengine CLI 工具进行登录。 + + + + + + + + + + diff --git a/docs-cn/14-reference/03-connector/_category_.yml b/docs-cn/14-reference/03-connector/_category_.yml new file mode 100644 index 0000000000..abd3f666f3 --- /dev/null +++ b/docs-cn/14-reference/03-connector/_category_.yml @@ -0,0 +1 @@ +label: "连接器" \ No newline at end of file diff --git a/docs-cn/14-reference/03-connector/_linux_install.mdx b/docs-cn/14-reference/03-connector/_linux_install.mdx new file mode 100644 index 0000000000..eb7f683288 --- /dev/null +++ b/docs-cn/14-reference/03-connector/_linux_install.mdx @@ -0,0 +1,30 @@ +import PkgList from "/components/PkgList"; + +1. 下载客户端安装包 + + + + [所有下载](https://www.taosdata.com/cn/all-downloads/) + +2. 解压缩软件包 + + 将软件包放置在当前用户可读写的任意目录下,然后执行下面的命令:`tar -xzvf TDengine-client-VERSION.tar.gz` + 其中 VERSION 需要替换为实际版本的字符串。 +3. 执行安装脚本 + + 解压软件包之后,会在解压目录下看到以下文件(目录): + - _ install_client.sh_:安装脚本,用于应用驱动程序 + - _ taos.tar.gz_:应用驱动安装包 + - _ driver_:TDengine 应用驱动 driver + - _examples_: 各种编程语言的示例程序(c/C#/go/JDBC/MATLAB/python/R) + 运行 install_client.sh 进行安装。 +4. 配置 taos.cfg + + 编辑 `taos.cfg` 文件(默认路径/etc/taos/taos.cfg),将 `firstEP` 修改为 TDengine 服务器的 End Point,例如:`h1.tdengine.com:6030` + +:::tip + +1. 如本机没有部署 TDengine 服务,仅安装了应用驱动,则 `taos.cfg` 中仅需配置 `firstEP`,无需在本机配置 `FQDN`。 +2. 为防止与服务器端连接时出现“Unable to resolve FQDN”错误,建议确认本机的 `/etc/hosts` 文件已经配置了服务器正确的 FQDN 值,或配置好了 DNS 服务。 + +::: diff --git a/docs-cn/14-reference/03-connector/_preparition.mdx b/docs-cn/14-reference/03-connector/_preparition.mdx new file mode 100644 index 0000000000..87538ebfd8 --- /dev/null +++ b/docs-cn/14-reference/03-connector/_preparition.mdx @@ -0,0 +1,10 @@ +- 已安装客户端驱动(使用原生连接必须安装,使用 REST 连接无需安装) + +:::info + +由于 TDengine 的客户端驱动使用 C 语言编写,使用原生连接时需要加载系统对应安装在本地的客户端驱动共享库文件,通常包含在 TDengine 安装包。TDengine Linux 服务端安装包附带了 TDengine 客户端,也可以单独安装 [Linux 客户端](/get-started/) 。在 Windows 环境开发时需要安装 TDengine 对应的 [Windows 客户端](https://www.taosdata.com/cn/all-downloads/#TDengine-Windows-Client) 。 + +- libtaos.so: 在 Linux 系统中成功安装 TDengine 后,依赖的 Linux 版客户端驱动 libtaos.so 文件会被自动拷贝至 /usr/lib/libtaos.so,该目录包含在 Linux 自动扫描路径上,无需单独指定。 +- taos.dll: 在 Windows 系统中安装完客户端之后,依赖的 Windows 版客户端驱动 taos.dll 文件会自动拷贝到系统默认搜索路径 C:/Windows/System32 下,同样无需要单独指定。 + +::: diff --git a/docs-cn/14-reference/03-connector/_verify_linux.mdx b/docs-cn/14-reference/03-connector/_verify_linux.mdx new file mode 100644 index 0000000000..fcb8aae6ae --- /dev/null +++ b/docs-cn/14-reference/03-connector/_verify_linux.mdx @@ -0,0 +1,14 @@ +在 Linux shell 下直接执行 `taos` 连接到 TDengine 服务,进入到 TDengine CLI 界面,示例如下: + +```text +$ taos +Welcome to the TDengine shell from Linux, Client Version:2.0.5.0 +Copyright (c) 2017 by TAOS Data, Inc. All rights reserved. +taos> show databases; +name | created_time | ntables | vgroups | replica | quorum | days | keep1,keep2,keep(D) | cache(MB)| blocks | minrows | maxrows | wallevel | fsync | comp | precision | status | +========================================================================================================================================================================================================================= +test | 2020-10-14 10:35:48.617 | 10 | 1 | 1 | 1 | 2 | 3650,3650,3650 | 16| 6 | 100 | 4096 | 1 | 3000 | 2 | ms | ready | +log | 2020-10-12 09:08:21.651 | 4 | 1 | 1 | 1 | 10 | 30,30,30 | 1| 3 | 100 | 4096 | 1 | 3000 | 2 | us | ready | +Query OK, 2 row(s) in set (0.001198s) +taos> +``` diff --git a/docs-cn/14-reference/03-connector/_verify_windows.mdx b/docs-cn/14-reference/03-connector/_verify_windows.mdx new file mode 100644 index 0000000000..87c9fbd024 --- /dev/null +++ b/docs-cn/14-reference/03-connector/_verify_windows.mdx @@ -0,0 +1,14 @@ +在 cmd 下进入到 C:\TDengine 目录下直接执行 `taos.exe`,连接到 TDengine 服务,进入到 TDengine CLI 界面,示例如下: + +```text + C:\TDengine>taos + Welcome to the TDengine shell from Linux, Client Version:2.0.5.0 + Copyright (c) 2017 by TAOS Data, Inc. All rights reserved. + taos> show databases; + name | created_time | ntables | vgroups | replica | quorum | days | keep1,keep2,keep(D) | cache(MB) | blocks | minrows | maxrows | wallevel | fsync | comp | precision | status | + =================================================================================================================================================================================================================================================================== + test | 2020-10-14 10:35:48.617 | 10 | 1 | 1 | 1 | 2 | 3650,3650,3650 | 16 | 6 | 100 | 4096 | 1 | 3000 | 2 | ms | ready | + log | 2020-10-12 09:08:21.651 | 4 | 1 | 1 | 1 | 10 | 30,30,30 | 1 | 3 | 100 | 4096 | 1 | 3000 | 2 | us | ready | + Query OK, 2 row(s) in set (0.045000s) + taos> +``` diff --git a/docs-cn/14-reference/03-connector/_windows_install.mdx b/docs-cn/14-reference/03-connector/_windows_install.mdx new file mode 100644 index 0000000000..755f96b2d7 --- /dev/null +++ b/docs-cn/14-reference/03-connector/_windows_install.mdx @@ -0,0 +1,31 @@ +import PkgList from "/components/PkgList"; + +1. 下载客户端安装包 + + + + [所有下载](https://www.taosdata.com/cn/all-downloads/) + +2. 执行安装程序,按提示选择默认值,完成安装 +3. 安装路径 + + 默认安装路径为:C:\TDengine,其中包括以下文件(目录): + + - _taos.exe_:TDengine CLI 命令行程序 + - _cfg_ : 配置文件目录 + - _driver_: 应用驱动动态链接库 + - _examples_: 示例程序 bash/C/C#/go/JDBC/Python/Node.js + - _include_: 头文件 + - _log_ : 日志文件 + - _unins000.exe_: 卸载程序 + +4. 配置 taos.cfg + + 编辑 taos.cfg 文件(默认路径 C:\TDengine\cfg\taos.cfg),将 firstEP 修改为 TDengine 服务器的 End Point,例如:`h1.tdengine.com:6030`。 + +:::tip + +1. 如利用 FQDN 连接服务器,必须确认本机网络环境 DNS 已配置好,或在 hosts 文件中添加 FQDN 寻址记录, 如编辑 C:\Windows\system32\drivers\etc\hosts,添加类似如下的记录:`192.168.1.99 h1.taos.com` +2. 卸载:运行 unins000.exe 可卸载 TDengine 应用驱动。 + +::: diff --git a/docs-cn/14-reference/03-connector/cpp.mdx b/docs-cn/14-reference/03-connector/cpp.mdx new file mode 100644 index 0000000000..aba1d6c717 --- /dev/null +++ b/docs-cn/14-reference/03-connector/cpp.mdx @@ -0,0 +1,451 @@ +--- +sidebar_position: 1 +sidebar_label: C/C++ +title: C/C++ Connector +--- + +C/C++ 开发人员可以使用 TDengine 的客户端驱动,即 C/C++连接器 (以下都用 TDengine 客户端驱动表示),开发自己的应用来连接 TDengine 集群完成数据存储、查询以及其他功能。TDengine 客户端驱动的 API 类似于 MySQL 的 C API。应用程序使用时,需要包含 TDengine 头文件 _taos.h_,里面列出了提供的 API 的函数原型;应用程序还要链接到所在平台上对应的动态库。 + +```c +#include +``` + +TDengine 服务端或客户端安装后,`taos.h` 位于: + +- Linux:`/usr/local/taos/include` +- Windows:`C:\TDengine\include` + +TDengine 客户端驱动的动态库位于: + +- Linux: `/usr/local/taos/driver/libtaos.so` +- Windows: `C:\TDengine\taos.dll` + +## 支持的平台 + +请参考[支持的平台列表](/reference/connector#支持的平台) + +## 支持的版本 + +TDengine 客户端驱动的版本号与 TDengine 服务端的版本号是一一对应的强对应关系,建议使用与 TDengine 服务端完全相同的客户端驱动。虽然低版本的客户端驱动在前三段版本号一致(即仅第四段版本号不同)的情况下也能够与高版本的服务端相兼容,但这并非推荐用法。强烈不建议使用高版本的客户端驱动访问低版本的服务端。 + +## 安装步骤 + +TDengine 客户端驱动的安装请参考 [安装指南](/reference/connector#安装步骤) + +## 建立连接 + +使用客户端驱动访问 TDengine 集群的基本过程为:建立连接、查询和写入、关闭连接、清除资源。 + +下面为建立连接的示例代码,其中省略了查询和写入部分,展示了如何建立连接、关闭连接以及清除资源。 + +```c + TAOS *taos = taos_connect("localhost:6030", "root", "taosdata", NULL, 0); + if (taos == NULL) { + printf("failed to connect to server, reason:%s\n", "null taos" /*taos_errstr(taos)*/); + exit(1); + } + + /* put your code here for read and write */ + + taos_close(taos); + taos_cleanup(); +``` + +在上面的示例代码中, `taos_connect()` 建立到客户端程序所在主机的 6030 端口的连接,`taos_close()`关闭当前连接,`taos_cleanup()`清除客户端驱动所申请和使用的资源。 + +:::note + +- 如未特别说明,当 API 的返回值是整数时,_0_ 代表成功,其它是代表失败原因的错误码,当返回值是指针时, _NULL_ 表示失败。 +- 所有的错误码以及对应的原因描述在 `taoserror.h` 文件中。 + +::: + +## 示例程序 + +本节展示了使用客户端驱动访问 TDengine 集群的常见访问方式的示例代码。 + +### 同步查询示例 + +
+同步查询 + +```c +{{#include examples/c/demo.c}} +``` + +
+ +### 异步查询示例 + +
+异步查询 + +```c +{{#include examples/c/asyncdemo.c}} +``` + +
+ +### 参数绑定示例 + +
+参数绑定 + +```c +{{#include examples/c/prepare.c}} +``` + +
+ +### 无模式写入示例 + +
+无模式写入 + +```c +{{#include examples/c/schemaless.c}} +``` + +
+ +### 订阅和消费示例 + +
+订阅和消费 + +```c +{{#include examples/c/subscribe.c}} +``` + +
+ +:::info +更多示例代码及下载请见 [GitHub](https://github.com/taosdata/TDengine/tree/develop/examples/c)。 +也可以在安装目录下的 `examples/c` 路径下找到。 该目录下有 makefile,在 Linux 环境下,直接执行 make 就可以编译得到执行文件。 +**提示:**在 ARM 环境下编译时,请将 makefile 中的 `-msse4.2` 去掉,这个选项只有在 x64/x86 硬件平台上才能支持。 + +::: + +## API 参考 + +以下分别介绍 TDengine 客户端驱动的基础 API、同步 API、异步 API、订阅 API 和无模式写入 API。 + +### 基础 API + +基础 API 用于完成创建数据库连接等工作,为其它 API 的执行提供运行时环境。 + +- `void taos_init()` + + 初始化运行环境。如果没有主动调用该 API,那么调用 `taos_connect()` 时驱动将自动调用该 API,故程序一般无需手动调用。 + +- `void taos_cleanup()` + + 清理运行环境,应用退出前应调用。 + +- `int taos_options(TSDB_OPTION option, const void * arg, ...)` + + 设置客户端选项,目前支持区域设置(`TSDB_OPTION_LOCALE`)、字符集设置(`TSDB_OPTION_CHARSET`)、时区设置(`TSDB_OPTION_TIMEZONE`)、配置文件路径设置(`TSDB_OPTION_CONFIGDIR`)。区域设置、字符集、时区默认为操作系统当前设置。 + +- `char *taos_get_client_info()` + + 获取客户端版本信息。 + +- `TAOS *taos_connect(const char *host, const char *user, const char *pass, const char *db, int port)` + + 创建数据库连接,初始化连接上下文。其中需要用户提供的参数包含: + + - host:TDengine 集群中任一节点的 FQDN + - user:用户名 + - pass:密码 + - db: 数据库名字,如果用户没有提供,也可以正常连接,用户可以通过该连接创建新的数据库,如果用户提供了数据库名字,则说明该数据库用户已经创建好,缺省使用该数据库 + - port:taosd 程序监听的端口 + + 返回值为空表示失败。应用程序需要保存返回的参数,以便后续使用。 + + :::info + 同一进程可以根据不同的 host/port 连接多个 TDengine 集群 + + ::: + +- `char *taos_get_server_info(TAOS *taos)` + + 获取服务端版本信息。 + +- `int taos_select_db(TAOS *taos, const char *db)` + + 将当前的缺省数据库设置为 `db`。 + +- `void taos_close(TAOS *taos)` + + 关闭连接,其中`taos`是 `taos_connect()` 返回的句柄。 + +### 同步查询 API + +本小节介绍 API 均属于同步接口。应用调用后,会阻塞等待响应,直到获得返回结果或错误信息。 + +- `TAOS_RES* taos_query(TAOS *taos, const char *sql)` + + 执行 SQL 语句,可以是 DQL、DML 或 DDL 语句。 其中的 `taos` 参数是通过 `taos_connect()` 获得的句柄。不能通过返回值是否是 `NULL` 来判断执行结果是否失败,而是需要用 `taos_errno()` 函数解析结果集中的错误代码来进行判断。 + +- `int taos_result_precision(TAOS_RES *res)` + + 返回结果集时间戳字段的精度,`0` 代表毫秒,`1` 代表微秒,`2` 代表纳秒。 + +- `TAOS_ROW taos_fetch_row(TAOS_RES *res)` + + 按行获取查询结果集中的数据。 + +- `int taos_fetch_block(TAOS_RES *res, TAOS_ROW *rows)` + + 批量获取查询结果集中的数据,返回值为获取到的数据的行数。 + +- `int taos_num_fields(TAOS_RES *res)` 和 `int taos_field_count(TAOS_RES *res)` + + 这两个 API 等价,用于获取查询结果集中的列数。 + +- `int* taos_fetch_lengths(TAOS_RES *res)` + + 获取结果集中每个字段的长度。返回值是一个数组,其长度为结果集的列数。 + +- `int taos_affected_rows(TAOS_RES *res)` + + 获取被所执行的 SQL 语句影响的行数。 + +- `TAOS_FIELD *taos_fetch_fields(TAOS_RES *res)` + + 获取查询结果集每列数据的属性(列的名称、列的数据类型、列的长度),与 `taos_num_fileds()` 配合使用,可用来解析 `taos_fetch_row()` 返回的一个元组(一行)的数据。 `TAOS_FIELD` 的结构如下: + +```c +typedef struct taosField { + char name[65]; // column name + uint8_t type; // data type + int16_t bytes; // length, in bytes +} TAOS_FIELD; +``` + +- `void taos_stop_query(TAOS_RES *res)` + + 停止当前查询的执行。 + +- `void taos_free_result(TAOS_RES *res)` + + 释放查询结果集以及相关的资源。查询完成后,务必调用该 API 释放资源,否则可能导致应用内存泄露。但也需注意,释放资源后,如果再调用 `taos_consume()` 等获取查询结果的函数,将导致应用崩溃。 + +- `char *taos_errstr(TAOS_RES *res)` + + 获取最近一次 API 调用失败的原因,返回值为字符串标识的错误提示信息。 + +- `int taos_errno(TAOS_RES *res)` + + 获取最近一次 API 调用失败的原因,返回值为错误代码。 + +:::note +2.0 及以上版本 TDengine 推荐数据库应用的每个线程都建立一个独立的连接,或基于线程建立连接池。而不推荐在应用中将该连接 (TAOS\*) 结构体传递到不同的线程共享使用。基于 TAOS 结构体发出的查询、写入等操作具有多线程安全性,但 “USE statement” 等状态量有可能在线程之间相互干扰。此外,C 语言的连接器可以按照需求动态建立面向数据库的新连接(该过程对用户不可见),同时建议只有在程序最后退出的时候才调用 `taos_close()` 关闭连接。 + +::: + +### 异步查询 API + +TDengine 还提供性能更高的异步 API 处理数据插入、查询操作。在软硬件环境相同的情况下,异步 API 处理数据插入的速度比同步 API 快 2 ~ 4 倍。异步 API 采用非阻塞式的调用方式,在系统真正完成某个具体数据库操作前,立即返回。调用的线程可以去处理其他工作,从而可以提升整个应用的性能。异步 API 在网络延迟严重的情况下,优势尤为突出。 + +异步 API 都需要应用提供相应的回调函数,回调函数参数设置如下:前两个参数都是一致的,第三个参数依不同的 API 而定。第一个参数 param 是应用调用异步 API 时提供给系统的,用于回调时,应用能够找回具体操作的上下文,依具体实现而定。第二个参数是 SQL 操作的结果集,如果为空,比如 insert 操作,表示没有记录返回,如果不为空,比如 select 操作,表示有记录返回。 + +异步 API 对于使用者的要求相对较高,用户可根据具体应用场景选择性使用。下面是两个重要的异步 API: + +- `void taos_query_a(TAOS *taos, const char *sql, void (*fp)(void *param, TAOS_RES *, int code), void *param);` + + 异步执行 SQL 语句。 + + - taos:调用 `taos_connect()` 返回的数据库连接 + - sql:需要执行的 SQL 语句 + - fp:用户定义的回调函数,其第三个参数 `code` 用于指示操作是否成功,`0` 表示成功,负数表示失败(调用 `taos_errstr()` 可获取失败原因)。应用在定义回调函数的时候,主要处理第二个参数 `TAOS_RES *`,该参数是查询返回的结果集 + - param:应用提供一个用于回调的参数 + +- `void taos_fetch_rows_a(TAOS_RES *res, void (*fp)(void *param, TAOS_RES *, int numOfRows), void *param);` + + 批量获取异步查询的结果集,只能与 `taos_query_a()` 配合使用。其中: + + - res:`taos_query_a()` 回调时返回的结果集 + - fp:回调函数。其参数 `param` 是用户可定义的传递给回调函数的参数结构体;`numOfRows` 是获取到的数据的行数(不是整个查询结果集的函数)。 在回调函数中,应用可以通过调用 `taos_fetch_row()` 前向迭代获取批量记录中每一行记录。读完一块内的所有记录后,应用需要在回调函数中继续调用 `taos_fetch_rows_a()` 获取下一批记录进行处理,直到返回的记录数 `numOfRows` 为零(结果返回完成)或记录数为负值(查询出错)。 + +TDengine 的异步 API 均采用非阻塞调用模式。应用程序可以用多线程同时打开多张表,并可以同时对每张打开的表进行查询或者插入操作。需要指出的是,**客户端应用必须确保对同一张表的操作完全串行化**,即对同一个表的插入或查询操作未完成时(未返回时),不能够执行第二个插入或查询操作。 + +### 参数绑定 API + +除了直接调用 `taos_query()` 进行查询,TDengine 也提供了支持参数绑定的 Prepare API,风格与 MySQL 类似,目前也仅支持用问号 `?` 来代表待绑定的参数。 + +从 2.1.1.0 和 2.1.2.0 版本开始,TDengine 大幅改进了参数绑定接口对数据写入(INSERT)场景的支持。这样在通过参数绑定接口写入数据时,就避免了 SQL 语法解析的资源消耗,从而在绝大多数情况下显著提升写入性能。此时的典型操作步骤如下: + +1. 调用 `taos_stmt_init()` 创建参数绑定对象; +2. 调用 `taos_stmt_prepare()` 解析 INSERT 语句; +3. 如果 INSERT 语句中预留了表名但没有预留 TAGS,那么调用 `taos_stmt_set_tbname()` 来设置表名; +4. 如果 INSERT 语句中既预留了表名又预留了 TAGS(例如 INSERT 语句采取的是自动建表的方式),那么调用 `taos_stmt_set_tbname_tags()` 来设置表名和 TAGS 的值; +5. 调用 `taos_stmt_bind_param_batch()` 以多列的方式设置 VALUES 的值,或者调用 `taos_stmt_bind_param()` 以单行的方式设置 VALUES 的值; +6. 调用 `taos_stmt_add_batch()` 把当前绑定的参数加入批处理; +7. 可以重复第 3 ~ 6 步,为批处理加入更多的数据行; +8. 调用 `taos_stmt_execute()` 执行已经准备好的批处理指令; +9. 执行完毕,调用 `taos_stmt_close()` 释放所有资源。 + +说明:如果 `taos_stmt_execute()` 执行成功,假如不需要改变 SQL 语句的话,那么是可以复用 `taos_stmt_prepare()` 的解析结果,直接进行第 3 ~ 6 步绑定新数据的。但如果执行出错,那么并不建议继续在当前的环境上下文下继续工作,而是建议释放资源,然后从 `taos_stmt_init()` 步骤重新开始。 + +接口相关的具体函数如下(也可以参考 [prepare.c](https://github.com/taosdata/TDengine/blob/develop/examples/c/prepare.c) 文件中使用对应函数的方式): + +- `TAOS_STMT* taos_stmt_init(TAOS *taos)` + + 创建一个 TAOS_STMT 对象用于后续调用。 + +- `int taos_stmt_prepare(TAOS_STMT *stmt, const char *sql, unsigned long length)` + + 解析一条 SQL 语句,将解析结果和参数信息绑定到 stmt 上,如果参数 length 大于 0,将使用此参数作为 SQL 语句的长度,如等于 0,将自动判断 SQL 语句的长度。 + +- `int taos_stmt_bind_param(TAOS_STMT *stmt, TAOS_BIND *bind)` + + 不如 `taos_stmt_bind_param_batch()` 效率高,但可以支持非 INSERT 类型的 SQL 语句。 + 进行参数绑定,bind 指向一个数组(代表所要绑定的一行数据),需保证此数组中的元素数量和顺序与 SQL 语句中的参数完全一致。TAOS_BIND 的使用方法与 MySQL 中的 MYSQL_BIND 类似,具体定义如下: + + ```c + typedef struct TAOS_BIND { + int buffer_type; + void * buffer; + uintptr_t buffer_length; // not in use + uintptr_t * length; + int * is_null; + int is_unsigned; // not in use + int * error; // not in use + } TAOS_BIND; + ``` + +- `int taos_stmt_set_tbname(TAOS_STMT* stmt, const char* name)` + + (2.1.1.0 版本新增,仅支持用于替换 INSERT 语句中的参数值) + 当 SQL 语句中的表名使用了 `?` 占位时,可以使用此函数绑定一个具体的表名。 + +- `int taos_stmt_set_tbname_tags(TAOS_STMT* stmt, const char* name, TAOS_BIND* tags)` + + (2.1.2.0 版本新增,仅支持用于替换 INSERT 语句中的参数值) + 当 SQL 语句中的表名和 TAGS 都使用了 `?` 占位时,可以使用此函数绑定具体的表名和具体的 TAGS 取值。最典型的使用场景是使用了自动建表功能的 INSERT 语句(目前版本不支持指定具体的 TAGS 列)。TAGS 参数中的列数量需要与 SQL 语句中要求的 TAGS 数量完全一致。 + +- `int taos_stmt_bind_param_batch(TAOS_STMT* stmt, TAOS_MULTI_BIND* bind)` + + (2.1.1.0 版本新增,仅支持用于替换 INSERT 语句中的参数值) + 以多列的方式传递待绑定的数据,需要保证这里传递的数据列的顺序、列的数量与 SQL 语句中的 VALUES 参数完全一致。TAOS_MULTI_BIND 的具体定义如下: + + ```c + typedef struct TAOS_MULTI_BIND { + int buffer_type; + void * buffer; + uintptr_t buffer_length; + uintptr_t * length; + char * is_null; + int num; // the number of columns + } TAOS_MULTI_BIND; + ``` + +- `int taos_stmt_add_batch(TAOS_STMT *stmt)` + + 将当前绑定的参数加入批处理中,调用此函数后,可以再次调用 `taos_stmt_bind_param()` 或 `taos_stmt_bind_param_batch()` 绑定新的参数。需要注意,此函数仅支持 INSERT/IMPORT 语句,如果是 SELECT 等其他 SQL 语句,将返回错误。 + +- `int taos_stmt_execute(TAOS_STMT *stmt)` + + 执行准备好的语句。目前,一条语句只能执行一次。 + +- `TAOS_RES* taos_stmt_use_result(TAOS_STMT *stmt)` + + 获取语句的结果集。结果集的使用方式与非参数化调用时一致,使用完成后,应对此结果集调用 `taos_free_result()` 以释放资源。 + +- `int taos_stmt_close(TAOS_STMT *stmt)` + + 执行完毕,释放所有资源。 + +- `char * taos_stmt_errstr(TAOS_STMT *stmt)` + + (2.1.3.0 版本新增) + 用于在其他 STMT API 返回错误(返回错误码或空指针)时获取错误信息。 + +### 无模式(schemaless)写入 API + +除了使用 SQL 方式或者使用参数绑定 API 写入数据外,还可以使用 Schemaless 的方式完成写入。Schemaless 可以免于预先创建超级表/数据子表的数据结构,而是可以直接写入数据,TDengine 系统会根据写入的数据内容自动创建和维护所需要的表结构。Schemaless 的使用方式详见 [Schemaless 写入](/reference/schemaless/) 章节,这里介绍与之配套使用的 C/C++ API。 + +- `TAOS_RES* taos_schemaless_insert(TAOS* taos, const char* lines[], int numLines, int protocol, int precision)` + + **功能说明** + 该接口将行协议的文本数据写入到 TDengine 中。 + + **参数说明** + taos: 数据库连接,通过 `taos_connect()` 函数建立的数据库连接。 + lines:文本数据。满足解析格式要求的无模式文本字符串。 + numLines:文本数据的行数,不能为 0 。 + protocol: 行协议类型,用于标识文本数据格式。 + precision:文本数据中的时间戳精度字符串。 + + **返回值** + TAOS_RES 结构体,应用可以通过使用 `taos_errstr()` 获得错误信息,也可以使用 `taos_errno()` 获得错误码。 + 在某些情况下,返回的 TAOS_RES 为 `NULL`,此时仍然可以调用 `taos_errno()` 来安全地获得错误码信息。 + 返回的 TAOS_RES 需要调用方来负责释放,否则会出现内存泄漏。 + + **说明** + 协议类型是枚举类型,包含以下三种格式: + + - TSDB_SML_LINE_PROTOCOL:InfluxDB 行协议(Line Protocol) + - TSDB_SML_TELNET_PROTOCOL: OpenTSDB Telnet 文本行协议 + - TSDB_SML_JSON_PROTOCOL: OpenTSDB Json 协议格式 + + 时间戳分辨率的定义,定义在 `taos.h` 文件中,具体内容如下: + + - TSDB_SML_TIMESTAMP_NOT_CONFIGURED = 0, + - TSDB_SML_TIMESTAMP_HOURS, + - TSDB_SML_TIMESTAMP_MINUTES, + - TSDB_SML_TIMESTAMP_SECONDS, + - TSDB_SML_TIMESTAMP_MILLI_SECONDS, + - TSDB_SML_TIMESTAMP_MICRO_SECONDS, + - TSDB_SML_TIMESTAMP_NANO_SECONDS + + 需要注意的是,时间戳分辨率参数只在协议类型为 `SML_LINE_PROTOCOL` 的时候生效。 + 对于 OpenTSDB 的文本协议,时间戳的解析遵循其官方解析规则 — 按照时间戳包含的字符的数量来确认时间精度。 + + **支持版本** + 该功能接口从 2.3.0.0 版本开始支持。 + +### 订阅和消费 API + +订阅 API 目前支持订阅一张或多张表,并通过定期轮询的方式不断获取写入表中的最新数据。 + +- `TAOS_SUB *taos_subscribe(TAOS* taos, int restart, const char* topic, const char *sql, TAOS_SUBSCRIBE_CALLBACK fp, void *param, int interval)` + + 该函数负责启动订阅服务,成功时返回订阅对象,失败时返回 `NULL`,其参数为: + + - taos:已经建立好的数据库连接 + - restart:如果订阅已经存在,是重新开始,还是继续之前的订阅 + - topic:订阅的主题(即名称),此参数是订阅的唯一标识 + - sql:订阅的查询语句,此语句只能是 `select` 语句,只应查询原始数据,只能按时间正序查询数据 + - fp:收到查询结果时的回调函数(稍后介绍函数原型),只在异步调用时使用,同步调用时此参数应该传 `NULL` + - param:调用回调函数时的附加参数,系统 API 将其原样传递到回调函数,不进行任何处理 + - interval:轮询周期,单位为毫秒。异步调用时,将根据此参数周期性的调用回调函数,为避免对系统性能造成影响,不建议将此参数设置的过小;同步调用时,如两次调用 `taos_consume()` 的间隔小于此周期,API 将会阻塞,直到时间间隔超过此周期。 + +- `typedef void (*TAOS_SUBSCRIBE_CALLBACK)(TAOS_SUB* tsub, TAOS_RES *res, void* param, int code)` + + 异步模式下,回调函数的原型,其参数为: + + - tsub:订阅对象 + - res:查询结果集,注意结果集中可能没有记录 + - param:调用 `taos_subscribe()` 时客户程序提供的附加参数 + - code:错误码 + + :::note + 在这个回调函数里不可以做耗时过长的处理,尤其是对于返回的结果集中数据较多的情况,否则有可能导致客户端阻塞等异常状态。如果必须进行复杂计算,则建议在另外的线程中进行处理。 + + ::: + +- `TAOS_RES *taos_consume(TAOS_SUB *tsub)` + + 同步模式下,该函数用来获取订阅的结果。 用户应用程序将其置于一个循环之中。 如两次调用 `taos_consume()` 的间隔小于订阅的轮询周期,API 将会阻塞,直到时间间隔超过此周期。如果数据库有新记录到达,该 API 将返回该最新的记录,否则返回一个没有记录的空结果集。 如果返回值为 `NULL`,说明系统出错。 异步模式下,用户程序不应调用此 API。 + + :::note + 在调用 `taos_consume()` 之后,用户应用应确保尽快调用 `taos_fetch_row()` 或 `taos_fetch_block()` 来处理订阅结果,否则服务端会持续缓存查询结果数据等待客户端读取,极端情况下会导致服务端内存消耗殆尽,影响服务稳定性。 + + ::: + +- `void taos_unsubscribe(TAOS_SUB *tsub, int keepProgress)` + + 取消订阅。 如参数 `keepProgress` 不为 0,API 会保留订阅的进度信息,后续调用 `taos_subscribe()` 时可以基于此进度继续;否则将删除进度信息,后续只能重新开始读取数据。 + diff --git a/docs-cn/14-reference/03-connector/csharp.mdx b/docs-cn/14-reference/03-connector/csharp.mdx new file mode 100644 index 0000000000..c2fbb3b67f --- /dev/null +++ b/docs-cn/14-reference/03-connector/csharp.mdx @@ -0,0 +1,189 @@ +--- +toc_max_heading_level: 4 +sidebar_position: 7 +sidebar_label: C# +title: C# Connector +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import Preparition from "./_preparition.mdx" +import CSInsert from "../../07-develop/03-insert-data/_cs_sql.mdx" +import CSInfluxLine from "../../07-develop/03-insert-data/_cs_line.mdx" +import CSOpenTSDBTelnet from "../../07-develop/03-insert-data/_cs_opts_telnet.mdx" +import CSOpenTSDBJson from "../../07-develop/03-insert-data/_cs_opts_json.mdx" +import CSQuery from "../../07-develop/04-query-data/_cs.mdx" +import CSAsyncQuery from "../../07-develop/04-query-data/_cs_async.mdx" + +`TDengine.Connector` 是 TDengine 提供的 C# 语言连接器。C# 开发人员可以通过它开发存取 TDengine 集群数据的 C# 应用软件。 + +`TDengine.Connector` 连接器支持通过 TDengine 客户端驱动(taosc)建立与 TDengine 运行实例的连接,提供数据写入、查询、订阅、schemaless 数据写入、参数绑定接口数据写入等功能 `TDengine.Connector` 目前暂未提供 REST 连接方式,用户可以参考 [RESTful APIs](https://docs.taosdata.com//reference/restful-api/) 文档自行编写。 + +本文介绍如何在 Linux 或 Windows 环境中安装 `TDengine.Connector`,并通过 `TDengine.Connector` 连接 TDengine 集群,进行数据写入、查询等基本操作。 + +`TDengine.Connector` 的源码托管在 [GitHub](https://github.com/taosdata/taos-connector-dotnet)。 + +## 支持的平台 + +支持的平台和 TDengine 客户端驱动支持的平台一致。 + +## 版本支持 + +请参考[版本支持列表](/reference/connector#版本支持) + +## 支持的功能特性 + +1. 连接管理 +2. 普通查询 +3. 连续查询 +4. 参数绑定 +5. 订阅功能 +6. Schemaless + +## 安装步骤 + +### 安装前准备 + +* 安装 [.NET SDK](https://dotnet.microsoft.com/download) +* [Nuget 客户端](https://docs.microsoft.com/en-us/nuget/install-nuget-client-tools) (可选安装) +* 安装 TDengine 客户端驱动,具体步骤请参考[安装客户端驱动](/reference/connector#安装客户端驱动) + +### 使用 dotnet CLI 安装 + + + + +可以在当前 .NET 项目的路径下,通过 dotnet 命令引用 Nuget 中发布的 `TDengine.Connector` 到当前项目。 + +``` bash +dotnet add package TDengine.Connector +``` + + + + +可以下载 TDengine 的源码,直接引用最新版本的 TDengine.Connector 库 + +```bash +git clone https://github.com/taosdata/TDengine.git +cd TDengine/src/connector/C#/src/ +cp -r TDengineDriver/ myProject + +cd myProject +dotnet add TDengineDriver/TDengineDriver.csproj +``` + + + +## 建立连接 + +``` C# +using TDengineDriver; + +namespace TDengineExample +{ + + internal class EstablishConnection + { + static void Main(String[] args) + { + string host = "localhost"; + short port = 6030; + string username = "root"; + string password = "taosdata"; + string dbname = ""; + + var conn = TDengine.Connect(host, username, password, dbname, port); + if (conn == IntPtr.Zero) + { + Console.WriteLine("Connect to TDengine failed"); + } + else + { + Console.WriteLine("Connect to TDengine success"); + } + TDengine.Close(conn); + TDengine.Cleanup(); + } + } +} + +``` + +## 使用示例 + +### 写入数据 + +#### SQL 写入 + + + +#### InfluxDB 行协议写入 + + + +#### OpenTSDB Telnet 行协议写入 + + + +#### OpenTSDB JSON 行协议写入 + + + +### 查询数据 + +#### 同步查询 + + + +#### 异步查询 + + + +### 更多示例程序 + +|示例程序 | 示例程序描述 | +|--------------------------------------------------------------------------------------------------------------------|--------------------------------------------| +| [C#checker](https://github.com/taosdata/TDengine/tree/develop/examples/C%23/C%23checker) | 使用 TDengine.Connector 可以通过 help 命令中提供的参数,测试C# Driver的同步写入和查询 | +| [TDengineTest](https://github.com/taosdata/TDengine/tree/develop/examples/C%23/TDengineTest) | 使用 TDengine.Connector 实现的简单写入和查询的示例 | +| [insertCn](https://github.com/taosdata/TDengine/tree/develop/examples/C%23/insertCn) | 使用 TDengine.Connector 实现的写入和查询中文字符的示例 | +| [jsonTag](https://github.com/taosdata/TDengine/tree/develop/examples/C%23/jsonTag) | 使用 TDengine.Connector 实现的写入和查询 json tag 类型数据的示例 | +| [stmt](https://github.com/taosdata/TDengine/tree/develop/examples/C%23/stmt) | 使用 TDengine.Connector 实现的参数绑定的示例 | +| [schemaless](https://github.com/taosdata/TDengine/tree/develop/examples/C%23/schemaless) | 使用 TDengine.Connector 实现的使用 schemaless 写入的示例 | +| [benchmark](https://github.com/taosdata/TDengine/tree/develop/examples/C%23/taosdemo) | 使用 TDengine.Connector 实现的简易 Benchmark | +| [async query](https://github.com/taosdata/taos-connector-dotnet/blob/develop/examples/QueryAsyncSample.cs) | 使用 TDengine.Connector 实现的异步查询的示例 | +| [subscribe](https://github.com/taosdata/taos-connector-dotnet/blob/develop/examples/SubscribeSample.cs) | 使用 TDengine.Connector 实现的订阅数据的示例 | + +## 重要更新记录 + +| TDengine.Connector | 说明 | +|--------------------|--------------------------------| +| 1.0.6 | 修复 schemaless 在 1.0.4 和 1.0.5 中失效 bug。 | +| 1.0.5 | 修复 Windows 同步查询中文报错 bug。 | +| 1.0.4 | 新增异步查询,订阅等功能。修复绑定参数 bug。 | +| 1.0.3 | 新增参数绑定、schemaless、 json tag等功能。 | +| 1.0.2 | 新增连接管理、同步查询、错误信息等功能。 | + +## 其他说明 + +### 第三方驱动 + +`Maikebing.Data.Taos` 是一个 TDengine 的 ADO.NET 连接器,支持 Linux,Windows 平台。该连接器由社区贡献者`麦壳饼@@maikebing` 提供,具体请参考: + +* 接口下载: +* 用法说明: + +## 常见问题 + +1. "Unable to establish connection","Unable to resolve FQDN" + + 一般是因为 FQDN 配置不正确。可以参考[如何彻底搞懂 TDengine 的 FQDN](https://www.taosdata.com/blog/2021/07/29/2741.html)解决。 + +2. Unhandled exception. System.DllNotFoundException: Unable to load DLL 'taos' or one of its dependencies: 找不到指定的模块。 + + 一般是因为程序没有找到依赖的客户端驱动。解决方法为:Windows 下可以将 `C:\TDengine\driver\taos.dll` 拷贝到 `C:\Windows\System32\ ` 目录下,Linux 下建立如下软链接 `ln -s /usr/local/taos/driver/libtaos.so.x.x.x.x /usr/lib/libtaos.so` 即可。 + +## API 参考 + +[API 参考](https://docs.taosdata.com/api/connector-csharp/html/860d2ac1-dd52-39c9-e460-0829c4e5a40b.htm) diff --git a/docs-cn/14-reference/03-connector/go.mdx b/docs-cn/14-reference/03-connector/go.mdx new file mode 100644 index 0000000000..88b09aa5d0 --- /dev/null +++ b/docs-cn/14-reference/03-connector/go.mdx @@ -0,0 +1,411 @@ +--- +toc_max_heading_level: 4 +sidebar_position: 4 +sidebar_label: Go +title: TDengine Go Connector +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import Preparition from "./_preparition.mdx" +import GoInsert from "../../07-develop/03-insert-data/_go_sql.mdx" +import GoInfluxLine from "../../07-develop/03-insert-data/_go_line.mdx" +import GoOpenTSDBTelnet from "../../07-develop/03-insert-data/_go_opts_telnet.mdx" +import GoOpenTSDBJson from "../../07-develop/03-insert-data/_go_opts_json.mdx" +import GoQuery from "../../07-develop/04-query-data/_go.mdx" + +`driver-go` 是 TDengine 的官方 Go 语言连接器,实现了 Go 语言[ database/sql ](https://golang.org/pkg/database/sql/) 包的接口。Go 开发人员可以通过它开发存取 TDengine 集群数据的应用软件。 + +`driver-go` 提供两种建立连接的方式。一种是**原生连接**,它通过 TDengine 客户端驱动程序(taosc)原生连接 TDengine 运行实例,支持数据写入、查询、订阅、schemaless 接口和参数绑定接口等功能。另外一种是 **REST 连接**,它通过 taosAdapter 提供的 REST 接口连接 TDengine 运行实例。REST 连接实现的功能特性集合和原生连接有少量不同。 + +本文介绍如何安装 `driver-go`,并通过 `driver-go` 连接 TDengine 集群、进行数据查询、数据写入等基本操作。 + +`driver-go` 的源码托管在 [GitHub](https://github.com/taosdata/driver-go)。 + +## 支持的平台 + +原生连接支持的平台和 TDengine 客户端驱动支持的平台一致。 +REST 连接支持所有能运行 Go 的平台。 + +## 版本支持 + +请参考[版本支持列表](/reference/connector#版本支持) + +## 支持的功能特性 + +### 原生连接 + +“原生连接”指连接器通过 TDengine 客户端驱动(taosc)直接与 TDengine 运行实例建立的连接。支持的功能特性有: + +* 普通查询 +* 连续查询 +* 订阅 +* schemaless 接口 +* 参数绑定接口 + +### REST 连接 + +"REST 连接"指连接器通过 taosAdapter 组件提供的 REST API 与 TDengine 运行实例建立的连接。支持的功能特性有: + +* 普通查询 +* 连续查询 + +## 安装步骤 + +### 安装前准备 + +* 安装 Go 开发环境(Go 1.14 及以上,GCC 4.8.5 及以上) +* 如果使用原生连接器,请安装 TDengine 客户端驱动,具体步骤请参考[安装客户端驱动](/reference/connector#安装客户端驱动) + +配置好环境变量,检查命令: + +* ```go env``` +* ```gcc -v``` + +### 使用 go get 安装 + +`go get -u github.com/taosdata/driver-go/v2@develop` + +### 使用 go mod 管理 + +1. 使用 `go mod` 命令初始化项目: + + ```text + go mod init taos-demo + ``` + +2. 引入 taosSql : + + ```go + import ( + "database/sql" + _ "github.com/taosdata/driver-go/v2/taosSql" + ) + ``` + +3. 使用 `go mod tidy` 更新依赖包: + + ```text + go mod tidy + ``` + +4. 使用 `go run taos-demo` 运行程序或使用 `go build` 命令编译出二进制文件。 + + ```text + go run taos-demo + go build + ``` + +## 建立连接 + +### 数据源名称(DSN) + +数据源名称具有通用格式,例如 [PEAR DB](http://pear.php.net/manual/en/package.database.db.intro-dsn.php),但没有类型前缀(方括号表示可选): + +``` text +[username[:password]@][protocol[(address)]]/[dbname][?param1=value1&...¶mN=valueN] +``` + +完整形式的 DSN: + +```text +username:password@protocol(address)/dbname?param=value +``` +### 使用连接器进行连接 + + + + +_taosSql_ 通过 cgo 实现了 Go 的 `database/sql/driver` 接口。只需要引入驱动就可以使用 [`database/sql`](https://golang.org/pkg/database/sql/) 的接口。 + +使用 `taosSql` 作为 `driverName` 并且使用一个正确的 [DSN](#DSN) 作为 `dataSourceName`,DSN 支持的参数: + +* configPath 指定 taos.cfg 目录 + +示例: + +```go +package main + +import ( + "database/sql" + "fmt" + + _ "github.com/taosdata/driver-go/v2/taosSql" +) + +func main() { + var taosUri = "root:taosdata@tcp(localhost:6030)/" + taos, err := sql.Open("taosSql", taosUri) + if err != nil { + fmt.Println("failed to connect TDengine, err:", err) + return + } +} +``` + + + + +_taosRestful_ 通过 `http client` 实现了 Go 的 `database/sql/driver` 接口。只需要引入驱动就可以使用[`database/sql`](https://golang.org/pkg/database/sql/)的接口。 + +使用 `taosRestful` 作为 `driverName` 并且使用一个正确的 [DSN](#DSN) 作为 `dataSourceName`,DSN 支持的参数: + +* `disableCompression` 是否接受压缩数据,默认为 true 不接受压缩数据,如果传输数据使用 gzip 压缩设置为 false。 +* `readBufferSize` 读取数据的缓存区大小默认为 4K(4096),当查询结果数据量多时可以适当调大该值。 + +示例: + +```go +package main + +import ( + "database/sql" + "fmt" + + _ "github.com/taosdata/driver-go/v2/taosRestful" +) + +func main() { + var taosUri = "root:taosdata@http(localhost:6041)/" + taos, err := sql.Open("taosRestful", taosUri) + if err != nil { + fmt.Println("failed to connect TDengine, err:", err) + return + } +} +``` + + + +## 使用示例 + +### 写入数据 + +#### SQL 写入 + + + +#### InfluxDB 行协议写入 + + + +#### OpenTSDB Telnet 行协议写入 + + + +#### OpenTSDB JSON 行协议写入 + + + +### 查询数据 + + + +### 更多示例程序 + +* [示例程序](https://github.com/taosdata/TDengine/tree/develop/examples/go) +* [视频教程](https://www.taosdata.com/blog/2020/11/11/1951.html)。 + +## 使用限制 + +由于 REST 接口无状态所以 `use db` 语法不会生效,需要将 db 名称放到 SQL 语句中,如:`create table if not exists tb1 (ts timestamp, a int)`改为`create table if not exists test.tb1 (ts timestamp, a int)`否则将报错`[0x217] Database not specified or available`。 + +也可以将 db 名称放到 DSN 中,将 `root:taosdata@http(localhost:6041)/` 改为 `root:taosdata@http(localhost:6041)/test`,此方法在 TDengine 2.4.0.5 版本的 taosAdapter 开始支持。当指定的 db 不存在时执行 `create database` 语句不会报错,而执行针对该 db 的其他查询或写入操作会报错。 + +完整示例如下: + +```go +package main + +import ( + "database/sql" + "fmt" + "time" + + _ "github.com/taosdata/driver-go/v2/taosRestful" +) + +func main() { + var taosDSN = "root:taosdata@http(localhost:6041)/test" + taos, err := sql.Open("taosRestful", taosDSN) + if err != nil { + fmt.Println("failed to connect TDengine, err:", err) + return + } + defer taos.Close() + taos.Exec("create database if not exists test") + taos.Exec("create table if not exists tb1 (ts timestamp, a int)") + _, err = taos.Exec("insert into tb1 values(now, 0)(now+1s,1)(now+2s,2)(now+3s,3)") + if err != nil { + fmt.Println("failed to insert, err:", err) + return + } + rows, err := taos.Query("select * from tb1") + if err != nil { + fmt.Println("failed to select from table, err:", err) + return + } + + defer rows.Close() + for rows.Next() { + var r struct { + ts time.Time + a int + } + err := rows.Scan(&r.ts, &r.a) + if err != nil { + fmt.Println("scan error:\n", err) + return + } + fmt.Println(r.ts, r.a) + } +} +``` + +## 常见问题 + +1. 无法找到包 `github.com/taosdata/driver-go/v2/taosRestful` + + 将 `go.mod` 中 require 块对`github.com/taosdata/driver-go/v2`的引用改为`github.com/taosdata/driver-go/v2 develop`,之后执行 `go mod tidy`。 + +2. database/sql 中 stmt(参数绑定)相关接口崩溃 + + REST 不支持参数绑定相关接口,建议使用`db.Exec`和`db.Query`。 + +3. 使用 `use db` 语句后执行其他语句报错 `[0x217] Database not specified or available` + + 在 REST 接口中 SQL 语句的执行无上下文关联,使用 `use db` 语句不会生效,解决办法见上方使用限制章节。 + +4. 使用 taosSql 不报错使用 taosRestful 报错 `[0x217] Database not specified or available` + + 因为 REST 接口无状态,使用 `use db` 语句不会生效,解决办法见上方使用限制章节。 + +5. 升级 `github.com/taosdata/driver-go/v2/taosRestful` + + 将 `go.mod` 文件中对 `github.com/taosdata/driver-go/v2` 的引用改为 `github.com/taosdata/driver-go/v2 develop`,之后执行 `go mod tidy`。 + +6. `readBufferSize` 参数调大后无明显效果 + + `readBufferSize` 调大后会减少获取结果时 `syscall` 的调用。如果查询结果的数据量不大,修改该参数不会带来明显提升,如果该参数修改过大,瓶颈会在解析 JSON 数据。如果需要优化查询速度,需要根据实际情况调整该值来达到查询效果最优。 + +7. `disableCompression` 参数设置为 `false` 时查询效率降低 + + 当 `disableCompression` 参数设置为 `false` 时查询结果会使用 `gzip` 压缩后传输,拿到数据后要先进行 `gzip` 解压。 + +8. `go get` 命令无法获取包,或者获取包超时 + + 设置 Go 代理 `go env -w GOPROXY=https://goproxy.cn,direct`。 + +## 常用 API + +### database/sql API + +* `sql.Open(DRIVER_NAME string, dataSourceName string) *DB` + + 该 API 用来打开 DB,返回一个类型为 \*DB 的对象。 + +:::info +该 API 成功创建的时候,并没有做权限等检查,只有在真正执行 Query 或者 Exec 的时候才能真正的去创建连接,并同时检查 user/password/host/port 是不是合法。 +::: + +* `func (db *DB) Exec(query string, args ...interface{}) (Result, error)` + + `sql.Open` 内置的方法,用来执行非查询相关 SQL。 + +* `func (db *DB) Query(query string, args ...interface{}) (*Rows, error)` + + `sql.Open` 内置的方法,用来执行查询语句。 + +### 高级功能(af)API + +`af` 包封装了连接管理、订阅、schemaless、参数绑定等 TDengine 高级功能。 + +#### 连接管理 + +* `af.Open(host, user, pass, db string, port int) (*Connector, error)` + + 该 API 通过 cgo 创建与 taosd 的连接。 + +* `func (conn *Connector) Close() error` + + 关闭与 taosd 的连接。 + +#### 订阅 + +* `func (conn *Connector) Subscribe(restart bool, topic string, sql string, interval time.Duration) (Subscriber, error)` + + 订阅数据。 + +* `func (s *taosSubscriber) Consume() (driver.Rows, error)` + + 消费订阅数据,返回 `database/sql/driver` 包的 `Rows` 结构。 + +* `func (s *taosSubscriber) Unsubscribe(keepProgress bool)` + + 取消订阅数据。 + +#### schemaless + +* `func (conn *Connector) InfluxDBInsertLines(lines []string, precision string) error` + + 写入 influxDB 行协议。 + +* `func (conn *Connector) OpenTSDBInsertTelnetLines(lines []string) error` + + 写入 OpenTDSB telnet 协议数据。 + +* `func (conn *Connector) OpenTSDBInsertJsonPayload(payload string) error` + + 写入 OpenTSDB JSON 协议数据。 + +#### 参数绑定 + +* `func (conn *Connector) StmtExecute(sql string, params *param.Param) (res driver.Result, err error)` + + 参数绑定单行插入。 + +* `func (conn *Connector) StmtQuery(sql string, params *param.Param) (rows driver.Rows, err error)` + + 参数绑定查询,返回 `database/sql/driver` 包的 `Rows` 结构。 + +* `func (conn *Connector) InsertStmt() *insertstmt.InsertStmt` + + 初始化参数。 + +* `func (stmt *InsertStmt) Prepare(sql string) error` + + 参数绑定预处理 SQL 语句。 + +* `func (stmt *InsertStmt) SetTableName(name string) error` + + 参数绑定设置表名。 + +* `func (stmt *InsertStmt) SetSubTableName(name string) error` + + 参数绑定设置子表名。 + +* `func (stmt *InsertStmt) BindParam(params []*param.Param, bindType *param.ColumnType) error` + + 参数绑定多行数据。 + +* `func (stmt *InsertStmt) AddBatch() error` + + 添加到参数绑定批处理。 + +* `func (stmt *InsertStmt) Execute() error` + + 执行参数绑定。 + +* `func (stmt *InsertStmt) GetAffectedRows() int` + + 获取参数绑定插入受影响行数。 + +* `func (stmt *InsertStmt) Close() error` + + 结束参数绑定。 + +## API 参考 + +全部 API 见 [driver-go 文档](https://pkg.go.dev/github.com/taosdata/driver-go/v2) diff --git a/docs-cn/14-reference/03-connector/java.mdx b/docs-cn/14-reference/03-connector/java.mdx new file mode 100644 index 0000000000..55abf84fd5 --- /dev/null +++ b/docs-cn/14-reference/03-connector/java.mdx @@ -0,0 +1,839 @@ +--- +toc_max_heading_level: 4 +sidebar_position: 2 +sidebar_label: Java +title: TDengine Java Connector +description: TDengine Java 连接器基于标准 JDBC API 实现, 并提供原生连接与 REST连接两种连接器。 +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +`taos-jdbcdriver` 是 TDengine 的官方 Java 语言连接器,Java 开发人员可以通过它开发存取 TDengine 数据库的应用软件。`taos-jdbcdriver` 实现了 JDBC driver 标准的接口,并提供两种形式的连接器。一种是通过 TDengine 客户端驱动程序(taosc)原生连接 TDengine 实例,支持数据写入、查询、订阅、schemaless 接口和参数绑定接口等功能,一种是通过 taosAdapter 提供的 REST 接口连接 TDengine 实例(2.4.0.0 及更高版本)。REST 连接实现的功能集合和原生连接有少量不同。 + +![tdengine-connector](tdengine-jdbc-connector.png) + +上图显示了两种 Java 应用使用连接器访问 TDengine 的两种方式: + +- JDBC 原生连接:Java 应用在物理节点 1(pnode1)上使用 TSDBDriver 直接调用客户端驱动(libtaos.so 或 taos.dll)的 API 将写入和查询请求发送到位于物理节点 2(pnode2)上的 taosd 实例。 +- JDBC REST 连接:Java 应用通过 RestfulDriver 将 SQL 封装成一个 REST 请求,发送给物理节点 2 的 REST 服务器(taosAdapter),通过 REST 服务器请求 taosd 并返回结果。 + +使用 REST 连接,不依赖 TDengine 客户端驱动,可以跨平台,更加方便灵活,但性能比原生连接器低约 30%。 + +:::info +TDengine 的 JDBC 驱动实现尽可能与关系型数据库驱动保持一致,但 TDengine 与关系对象型数据库的使用场景和技术特征存在差异,所以`taos-jdbcdriver` 与传统的 JDBC driver 也存在一定差异。在使用时需要注意以下几点: + +- TDengine 目前不支持针对单条数据记录的删除操作。 +- 目前不支持事务操作。 + +::: + +## 支持的平台 + +原生连接支持的平台和 TDengine 客户端驱动支持的平台一致。 +REST 连接支持所有能运行 Java 的平台。 + +## 版本支持 + +请参考[版本支持列表](/reference/connector#版本支持) + +## TDengine DataType 和 Java DataType + +TDengine 目前支持时间戳、数字、字符、布尔类型,与 Java 对应类型转换如下: + +| TDengine DataType | JDBCType (driver 版本 < 2.0.24) | JDBCType (driver 版本 >= 2.0.24) | +| ----------------- | --------------------------------- | ---------------------------------- | +| TIMESTAMP | java.lang.Long | java.sql.Timestamp | +| INT | java.lang.Integer | java.lang.Integer | +| BIGINT | java.lang.Long | java.lang.Long | +| FLOAT | java.lang.Float | java.lang.Float | +| DOUBLE | java.lang.Double | java.lang.Double | +| SMALLINT | java.lang.Short | java.lang.Short | +| TINYINT | java.lang.Byte | java.lang.Byte | +| BOOL | java.lang.Boolean | java.lang.Boolean | +| BINARY | java.lang.String | byte array | +| NCHAR | java.lang.String | java.lang.String | +| JSON | - | java.lang.String | + +**注意**:JSON 类型仅在 tag 中支持。 + +## 安装步骤 + +### 安装前准备 + +使用 Java Connector 连接数据库前,需要具备以下条件: + +- 已安装 Java 1.8 或以上版本运行时环境和 Maven 3.6 或以上版本 +- 已安装 TDengine 客户端驱动(使用原生连接必须安装,使用 REST 连接无需安装),具体步骤请参考[安装客户端驱动](/reference/connector#安装客户端驱动) + +### 安装连接器 + + + + +目前 taos-jdbcdriver 已经发布到 [Sonatype Repository](https://search.maven.org/artifact/com.taosdata.jdbc/taos-jdbcdriver) 仓库,且各大仓库都已同步。 + +- [sonatype](https://search.maven.org/artifact/com.taosdata.jdbc/taos-jdbcdriver) +- [mvnrepository](https://mvnrepository.com/artifact/com.taosdata.jdbc/taos-jdbcdriver) +- [maven.aliyun](https://maven.aliyun.com/mvn/search) + +Maven 项目中,在 pom.xml 中添加以下依赖: + +```xml-dtd + + com.taosdata.jdbc + taos-jdbcdriver + 2.0.** + +``` + + + + +可以通过下载 TDengine 的源码,自己编译最新版本的 Java connector + +```shell +git clone https://github.com/taosdata/TDengine.git +cd TDengine/src/connector/jdbc +mvn clean install -Dmaven.test.skip=true +``` + +编译后,在 target 目录下会产生 taos-jdbcdriver-2.0.XX-dist.jar 的 jar 包,并自动将编译的 jar 文件放在本地的 Maven 仓库中。 + + + + +## 建立连接 + +TDengine 的 JDBC URL 规范格式为: +`jdbc:[TAOS|TAOS-RS]://[host_name]:[port]/[database_name]?[user={user}|&password={password}|&charset={charset}|&cfgdir={config_dir}|&locale={locale}|&timezone={timezone}]` + +对于建立连接,原生连接与 REST 连接有细微不同。 + + + + +```java +Class.forName("com.taosdata.jdbc.TSDBDriver"); +String jdbcUrl = "jdbc:TAOS://taosdemo.com:6030/test?user=root&password=taosdata"; +Connection conn = DriverManager.getConnection(jdbcUrl); +``` + +以上示例,使用了 JDBC 原生连接的 TSDBDriver,建立了到 hostname 为 taosdemo.com,端口为 6030(TDengine 的默认端口),数据库名为 test 的连接。这个 URL 中指定用户名(user)为 root,密码(password)为 taosdata。 + +**注意**:使用 JDBC 原生连接,taos-jdbcdriver 需要依赖客户端驱动(Linux 下是 libtaos.so;Windows 下是 taos.dll)。 + +url 中的配置参数如下: + +- user:登录 TDengine 用户名,默认值 'root'。 +- password:用户登录密码,默认值 'taosdata'。 +- cfgdir:客户端配置文件目录路径,Linux OS 上默认值 `/etc/taos`,Windows OS 上默认值 `C:/TDengine/cfg`。 +- charset:客户端使用的字符集,默认值为系统字符集。 +- locale:客户端语言环境,默认值系统当前 locale。 +- timezone:客户端使用的时区,默认值为系统当前时区。 +- batchfetch: true:在执行查询时批量拉取结果集;false:逐行拉取结果集。默认值为:false。开启批量拉取同时获取一批数据在查询数据量较大时批量拉取可以有效的提升查询性能。 +- batchErrorIgnore:true:在执行 Statement 的 executeBatch 时,如果中间有一条 SQL 执行失败将继续执行下面的 SQL。false:不再执行失败 SQL 后的任何语句。默认值为:false。 + +JDBC 原生连接的使用请参见[视频教程](https://www.taosdata.com/blog/2020/11/11/1955.html)。 + +**使用 TDengine 客户端驱动配置文件建立连接 ** + +当使用 JDBC 原生连接连接 TDengine 集群时,可以使用 TDengine 客户端驱动配置文件,在配置文件中指定集群的 firstEp、secondEp 等参数。如下所示: + +1. 在 Java 应用中不指定 hostname 和 port + +```java +public Connection getConn() throws Exception{ + Class.forName("com.taosdata.jdbc.TSDBDriver"); + String jdbcUrl = "jdbc:TAOS://:/test?user=root&password=taosdata"; + Properties connProps = new Properties(); + connProps.setProperty(TSDBDriver.PROPERTY_KEY_CHARSET, "UTF-8"); + connProps.setProperty(TSDBDriver.PROPERTY_KEY_LOCALE, "en_US.UTF-8"); + connProps.setProperty(TSDBDriver.PROPERTY_KEY_TIME_ZONE, "UTC-8"); + Connection conn = DriverManager.getConnection(jdbcUrl, connProps); + return conn; +} +``` + +2. 在配置文件中指定 firstEp 和 secondEp + +```shell +# first fully qualified domain name (FQDN) for TDengine system +firstEp cluster_node1:6030 + +# second fully qualified domain name (FQDN) for TDengine system, for cluster only +secondEp cluster_node2:6030 + +# default system charset +# charset UTF-8 + +# system locale +# locale en_US.UTF-8 +``` + +以上示例,jdbc 会使用客户端的配置文件,建立到 hostname 为 cluster_node1、端口为 6030、数据库名为 test 的连接。当集群中 firstEp 节点失效时,JDBC 会尝试使用 secondEp 连接集群。 + +TDengine 中,只要保证 firstEp 和 secondEp 中一个节点有效,就可以正常建立到集群的连接。 + +> **注意**:这里的配置文件指的是调用 JDBC Connector 的应用程序所在机器上的配置文件,Linux OS 上默认值 /etc/taos/taos.cfg ,Windows OS 上默认值 C://TDengine/cfg/taos.cfg。 + + + + +```java +Class.forName("com.taosdata.jdbc.rs.RestfulDriver"); +String jdbcUrl = "jdbc:TAOS-RS://taosdemo.com:6041/test?user=root&password=taosdata"; +Connection conn = DriverManager.getConnection(jdbcUrl); +``` + +以上示例,使用了 JDBC REST 连接的 RestfulDriver,建立了到 hostname 为 taosdemo.com,端口为 6041,数据库名为 test 的连接。这个 URL 中指定用户名(user)为 root,密码(password)为 taosdata。 + +使用 JDBC REST 连接,不需要依赖客户端驱动。与 JDBC 原生连接相比,仅需要: + +1. driverClass 指定为“com.taosdata.jdbc.rs.RestfulDriver”; +2. jdbcUrl 以“jdbc:TAOS-RS://”开头; +3. 使用 6041 作为连接端口。 + +url 中的配置参数如下: + +- user:登录 TDengine 用户名,默认值 'root'。 +- password:用户登录密码,默认值 'taosdata'。 +- batchfetch: true:在执行查询时批量拉取结果集;false:逐行拉取结果集。默认值为:false。逐行拉取结果集使用 HTTP 方式进行数据传输。从 taos-jdbcdriver-2.0.38 和 TDengine 2.4.0.12 版本开始,JDBC REST 连接增加批量拉取数据功能。taos-jdbcdriver 与 TDengine 之间通过 WebSocket 连接进行数据传输。相较于 HTTP,WebSocket 可以使 JDBC REST 连接支持大数据量查询,并提升查询性能。 +- batchErrorIgnore:true:在执行 Statement 的 executeBatch 时,如果中间有一条 SQL 执行失败,继续执行下面的 SQL 了。false:不再执行失败 SQL 后的任何语句。默认值为:false。 + +**注意**:部分配置项(比如:locale、timezone)在 REST 连接中不生效。 + +:::note + +- 与原生连接方式不同,REST 接口是无状态的。在使用 JDBC REST 连接时,需要在 SQL 中指定表、超级表的数据库名称。例如: + +```sql +INSERT INTO test.t1 USING test.weather (ts, temperature) TAGS('beijing') VALUES(now, 24.6); +``` + +- 从 taos-jdbcdriver-2.0.36 和 TDengine 2.2.0.0 版本开始,如果在 url 中指定了 dbname,那么,JDBC REST 连接会默认使用/rest/sql/dbname 作为 restful 请求的 url,在 SQL 中不需要指定 dbname。例如:url 为 jdbc:TAOS-RS://127.0.0.1:6041/test,那么,可以执行 sql:insert into t1 using weather(ts, temperature) tags('beijing') values(now, 24.6); + +::: + + + + +### 指定 URL 和 Properties 获取连接 + +除了通过指定的 URL 获取连接,还可以使用 Properties 指定建立连接时的参数。 + +**注意**: + +- 应用中设置的 client parameter 为进程级别的,即如果要更新 client 的参数,需要重启应用。这是因为 client parameter 是全局参数,仅在应用程序的第一次设置生效。 +- 以下示例代码基于 taos-jdbcdriver-2.0.36。 + +```java +public Connection getConn() throws Exception{ + Class.forName("com.taosdata.jdbc.TSDBDriver"); + String jdbcUrl = "jdbc:TAOS://taosdemo.com:6030/test?user=root&password=taosdata"; + Properties connProps = new Properties(); + connProps.setProperty(TSDBDriver.PROPERTY_KEY_CHARSET, "UTF-8"); + connProps.setProperty(TSDBDriver.PROPERTY_KEY_LOCALE, "en_US.UTF-8"); + connProps.setProperty(TSDBDriver.PROPERTY_KEY_TIME_ZONE, "UTC-8"); + connProps.setProperty("debugFlag", "135"); + connProps.setProperty("maxSQLLength", "1048576"); + Connection conn = DriverManager.getConnection(jdbcUrl, connProps); + return conn; +} + +public Connection getRestConn() throws Exception{ + Class.forName("com.taosdata.jdbc.rs.RestfulDriver"); + String jdbcUrl = "jdbc:TAOS-RS://taosdemo.com:6041/test?user=root&password=taosdata"; + Properties connProps = new Properties(); + connProps.setProperty(TSDBDriver.PROPERTY_KEY_BATCH_LOAD, "true"); + Connection conn = DriverManager.getConnection(jdbcUrl, connProps); + return conn; +} +``` + +以上示例,建立一个到 hostname 为 taosdemo.com,端口为 6030/6041,数据库名为 test 的连接。这个连接在 url 中指定了用户名(user)为 root,密码(password)为 taosdata,并在 connProps 中指定了使用的字符集、语言环境、时区、是否开启批量拉取等信息。 + +properties 中的配置参数如下: + +- TSDBDriver.PROPERTY_KEY_USER:登录 TDengine 用户名,默认值 'root'。 +- TSDBDriver.PROPERTY_KEY_PASSWORD:用户登录密码,默认值 'taosdata'。 +- TSDBDriver.PROPERTY_KEY_BATCH_LOAD: true:在执行查询时批量拉取结果集;false:逐行拉取结果集。默认值为:false。 +- TSDBDriver.PROPERTY_KEY_BATCH_ERROR_IGNORE:true:在执行 Statement 的 executeBatch 时,如果中间有一条 SQL 执行失败,继续执行下面的 sq 了。false:不再执行失败 SQL 后的任何语句。默认值为:false。 +- TSDBDriver.PROPERTY_KEY_CONFIG_DIR:仅在使用 JDBC 原生连接时生效。客户端配置文件目录路径,Linux OS 上默认值 `/etc/taos`,Windows OS 上默认值 `C:/TDengine/cfg`。 +- TSDBDriver.PROPERTY_KEY_CHARSET:仅在使用 JDBC 原生连接时生效。 客户端使用的字符集,默认值为系统字符集。 +- TSDBDriver.PROPERTY_KEY_LOCALE:仅在使用 JDBC 原生连接时生效。 客户端语言环境,默认值系统当前 locale。 +- TSDBDriver.PROPERTY_KEY_TIME_ZONE:仅在使用 JDBC 原生连接时生效。 客户端使用的时区,默认值为系统当前时区。 +- 此外对 JDBC 原生连接,通过指定 URL 和 Properties 还可以指定其他参数,比如日志级别、SQL 长度等。更多详细配置请参考[客户端配置](/reference/config/#仅客户端适用)。 + +### 配置参数的优先级 + +通过前面三种方式获取连接,如果配置参数在 url、Properties、客户端配置文件中有重复,则参数的`优先级由高到低`分别如下: + +1. JDBC URL 参数,如上所述,可以在 JDBC URL 的参数中指定。 +2. Properties connProps +3. 使用原生连接时,TDengine 客户端驱动的配置文件 taos.cfg + +例如:在 url 中指定了 password 为 taosdata,在 Properties 中指定了 password 为 taosdemo,那么,JDBC 会使用 url 中的 password 建立连接。 + +## 使用示例 + +### 创建数据库和表 + +```java +Statement stmt = conn.createStatement(); + +// create database +stmt.executeUpdate("create database if not exists db"); + +// use database +stmt.executeUpdate("use db"); + +// create table +stmt.executeUpdate("create table if not exists tb (ts timestamp, temperature int, humidity float)"); +``` + +> **注意**:如果不使用 `use db` 指定数据库,则后续对表的操作都需要增加数据库名称作为前缀,如 db.tb。 + +### 插入数据 + +```java +// insert data +int affectedRows = stmt.executeUpdate("insert into tb values(now, 23, 10.3) (now + 1s, 20, 9.3)"); + +System.out.println("insert " + affectedRows + " rows."); +``` + +> now 为系统内部函数,默认为客户端所在计算机当前时间。 +> `now + 1s` 代表客户端当前时间往后加 1 秒,数字后面代表时间单位:a(毫秒),s(秒),m(分),h(小时),d(天),w(周),n(月),y(年)。 + +### 查询数据 + +```java +// query data +ResultSet resultSet = stmt.executeQuery("select * from tb"); + +Timestamp ts = null; +int temperature = 0; +float humidity = 0; +while(resultSet.next()){ + + ts = resultSet.getTimestamp(1); + temperature = resultSet.getInt(2); + humidity = resultSet.getFloat("humidity"); + + System.out.printf("%s, %d, %s\n", ts, temperature, humidity); +} +``` + +> 查询和操作关系型数据库一致,使用下标获取返回字段内容时从 1 开始,建议使用字段名称获取。 + +### 处理异常 + +在报错后,通过 SQLException 可以获取到错误的信息和错误码: + +```java +try (Statement statement = connection.createStatement()) { + // executeQuery + ResultSet resultSet = statement.executeQuery(sql); + // print result + printResult(resultSet); +} catch (SQLException e) { + System.out.println("ERROR Message: " + e.getMessage()); + System.out.println("ERROR Code: " + e.getErrorCode()); + e.printStackTrace(); +} +``` + +JDBC 连接器可能报错的错误码包括 3 种:JDBC driver 本身的报错(错误码在 0x2301 到 0x2350 之间),原生连接方法的报错(错误码在 0x2351 到 0x2400 之间),TDengine 其他功能模块的报错。 + +具体的错误码请参考: + +- [TDengine Java Connector](https://github.com/taosdata/TDengine/blob/develop/src/connector/jdbc/src/main/java/com/taosdata/jdbc/TSDBErrorNumbers.java) +- [TDengine_ERROR_CODE](https://github.com/taosdata/TDengine/blob/develop/src/inc/taoserror.h) + +### 通过参数绑定写入数据 + +从 2.1.2.0 版本开始,TDengine 的 JDBC 原生连接实现大幅改进了参数绑定方式对数据写入(INSERT)场景的支持。采用这种方式写入数据时,能避免 SQL 语法解析的资源消耗,从而在很多情况下显著提升写入性能。 + +**注意**: + +- JDBC REST 连接目前不支持参数绑定 +- 以下示例代码基于 taos-jdbcdriver-2.0.36 +- binary 类型数据需要调用 setString 方法,nchar 类型数据需要调用 setNString 方法 +- setString 和 setNString 都要求用户在 size 参数里声明表定义中对应列的列宽 + +```java +public class ParameterBindingDemo { + + private static final String host = "127.0.0.1"; + private static final Random random = new Random(System.currentTimeMillis()); + private static final int BINARY_COLUMN_SIZE = 20; + private static final String[] schemaList = { + "create table stable1(ts timestamp, f1 tinyint, f2 smallint, f3 int, f4 bigint) tags(t1 tinyint, t2 smallint, t3 int, t4 bigint)", + "create table stable2(ts timestamp, f1 float, f2 double) tags(t1 float, t2 double)", + "create table stable3(ts timestamp, f1 bool) tags(t1 bool)", + "create table stable4(ts timestamp, f1 binary(" + BINARY_COLUMN_SIZE + ")) tags(t1 binary(" + BINARY_COLUMN_SIZE + "))", + "create table stable5(ts timestamp, f1 nchar(" + BINARY_COLUMN_SIZE + ")) tags(t1 nchar(" + BINARY_COLUMN_SIZE + "))" + }; + private static final int numOfSubTable = 10, numOfRow = 10; + + public static void main(String[] args) throws SQLException { + + String jdbcUrl = "jdbc:TAOS://" + host + ":6030/"; + Connection conn = DriverManager.getConnection(jdbcUrl, "root", "taosdata"); + + init(conn); + + bindInteger(conn); + + bindFloat(conn); + + bindBoolean(conn); + + bindBytes(conn); + + bindString(conn); + + conn.close(); + } + + private static void init(Connection conn) throws SQLException { + try (Statement stmt = conn.createStatement()) { + stmt.execute("drop database if exists test_parabind"); + stmt.execute("create database if not exists test_parabind"); + stmt.execute("use test_parabind"); + for (int i = 0; i < schemaList.length; i++) { + stmt.execute(schemaList[i]); + } + } + } + + private static void bindInteger(Connection conn) throws SQLException { + String sql = "insert into ? using stable1 tags(?,?,?,?) values(?,?,?,?,?)"; + + try (TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class)) { + + for (int i = 1; i <= numOfSubTable; i++) { + // set table name + pstmt.setTableName("t1_" + i); + // set tags + pstmt.setTagByte(0, Byte.parseByte(Integer.toString(random.nextInt(Byte.MAX_VALUE)))); + pstmt.setTagShort(1, Short.parseShort(Integer.toString(random.nextInt(Short.MAX_VALUE)))); + pstmt.setTagInt(2, random.nextInt(Integer.MAX_VALUE)); + pstmt.setTagLong(3, random.nextLong()); + // set columns + ArrayList tsList = new ArrayList<>(); + long current = System.currentTimeMillis(); + for (int j = 0; j < numOfRow; j++) + tsList.add(current + j); + pstmt.setTimestamp(0, tsList); + + ArrayList f1List = new ArrayList<>(); + for (int j = 0; j < numOfRow; j++) + f1List.add(Byte.parseByte(Integer.toString(random.nextInt(Byte.MAX_VALUE)))); + pstmt.setByte(1, f1List); + + ArrayList f2List = new ArrayList<>(); + for (int j = 0; j < numOfRow; j++) + f2List.add(Short.parseShort(Integer.toString(random.nextInt(Short.MAX_VALUE)))); + pstmt.setShort(2, f2List); + + ArrayList f3List = new ArrayList<>(); + for (int j = 0; j < numOfRow; j++) + f3List.add(random.nextInt(Integer.MAX_VALUE)); + pstmt.setInt(3, f3List); + + ArrayList f4List = new ArrayList<>(); + for (int j = 0; j < numOfRow; j++) + f4List.add(random.nextLong()); + pstmt.setLong(4, f4List); + + // add column + pstmt.columnDataAddBatch(); + } + // execute column + pstmt.columnDataExecuteBatch(); + } + } + + private static void bindFloat(Connection conn) throws SQLException { + String sql = "insert into ? using stable2 tags(?,?) values(?,?,?)"; + + TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class); + + for (int i = 1; i <= numOfSubTable; i++) { + // set table name + pstmt.setTableName("t2_" + i); + // set tags + pstmt.setTagFloat(0, random.nextFloat()); + pstmt.setTagDouble(1, random.nextDouble()); + // set columns + ArrayList tsList = new ArrayList<>(); + long current = System.currentTimeMillis(); + for (int j = 0; j < numOfRow; j++) + tsList.add(current + j); + pstmt.setTimestamp(0, tsList); + + ArrayList f1List = new ArrayList<>(); + for (int j = 0; j < numOfRow; j++) + f1List.add(random.nextFloat()); + pstmt.setFloat(1, f1List); + + ArrayList f2List = new ArrayList<>(); + for (int j = 0; j < numOfRow; j++) + f2List.add(random.nextDouble()); + pstmt.setDouble(2, f2List); + + // add column + pstmt.columnDataAddBatch(); + } + // execute + pstmt.columnDataExecuteBatch(); + // close if no try-with-catch statement is used + pstmt.close(); + } + + private static void bindBoolean(Connection conn) throws SQLException { + String sql = "insert into ? using stable3 tags(?) values(?,?)"; + + try (TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class)) { + for (int i = 1; i <= numOfSubTable; i++) { + // set table name + pstmt.setTableName("t3_" + i); + // set tags + pstmt.setTagBoolean(0, random.nextBoolean()); + // set columns + ArrayList tsList = new ArrayList<>(); + long current = System.currentTimeMillis(); + for (int j = 0; j < numOfRow; j++) + tsList.add(current + j); + pstmt.setTimestamp(0, tsList); + + ArrayList f1List = new ArrayList<>(); + for (int j = 0; j < numOfRow; j++) + f1List.add(random.nextBoolean()); + pstmt.setBoolean(1, f1List); + + // add column + pstmt.columnDataAddBatch(); + } + // execute + pstmt.columnDataExecuteBatch(); + } + } + + private static void bindBytes(Connection conn) throws SQLException { + String sql = "insert into ? using stable4 tags(?) values(?,?)"; + + try (TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class)) { + + for (int i = 1; i <= numOfSubTable; i++) { + // set table name + pstmt.setTableName("t4_" + i); + // set tags + pstmt.setTagString(0, new String("abc")); + + // set columns + ArrayList tsList = new ArrayList<>(); + long current = System.currentTimeMillis(); + for (int j = 0; j < numOfRow; j++) + tsList.add(current + j); + pstmt.setTimestamp(0, tsList); + + ArrayList f1List = new ArrayList<>(); + for (int j = 0; j < numOfRow; j++) { + f1List.add(new String("abc")); + } + pstmt.setString(1, f1List, BINARY_COLUMN_SIZE); + + // add column + pstmt.columnDataAddBatch(); + } + // execute + pstmt.columnDataExecuteBatch(); + } + } + + private static void bindString(Connection conn) throws SQLException { + String sql = "insert into ? using stable5 tags(?) values(?,?)"; + + try (TSDBPreparedStatement pstmt = conn.prepareStatement(sql).unwrap(TSDBPreparedStatement.class)) { + + for (int i = 1; i <= numOfSubTable; i++) { + // set table name + pstmt.setTableName("t5_" + i); + // set tags + pstmt.setTagNString(0, "北京-abc"); + + // set columns + ArrayList tsList = new ArrayList<>(); + long current = System.currentTimeMillis(); + for (int j = 0; j < numOfRow; j++) + tsList.add(current + j); + pstmt.setTimestamp(0, tsList); + + ArrayList f1List = new ArrayList<>(); + for (int j = 0; j < numOfRow; j++) { + f1List.add("北京-abc"); + } + pstmt.setNString(1, f1List, BINARY_COLUMN_SIZE); + + // add column + pstmt.columnDataAddBatch(); + } + // execute + pstmt.columnDataExecuteBatch(); + } + } +} +``` + +用于设定 TAGS 取值的方法总共有: + +```java +public void setTagNull(int index, int type) +public void setTagBoolean(int index, boolean value) +public void setTagInt(int index, int value) +public void setTagByte(int index, byte value) +public void setTagShort(int index, short value) +public void setTagLong(int index, long value) +public void setTagTimestamp(int index, long value) +public void setTagFloat(int index, float value) +public void setTagDouble(int index, double value) +public void setTagString(int index, String value) +public void setTagNString(int index, String value) +``` + +用于设定 VALUES 数据列的取值的方法总共有: + +```java +public void setInt(int columnIndex, ArrayList list) throws SQLException +public void setFloat(int columnIndex, ArrayList list) throws SQLException +public void setTimestamp(int columnIndex, ArrayList list) throws SQLException +public void setLong(int columnIndex, ArrayList list) throws SQLException +public void setDouble(int columnIndex, ArrayList list) throws SQLException +public void setBoolean(int columnIndex, ArrayList list) throws SQLException +public void setByte(int columnIndex, ArrayList list) throws SQLException +public void setShort(int columnIndex, ArrayList list) throws SQLException +public void setString(int columnIndex, ArrayList list, int size) throws SQLException +public void setNString(int columnIndex, ArrayList list, int size) throws SQLException +``` + +### 无模式写入 + +从 2.2.0.0 版本开始,TDengine 增加了对无模式写入功能。无模式写入兼容 InfluxDB 的 行协议(Line Protocol)、OpenTSDB 的 telnet 行协议和 OpenTSDB 的 JSON 格式协议。详情请参见[无模式写入](/reference/schemaless/)。 + +**注意**: + +- JDBC REST 连接目前不支持无模式写入 +- 以下示例代码基于 taos-jdbcdriver-2.0.36 + +```java +public class SchemalessInsertTest { + private static final String host = "127.0.0.1"; + private static final String lineDemo = "st,t1=3i64,t2=4f64,t3=\"t3\" c1=3i64,c3=L\"passit\",c2=false,c4=4f64 1626006833639000000"; + private static final String telnetDemo = "stb0_0 1626006833 4 host=host0 interface=eth0"; + private static final String jsonDemo = "{\"metric\": \"meter_current\",\"timestamp\": 1346846400,\"value\": 10.3, \"tags\": {\"groupid\": 2, \"location\": \"Beijing\", \"id\": \"d1001\"}}"; + + public static void main(String[] args) throws SQLException { + final String url = "jdbc:TAOS://" + host + ":6030/?user=root&password=taosdata"; + try (Connection connection = DriverManager.getConnection(url)) { + init(connection); + + SchemalessWriter writer = new SchemalessWriter(connection); + writer.write(lineDemo, SchemalessProtocolType.LINE, SchemalessTimestampType.NANO_SECONDS); + writer.write(telnetDemo, SchemalessProtocolType.TELNET, SchemalessTimestampType.MILLI_SECONDS); + writer.write(jsonDemo, SchemalessProtocolType.JSON, SchemalessTimestampType.NOT_CONFIGURED); + } + } + + private static void init(Connection connection) throws SQLException { + try (Statement stmt = connection.createStatement()) { + stmt.executeUpdate("drop database if exists test_schemaless"); + stmt.executeUpdate("create database if not exists test_schemaless"); + stmt.executeUpdate("use test_schemaless"); + } + } +} +``` + +### 订阅 + +TDengine Java 连接器支持订阅功能,应用 API 如下: + +#### 创建订阅 + +```java +TSDBSubscribe sub = ((TSDBConnection)conn).subscribe("topic", "select * from meters", false); +``` + +`subscribe` 方法的三个参数含义如下: + +- topic:订阅的主题(即名称),此参数是订阅的唯一标识 +- sql:订阅的查询语句,此语句只能是 `select` 语句,只应查询原始数据,只能按时间正序查询数据 +- restart:如果订阅已经存在,是重新开始,还是继续之前的订阅 + +如上面的例子将使用 SQL 语句 `select * from meters` 创建一个名为 `topic` 的订阅,如果这个订阅已经存在,将继续之前的查询进度,而不是从头开始消费所有的数据。 + +#### 订阅消费数据 + +```java +int total = 0; +while(true) { + TSDBResultSet rs = sub.consume(); + int count = 0; + while(rs.next()) { + count++; + } + total += count; + System.out.printf("%d rows consumed, total %d\n", count, total); + Thread.sleep(1000); +} +``` + +`consume` 方法返回一个结果集,其中包含从上次 `consume` 到目前为止的所有新数据。请务必按需选择合理的调用 `consume` 的频率(如例子中的 `Thread.sleep(1000)`),否则会给服务端造成不必要的压力。 + +#### 关闭订阅 + +```java +sub.close(true); +``` + +`close` 方法关闭一个订阅。如果其参数为 `true` 表示保留订阅进度信息,后续可以创建同名订阅继续消费数据;如为 `false` 则不保留订阅进度。 + +### 关闭资源 + +```java +resultSet.close(); +stmt.close(); +conn.close(); +``` + +> `注意务必要将 connection 进行关闭`,否则会出现连接泄露。 + +### 与连接池使用 + +#### HikariCP + +使用示例如下: + +```java + public static void main(String[] args) throws SQLException { + HikariConfig config = new HikariConfig(); + // jdbc properties + config.setJdbcUrl("jdbc:TAOS://127.0.0.1:6030/log"); + config.setUsername("root"); + config.setPassword("taosdata"); + // connection pool configurations + config.setMinimumIdle(10); //minimum number of idle connection + config.setMaximumPoolSize(10); //maximum number of connection in the pool + config.setConnectionTimeout(30000); //maximum wait milliseconds for get connection from pool + config.setMaxLifetime(0); // maximum life time for each connection + config.setIdleTimeout(0); // max idle time for recycle idle connection + config.setConnectionTestQuery("select server_status()"); //validation query + + HikariDataSource ds = new HikariDataSource(config); //create datasource + + Connection connection = ds.getConnection(); // get connection + Statement statement = connection.createStatement(); // get statement + + //query or insert + // ... + + connection.close(); // put back to conneciton pool +} +``` + +> 通过 HikariDataSource.getConnection() 获取连接后,使用完成后需要调用 close() 方法,实际上它并不会关闭连接,只是放回连接池中。 +> 更多 HikariCP 使用问题请查看[官方说明](https://github.com/brettwooldridge/HikariCP)。 + +#### Druid + +使用示例如下: + +```java +public static void main(String[] args) throws Exception { + + DruidDataSource dataSource = new DruidDataSource(); + // jdbc properties + dataSource.setDriverClassName("com.taosdata.jdbc.TSDBDriver"); + dataSource.setUrl(url); + dataSource.setUsername("root"); + dataSource.setPassword("taosdata"); + // pool configurations + dataSource.setInitialSize(10); + dataSource.setMinIdle(10); + dataSource.setMaxActive(10); + dataSource.setMaxWait(30000); + dataSource.setValidationQuery("select server_status()"); + + Connection connection = dataSource.getConnection(); // get connection + Statement statement = connection.createStatement(); // get statement + //query or insert + // ... + + connection.close(); // put back to conneciton pool +} +``` + +> 更多 druid 使用问题请查看[官方说明](https://github.com/alibaba/druid)。 + +**注意事项:** + +- TDengine `v1.6.4.1` 版本开始提供了一个专门用于心跳检测的函数 `select server_status()`,所以在使用连接池时推荐使用 `select server_status()` 进行 Validation Query。 + +如下所示,`select server_status()` 执行成功会返回 `1`。 + +```sql +taos> select server_status(); +server_status()| +================ +1 | +Query OK, 1 row(s) in set (0.000141s) +``` + +### 更多示例程序 + +示例程序源码位于 `TDengine/examples/JDBC` 下: + +- JDBCDemo:JDBC 示例源程序。 +- JDBCConnectorChecker:JDBC 安装校验源程序及 jar 包。 +- connectionPools:HikariCP, Druid, dbcp, c3p0 等连接池中使用 taos-jdbcdriver。 +- SpringJdbcTemplate:Spring JdbcTemplate 中使用 taos-jdbcdriver。 +- mybatisplus-demo:Springboot + Mybatis 中使用 taos-jdbcdriver。 + +请参考:[JDBC example](https://github.com/taosdata/TDengine/tree/develop/examples/JDBC) + +## 最近更新记录 + +| taos-jdbcdriver 版本 | 主要变化 | +| :------------------: | :----------------------------: | +| 2.0.38 | JDBC REST 连接增加批量拉取功能 | +| 2.0.37 | 增加对 json tag 支持 | +| 2.0.36 | 增加对 schemaless 写入支持 | + +## 常见问题 + +1. 使用 Statement 的 `addBatch()` 和 `executeBatch()` 来执行“批量写入/更新”,为什么没有带来性能上的提升? + + **原因**:TDengine 的 JDBC 实现中,通过 `addBatch` 方法提交的 SQL 语句,会按照添加的顺序,依次执行,这种方式没有减少与服务端的交互次数,不会带来性能上的提升。 + + **解决方法**:1. 在一条 insert 语句中拼接多个 values 值;2. 使用多线程的方式并发插入;3. 使用参数绑定的写入方式 + +2. java.lang.UnsatisfiedLinkError: no taos in java.library.path + + **原因**:程序没有找到依赖的本地函数库 taos。 + + **解决方法**:Windows 下可以将 C:\TDengine\driver\taos.dll 拷贝到 C:\Windows\System32\ 目录下,Linux 下将建立如下软链 `ln -s /usr/local/taos/driver/libtaos.so.x.x.x.x /usr/lib/libtaos.so` 即可。 + +3. java.lang.UnsatisfiedLinkError: taos.dll Can't load AMD 64 bit on a IA 32-bit platform + + **原因**:目前 TDengine 只支持 64 位 JDK。 + + **解决方法**:重新安装 64 位 JDK。 + +4. 其它问题请参考 [FAQ](/train-faq/faq) + +## API 参考 + +[taos-jdbcdriver doc](https://docs.taosdata.com/api/taos-jdbcdriver) diff --git a/docs-cn/14-reference/03-connector/node.mdx b/docs-cn/14-reference/03-connector/node.mdx new file mode 100644 index 0000000000..12345fa9fe --- /dev/null +++ b/docs-cn/14-reference/03-connector/node.mdx @@ -0,0 +1,259 @@ +--- +toc_max_heading_level: 4 +sidebar_position: 6 +sidebar_label: Node.js +title: TDengine Node.js Connector +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +import Preparition from "./_preparition.mdx"; +import NodeInsert from "../../07-develop/03-insert-data/_js_sql.mdx"; +import NodeInfluxLine from "../../07-develop/03-insert-data/_js_line.mdx"; +import NodeOpenTSDBTelnet from "../../07-develop/03-insert-data/_js_opts_telnet.mdx"; +import NodeOpenTSDBJson from "../../07-develop/03-insert-data/_js_opts_json.mdx"; +import NodeQuery from "../../07-develop/04-query-data/_js.mdx"; +import NodeAsyncQuery from "../../07-develop/04-query-data/_js_async.mdx"; + +`td2.0-connector` 和 `td2.0-rest-connector` 是 TDengine 的官方 Node.js 语言连接器。Node.js 开发人员可以通过它开发可以存取 TDengine 集群数据的应用软件。 + +`td2.0-connector` 是**原生连接器**,它通过 TDengine 客户端驱动程序(taosc)连接 TDengine 运行实例,支持数据写入、查询、订阅、schemaless 接口和参数绑定接口等功能。`td2.0-rest-connector` 是 **REST 连接器**,它通过 taosAdapter 提供的 REST 接口连接 TDengine 的运行实例。REST 连接器可以在任何平台运行,但性能略为下降,接口实现的功能特性集合和原生接口有少量不同。 + +Node.js 连接器源码托管在 [GitHub](https://github.com/taosdata/taos-connector-node)。 + +## 支持的平台 + +原生连接器支持的平台和 TDengine 客户端驱动支持的平台一致。 +REST 连接器支持所有能运行 Node.js 的平台。 + +## 版本支持 + +请参考[版本支持列表](/reference/connector#版本支持) + +## 支持的功能特性 + +### 原生连接器 + +1. 连接管理 +2. 普通查询 +3. 连续查询 +4. 参数绑定 +5. 订阅功能 +6. Schemaless + +### REST 连接器 + +1. 连接管理 +2. 普通查询 +3. 连续查询 + +## 安装步骤 + +### 安装前准备 + +- 安装 Node.js 开发环境 +- 如果使用 REST 连接器,跳过此步。但如果使用原生连接器,请安装 TDengine 客户端驱动,具体步骤请参考[安装客户端驱动](/reference/connector#安装客户端驱动)。我们使用 [node-gyp](https://github.com/nodejs/node-gyp) 和 TDengine 实例进行交互,还需要根据具体操作系统来安装下文提到的一些依赖工具。 + + + + +- `python` (建议`v2.7` , `v3.x.x` 目前还不支持) +- `td2.0-connector` 2.0.6 支持 Node.js LTS v10.9.0 或更高版本, Node.js LTS v12.8.0 或更高版本;2.0.5 及更早版本支持 Node.js LTS v10.x 版本。其他版本可能存在包兼容性的问题 +- `make` +- C 语言编译器,[GCC](https://gcc.gnu.org) v4.8.5 或更高版本 + + + + +- 安装方法 1 + +使用微软的[ windows-build-tools ](https://github.com/felixrieseberg/windows-build-tools)在`cmd` 命令行界面执行`npm install --global --production windows-build-tools` 即可安装所有的必备工具。 + +- 安装方法 2 + +手动安装以下工具: + +- 安装 Visual Studio 相关:[Visual Studio Build 工具](https://visualstudio.microsoft.com/thank-you-downloading-visual-studio/?sku=BuildTools) 或者 [Visual Studio 2017 Community](https://visualstudio.microsoft.com/pl/thank-you-downloading-visual-studio/?sku=Community) +- 安装 [Python](https://www.python.org/downloads/) 2.7(`v3.x.x` 暂不支持) 并执行 `npm config set python python2.7` +- 进入`cmd`命令行界面,`npm config set msvs_version 2017` + +参考微软的 Node.js 用户手册[ Microsoft's Node.js Guidelines for Windows](https://github.com/Microsoft/nodejs-guidelines/blob/master/windows-environment.md#compiling-native-addon-modules)。 + +如果在 Windows 10 ARM 上使用 ARM64 Node.js,还需添加 "Visual C++ compilers and libraries for ARM64" 和 "Visual C++ ATL for ARM64"。 + + + + +### 使用 npm 安装 + + + + +```bash +npm install td2.0-connector +``` + + + + +```bash +npm i td2.0-rest-connector +``` + + + + +### 安装验证 + +在安装好 TDengine 客户端后,使用 nodejsChecker.js 程序能够验证当前环境是否支持 Node.js 方式访问 TDengine。 + +验证方法: + +- 新建安装验证目录,例如:`~/tdengine-test`,下载 GitHub 上 [nodejsChecker.js 源代码](https://github.com/taosdata/TDengine/tree/develop/examples/nodejs/nodejsChecker.js)到本地。 + +- 在命令行中执行以下命令。 + +```bash +npm init -y +npm install td2.0-connector +node nodejsChecker.js host=localhost +``` + +- 执行以上步骤后,在命令行会输出 nodejsChecker.js 连接 TDengine 实例,并执行简单插入和查询的结果。 + +## 建立连接 + +请选择使用一种连接器。 + + + + +安装并引用 `td2.0-connector` 包。 + +```javascript +//A cursor also needs to be initialized in order to interact with TDengine from Node.js. +const taos = require("td2.0-connector"); +var conn = taos.connect({ + host: "127.0.0.1", + user: "root", + password: "taosdata", + config: "/etc/taos", + port: 0, +}); +var cursor = conn.cursor(); // Initializing a new cursor + +//Close a connection +conn.close(); +``` + + + + +安装并引用 `td2.0-rest-connector` 包。 + +```javascript +//A cursor also needs to be initialized in order to interact with TDengine from Node.js. +import { options, connect } from "td2.0-rest-connector"; +options.path = "/rest/sqlt"; +// set host +options.host = "localhost"; +// set other options like user/passwd + +let conn = connect(options); +let cursor = conn.cursor(); +``` + + + + +## 使用示例 + +### 写入数据 + +#### SQL 写入 + + + +#### InfluxDB 行协议写入 + + + +#### OpenTSDB Telnet 行协议写入 + + + +#### OpenTSDB JSON 行协议写入 + + + +### 查询数据 + +#### 同步查询 + + + +#### 异步查询 + + + +## 更多示例程序 + +| 示例程序 | 示例程序描述 | +| ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------- | +| [connection](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/cursorClose.js) | 建立连接的示例。 | +| [stmtBindBatch](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/stmtBindParamBatchSample.js) | 绑定多行参数插入的示例。 | +| [stmtBind](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/stmtBindParamSample.js) | 一行一行绑定参数插入的示例。 | +| [stmtBindSingleParamBatch](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/stmtBindSingleParamBatchSample.js) | 按列绑定参数插入的示例。 | +| [stmtUseResult](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/stmtUseResultSample.js) | 绑定参数查询的示例。 | +| [json tag](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/testJsonTag.js) | Json tag 的使用示例。 | +| [Nanosecond](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/testNanoseconds.js) | 时间戳为纳秒精度的使用的示例。 | +| [Microsecond](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/testMicroseconds.js) | 时间戳为微秒精度的使用的示例。 | +| [schemless insert](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/testSchemalessInsert.js) | schemless 插入的示例。 | +| [subscribe](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/testSubscribe.js) | 订阅的使用示例。 | +| [asyncQuery](https://github.com/taosdata/taos-connector-node/tree/develop/nodejs/examples/tset.js) | 异步查询的使用示例。 | +| [REST](https://github.com/taosdata/taos-connector-node/blob/develop/typescript-rest/example/example.ts) | 使用 REST 连接的 TypeScript 使用示例。 | + +## 使用限制 + +Node.js 连接器 >= v2.0.6 目前支持 node 的版本为:支持 >=v12.8.0 <= v12.9.1 || >=v10.20.0 <= v10.9.0 ;2.0.5 及更早版本支持 v10.x 版本,其他版本可能存在包兼容性的问题。 + +## 其他说明 + +Node.js 连接器的使用参见[视频教程](https://www.taosdata.com/blog/2020/11/11/1957.html)。 + +## 常见问题 + +1. 使用 REST 连接需要启动 taosadapter。 + + ```bash + sudo systemctl start taosadapter + ``` + +2. Node.js 版本 + + 连接器 >v2.0.6 目前兼容的 Node.js 版本为:>=v10.20.0 <= v10.9.0 || >=v12.8.0 <= v12.9.1 + +3. "Unable to establish connection","Unable to resolve FQDN" + + 一般都是因为配置 FQDN 不正确。 可以参考[如何彻底搞懂 TDengine 的 FQDN](https://www.taosdata.com/blog/2021/07/29/2741.html) 。 + +## 重要更新记录 + +### 原生连接器 + +| td2.0-connector 版本 | 说明 | +| -------------------- | ---------------------------------------------------------------- | +| 2.0.12 | 修复 cursor.close() 报错的 bug。 | +| 2.0.11 | 支持绑定参数、json tag、schemaless 接口等功能。 | +| 2.0.10 | 支持连接管理,普通查询、连续查询、获取系统信息、订阅功能等功能。 | + +### REST 连接器 + +| td2.0-rest-connector 版本 | 说明 | +| ------------------------- | ---------------------------------------------------------------- | +| 1.0.3 | 支持连接管理、普通查询、获取系统信息、错误信息、连续查询等功能。 | + +## API 参考 + +[API 参考](https://docs.taosdata.com/api/td2.0-connector/) diff --git a/docs-cn/14-reference/03-connector/python.mdx b/docs-cn/14-reference/03-connector/python.mdx new file mode 100644 index 0000000000..6608fb7bd2 --- /dev/null +++ b/docs-cn/14-reference/03-connector/python.mdx @@ -0,0 +1,349 @@ +--- +sidebar_position: 3 +sidebar_label: Python +title: TDengine Python Connector +description: "taospy 是 TDengine 的官方 Python 连接器。taospy 提供了丰富的 API, 使得 Python 应用可以很方便地使用 TDengine。tasopy 对 TDengine 的原生接口和 REST 接口都进行了封装, 分别对应 tasopy 的两个子模块:tasos 和 taosrest。除了对原生接口和 REST 接口的封装,taospy 还提供了符合 Python 数据访问规范(PEP 249)的编程接口。这使得 taospy 和很多第三方工具集成变得简单,比如 SQLAlchemy 和 pandas" +--- + +import Tabs from "@theme/Tabs"; +import TabItem from "@theme/TabItem"; + +`taospy` 是 TDengine 的官方 Python 连接器。`taospy` 提供了丰富的 API, 使得 Python 应用可以很方便地使用 TDengine。`taospy` 对 TDengine 的[原生接口](/reference/connector/cpp)和 [REST 接口](/reference/rest-api)都进行了封装, 分别对应 `taospy` 包的 `taos` 模块 和 `taosrest` 模块。 +除了对原生接口和 REST 接口的封装,`taospy` 还提供了符合 [Python 数据访问规范(PEP 249)](https://peps.python.org/pep-0249/) 的编程接口。这使得 `taospy` 和很多第三方工具集成变得简单,比如 [SQLAlchemy](https://www.sqlalchemy.org/) 和 [pandas](https://pandas.pydata.org/)。 + +使用客户端驱动提供的原生接口直接与服务端建立的连接的方式下文中称为“原生连接”;使用 taosAdapter 提供的 REST 接口与服务端建立的连接的方式下文中称为“REST 连接”。 + +Python 连接器的源码托管在 [GitHub](https://github.com/taosdata/taos-connector-python)。 + +## 支持的平台 + +- 原生连接[支持的平台](/reference/connector/#支持的平台)和 TDengine 客户端支持的平台一致。 +- REST 连接支持所有能运行 Python 的平台。 + +## 版本选择 + +无论使用什么版本的 TDengine 都建议使用最新版本的 `taospy`。 + +## 支持的功能 + +- 原生连接支持 TDeingine 的所有核心功能, 包括: 连接管理、执行 SQL、参数绑定、订阅、无模式写入(schemaless)。 +- REST 连接支持的功能包括:连接管理、执行 SQL。 (通过执行 SQL 可以: 管理数据库、管理表和超级表、写入数据、查询数据、创建连续查询等)。 + +## 安装 + +### 准备 + +1. 安装 Python。建议使用 Python >= 3.6。如果系统上还没有 Python 可参考 [Python BeginnersGuide](https://wiki.python.org/moin/BeginnersGuide/Download) 安装。 +2. 安装 [pip](https://pypi.org/project/pip/)。大部分情况下 Python 的安装包都自带了 pip 工具, 如果没有请参考 [pip docuemntation](https://pip.pypa.io/en/stable/installation/) 安装。 +3. 如果使用原生连接,还需[安装客户端驱动](../#安装客户端驱动)。客户端软件包含了 TDengine 客户端动态链接库(libtaos.so 或 taos.dll) 和 TDengine CLI。 + +### 使用 pip 安装 + +#### 卸载旧版本 + +如果以前安装过旧版本的 Python 连接器, 请提前卸载。 + +``` +pip3 uninstall taos taospy +``` + +:::note +较早的 TDengine 客户端软件包含了 Python 连接器。如果从客户端软件的安装目录安装了 Python 连接器,那么对应的 Python 包名是 `taos`。 所以上述卸载命令包含了 `taos`, 不存在也没关系。 + +::: + +#### 安装 `taospy` + + + + +安装最新版本 + +``` +pip3 install taospy +``` + +也可以指定某个特定版本安装。 + +``` +pip3 install taospy==2.3.0 +``` + + + + +``` +pip3 install git+https://github.com/taosdata/taos-connector-python.git +``` + + + + +### 安装验证 + + + + +对于原生连接,需要验证客户端驱动和 Python 连接器本身是否都正确安装。如果能成功导入 `taos` 模块,则说明已经正确安装了客户端驱动和 Python 连接器。可在 Python 交互式 Shell 中输入: + +```python +import taos +``` + + + + +对于 REST 连接,只需验证是否能成功导入 `taosrest` 模块。可在 Python 交互式 Shell 中输入: + +```python +import taosrest +``` + + + + +:::tip +如果系统上有多个版本的 Python,则可能有多个 `pip` 命令。要确保使用的 `pip` 命令路径是正确的。上面我们用 `pip3` 命令安装,排除了使用 Python 2.x 版本对应的 `pip` 的可能性。但是如果系统上有多个 Python 3.x 版本,仍需检查安装路径是否正确。最简单的验证方式是,在命令再次输入 `pip3 install taospy`, 就会打印出 `taospy` 的具体安装位置,比如在 Windows 上: + +``` +C:\> pip3 install taospy +Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple +Requirement already satisfied: taospy in c:\users\username\appdata\local\programs\python\python310\lib\site-packages (2.3.0) +``` + +::: + +## 建立连接 + +### 连通性测试 + +在用连接器建立连接之前,建议先测试本地 TDengine CLI 到 TDengine 集群的连通性。 + + + + +请确保 TDengine 集群已经启动, 且集群中机器的 FQDN (如果启动的是单机版,FQDN 默认为 hostname)在本机能够解析, 可用 `ping` 命令进行测试: + +``` +ping +``` + +然后测试用 TDengine CLI 能否正常连接集群: + +``` +taos -h -p +``` + +上面的 FQDN 可以为集群中任意一个 dnode 的 FQDN, PORT 为这个 dnode 对应的 serverPort。 + + + + +对于 REST 连接, 除了确保集群已经启动,还要确保 taosAdapter 组件已经启动。可以使用如下 curl 命令测试: + +``` +curl -u root:taosdata http://:/rest/sql -d "select server_version()" +``` + +上面的 FQDN 为运行 taosAdapter 的机器的 FQDN, PORT 为 taosAdapter 配置的监听端口, 默认为 6041。 +如果测试成功,会输出服务器版本信息,比如: + +```json +{ + "status": "succ", + "head": ["server_version()"], + "column_meta": [["server_version()", 8, 8]], + "data": [["2.4.0.16"]], + "rows": 1 +} +``` + + + + +### 使用连接器建立连接 + +以下示例代码假设 TDengine 安装在本机, 且 FQDN 和 serverPort 都使用了默认配置。 + + + + +```python +{{#include docs-examples/python/connect_native_reference.py}} +``` + +`connect` 函数的所有参数都是可选的关键字参数。下面是连接参数的具体说明: + +- `host` : 要连接的节点的 FQDN。 没有默认值。如果不同提供此参数,则会连接客户端配置文件中的 firstEP。 +- `user` :TDengine 用户名。 默认值是 root。 +- `password` : TDengine 用户密码。 默认值是 taosdata。 +- `port` : 要连接的数据节点的起始端口,即 serverPort 配置。默认值是 6030。只有在提供了 host 参数的时候,这个参数才生效。 +- `config` : 客户端配置文件路径。 在 Windows 系统上默认是 `C:\TDengine\cfg`。 在 Linux 系统上默认是 `/etc/taos/`。 +- `timezone` : 查询结果中 TIMESTAMP 类型的数据,转换为 python 的 datetime 对象时使用的时区。默认为本地时区。 + +:::warning +`config` 和 `timezone` 都是进程级别的配置。建议一个进程建立的所有连接都使用相同的参数值。否则可能产生无法预知的错误。 +::: + +:::tip +`connect` 函数返回 `taos.TaosConnection` 实例。 在客户端多线程的场景下,推荐每个线程申请一个独立的连接实例,而不建议多线程共享一个连接。 + +::: + + + + +```python +{{#include docs-examples/python/connect_rest_examples.py:connect}} +``` + +`connect()` 函数的所有参数都是可选的关键字参数。下面是连接参数的具体说明: + +- `host`: 要连接的主机。默认是 localhost。 +- `user`: TDenigne 用户名。默认是 root。 +- `password`: TDeingine 用户密码。默认是 taosdata。 +- `port`: taosAdapter REST 服务监听端口。默认是 6041. +- `timeout`: HTTP 请求超时时间。单位为秒。默认为 `socket._GLOBAL_DEFAULT_TIMEOUT`。 一般无需配置。 + + + + +## 示例程序 + +### 基本使用 + + + + +##### TaosConnection 类的使用 + +`TaosConnection` 类既包含对 PEP249 Connection 接口的实现(如:`cursor`方法和 `close` 方法),也包含很多扩展功能(如: `execute`、 `query`、`schemaless_insert` 和 `subscribe` 方法。 + +```python title="execute 方法" +{{#include docs-examples/python/connection_usage_native_reference.py:insert}} +``` + +```python title="query 方法" +{{#include docs-examples/python/connection_usage_native_reference.py:query}} +``` + +:::tip +查询结果只能获取一次。比如上面的示例中 `fetch_all()` 和 `fetch_all_into_dict()` 只能用一个。重复获取得到的结果为空列表。 +::: + +##### TaosResult 类的使用 + +上面 `TaosConnection` 类的使用示例中,我们已经展示了两种获取查询结果的方法: `fetch_all()` 和 `fetch_all_into_dict()`。除此之外 `TaosResult` 还提供了按行迭代(`rows_iter`)或按数据块迭代(`blocks_iter`)结果集的方法。在查询数据量较大的场景,使用这两个方法会更高效。 + +```python title="blocks_iter 方法" +{{#include docs-examples/python/result_set_examples.py}} +``` +##### TaosCursor 类的使用 + +`TaosConnection` 类和 `TaosResult` 类已经实现了原生接口的所有功能。如果你对 PEP249 规范中的接口比较熟悉也可以使用 `TaosCursor` 类提供的方法。 + +```python title="TaosCursor 的使用" +{{#include docs-examples/python/cursor_usage_native_reference.py}} +``` + +:::note +TaosCursor 类使用原生连接进行写入、查询操作。在客户端多线程的场景下,这个游标实例必须保持线程独享,不能跨线程共享使用,否则会导致返回结果出现错误。 + +::: + + + + +##### TaosRestCursor 类的使用 + +`TaosRestCursor` 类是对 PEP249 Cursor 接口的实现。 + +```python title="TaosRestCursor 的使用" +{{#include docs-examples/python/connect_rest_examples.py:basic}} +``` +- `cursor.execute` : 用来执行任意 SQL 语句。 +- `cursor.rowcount`: 对于写入操作返回写入成功记录数。对于查询操作,返回结果集行数。 +- `cursor.description` : 返回字段的描述信息。关于描述信息的具体格式请参考[TaosRestCursor](https://docs.taosdata.com/api/taospy/taosrest/cursor.html)。 + +##### RestClient 类的使用 + +`RestClient` 类是对于 [REST API](/reference/rest-api) 的直接封装。它只包含一个 `sql()` 方法用于执行任意 SQL 语句, 并返回执行结果。 + +```python title="RestClient 的使用" +{{#include docs-examples/python/rest_client_example.py}} +``` + +对于 `sql()` 方法更详细的介绍, 请参考 [RestClient](https://docs.taosdata.com/api/taospy/taosrest/restclient.html)。 + + + + + + +### 与 pandas 一起使用 + + + + +```python +{{#include docs-examples/python/conn_native_pandas.py}} +``` + + + + +```python +{{#include docs-examples/python/conn_rest_pandas.py}} +``` + + + + +### 其它示例程序 + +| 示例程序链接 | 示例程序内容 | +| ------------------------------------------------------------------------------------------------------------- | ----------------------- | +| [bind_multi.py](https://github.com/taosdata/taos-connector-python/blob/main/examples/bind-multi.py) | 参数绑定, 一次绑定多行 | +| [bind_row.py](https://github.com/taosdata/taos-connector-python/blob/main/examples/bind-row.py) | 参数绑定,一次绑定一行 | +| [insert_lines.py](https://github.com/taosdata/taos-connector-python/blob/main/examples/insert-lines.py) | InfluxDB 行协议写入 | +| [json_tag.py](https://github.com/taosdata/taos-connector-python/blob/main/examples/json-tag.py) | 使用 JSON 类型的标签 | +| [subscribe-async.py](https://github.com/taosdata/taos-connector-python/blob/main/examples/subscribe-async.py) | 异步订阅 | +| [subscribe-sync.py](https://github.com/taosdata/taos-connector-python/blob/main/examples/subscribe-sync.py) | 同步订阅 | + +## 其它说明 + +### 异常处理 + +所有数据库操作如果出现异常,都会直接抛出来。由应用程序负责异常处理。比如: + +```python +{{#include docs-examples/python/handle_exception.py}} +``` + +### 关于纳秒 (nanosecond) + +由于目前 Python 对 nanosecond 支持的不完善(见下面的链接),目前的实现方式是在 nanosecond 精度时返回整数,而不是 ms 和 us 返回的 datetime 类型,应用开发者需要自行处理,建议使用 pandas 的 to_datetime()。未来如果 Python 正式完整支持了纳秒,Python 连接器可能会修改相关接口。 + +1. https://stackoverflow.com/questions/10611328/parsing-datetime-strings-containing-nanoseconds +2. https://www.python.org/dev/peps/pep-0564/ + + +## 常见问题 + +欢迎[提问或报告问题](https://github.com/taosdata/taos-connector-python/issues)。 + +## 重要更新 + +| 连接器版本 | 重要更新 | 发布日期 | +| ---------- | --------------------------------------------------------------------------------- | ---------- | +| 2.3.1 | 1. support TDengine REST API
2. remove support for Python version below 3.6 | 2022-04-28 | +| 2.2.5 | support timezone option when connect | 2022-04-13 | +| 2.2.2 | support sqlalchemy dialect plugin | 2022-03-28 | + + +[**Release Notes**](https://github.com/taosdata/taos-connector-python/releases) + +## API 参考 + +- [taos](https://docs.taosdata.com/api/taospy/taos/) +- [taosrest](https://docs.taosdata.com/api/taospy/taosrest) diff --git a/docs-cn/14-reference/03-connector/rust.mdx b/docs-cn/14-reference/03-connector/rust.mdx new file mode 100644 index 0000000000..25a8409b6e --- /dev/null +++ b/docs-cn/14-reference/03-connector/rust.mdx @@ -0,0 +1,388 @@ +--- +toc_max_heading_level: 4 +sidebar_position: 5 +sidebar_label: Rust +title: TDengine Rust Connector +--- + +import Tabs from '@theme/Tabs'; +import TabItem from '@theme/TabItem'; + +import Preparition from "./_preparition.mdx" +import RustInsert from "../../07-develop/03-insert-data/_rust_sql.mdx" +import RustInfluxLine from "../../07-develop/03-insert-data/_rust_line.mdx" +import RustOpenTSDBTelnet from "../../07-develop/03-insert-data/_rust_opts_telnet.mdx" +import RustOpenTSDBJson from "../../07-develop/03-insert-data/_rust_opts_json.mdx" +import RustQuery from "../../07-develop/04-query-data/_rust.mdx" + +[![Crates.io](https://img.shields.io/crates/v/libtaos)](https://crates.io/crates/libtaos) ![Crates.io](https://img.shields.io/crates/d/libtaos) [![docs.rs](https://img.shields.io/docsrs/libtaos)](https://docs.rs/libtaos) + +`libtaos` 是 TDengine 的官方 Rust 语言连接器。Rust 开发人员可以通过它开发存取 TDengine 数据库的应用软件。 + +`libtaos` 提供两种建立连接的方式。一种是**原生连接**,它通过 TDengine 客户端驱动程序(taosc)连接 TDengine 运行实例。另外一种是 **REST 连接**,它通过 taosAdapter 的 REST 接口连接 TDengine 运行实例。你可以通过不同的 “特性(即 Cargo 关键字 features)” 来指定使用哪种连接器。REST 连接支持任何平台,但原生连接支持所有 TDengine 客户端能运行的平台。 + +`libtaos` 的源码托管在 [GitHub](https://github.com/taosdata/libtaos-rs)。 + +## 支持的平台 + +原生连接支持的平台和 TDengine 客户端驱动支持的平台一致。 +REST 连接支持所有能运行 Rust 的平台。 + +## 版本支持 + +请参考[版本支持列表](/reference/connector#版本支持) + +Rust 连接器仍然在快速开发中,1.0 之前无法保证其向后兼容。建议使用 2.4 版本以上的 TDengine,以避免已知问题。 + +## 安装 + +### 安装前准备 +* 安装 Rust 开发工具链 +* 如果使用原生连接,请安装 TDengine 客户端驱动,具体步骤请参考[安装客户端驱动](/reference/connector#安装客户端驱动) + +### 添加 libtaos 依赖 + +根据选择的连接方式,按照如下说明在 [Rust](https://rust-lang.org) 项目中添加 [libtaos][libtaos] 依赖: + + + + +在 `Cargo.toml` 文件中添加 [libtaos][libtaos]: + +```toml +[dependencies] +# use default feature +libtaos = "*" +``` + + + + +在 `Cargo.toml` 文件中添加 [libtaos][libtaos],并启用 `rest` 特性。 + +```toml +[dependencies] +# use rest feature +libtaos = { version = "*", features = ["rest"]} +``` + + + + + +### 使用连接池 + +请在 `Cargo.toml` 中启用 `r2d2` 特性。 + +```toml +[dependencies] +# with taosc +libtaos = { version = "*", features = ["r2d2"] } +# or rest +libtaos = { version = "*", features = ["rest", "r2d2"] } +``` + +## 建立连接 + +[TaosCfgBuilder] 为使用者提供构造器形式的 API,以便于后续创建连接或使用连接池。 + +```rust +let cfg: TaosCfg = TaosCfgBuilder::default() + .ip("127.0.0.1") + .user("root") + .pass("taosdata") + .db("log") // do not set if not require a default database. + .port(6030u16) + .build() + .expect("TaosCfg builder error"); +} +``` + +现在您可以使用该对象创建连接: + +```rust +let conn = cfg.connect()?; +``` + +连接对象可以创建多个: + +```rust +let conn = cfg.connect()?; +let conn2 = cfg.connect()?; +``` + +可以在应用中使用连接池: + +```rust +let pool = r2d2::Pool::builder() + .max_size(10000) // max connections + .build(cfg)?; + +// ... +// Use pool to get connection +let conn = pool.get()?; +``` + +之后您可以对数据库进行相关操作: + +```rust +async fn demo() -> Result<(), Error> { + // get connection ... + + // create database + conn.exec("create database if not exists demo").await?; + // change database context + conn.exec("use demo").await?; + // create table + conn.exec("create table if not exists tb1 (ts timestamp, v int)").await?; + // insert + conn.exec("insert into tb1 values(now, 1)").await?; + // query + let rows = conn.query("select * from tb1").await?; + for row in rows.rows { + println!("{}", row.into_iter().join(",")); + } +} +``` + +## 使用示例 + +### 写入数据 + +#### SQL 写入 + + + +#### InfluxDB 行协议写入 + + + +#### OpenTSDB Telnet 行协议写入 + + + +#### OpenTSDB JSON 行协议写入 + + + +### 查询数据 + + + +### 更多示例程序 + +| 程序路径 | 程序说明 | +| -------------- | ----------------------------------------------------------------------------- | +| [demo.rs] | 基本API 使用示例 | +| [bailongma-rs] | 使用 TDengine 作为存储后端的 Prometheus 远程存储 API 适配器,使用 r2d2 连接池 | + +## API 参考 + +### 连接构造器 API + +[Builder Pattern](https://doc.rust-lang.org/1.0.0/style/ownership/builders.html) 构造器模式是 Rust 处理复杂数据类型或可选配置类型的解决方案。[libtaos] 实现中,使用连接构造器 [TaosCfgBuilder] 作为 TDengine Rust 连接器的入口。[TaosCfgBuilder] 提供对服务器、端口、数据库、用户名和密码等的可选配置。 + +使用 `default()` 方法可以构建一个默认参数的 [TaosCfg],用于后续连接数据库或建立连接池。 + +```rust +let cfg = TaosCfgBuilder::default().build()?; +``` + +使用构造器模式,用户可按需设置: + +```rust +let cfg = TaosCfgBuilder::default() + .ip("127.0.0.1") + .user("root") + .pass("taosdata") + .db("log") + .port(6030u16) + .build()?; +``` + +使用 [TaosCfg] 对象创建 TDengine 连接: + +```rust +let conn: Taos = cfg.connect(); +``` + +### 连接池 + +在复杂应用中,建议启用连接池。[libtaos] 的连接池使用 [r2d2] 实现。 + +如下,可以生成一个默认参数的连接池。 + +```rust +let pool = r2d2::Pool::new(cfg)?; +``` + +同样可以使用连接池的构造器,对连接池参数进行设置: + +```rust + use std::time::Duration; + let pool = r2d2::Pool::builder() + .max_size(5000) // max connections + .max_lifetime(Some(Duration::from_minutes(100))) // lifetime of each connection + .min_idle(Some(1000)) // minimal idle connections + .connection_timeout(Duration::from_minutes(2)) + .build(cfg); +``` + +在应用代码中,使用 `pool.get()?` 来获取一个连接对象 [Taos]。 + +```rust +let taos = pool.get()?; +``` + +### 连接 + +[Taos] 结构体是 [libtaos] 中的连接管理者,主要提供了两个 API: + +1. `exec`: 执行某个非查询类 SQL 语句,例如 `CREATE`,`ALTER`,`INSERT` 等。 + + ```rust + taos.exec().await?; + ``` + +2. `query`:执行查询语句,返回 [TaosQueryData] 对象。 + + ```rust + let q = taos.query("select * from log.logs").await?; + ``` + + [TaosQueryData] 对象存储了查询结果数据和返回的列的基本信息(列名,类型,长度): + + 列信息使用 [ColumnMeta] 存储: + + ```rust + let cols = &q.column_meta; + for col in cols { + println!("name: {}, type: {:?}, bytes: {}", col.name, col.type_, col.bytes); + } + ``` + + 逐行获取数据: + + ```rust + for (i, row) in q.rows.iter().enumerate() { + for (j, cell) in row.iter().enumerate() { + println!("cell({}, {}) data: {}", i, j, cell); + } + } + ``` + +需要注意的是,需要使用 Rust 异步函数和异步运行时。 + +[Taos] 提供部分 SQL 的 Rust 方法化以减少 `format!` 代码块的频率: + +- `.describe(table: &str)`: 执行 `DESCRIBE` 并返回一个 Rust 数据结构。 +- `.create_database(database: &str)`: 执行 `CREATE DATABASE` 语句。 +- `.use_database(database: &str)`: 执行 `USE` 语句。 + +除此之外,该结构也是 [参数绑定](#参数绑定接口) 和 [行协议接口](#行协议接口) 的入口,使用方法请参考具体的 API 说明。 + +### 参数绑定接口 + +与 C 接口类似,Rust 提供参数绑定接口。首先,通过 [Taos] 对象创建一个 SQL 语句的参数绑定对象 [Stmt]: + +```rust +let mut stmt: Stmt = taos.stmt("insert into ? values(?,?)")?; +``` + +参数绑定对象提供了一组接口用于实现参数绑定: + +##### `.set_tbname(tbname: impl ToCString)` + +用于绑定表名。 + +##### `.set_tbname_tags(tbname: impl ToCString, tags: impl IntoParams)` + +当 SQL 语句使用超级表时,用于绑定子表表名和标签值: + +```rust +let mut stmt = taos.stmt("insert into ? using stb0 tags(?) values(?,?)")?; +// tags can be created with any supported type, here is an example using JSON +let v = Field::Json(serde_json::from_str("{\"tag1\":\"一二三四五六七八九十\"}").unwrap()); +stmt.set_tbname_tags("tb0", [&tag])?; +``` + +##### `.bind(params: impl IntoParams)` + +用于绑定值类型。使用 [Field] 结构体构建需要的类型并绑定: + +```rust +let ts = Field::Timestamp(Timestamp::now()); +let value = Field::Float(0.0); +stmt.bind(vec![ts, value].iter())?; +``` + +##### `.execute()` + +执行 SQL。[Stmt] 对象可以复用,在执行后可以重新绑定并执行。 + +```rust +stmt.execute()?; + +// next bind cycle. +//stmt.set_tbname()?; +//stmt.bind()?; +//stmt.execute()?; +``` + +### 行协议接口 + +行协议接口支持多种模式和不同精度,需要引入 schemaless 模块中的常量以进行设置: + +```rust +use libtaos::*; +use libtaos::schemaless::*; +``` + +- InfluxDB 行协议 + + ```rust + let lines = [ + "st,t1=abc,t2=def,t3=anything c1=3i64,c3=L\"pass\",c2=false 1626006833639000000" + "st,t1=abc,t2=def,t3=anything c1=3i64,c3=L\"abc\",c4=4f64 1626006833639000000" + ]; + taos.schemaless_insert(&lines, TSDB_SML_LINE_PROTOCOL, TSDB_SML_TIMESTAMP_NANOSECONDS)?; + ``` + +- OpenTSDB Telnet 协议 + + ```rust + let lines = ["sys.if.bytes.out 1479496100 1.3E3 host=web01 interface=eth0"]; + taos.schemaless_insert(&lines, TSDB_SML_LINE_PROTOCOL, TSDB_SML_TIMESTAMP_SECONDS)?; + ``` + +- OpenTSDB JSON 协议 + + ```rust + let lines = [r#" + { + "metric": "st", + "timestamp": 1626006833, + "value": 10, + "tags": { + "t1": true, + "t2": false, + "t3": 10, + "t4": "123_abc_.!@#$%^&*:;,./?|+-=()[]{}<>" + } + }"#]; + taos.schemaless_insert(&lines, TSDB_SML_LINE_PROTOCOL, TSDB_SML_TIMESTAMP_SECONDS)?; + ``` + +其他相关结构体 API 使用说明请移步 Rust 文档托管网页:。 + +[libtaos]: https://github.com/taosdata/libtaos-rs +[tdengine]: https://github.com/taosdata/TDengine +[bailongma-rs]: https://github.com/taosdata/bailongma-rs +[r2d2]: https://crates.io/crates/r2d2 +[demo.rs]: https://github.com/taosdata/libtaos-rs/blob/main/examples/demo.rs +[TaosCfgBuilder]: https://docs.rs/libtaos/latest/libtaos/struct.TaosCfgBuilder.html +[TaosCfg]: https://docs.rs/libtaos/latest/libtaos/struct.TaosCfg.html +[Taos]: https://docs.rs/libtaos/latest/libtaos/struct.Taos.html +[TaosQueryData]: https://docs.rs/libtaos/latest/libtaos/field/struct.TaosQueryData.html +[Field]: https://docs.rs/libtaos/latest/libtaos/field/enum.Field.html +[Stmt]: https://docs.rs/libtaos/latest/libtaos/stmt/struct.Stmt.html diff --git a/docs-cn/14-reference/03-connector/tdengine-jdbc-connector.png b/docs-cn/14-reference/03-connector/tdengine-jdbc-connector.png new file mode 100644 index 0000000000000000000000000000000000000000..1cb8401ea30b01d8db652ed4ea70ecc511de7461 GIT binary patch literal 192539 zcmeFZiC>-x!`r=;<00k zcB=PRE?T-+f6=mUr>I}h#Rfn3ofiMLXvzN_*I2YDHfYh(@8`Ixx8HsqsbAmT^Urq4 zUyHw=tv>db#*edC<^8qf$Nmz(Z*Rk*a{pFuR$X;IgI=^qXZ^R=;y~&r^=0p0bmZ`X zlbFTheJfHfL_T~j%iVYUIOhI+=ASFh_?$X%%CbXJ7I-?6`S;k9iod1~uI&2L;sUwu zP})RS^~o#Kr+y`W&|c4g;9jSoCv1uH8D8U_^SyT!)~e@s_b=W0>y4F%H*2i>clY1t z@UId4>kR)Q!M}+3Z#eijEdI+0{^dsh62pI4@xQG2Usn7tEB==i|NoN}(*u?+j2ju0 z-2nZM56tdbtTGaz;7RiX+NyxIF=A5Of5%F?-i_%$yKQ+Ot89iA8=J z!tpX1t5jX-Iq(n391Py_2K>1HQLX;A|FJU6z=o-TUy0`BQPO+u)dnW}7A{La@xQcJ zC-u8LxqcZq|LIzMc9P?Nm}*Ur+x6Mu?J(QnJ+gcvK#7-r;Fr({We(XtPntZT5w?&G zry9JMiRY^mI0_y3*CUe;GgOm-uT_5aC0>Pc9hJNC(q`@Npa;8seVh0HLj_BHG*(}W zOPWmxF)-;iv4h&nhxlyTKtjK8DE{*Nvtm+f1tr>rtzHSJz?6LOXh6;>w5e& z@Ga5UK22w$a)btA&&CVFs#ESyK2Ld3m@#|qg^RDlunEdQl!Dit;K3IjqX1WaKFL{| z#YF6}wrUi|MR2gSVHypxN{vjOEHJIA2Zuyo$%;6Pr|Ex+#uHK~|{nokp~IIMqnzxKjMIvvB8+cVZ?t94C@_1V8025HYa@wr-;NoXRY!WEAgv z(%w>h!M^F06Khg4I9hfCb#jShJI>vH804!jx=-hTXiAp?w45RfJn_2t$7&r}>HOxW zrGB@+=IwvIc;UsnFtQfp=nzxRb3*LhIasOlPSXSY8U-++EHsh9c`|>>!2RiW@~-xZ z*LUq){&RuxOF7|y>qJ=#pVi(6>Th&8>6zvm4Y|U`qU=% zYd*Qp-#)enL<}GG06BW75)aQv5nau@e>|bXgBB|p!Y~MM@Y%~0atR5a)~X3-GZX(# z`{j0|4a+(TfSG@_n}hxdIrH%~QE&Rb__YzO@h{_bM4Cxe$0y^FzEC^)xDH?@#foUj ztGas;cG;Oe5~b_gDA~SmUZm4?`)tPsQ1#T94lMo$s=01CzJ;r8b}s3EEYI}Fe)AB` z1n<-yX3F>+oc=NuqdWiLWax<#A{LqW{DX)_BkG&%mA7`*DO;Pi{(!gH#fuU99X>ZZ zsieu^H}moO_?iyTEOGDf#<<8wHBo!Ok+aJt5xBs>5b2NaU+7$##G4zTsyYR>86=e? ze$7JojcNlWiq_+_;BHvWZw~tESZyd<-~9^2R!lE!1P84lo@vwRQ+UuCC0%n1_dfe3 z{B$JOEgw_Zu8TWHe|-G^t@_}DrP}%ft?EUfUORrNci_$`X9a_Ss-ox-!Z|U;H?#IF zn8a3n*Laqm4_ECuAX~wI?E~Rbe1azoLx2%e=$0L zsE3A10O%?GId+qLoo&jkx1oVfRdZZ$$xFwVmNJcW-$(S&YY|c=NOe@12mfjCO)J&t z8?axl`=1nI%6R*r=HX}@+3ghn#BTBWcGUKrYh;Oz)N`|=+k~g(6Pj@05nT25m;19+ zL4FY8g(jr@Sx~CtX1r$bcr4RB)ykc!5P|%M40Z~gnuD9$_dvE!; zS+1xx5P^ebprBPylI_M$X?vUKb^XkMgWUw(enQ;qV9M^B9c@9vs!}5c_|Mw|omR)j z#aSmPmgmS$qtAX1b=9DhTZn{B>N^~N{Cno_Abo6~T@a3a)U z02#T!R_U|p@-D41CzharSEb147|;~9WK&aU_SVm5UQQlCPts%8L<$?<)xx=UIA_UM8Bqp7#WWb{ppPImn2ShVwNI(3C_#EDSwwIrN=* zOuA`^Z)R);GHjthCMx&6XZMwme!NXe|DXxyblGKxnE6=0-G21eLr@g-3w0^g?-+M0 zp+xmIvKPvQ1oxLs-|5Vc7^o=8Y&yXxw$C+m5I3~Pv`aur*VT?@U<;IfrXJxM zXW468<(<(&(8yVme{z}j+nkP!)$XiPfyDbyW+FmQ?@;a0bwYQ)$v9$TRY=h?%lRTe zgBe<{Id$evjBQRRFTJBVY0pAh2OcwlpSgIapXhn~};2AmLza~roBv$8BBvaA=f3yxdG*fnKB2TYFU{W1P{ z&H=4AfTwG~q+)(4&yu1*31=FSB8QSh%w|ABiFuXnq@KuNM)`?>k$Z0-)gLP3E+m5h z4gFe&3`Rm=X#F0mSC%43_4)+qny_1V6dVaMYk9(r0ivyujvjkE~^h^R!uQ|%B zH*~y1;ZbZ(?rt^ncdz$u1;1ZHzehLqY(|L}i&l5PzbAf>@f+#= zv4a!GZ+&!aJ03s|kfkJtJux)q>GB?tAx&_`GkG(*(Tum2W&jU+F8dteV|~ZA*g6kC z-9bZ-cy*-5geC;~a=lPHU>~M!yo{MehT})7-a-NIrkh*ItfLsoUNLyNJypdVsREtm*I`I|Ck&)M zty3zyvY-Av&&~fO#tCVe@{~;j&5oZNI#JsK(3-Uc`f5o+$Q>DMQcGlybNiPC(ea#~ z6dJRwOd~_%x3??~e%35IB_TKv;dlUNIndP}4w~nMTc}#d82{#QPTs2GaF>%@@PuO_op#+V_S_2R`!09!KCVnbYS}LTgBqE_DY(! zlfZzbljAl-Y)b?fh<3UqVd6%hS4=Ab>B&Z;C`m8$L7i+lSV@0#4duYB8Xt{sk$iL} z3ib{}Z7rjyN`vYr`|K=sbQw?9YUZHcz=VlGTHGP!cvo{EdMZvU0keJI%eWQxc8NAuuXNeKO+pvgdZYC5}=m5?6T=rJ*e zjymH49sS4w(F&`l0W8-dLdPpg)sRfex5I z^E2zPxv~FI@2=NNbMDz}?99dVc-6bA;~d^Kp-Wz%-A=u8TkFkN5TG_=YyibH3=t1+l(X(Ta8J z!k5`KxFKtWW>VuAm@p5RX_PkmsJ)eTuKFo$x~pU>_290q^qbjGu4Vt|?T8t_R_lb8 z3smbwAInWF?zH7!7`dPw>@>0K=-Ko@f}d3CeB!w1#0hdzY;nMo&be3XSpGe@5X$So z;tV(az!079)J#jAaCjjgmJeQy!YTKR@4H|%*puHDSh5_y%x&AO7^1jWHG%6gUNPxl z+blf8Q`jwgs&>|8gDm60lm&XolU0 zXw6m`fslhFgrO*^ksY!mQ0S1J$wa)V4U&x+KceyEl6BDLF=O-shtX{|)e?cCY#ext zlVn`I)|%RAsZ+%YahQ2sjine2?JBB%@CMH8jIJ`1e_$XIQJIP@*nM82wtvu(PF%&f zB`$*0fEg)biembcv+D!P(Odys`~b4m_rp0zl44gN11T39UN-AKXY&cnEyZ4S1#^AT z9}0a9-y^yC%Xy6t3MZfsWS@b(J@O>04-e1p>4w-v{c#?F;F*!SUJ)2ySeOoSNIEU! z5-K+td$k=NU8Ezi0myekg=6iSG~dKuW^I7rL3et-Oo{gTHODp|M;YEB;+~x+?-&g!h zy}sopbc2<@4NhgR4vlkK?KeyE@tAWT7vFRlf`|SddSs|c^Ki^wxchZ?LyzMgo#kjL zhd&8dv7JL*^FMfb@3u^`MH!9J2D5C&6wRO~gU{xky|zkirb7)}@=x3tALpm2^VZd; zRwLq)R%2q9S;XvetSSVd6+(V4StCPPrV4}?AY#nWJFlCpIU7ISHBd>@q^*Dt=n_y_ zOg}HnM{%j0iatIOXreLf$n8_6^+dHIl{4o7KI1Mh0k+UA$vn>>P2xf;r0ubJyaAri ztS@Ys_l3{!9kVt5dOB1G!YNn`D?!I3#%nSX4o);HWQ|Vpv3=$R#=YkGr!#)!TPw(m zs5M%Q_L3xeO)^ZjvFaA^K9iX+w^5cUO@@sIKR@B#cK#Pxy7kAVaKwRhqQ1zOTVW=zLDx1j)BBkXmp`y>t)>kNC~HmYNL5|BKAGU&6i zvzEuU69F=fRG*;T!uJ{COrQDy20E;40cMcap?u|(KHpL%X(n?yM-pm1A!h5tihGd` z;;(mXWx#Jn@rqUml8n5W&vzAkVOz~79~u?fJK^6cY1vLQbBQ!GR&JKVh9)pU3Lp)f zIIiZ*ZzPv@wcJzf0TFWA&tAP>*udpFR7Fe^m3efghnHX=VFHk;ccvN}>8fnyVW%vi z={Ra_GdhH%vMFeS!ztu_&0oruSMVX{32n=&pQ+s7)w;p+whrbkt%2Aia3aK;z^e12 zp??;BY;G*-aQ~h@{FB=E?f)a*W_7^ou+mOZcTS5eLzBYp8f%+=%s)j^72#y6%>c<41d^b=fVlbD(}s z(mLW+6*5~ZZ?h_$ZHEP`FEp2pw@$*WL`wYK`*|BO(%Inwdfr}nGpwg;MJu=k7m?Q0 zNIJQ45+KtPa6OCj_u;fzFER;U9pRugvkj(DdBwsf#+-dq210t4N}_G5))H4VYKHEx-<}6L7J|a_lX2U1N_0Bq^puEiO7M@vK?>UEiO4ULxHWrp|#x9 zi4U0owJunZQ=cx&RodqN0wa}>sx0I^?U^8Uy(Ne#=vAkiF=XsC_#Y!sy6gvZoxM*7 zff*Na-yfKGrIkNiH5&ACppsld@@f2|0a;1l21t_Vp=P{}LkZbBghsK%6Dz z<80Oxy=YXDBhduS(<7x_>jwM@GnWv^#xHom+TwgSW znb@piZ>*P|V7QmMr+OInYIIGbb5kRD&Nr@ThR0T$xChSkg$O+alo+ z*cGZ#+4GNY19sUwmOLWg9H};1C9vC5R0u_n2`;UTtD~eX;V z6?e$RUjrv?=542BO`w?OWPfTN&U?_445q13XOppkPv`bOE8j=Bp`vU{ih`OQ2yLKY7$0sIDQU>sLha1{l-%1 zfPBW+rU$}jKKNcKWm3s#<-UZ#H3Z=jcHR683XI=28A!luR!dt}>VRJ^W1bH#NoY%g ztYFWjr#Q`s>&Ui{yn%#-YC%P4n*+EPV$Vhy_>JOudsTje6$KIKs9)&WZ!264sRr2P z(oRNs4%4oGchQh)0!YBt8=N(U3L=G*1|a5~qCFhB>${V<{ef;5sRmMDKFPZ{V=LfV zB+gb_*IfBWXJ}Q3^cmS_C-r>=Rrh^fWCDjt)O#<9l?Y`%Rs4<84Pk~rN&VfF`?GVK z*~R2Kb;J!8gSF}mUQ|yl6Tf`C2Ia53$-H_Fp!mvfafS&ghU!qgBr}}}7!3>%@8Xsr z>+QSJtvWl7fsjL5oGS!2bIM_2!1~kdW3os(a;S)K{yeqx?5+txXwEtqK0mt93-+Wk znsuQsRaZv~$q6=1Fe{!=*`AHCX3BJ&yw1e(%5Y}Tv0J1!o&pt^w!%rS%Y=}UAn;&W zt;@30te{*=5TK2s%<#MLItluNiLcDqf4$4(|DMKoA>35RmNzk3DsxTu-A~uD=@49) zw6&e6pJ`z7*<_o^YHaQEH_!eWEfhg*-4BN;`^h)ic}|F@=l;Hwb@fuTygk3&(J=oak_)2Hx? z@s%i~c?oY$%FjCZwD?k_duc8S6okWr(Jz1a$zLlOvFWhcQmk(k$EC_#-p?1UtKLEs zZxhe3;gAPN{GvQ{JI)VrH7dVU#v+4DFL0i{P_&JdHr7EbqpnI-33FSOGL3+<9bahG z`XYk7fA6rz+#?*Ebv^@46E;gA`-bZ#^hE~)of;LRceCeEG}T@^Sb>f}#%BNwr}M4l zEetOp@Jz=YQPJo*MwM0_fiQkRmF<)ptlNyh*ejrr;n?v*-dD!g9_r__$jV*|3(7*p zEHPE0f}C{=a--}4W(#%HTyR1!tqF=U?4m}%^c)}-u*q#cI?+28M)@!DtZ)EUWVDp; zOhkKY<;{O!K(7|HDY2;?>;QJG!NSZe8wB%Z1v0xKFpH)VC)Fgsm!ATPF8JpuY5lU; z_JHft))V6R?pxqfN{n=trh-1O{|w-O%Uq$4R*d=;-$lZ6-;dzm_kqfU*&tc7p@Dr0 zlM5R8;K)qk7-PKq|W@c-g z+Xz>s@0?Bl3>2OS{s15nq_S~CVY^+3zUbHP=4o!Y_Sd88>>n^RB_msP$M>49P*W3S zdxyHfItP~YnaRI00Ha}kmD!`wvkps;1*~~^WYTz-E^D-lfZE3EDGbLvqNr1shyg}1 zI3cGNL!nWZAxGdj$}i+Mli5y}PgYdfR@&SNmG)5gu(bk+7tN|J*2DTBc9vo~q{25b zM-oFWYxVrW#I9;AzE}F)n*J$z;%;1j+8#jLv!J}%wsFx$72VlG+kL6WNB3>R;hDR< zOrSfrnyfcDZE{q3>DtTV8R9VE5cyyR*vod|+Ib(z>XB6==}r3DjfNY?={_DWJe&)6 zIPMW@7qum64;NWd{nKRHs_{vM^8w+T&6n3K1CMlNhhI6-<`bC^ZEj~Bb#0qeCgtBD z4+Bl?qA^)>aScQRldy&JS^T>tZ(ss57!vDb!=%uBc$OqucYa_(D z_24z5sQy})(rL}^4_^?bc99&bnFhWUy3H4Jpkhl3G2|MPpXa1lgYt6?CSKjVa2seN zU9Gr1*qk9*gE+455j`urN46?exzSSKior>zZeeP7#e#rbiO?0!%}9C~UiiC}ojpxX zo{4jIfCjulTWj-%2XDEXM~tE@11cuv%TWP@KqqcpM|aMcVkK~%Thc-t;3H|?@NO}Q zlmO5b<>A?ypy>Dd`=-otan*W5C^ZKgCr5HZu{PtfVwV_#9y4P=&%w5!UkBdU)W=Mq zg;O>OKtkc$_Daw^x-DLQrcPJ^f+Y_C9H0f%f5-+Dv-B4w(r?CTb*1$~Wkbl1? zG4vdO3!hsoNKTjCD+D1Q(25^=w>DW4(9%x6y0B%y^_b6xTWt&Y8p-2Zs{N`EBMa*+ zBY%w5v9*^E-);JcnA)N;vj9IQC5@kmZanF0dj_hSTd6}ih?tcX4T3PQ=AC@Mm3=x& zx%jI^N6pp1@R3d5OLaKJoDBp?aJiV_3&E@p**5Gp%i3GH+pZSr2psIo1EddWlD-ip z&yqC#nZf*}ic&Z9m>2M)9lJ;)VgHZ)|0{dVYTWQ`-MaDbg=pyr`5@)#=R3oso2*S6 zH&V1;K`q>0xW94F=-^h9`dc)Sv%SuKM{&!V)cvYst9=aOu5wQ#L}9X4%HEToZWflk zOC5eRJn$Os&T+q66d@=!jwzZ+;Q?urEzXN4C7>9)q0zrm z$n1m=UF_nQq^+46Bav{`g)GLs!juXFwJRo0XOd<3Mz?h$v+;ns?s4VY_7r&BpDaIN zT+#sSlg##PUa{dFtoBTmRl$ycKIl7U;mPZ#&W9&G;e~B~0^9hb-gWRBoD?bt+foQlJ-OvBB4i=}%gVP^Y`YT7tvZ4{c zYI}MAN{}E@iJ5xLgA1nVnBpDC*&Hae04A6dGBRe8?u`_rcf=2sQ&Uh$3myE2Wa!KU z#p=p{y62a$eI~6F971%Er?&@#kTo(-kU-h4uEKENeFS{32L_|Rmz|(|;uYQZ1Ok=p_FL-o9v^48Q?*JUKC_3LPxhH7 zudCjsY7U2JplYm3jjg?#(#ZOEBf@s6BrOadn693r6lO^cf6wfG-yrYSdYP~54U1-W zs-n-Iv+=QhaI3fwKyOPlYhY!BN#twr(GCTZvGB(EF1`y0w|4?cb7*@O|Kzp0QAD_K zJEpi3bb3Hfd5MEftLy)zuGrX#<;+eRZ-rttaS8=PAG-HlgKkq(nhQ!Fn7OG@^^T8n z!gn5{A)VfxHJdFAI2LVcR$KK0?FW}?Jc#~(jDK;1!?In=fbf{Vk`GqwS!v>If-!k& z5@+(K$$q=4G@a9Q_m%jtEQV~{`9O$f5<1Gf$YUz%Le}*evAQ(<#OC?ni_S~X7$|#V zvod9*Zq?H9_`*D2W#=OqIr1ZapzNiTE4ABwYTg0crH@b^O^8>hFsj` zYa!>4hSIMFUTJU=jvpaU#DjL{sIodMM}i$#o3$;7(5UG1A>#>8u3l0aP*Oy#uLiU4 zUa8CtihWtxnT=*PqM=nYWhZ||wwR@yq1FcRKCQuLPe9bIL?CI}I1J*u4cshBFT~zB zq-r2BMiF<`4{VioQ1dKOLZ_2euY>ArnHToZso|G*74g<3Fi~f2;pDna<@~F38OFRf zdu@M!?|$(%NNlv?qudZJ?3Y?mmFr2g>gk{W2RSlefSw^a7f*w-7RJapMKU(A*1$Hh z71J$xbNx(p!UC5hu>3+tHvLK_)&I6&CPeAhv9aiIQeI&Zyt zwswof4;1^h%yS;Uc0PUh^y6jrlc{IU&weX$d|MfbIf?Jlc&L*Wr>3C4Z41s1@_q1F zfbohSrkHCpOs%pHreN{4zuPx}W6ULrhW7SA@tJk5qR{vx@6M=;xq64TOt||6ul_48 zFD`8LEAK0ou?HXLZ(J~Jz2LgcZW(kW-D9z^ccll;Y9i>#V0NbJPU+?I)L7xHp$l@Z zlY!^XV(SXcRGUr}OAMv31;ZlrF#~3+zSQ!$VE*8j1+zD?qyHtQBQ9E8cDnyj0QOG_ z2;4uHF|vh}S3TvP%u|L~st}L>T;h+pH zjXF>C&OWz~3Mq%i#)IR0Q$b*Q^a_I!y;v4I7yP9^-UC@}NIaiOnN&C1d1f4ex%J2zQ#)CXRM zz~k$^uR!;bqL~R4U|N%!;mmU_V;%m^7j|Pv)7ot%A=6#9C7T3q`djNRADJ2JPBPC% zt(+e+E^3%eYJbb?avl&e-SGbBp-71IPWMy{CAoO&Tz6S?X9yI5hst|MorKHT;9UIL zN2xhn>F19rOnEQAh5wwa{J4jf4yTZ&Up&Mvzzhij{v5|pvTMl?&7@CU%_c|FFVAuR zYl$0U@u3YhH;#Wt#{P>M;#a+@cX!#gdpN<-ci~rkV0D^PFtIgoGj+bjm@$%Tbcd;E z4unG`kt zkqxf3f7$^iZ)Z?an!ljiXkFP>{x{Q+jp$Yko#_T=CedFsdZxMob1+ZW3Z`2Swhz!q zVqwjlJZTQrV`RMWO$b`uo?gEEyc^eEjVrg?AKsoOQ_0>=y84m6rjrYA-c-TiZV6L1 zK1}?N)};j>2qzZyQ}ax1Nv`i+>TZ-B>>my^yhh zU{r(^6{{PnjSmM}x6i(h;c{1fK5Td{MQe0_|8N3@3#n5h916znz?mPq89!D0Y*`Wkmz%N)L6>JozLhQ&{QPxVtsbmb7s^k$)NMh zZPgLKH4;J75<=Luh-(9UO&T~U#Av8~(yxNa5!tF(>K+G0H*Lfnrirfj5ta8J{Hb^R zc4#VO$6qyt%MQm>Ht(7H?RfB(<4+iYF~-YY>{)I%ftdi_5v)e8j!M~?G)Bvt>9(El z;?mYGI}l^x2zIpPb)TiCJYbT>!`XYYjT_stGpkr-=&x^L;L8p!`yEYwN>f(cNgRHX z^5^QfSS4>{GG=}z5K3?h@UQm1c5+S8okPo02h7ycm~lpF>@06Y;FtNX8>L5xxMo99 zQBEAb_FDB;Z{>F##bEG`%{r2UVpUX9E3+y4K?hsQ2vtX%G7hVv_3o+0y<`{|-ocfS z5=LE4_VBjl`2*P4+dWQ=K5m1Tb}z(Qcgc9txOa1$XIVP!;6!yRyIJ#CKzdzZFx{*2 zGAAa_YAQ(Xqx;(WQXSz-!x9%g_c44_{;8(>=ewo`_kL8}}W%cr80;^y@k& z?q1LDgs2npTQ;N88~(tU^Rjsr1&&)dwD55LHbmeP9Y&C_)IpYWDd1!JjpI?VKmHd$ zs|oY6Te`RKKrV>4P3r?eQ7hje97Qhd-!m2_o(Rj2R@a`j>Ht`a`D;}Y*%hNy@v5)R z;=G0I&{S`oNnuqHY$0ZUaD7s3VHa?~kBRH+RjfzdjkhX|I6L?i3XABu+jJJ3&ulm3 z_6X4OJ0AzCTAQhTX64vx0y4fT=-L+s9-KhYKd0*eRYznD=P|e_=+IApU))99OEvP?!)MJ}R(aB3aPt$K^Jf+dh8#Xl}=B3rE zlRVx3uxPy8)I}4l4qknJ^&fFztda8JCprXmk1`Z>n)5lZuIlaPQnn$MfF25h76h`@ zMdD?y^Ta~g^Y;RLQN#2$Q`QBj&qmMIF@;&q*rWC@AfyexUm{wYKVQTpgnr%ryc3sN zKI!#?!cEQm+Nsz*@Ie_1_H+|3y7+}v`4DN`@B}!l!R{MsIqQW9GC$UhVwTu8ppSN$og=wu1Mf+>X8#T2A;L#~6Hz~I7j+KCieOyO3P8KcA*d zG_NPzt8U1SJJPeh!c+p-wB+>#Dz8enGGo=W2d1BQsWlp)y_~9NvT`{4+>fuX>2di( zG7J0~pKu7TL9D5}&)n9V(m}ZQbt-0{pW1AECmNiDp#}?{S>_Es%N6DLR+Q+=qq@|} zuzO^kmsOn?pkPd%9`-;~9T}hB+I)=CJ8BsQXJ4scDthSpRVZOtz-L5@B5Bw*^d2=zU6aR{9=T)*KiiE=0=nDJGqdWb_mj0p%kAugSe+mz|@%!S-nn>>i zk45%Sc2ZvHv*Tl>!8859Fh~ZDSK`#L-*LZy(|)ev{RbHnH?Jr84!dAE}^a{ zsk$c4JC9DvxkW!B=AhQLH!?(YpN}Gf({1EpIso<(mWnH@`$zLU6&TL4C*kxo&(s}x zxRP@vU>NouCWLcgJ8D}man}>7Iem(5Ccdz9c!1tQzg+Km4ptt*(F~dpfvawhF3TP~ z0<#qB*p5?4l9*zoQ)+3TPr&faS~M*X-e^uSe+0VN%nS!GQ`^ASnCZkA1B z&x~&$pLF8(mvN;g%COH|d{8g2pUGr+fRNslAF|68 zsWn_$>mJXzgA4YG2aguQsD_xAkkVN#NkJ9yQtJHG=#j4UsQbOauUU$V`Gn8+)dbTl z7SaL}mN|5h%t+O7>Zu#3pDTD*4vN|wWYmJT8F*W`!wTs8>xd@iNwXd*O(>rp3Zxij z8dO3GJkcRj2TDj2Mt#=uVYTu)3PRz(Y%e1}-lRur!Ub`l#GftusZA>lt&LHBcgugE z^S9}%!C>RR7>D1*TRRps?_rD;<`XtbXO>YyEAL9g?V*}y&{6*5cCK{UCez2s+Jg}x zI1dE~plpqcnn0Ei#wXt}hXBIlmXetLj`jqlBS|Ul&On?_O-TraM68`Do`PgA(%kC?G|HolRB2<#7v(k=5Wc?8zMJwO%ATO5`{b%PZ~2Dxc%B zV;1NI#b@FjRs~4NGh>?mjc48VWmoH@l+WT4Nv2n*go`s@WiShQ?OnFSGL-!dZ|Co| zThR>E(h@r3t&`vf?Yq25979utZ4dQ=51YJA+S-(ZvTdobe~WxoITH(b-Itn9GP2eI z=7B1^d>TJB>98?tuKZG0I%7Pb$>VyfilQ4*dK2@l-r71?MJTyZg@O5!bhhk{a(=5S z8PtEjMB>9Q`q@k@N*1hFJjF8S< zk}<~&-W8rr6C{Ih(~#G74go?$$zOKgwK(74>%vk_a40`^?#2jF zOH7JFF>?x}G-IO(g~h3}t&owuWy5AW(nb_~ZyJyU0G_E+!e9dA4HJ|?V0u?@5{KXry zqBxC7;vAy*^{6w-8}S5@UTNnmW$@H`ZhpB60Zpu z%==0;?}Q8=0~lo*h<;-Ymt{2m5%RO->|=TZcBi(yxA^ma4Yq97FkaVrFl9LPX3Eo! zXa4agSD!-EjE(sGcE_a3#1vcm%0L0yCiYGt~nh~NCsi&@;gzPQB&4`Abdq$MJ@1o#!cWWx{ zU)PDjFQQ(ZbHA4IVb`U)k{sU$vu+=z1&c9D!6#)~qhnC!E8N6V2F7@r+N^U6e#YpW zou~$0xz}RHz{NxbBhbk@DM$I796n`@xO}-;!bcvfYMD{o^$bhQ9znlmmcORDp+ij9 zVBqv3otXgCoX9;l&$?iF|2<#OyhRE$J7!-+5|e2=&~QtSO=nFtuX77Lhl<>dLrCB8 z?bTiM(lTDR7QD3Aw)_HTyW9Z;V0BY$?1qyZ|A9x~ZjE%_=-O~l*+Z6&&rYomquz&x zO*rYV9p1-?TjZN$o^5-XKu`;qB+(EUbFjth`fstP&h%HEWDHRCMJ}a=tZN+K=$me# zu3U>ef%xwQMG_iu0MIBb%81u`&ZURkew9%fI!Dh)?@`NPW}&`^Aa{!gufJ!dsbk=f zt+x)8^5Id6M=^?S>+WzstI~$y3KYuAIEyvo+bj6ZmS-#^c=XgtYmF zb{CMNrDWIMom_0%%l#2vAJa+II%Y^Qd<(#ygO8V6FlzT6syj>cf*aFVm8`syj5K1r7^&$@~l#Uuq^9cKLv@8u@*Aj%SlcU9=Ts_gT&NGrD3n8YySD4A7 zn^fBeDXj;6`#-rL1jWnIgVPa>&<)5U9o!B{msXNmhS0z`tF*r1U^jfV=k8==lHQJN zhq|&;{}=IVR$RZ1-(K=Qh6;SICFUM^LV3CteBi6m!HGwQM)#LFA|Cke%Vv(*Z%Rjw z?hD^7s1D&2R=)50dO+Q|0jVo&gM0Agqrjt|Z*n@P4bQ zLGR3{ua~!mj__y5#S)#0JX^w{)RYyQt+rBX9>&mrZF*hm0w1baRt^$or zw(iWz)SjWGMnrHNGY@7gtj4S74M_C!MerPL!%C^gVBvCP*Kx>R-ZV(~`at@~jcWh? z6enLplRwXnh>jO+!+FeYlbp>nS6JWO`ZVyR&ELQME%YcQK$1f-cavxK=!`_Q^1I10O!0_Bzem5?O#W%*zzE=A`dT_xzyEUO z6J-$XQ9aG8BpkcR7<(vKrjJyM-yXKJ;d1?E32~U;%e-MLjMW+*$u$rb&h`=^YHdv4 zIw<`=?9%@5{sTYSE&uof?nVv#BDo}K;WEeZp+@r4hyKZ1)3$Cj`Q60ka^$SD`7v+D zzhj*cc$1pUlW#JbC!M`g%y_S85}LXOy5=Ou<1Pq!%9%dPFMOAIGed{|&SrH8;zPe} zIr8?se4nNfQTq~5%rWJZdF&4OiX*|&=^Wgk4cZ|P8gKoR&L4w^K(6>(+0ZLDEF zuZ0XAdD&9df5jEth8#zOl&z8Oov{QcbeOE3{~266soO<$m^#DV(EKaAT35DSS9B4g zA3Y-%^SeMY4T`ed)O2*QFH*!>U^5%#=a}{K(4LgfbNzeE!=?G_LRh$q99d6wc+?I# zAE(xY{v)@YTD)a56@R(jfUy>6r=Cyvjk7{_bYgIOm4*9cDPfy+bIRp?S|p0@djYW} zqab?B+A=SE%0zKT(V{cgVn4N8J;gu}zg50Qncs2fu28%*%G48SRy3{PDQ>LueP=zT zGt(TOqcg|PXf6{=zR+-Rja|x?X5&(D0xB0wngEJ)fqF)yzHGrv13N?AWG)y`izpQ7 zaPHKaao$+_6PQJwm3VJt!h``K{2JRChZvWU7Zfp-no32+U;q>ybju$05~vClGHfuP zhRS&(Fa1oJw-SO{bAN_OL(rRL4+B!>|7)iA*Bg;Wiyd>l;gFo+RPa?3NU#XL?!8ML z(rGq9`-L`DyIh;1-4|`cnxNV{x_<3}9jwa^MQj0mhEDalcgOE?Nt3sNb_33gIym z=u?Fk!B)k!?iJt!sHGT^q$BtR7Lfbq5^w-w=gF7i6E0NFAYrU0i;z^-l`FI3k_mEU zzs0B1wUZ)p;L95MCqD5%v*G>TOF4Kb4-P7$Hc38_={8&(-Eb7$mPp^vna8Z8+yoA- z3o#I_q9<16B_zDp60MP6nOdV*PI_mJwH4^2>T#))x$0R|x1_luwLFG1#DaaLRnOy% zs--}unay*X5x@K5KA)zp#gzal0yGFr6{711fTqs>kG(gKODb*S#wRDI#mZ(ZvP`X9 zXfiZw+_#n~rCi!I+_N;rrBvJy%u1cIv|!3zGASz;+|b;>G9|LYB|$}`l5#;H7eGPq zcbul4ntYz;_q_kTpU?Z>sW|7{_jO;__u9VKsRdy1YAEwORLAx{Qf*S|rlnR^>j%0c z(We|vku}$4sMYPzdeKnXMI%M+UKah_^IC);Ok%4HFL$QBzuaz<@-)qs{$j_w!W^m7 zwW-4fe^?FtO}sH($Whp+{48#~cBUxzY?B=?-sC!C>Zh8C7GN%-{!*r*(}tvHIRTq( z)((Ri>*-b>1}s3K#L^WWjegU>bo#xYX-||-2-w$N2Sue#VUGibP1mGP(Dw0b(7xf~XBG9QPraIpyr-JN^7n-Nal??H zKIT-f;xst;r1lzj@PoWPX7fb*-6yCQQI1!OA7=uZks?6pe^yjw<9NvZ*wSAsrC*Ks zM_Me{tKN@L3S8=a<@yyHLQ4J0qX!Qcb$%GNI|n^?c;o(Et*zvweL$1LcInN6)!B}9 z*6#Xxjw6ChRMgnL>r*f6-dz>_y+zPtToM5v{BVEyPJ zHR<+#6)i;@JfTI?#=xCH3>naLe?7bR1!auVwMY^ew1VztC;#YlbVFqT?i*p51+nCrCkKcknF7s%4J^>7mQL>iE6TvlMcy-UGCf4TL^$ie1#gnMu zQ`WMntmOlz)<8FKcr3d)%oXfv_MlA@$#_K|wS>H#-4;;9NMT3Y~o`i3=gG5gH2 zpc2g*0!Wr}GHC3S4AI{+=7al!s@w0ZgI5fCqs4lf@>O@-WA^N zs^g(94{ojvVFY^kTo*2Z4+`~H(`mG^L#pGt))TtuTVMQR%FoyT{C4cib@66-oA=KL z&e^peyg{+D-HWdS4DX8d(&#IPm!7y1kh0XjH>Q1h=uGlaYrUgEyMi4JK=)P^ywF>f z5p|l<^sYD7(rA)}mTVbqPuWpGSW{he;PHs9mcgpkiyNXEyic)~Ub*6T<-$?~z^BKp z{)viec1|!?e4&)DnX+nn)kn06`LYCKq{*O7>S(LvMM9ckF*|0}0>f&sc-0T9k>}7+ zmxRZ(njb?()%jsS!3-V+49i;&{K4HP(NlmW`H4oJWp{UjSM)N;Mm+^WCVQ^T(^GM> z5SOgC9I3_i=&{jt9`7rN{=2Rg<+;+7f0|=!*4J)zuExw+|L!&6?-nWCmRr(kRYvsB z8W+KDoTjX7J>tPG1TU-l3)1>dBdwq)J%fzsg$A27*RPd60>3koBPJ`_m9$V(ThJG) z%`M&`>l-UQJv#elx)O;Ky0Nbk+96sDOf;TNuuU2*dOYKy%Dh-bX+ps(43MTk z?Z+U|tk}r{E*A~?1Ph*|qup7t3PMQ>vmuBxXGqkTogewPX=<1|D7(~7e;&eZ%B`fsTEXsx}0 zaJ?_@!^Snwu-2^(p-&5J$E*7uT$_1hq-|T%j*{=Po{-C9B2lb#-Cs4`At!mwa+VgcgmHc2C1votiHP{ZdGjk z)gyl%KC8W#psWtPVQ0AihJE~gL%t@^&cAwYO5xRd{08FQUu}cdM?%74sNIG|sgnhB zJt(Dsl-@0W$fNMv3RaF(ynB}cwEU2O8%b?hIa7Bs^JE%aVreA#U}wm0&!>r^EQ=Iv z4P@Jn^;M9afz&%#^;ePEmdz)x2q(=qX?%C+sHtRZeO5?nY7>Dv(Hn<+x@OFH?vp}KliZBnnMA2X<4oSHo?1$> z^YocH^VD_vNym>`15t<0XsTI|wL*pybVD9Tx|v8`KXZ${i>MQ1BGSd%rXU7!(+4Ir zV}6k$X5Z%{E&IJ)utY19ch1q~DC1knNXzsF+$vFCalTC6WasKL+x0Bj1E?j8y#nH) z9xXyM!)(yD>`lehi}^yNF2x5|Fd)x1jR93=4+5MOUuKA>txqo96N#(of>LxpNH zn6wgSQnktSp_0J`XQNL|viFGwXL@d&i!cCqfoEgTm-38iQz0yK^K5KtD9&@d*kVB) z{>T^;wu^jhDZ=O^Zu?KwP4J1a@+VOei**-c=d^eh*C1V1K3Y$E+V4HSVMS<)_O9zc z*coC^{s6uF=TjI4bANR;y-ts?|6#ez-)3ruxUls-3<*Ix!xL`O|I{hkoVj$BN_v>? zja@%nT&AFkUwdf@i#NDdl&5wrH-n$S-zV;4y9(d*lf;+AvYd^A8i9-*wq)gooo`ku zSb29Y2CmNkus~tq;v%DX$Dw6d^PHBXQA_%)x_b#F3}y-Aw8dJ6vZ_T(a7dJOB#x12 zEt>8ZEcBIjq9b-!4~$;&b#+g?+`%ubQfH)M_yt&i#+H<1^;6D_SWFWe)dQc#(Ht>n9jD!p ztYh+Cu=a_2CJ{9>aW-D_peXh>v**U7TOM=B($}+)S(H;=#ifSWRtKOEdKZRLqc9YFgQh)I6Ih6R$7eM$WRFDKUpTgiOlY^~JgFC9Sj| z78(-ss%%G%&M+G3aV!;y2^Npxr8qKLLlVucN<1ChDwDI`!y^ur>#Ti6{HY1}1`eZ? za!*=sGwnFugx8#`Qf{hgoAEiGmeSA~-lwb^`Kmu+bfRIrO$jFM8DrfHbc!iLq8aE( z_T$|?d>@Buc35XEVwN>H3}u$`r)cs?&pG+18pe}+g~DP&g_X?^cdw>}ftks0>^@Je zOcF?h$R|Ki5Z?M-o(ISoec9nGl4C?o9-@H+LiE&OvoJ?6B;)?9a2!gOE$z*)@ocN5 z@#_ZlSdN~Hq%qw=lLFGKSF8|m4=S9=gS+AcKI+^I=%BAeVpI9}j&!YkEm;j4qK#7L z)$GS{MT6C(eq*b&;RvKg`{}yb;E5vqplT3eSYDf>-#J((6X@i$a%*;1cb!9ItH)7J zP`ugh3+v51_->vb&$9nUXUIoTAG9Qih=j&*4)}*TX0l3Ww{ha6#Vl>BX)H>#cG~^^-J3#*WFHmVb2Wexps9|!!lRv&L zhI%$hB`n4Z>;ACZ+yh%*Cr+f2(DDt#<-qBC*6~O!qIw?}{m8g}NiTciqCR6HD;GK0 z)IzexWxud;9+J3|T1CRzR#Da9W-HI3XuyQ=VF|feeK#g!?^jzH49jDzJ0{vw@&^|? z(0;9><#pZWBI5RV_Of&95UgC~h=$xBMQ%Q5t`7^%;8pdqLv&b?6!Gul19+6%8JXqG z1$tOPt`>kEWGNpQWy@s?-3lp{gYB%4v6N(p;;z+>{;hB+w+Hi0~uZQBq51OzptrI!_|X2$xaUhNky>c1n7gi~)Tn<4y^a@hsRc zm}_oD8Jcb{z-9q>${aREW5O+x;H-U~g_3o2VT^v*o({%PzX0y$se~-qR*i6Tz;V&i zUX*KSaA-j2;f^wdMULyR*)DPCx$rs%DV^~w2TJK4L)0`ictqe>3<#7~5#up+ku!{vYsA)^$&qgJSNBqhsVv%VCauZfOqlqDxJJsg=C<*ctHkA; zDm*P_pq38=;_8ZhT}X5ucZQCrbz=-$*QKTu4~zz-`bpc%BGjx}n(#iUeqtuYqGe+Z z@^#QqYu`ZEHKFP9TdifdeE#j#bI$jC;})f51OX~X!(%|2+0STx>o#z3@NigNFK-z^ zXhj)@^GkEy)(7&(n*iUl)CwFBgKkqcbFz;iwS^@F7s`ffptM6VxouKZV3c|YZjHGn zwaF%g!(?c@D2-f}RYMPSJLRoV(jMBFZ zrCI;eZ0;WQVfKE;@U)k-H;5>3FF>8Cz1Bq3XA2jJ<*eg`d#K@v6-FtRcYmJSt@)ZE zZtpO)nTo`S!D-qMx896clj$suEz}b%sqHpPRfl?NN^3hA4R5pW5Ry#es810Z6%(vbyb+N^5_VrW{`xUp{u^rfXq6Hm5$o`~l;f&=NIOT82H7(+5}69q6UKZF*Ef zer+6wYvP;)PX+WqDJg!XqZ1PlCgX0UcW4PSh;8x8(N{Euz#!7Mj=I1NN9$T42+tGm z|0w>+>i7XG58J*{#ZSvLUp)jzc;;vM_yYi3mnM-|&AR0cE|G0GXX-pOy?Zv8%N3us zZdcxt`axI^2q~Cm==!v4;(CqdLs2Z}ATyWAo*smr;FT5+Zgi+N%>Gd$B4Mw!S)@H; zxF(d97*_l?7XFd@C`tJiGNmfrI8rnDg#2J@_>Ap@;8eGVv|VoU$$O?7v@tSBEh`8~ zvN}St6?v%7)>B2={V~&YRf$k|+OU|bKC<51kgH56;0+w6H2@2-%6v<#pD;x=gdB3F zrNvlxGDIXg{Wfs;&7^+6_CaDESv{<}t0oilsw;H!Ev`$VJiI?ZPLlK;?wvtJuyP5A zI4d+OuG4ruC51={HKg35kWr)N*Zw}W`acw|Fg$*bt<(JsOD^S&AB&%rbVKg}VeamS zSmz*AV0vGs4K(6xJmMoxR5uL3krei)Ni4U*hda=u8Wwp1R&C-Efgwn<;6134-??R6 zx%sQN7vH5sss4NbENPp#RF$i>Y|y?TuBdp>%F4h$aiS!+ zB9$rEb%EWc!hq-cDNw6&?Vzn09eYYuCQc8J^_ zSA??v8;I#Q&c7of!b`(%{*@U3QSm#4rb`1e%Bw6+R2^+1u=D z1vd5XP-v$Ysqt*dw%M_p5@d~axBCM*1X0&y)_<5$Vfk0E93$%j&P8m{vB*+o~*OO8n=~oGttzO1A!9k4-M=vXHU}N3;(k5&&GcCV{&Sa-bnVL!4zX!i{;@Bzx@>- z0`M2}^TGNmkCZ>rzx@50l(=}%1ENs0bKGq_uslSB4k`HMuc+WJ>->B5z>V9IDos}9 z*v9x8H?S%9RKEPg>zdML57&W3cH+-BXlb`Cp~$GYoOn(mes3UZ^adw|Q}+7;*Oi}F z@vo;YRtiVo-y7hhE7oQo(Ab}w-q4^|o#QhSi~X{x(nLiG*8cOcp8p}9xMADO(V?pF z52KtXK!kw3;RH?vTVQdzkTYK;k)MUU@5t>nLqoJn!A`H7^z9t&Vw#C8YCe@kE%|yy zjj+{#T#lz+MgMIM3xWF-1F_Uwcx-AG#&MfyX`rm9O+IrGhnh#=s^0PF7PA*x!sP2Y zKw9oPcs=u#CPL$Z=lP@cpoO1hReD@;CU6L5wOz9J?}Jixhp9qH;>fm&G5wJ$Qej z^K#3^D~x@Unu*?Dl-^Krv#uo$qzu+R?$Y2}k z1M>dGd5_IyRv{-jCtbb8KDdA}d;RKXg#4oIX9E?ml&V_dU$O9a+kLZGL2Z0yn0l8Q z4IMl!);)6h3oduo8v=W?}{4IxML4J5!6l0U(cYc~XDl zd@FF#iI=+#{YpJerBZ){D)oMkpms{(9lw8pf^UpB{-z25d{t+7cR1sCn~e{nAdCM6 zqn|1U+j@ZrW1n^~8CPb1#oE_Dy?UEc@rC7AYoDe_22O(@VE2$R4!usQy$cRH;V<-Z77? zPAq7KYaxf0XMblOfIB|+OZRnIc7|JSHr8IJ4*TPL_y0!=k^ORxP#du~ba%Ny^oD5T z_J2qk2u=0cTD#!?X8EBC5DyEjn(Z~ZW(Gak--$W?2I%a2;tlJWpFYKM-#)x?%i?g2 zDrXO5bjPMDHH^bNYxiH^?4dT?6!LMt_ZMAgj1rYW{1><;GaT(>jEq0K`Y-4DU$j(v z8&H0_;Uc*RwO zYm;wHqW%v4zB#?)yKW~0)M1zyTg)P?Y`%^EX9F=;u7eC8V`v-*cHleAyYuon`xLdQ zna-!Qh1%P4Ws}a^d@&TP6ytIEWf$YZ?SCRfzTO+ZJ+|U*HM4mip|uSNr%0p0n|iR5 zUnRPqV=Mo7^nla9L&nvrA5J2fdZk8Ri%oCq#~Z1!r{2Yf^_xTs`jfzprqVkZ&Zpu` zN>LaHWe6keE={ac}Yee}qygd%b*T zF3w5^BeG?EubZ=4S|Ubqx!Kvd>FF8fZOK~jB$f1ZRZZNBkm>iBb|)tr;*mVc;X#eC9PcuRL%MspTA?-A}#b-St_97cOB%5bs@>mWJ#t!{?f zhE4#0djJH4%cr*M&AV4Gr~(9LVPvM&lWu9A<@R*vSBKzrcVTcnzjmzR99teg;fUkU`TQ1P}%fn-1!xbPHFnkMP8am*ut47y#cmHTb}gyYQuu;YCe@4%m?;B`LR4{?8BvczH2_!BxP1wb z_2}AVeaX$f_CJjq7fN=~pwzt;)eYmBH9Z@?f~Dh!-|Xp`5_U@cbSb*ZoRjb!1=)|H zN8g1xu4SGZ7hPO7GjQ|vNCtf_Do`xoOlWqpufAj7=8=QZEzdshIQ@O^~;`+Oy%WIqAO6^V>;TewJ z?8)?AQo<$6edilP=km(nL(H#~=U>5FqmrWenmVJNTY>vq{*{Z)d81XPE{(Tj#6Nabb2z}d zSf2Y0hJN~8{IQj1+Ix>sQPeQ%0I=b&H~ZHw8ts6s{0AShGnt_Q4c_nQs9`7iKY;OO zctdY*5OoRl;^lY5uT>3rdFcnmmd++$Ut>6_$@~#%;VMEgVc>4{eEy`!CSLyg86~=T zJK1pfShr*%?`voT?zq1*xk(p83dwe)?}xj$pQc6wd9*sUlNwqH>3Q}5O8zMPy}a8X zx+}o7+{&MsGiepUt3+(R9TlXm@tgaX64qCS;^n?%8Q^@kH1LythWK)QzJ1|VTw}oM z^Taa`n~&^ie;s=Dwr6!1cwx&%V>7sKpN2N3!(Ws1*g82kR%s4M5;eDhlK4*TMx&TF9{+7F0#1lk!(Jt z*mebQut!Rbuh{mde--4pK;j*G+I65xk6~?-fgQU`n=3tdftzCk9@A&&gVHF%PEiX! z!5Q){p#7-0BAtGxP*hgUV#x6V6=Yo4cp}GB7h)`R8DB#UT}8;r;?;xw+j|*UHEaX* zB#e+Ex`O9cll@-wqs^-i{Cc7F9Sv@!57{Z@;hD1&95D4{GDH7)IXTx2JE>}!Yk572 zt$s;J2X;}db&ovXx&9NPClq6hD#bbkdsthzU&n<)P_lZ-ONcf+9+u2bRH*Q72rgj8 z9T?Yx5~jLPGyWaBOPW`eB68r^WUMXFK+M9(Px*?GF`;`qP{QZF4f}gTD?yFKGbP3$ z`>iu$HxA$IUcm@B>pzYGYCjE`QxK?oTE1OTE+mnQHm5g(UK2kSbE`HJoP*$TnZu{f zxZg86gbFm1m3?Ur*SN_4b_LH&K}wBqq*2*B(|+7bihrTwXp^8QbiZur*n|%<8%h>Bi)U zKlw)ZdUbX+rfK!noa{k%cD?0;0Z*N{%*mzx6RteMen-1tM-9i6*8$8iJ`f^4dTbe~ z%#3NSjuEH7=IcSN2CF89|crB$H)M6ZX=k^nBdAFe_{T^I zhlHmIWo~KQ2x%mnhO! zPu;kzzN~Qjq=2TV!Twk7Rg_{yfP&ziAu2;PY%}}y!COTrJEU4T{gO)1C8uTIntUEL z`|EUYxj{QUedjFYbBX#N+THkkN%-N}NzQXTmM=T&<9z3z`Ry&}ZAC|jM~r#Rj_Jdp z@Q%U@L}5)8$d&l<;a+#h^%%r>3EtUU>Nd`R64ui*GxeG86A?%zSsK+TMkLpazzC!C zCuJZX#55PnA56g)%htjKk<8W}2^QxGgFOg=%7UpMQyzdt)1N94yq z94#z{)1FGElCYu*S+J!0G_@J3#OlA*m)2*2%u)-2ESWt2UG?CT-v`PJIpG46PN~DB zBgZCp^2b%*<{`J7yf$Cx#W{%l^j%9|Z>qHJS17!vc(4wWN37cVWC#Wzzrla*Q^UY2Q-SJ8zfcy=i`2DBY>*x92twf8fzv$=bzrh-+9(o@bz}13 zx!!fFfXMSF#C zXO-CXzqaXsHFhZ!f{b%+xn)(_apfEgo-5k#(^OR(oLm626Taslp#+zzZo1qp+YMXk zILM_kFrX95ixYf!PU0S>)@qphpQj}n6}&2ZLJX_rEi73?x>2br2%{E58N;$1sI-Sy zd0oJwT3|>xS?u^AG?@y>^+}~ZQ%nrYN!nf3U-c$%+emrCNoF%NSuBa1$mHPhsQV77 zolLB^n6kcz~DR-*N$(H(>EF1qz3x>`)YYSbBM;~Z+;C~ej+g|BeEu%Cx! zH-8s`e3FlgS-j()Vm0}ouAfd$k%@`gm(uiK`6^H|+5LdDM*1G$SM3c*)rMpSJU*&a z7}Hgvf|=bo?Bpx5VjjGTh~Rr>R!%+evK(2MI+zd+SrR!x77Y9a# zbnhmj-}+A>kMOH)AfqEP5xH0j$HAToxzr@AwWy3!%K<}iHb>Q#c9C)vVb4U9qZdrx zi&A=|>d0hrtpND$P#0ZpIDIHI`tU{m;AOmzKf5<+8ng#D@%r(*a-~oypHq(C8^Rl$ zhEn>F-3va=_}MUYj*8ZgAGkOiUoVa?%ix5kCkbv8R| zZT}TYeu?NB&us(>DQK^Dp7839B;c&%nW%;*MUmg$m~BSW!?gD8NZ;}*%=J!A3?Un z<<6Y!kVn*1@hcS1K(-=-#-HO+d>>_9u`a~g!VPKZWE|!yW>+F+c(&4RQ39tBDi!7d zV%XS|h9zi2PP*QIDIAvBVl{QeU|yJJoo=-M;n^&#HJh}Y;^V}xwhP*v-pVQy1Jo`} zu=+%s2gX9M|J4C>_RNMuk3g4%Z4zaVc|+?T=E{cnSeuHlJhQw(E@&wnY%b{sJX{*PBBCf^*gv}th;a$%js8auj zl-bG|83~^dCf32(p)s%Dyo4&f(zoZ^gDS{dBid?$Ssu(%a^#?#i51OOQ2j8VqUAJ( zZ?|o7H|*?G6{mq}g2{_2R*ZDkcGiUyNiT9jus=f8k=Z!=N~0}paZkUEI=_TVMcF&S zFEkGXACLr1pm(+@gT6H?7xj41??#aDUQD1P>^GxT*WdG%V9B|?u$}Zx59O>2blSEn7d32JU_K&omY~tq&^5Nmm9XLZPy$9U3n5~|- zDruSrNa^8BKd|}%sk0Q+rv5N4dOxykVa)k&cl#x12OtF9Sf*EFG@SI4vhKHuHBdOc zxllqXYW9GfiHYoZcVCWr^o|JHDV+8Q8oh&WKDuX@D_^5bxW$j0UT+q?d7412#;~X} z^i9Q!Fd%2*;vl~caf&Sy78r4EoJn=X1Xt|!YIxUMhy|U`xjt%dePeRkSy$6hvf}~U zNv^+E(srjD^bDaEsjcp$$;__bSYteVjAnh)^5*&nUBq0TpP-tUINZm%NHx%o1mo-j zfQWU(pCw)m4XP6a=R3RA{WjdyL$Ux|J{vc4JTzeZCdU91q77;6q+bX6&PL(e!V|v0 z!@n-#2M*t6ZxSr}*#>I@LbP4ceUhd=CwdGGGkC z9z}$9>@EZyf>bqZRgwcq_&5WAri7)8@sE$66njh;AuL%=$>k|`5k(M5KptHM8kbVx z`7q$Ib2VG94G^VHy-%(AG-J*g5D~Ho3_uH=?NuOlVkPc3!)t=2?>K2#kGG>AfP5hd z&$M#mT1)SitP?*BV)<6jc5o0CVlJI0tkGvYX%ZE;0x%x8q`SScXk5&z7OG`L(yynhHw2k)nIB|)7rxvD}#(L&LVY0Tr zp(`6HF7tR<;xiluIc{koy0&=ohyM_Dor>EYZ9cp(ZkCU)`8Lof)M^u-EWQzK z>3^dz+QV$Gv}8xBR0oPVp^JbdhB zFw{Dua%HK_9vDNPfo9>tHIo8}{M8>|UZO<&J;-JY@N~d`7o0^O?;U?`{R^moLGias0Ys{_`zVmA%1$y~vx<$>IU zRS8~6Tf*VH7_JzQzsz(R8hZcq#A^UgJ7IlBQ{k5Gvq{uAJJO9lLuq6(vp4Nt@EicD zVL=3|o6Gik1Ms=LRXL8RV>N&BVvQ2*{te zyftlSa+ySY}{3VyWTS*a4n=^CtSCr!?|Ko?k1dpxN*nTxox!sptb0V^>mCWDsk#%S2;kdjRFa&Iob7F z9ylkDTI2dM!d-jUsiY%I87_?Qq<;b6SJg&J!cH60wYndrkDpu7)cMK@2i?hnL4Byc6U=7^`M#dA6~R57 z4BLi)kEc~syB8$D8M=^5TxpZ&boX(#TCh`Z0SJLu-w^7WJPJ$iqMxqwNN(&+(`WB4 zai=y>UvbX3QcNQQ0hUT{E!(_O6IH(tLW0M{@ngX61(CXtdg;mWyZGiH_bO+Ylm%pu z12W$ZG}+8w?aGom_73a>mS5qB;#YEd@uX@nuk$Wc%zhv9qBn*)Sc_xZ!!|R-Z&>{} z?MQ7vHbc8UQH;Nnjo15y=E}^FcJZD>wChaCsr2-Jx`x!GbAs++Y0-Dx4=+QGO=TzYZ8(p>O?_*|3oK~n(m3a$@ikQAz6-;3ARzH8` z0NX}KMaJ$46%Wd4bj3~%u-_t2jJM!DpN~ZK&aP`Pp7sho*YU0lgh3e&M`!|tizka{==oF0P=EyI0igE zqSPEiEY3SylyI^RaCjKYOoNfIRtg^B4Ztt+H2V*o)>E?RZE*gL+oN=$!W1$9{5Q+NC=i_HVxa0Hmk9o zhIJfoq5z(%Rm8psz%EeH%e$DT_m7&#UwXDaO(DlJWDG`*?!P=E#iC8`e3J_|jxVm< zIb089kcv)_;B{9hu&ao$@nO78L(d?r&v=!g(8@LWZf&g6eb~_C?;Ug=acDRM8!XPn zD>^3yK!yT7RD+@rIN-8(1YPTz zFEV!2cs7+(oB0>IN@s846;Ha)mf&gP8h$|{WHYzB7dAy71k6WROV-q_)(L`Kk%EC% zb8W*cc>2uY))KZXJ8M!Fk?NC51L|fO;W@z-On$ml{5HiMsD&+?gXY9q)V#H@{R%l- zZH#Ju8+Iy-H97PZ)#|T&wpAP80EA(%ShV+03cmT+9=IV)=|O*1{nlwRmSh4y)OoP) z^^*sAJM|)!LACB#Oiw#lQI}o}q|_RsauAQUzsg&R!u4k6fwED$HNOsbTaZA} z%ZpQq_X&!D0=tn|*$87}-LKc{M0bEs2? zUTN_uu5_qchm6U-^?I-fPa#r@oiEpIn&tteg60H<4giMczW{}qlohv1<~ymWy_S~zEm zn_-YY0&ctprX^a4R5;e=04aA_b4X3yJy-6h#lH!cnnD0l|!6jEMtx}kSP3P4A{}`-A_EM_KCHjr~XcvQf1q4Cg(jgIgOOD%vlkB;}{`RlttYOWTUg! z*Oz=Oa-LcTOMWYvxFf4+B^5~50QwJGELubbW37Pp2`MtLH<5{xA*N=l?icb7?sb(> zUxF@ly#dnehpgPnv%ENaAds;mD58=AShn!1G4HG1-!~eWl8~kj0&@WD8}-IDkm;r# zrcx-GDFmM}lEq?f9)x0ZU%1T?HJ^^1Z%o!{TQXsb$-+85 zWaGlOyMOJ*e~9-{Ig{6OjB20*i5j8pLu(D+d<0XOxWyqKfOXTK5aZHf4STX)1SM^` zGqRm^oJ|v6@nmAyPW_fru}=+8v_hNG0gE!8%&Sh?@_QCJ)#F@=vnDqRoj#d-WDopp z{{bv0roko}73w-3rz6%8*!2U!tRNF_-4NieLXt()bKaKKY6MuIG*Kbr zmpQgnPq4BM#Dm+_Eje;W-Ic<`oh?3*3RF(jW9(tq#XIj5pNp3_b$new|3b|GXg&)i z8~2g(;*OMC4RdP#**pWZviJm9(FO8i1X)4@*I_(1j;d4RvL;8|Jso21RvKteuv3>O z_(0AFtmu+>@O`K?RGS0+H)l+zk8hue9r~RgvR^WhIY7Mu0|x6RlO4f2O|_7xa0l8g zJTse_D9^<%V&~L@d$2PM4jjski>R}}fQn@vq0ms|_#hP^v_1tm7C=QYL>F>FiVoc~ zZrAao)S8{0Yl$7TmxzC*wvnlQf+(r@qYGPFWtH8RRr#od@~}BV-6>5VH~-*aRGj)! zIASfg_TS}>3-L7X*TaG~MaWVL24n#pac5~9 zr^9wk1oELY|9IEr`j}Y`=h7*3p3B_KV0ptshNlitoY8@7hi~j^gWX0P3JvZM#|60y z4i*d6Fbj@gLFov4Rtpe4hXK{aU|el%Cw+HuTxO-FtmCM-hZGAWVg77=d)TDVZoD0@ z80@4Q)NrzAHZxXl+H3qCCkU!Uk!VCXkDu*0`KSUb8su{(IZaSXm#nKy7FbJziN+RT zxY%I7kPd~4YHi+NG?nQWBA^CInTE@Prhe(ZXL&HoL|0(nOD>6X9yfue@?_97pmC7P z`4K9UJ9O->n#BO|I@=$95a4~Uv)o+!`@984oi?c0ksf)cUh{X$tjVS&-xdv0Jl!Hb4opwzIx{ku<_=JOy+8Eo8-2{-^OURw) zI2xSQ5{a2Li}nXvFoQr{->W#)Gbj6cWn-b)QEpf#RN8x|P7MQkhfwp=K`Hm#Da(7w z>hFmz73^Rp=`(==&d&vnbFoGRSwj}r8v zmZ8qzJTR4HRFOaL&@6wPSVS?wXzH+hr5*HhMuR#b4loz`SYOVJHgMF;dz=>l_fU2w zhMaH?iE+|VZ*|7tbc1*UVoZvF0@Q-VV8MYHOU?ZxAk&$Pr)@TTE0Zsld))Chb`0VI z|27$Q(TE2fAiCpb+(Sb;3U3jk3impAI$7sfVqSzmYDXl_+9CEHR#LH54Qi{I8~ji= zTNiQ^?%mx7)TL~JGDIZMSSW4I9giUsB?7C2CjlLfN&z%bF*i=cMt!yj$ zsexxKIg+6sr}QjB8x@>Kzf%T6^Xy5tDuKdcz@RoU@Q{0PZ7S%25zlXykFG_#0@dC+ z4B5{5#tULdTrEySFoy7vo2CyRZqi5s@rY-Gp7qqQeR_GZX2X(~+g|mdJhXcKCS|DmIVzOGFrpo1u#Y2S|B_rTcc z;^tETM!pXC`M44uh;N<{K^J-UDX(iic94b7z1U8$Kj@^bcZr@&s=8sbcHHXH;#OtF zV0TiS7-)j{KvuyGPb4>_9Q~bfl0X2n9biSk?vG&PfqsNw#-=Jz&T_@TVzYIU9=*Ed zQUPhirur;U(w(fes3wM>(=D@K7fPc-#*?ARy)imO{fTS%R^rDZCU*c5ZsAt=SQ%qu zCkYvNf%j3f4|R4uFz1Ck+S}I5=Y><8fw}O=3uNrHY=bvHY030g<9Yz9$LlN|eKigV zT(`7Jf-Y=!fLu2}oOe9A7G~PDyE?d_*%5N3);fBFSub5y(p(rUwi-Iwd+<+_nb&>< zFQ6GiUn8^%5-2~26jTe*t`tv7!qOLs@5-zh??@IH*mCD!u8&yTNf%q2Qa()?vKuYYK~X<`_xCXNHu%N{_#U@05Iikrb)z2L^@->cvZcXj+Kk2YmTg zfX`I9vk<;QcXloA<$gg}L(uH))}l`HA*9mX4n@Ogk79__ym_!a57}_ z`5mqHk-n2;-x^pdv(JZ7ScD1|mA&60=aLgZP5yAVIVAdbt&Z9?L&q4B9RLfUO2Zy%4ras8#lwON;)@;dfCy}#EnRbzeWY`u#@tNVKK z$!ksM)ZM__4&6R6U%3=ui>*8})(g;~0E#K0-uD|Gas08LRh^8EKPuYe-rT+X^ZPYT$i`MF#n3zU3 z5Fc8WAw#arh#W4R2@Q<(9d9IgVCKf18pr2oN$4Z4^9NCG@gC8|Kw){anV$bfX3N*a zY%fK{lGZAN^xSN-Z98@#ykbTgwVW&)yv3cgmx0~e#ws2f5#dQ~xV5bb4BcEN^7igU zy1L*3WBb57#A1lD$_9X`euV-2uMoXosm;HVXqT%ymAZ{z-V=WvS|4|LvszQq_Cjlx zUk4-~yqfooQ5AQ|y`=8-A>r(K^xs+6v1jR_OX zerW8MRyp!!v&P08JaeqCEBz6a?l^(WOR|VKf-1LJV{VqQu4=eI6p}%LPteD#l3Xcs zlvh(2U{SVuepd5=!Q!nB8`eNfyAzpri-Y{X`US{fTW50OwQE+9?Xo%z+4vH+^;-;2 zWiX_7szM0nyUs-Hi=a+Wa?t^dss1vRdnT+<=4(wkg>A7-WTBPp{RS|`+d%by^;6>4 zkNx6^zPWg_cjYrDj|c+e+BMK=OEH^1MX#(xnQJqs*IqnU>j>*DKTi9>@Di zJ+4+Za{d#;UZtm*j-M!loUDV?81!U)MN|B=kpD8Aus42elv{&$_NcRowlupw-|a(} zzNZ#{s+{V9f#CuND1l_Ws!aG0E;Bpyo#)?HuAsIrg7cH(XNCG#><++vm&%!TSe$kE zvk_iT?stNNADtslxBr;$`jtGsn9F(-1+^WR3QU^rninYnPQkLkp{7GS_J6P4Xep{H_*6c$ugj{ z(JFUzKxYQR?xL8>|LHiqUM{sS>d75e{pUaY_4pL!_*YH9bjLU_At76GsCm92g*ozj zyS04ZyyW-iN|jMTuA%#Ya=8lDNwd7Q{u?&+UyAqN7@mzsMPp||eL_P+ z<2p9YJ0yVBvWmF~{<55}E*hUJ*`HdIg&jNP9BMm$@Xq;prg&mWC_txL z{`-&j|3BLcT?jwE2pK~tZfnu8Ja2Mk{=`}uE3b1~A_JjZq<@*g5 zVSsoL`JMm?hU+BXsMID^?)DzTu|FaP-p7h&Y ztzDp701T#qD!*%R0#h@pRFJxS>V7?;*{ObKi z`zn%2^p@e(-MJ254H19F_y002v*GLpY1F0~J^uwvEvo!~-?aT{%~aiLZLp3Q-0(}* zxBXgR;i~^_Cafa)@+r^pv!Rh4CykvwmA)IizFIPem;5r5(D~5kxb1Cc59ZBEm*TyF zNDQcaKB*6x$iGvhzme@k{M!*YtJQvcLwQ{VV!jv5qh{YL9`HAB6alypDg2^5@4tHW zC`!We0ECU{GII8qXOmTpoHNSop|2>>U$A0B>AShPx7_So+1ZuXgW~=TSgGxc9NIU( zQ$O}IkSYvQ-&1dKN;ox~o!_=L=0vgYIFM>Q(}?+TKC}b~D6sm7h;g9*>H$Y?i2m@# zYW6$k!qxkLYx>#hW1_=nz55z&A1(>k5WN8E+BWJjPUU9bBfx(P$r{FCghvs(LXQIh z9V`7iyaa43U3Iv9{;|b{fRZ!^Fflypy;GL|>#O$z6O%OgDbEh+d_#Qx3vATP|AR>n zj}ByT+IvvES3yeu`wT;4{Ahjx+(f59S6RD3;cMFh{MXeuj+W+H^7wN-iZj}o@>Zjy zRV~B0?>Chu^slyaNmaShl+EC2FmczsYHjJ{6(+B>j6N2+8>oQ}a2|YZ`CbVNeOr86 zY$P=6nCJY80)xIf?-&ZFoAZ=2`SeA68JhgNMK+N2w2YN`tniQdkO84tiyEQIHj+0X z?qcBM(`M^42Z3B;3oz$rvBg+#-s-1UyMpX;n@2S713n>8SqJ&e$jRzeQ`MT&)dOp+ zV%|^t3@qv=p9^d<+Hs`QwZQjYK?{ZAcl>NMrTHEO?5j&D0Q&)d!)I1c<7uo&`7PRM zuT=Fl7k4aHSV;d9P)j3(Y4|^bnmO}|A2@is8J-HJX{$g9$4hO#)(w|k@p>Wkp)+G) zxmKu{zzMjCy*&xl)N;941*_6I0F3YWEQB6edPk7Zn@Dd; zuK}b9gd)9#q5?`Mp$CZc4$=u-Q85(hC3GZ|fbxU1Y3K)|-zJ4Nih0qLFnN7VLD&Nna zlmF%bwJVi^8e{f0>Wdj~VMJUe8k5GGNE-a&Cp(FJS0|-;tpax2uZM7kLmpX@|L;?GL!rGTV9vdqj*bYA8n8zJ5{Tj!fY26p=5Rv4 z8g)U!hzVT7owNvqcv)LLQtAPjpTFHG4`fDqat6b{uHOu5`+&*-PHDBZ6*gk4{Ev9} zUz6>JTV2QtBGw?r>59`fKPOi9ZhlJc)mrzK_vW%-z~)3uVme7m zH_IR-?(Qn&70&olZMr{P18?CF&Z_4eu*8#pi!igSOhq0;Os}6h8{Mq|sea2eP0sFb1!s zNAaTgcNVZqSZ&=4^I+tD9yT60&H)rOrG-?7V@`wNwPLvFez92n*b(GYh2vB954nPW zMSR7LUY|SwxiL1lGU~xB;3fwFZlIJOV6!p5(ajd|u_Db~rJ@%~RmiC@dejg3h6N57 zbkIY6F!-2Yo4R?7L%H`2>m0++g7gcuwC=8J@z`KAEwEcw1ok#|&;fWUa1~4(+qAaD z>S;aeOb+?6i2eHl%)o-82RM{<9}RHkL78!}ePwBYAGut4s`-yVf1KYmUMAd4`H+lF zNrz)p80yY{ADRGVHZyeI&FldV{ZP=958`C~jiL2YkJu0AuU2{t;~{l72UEuK%C&*h z6#41?rZQ?krvT8=fPYC!$3A8?+4G=aQ<%MbUZZ)pG@jZpxJp^J+Pi3_PdGt1x@?)< zN-w*7ZXrXih{A>Kw5BA1KHkS}P1}K~NO*I-A3Bz}|75KQmIt&-G|26FSGg!B1IKa9 zyAPnkz&&-zrry@ZhQZj~>0(>F>=8-HXYHgtyNPDB4f@F^_(zgkmA7gVHsl?Uio0r= zj(SH0z4gO(&-BA1D|`X#IsHiZR~t|#{LHS*y>twR{o`MvUG`T<7vC-Wvi8&#?|JFY{kA|iKKTk)AX z#h&|coad;;IHfn+j(+$1%$)*%gCb>bdUquURiGPiQnz{FeGSC}UKKj_ER5oSJ#!Pfq6H7bIm=y`(5=OP#A4E=0&SWO zt%ncwlWTc%e8M{zVM6eVl$jjhrd2)$8b$8}FJY8O4nAdQqVsvV;Fgukdbs5glvlE$ zTDb>G8eaMIElE=e^spDp2gB2|SLbQ%HE0*H2@2ZqN2KvQp{4&W3H%!`8|Tk&ya=y> zjcvUM0QJ6SpythJjSYLryW6tlwk7}*sC zWBMQD)6lVxB#e7x6jN9O``lXHd9Kts<;(1ALy=w#nCOcIQd$;_n*#mbq{EOqqm?j0 z_N_Xb3)QyDq6qz{GO+=GwLq7y!N8-1&8f|0%Sc2ey+|Rn@U#`D-2A{ZXB2SRU5Da) z-`DpPDbD#mFyP3?j%2y$fK9n~?l5u~XeI(S0H-=r-x=@lnA6x98}DuG$z9mk^=ioP zpBj8$KJewxpwt4!`QA!ANn|X+Z$N4FO-eN@8i*r+WPKo)M2OOF3b;AdNb#KbYkxm6tP75@LERd z(i}bHuJkQf7)wq>3dgo_&dhte(rwN;vX|r<+YJX4mmk&n=&jjvYh#yEbq4^N?k!+> z$0%H>($#ovEPd+fQL>U%&y<5wce7By)}k3YT8?UNGbQi#W0X)OS_gz{dI0={cjDEU^876Ye>Zd?C@@~{+Wy{P5UP@rw8 z2{>0zQ=oqM!4a)Uy$rcz7Yi1kF9$PFyJ3=L4Y9LTR_Vj6138f0~=w(xRFL*q2`M{3>g zb19mLP39`9#}$CB$Rc@N?ae7{DH=E(daswFoa=3CWdJS$Q>YH5Aa@xa#m``=fj)p?xC$el&IVfFoN+iL z>^Dg>SQ~io%dCmuBA3wX3)s81PEq&yTC%*5U{aT=InG%zmk*uO zPL>rFKu`p+_g1%U0s+nzXoaA7R8F>#2=tFW$=_PWV1eTuj{~2%?1W`3>jWaNi}V+p zsfpRKYwo@8INHS|**2WER?-{iGmNRdYe^%D^x{L&9Ybf)Xxs=)IBr85X!9r=UhM1A zRQ0{W_4@Te}l zZ;_-GTUc@2=;5ypTsZJI=KuS_Z<-uPk#TZjFX#Bmm*rd-4!FL*%K}K@uc^F|Qgmf> zrDso_->alxG~n_S;|qkaztS!2Pt9G2^_cSwrWRzuB(D~>bGW=^D$p{oVL`EB63U43 zOsd3m6eo3>@A}o^}*e1I}6Q$!#Ao-!mJIXWs;o)#1T!$OFg!_~d3=fd-lcWWR9yC4(CdVR!g<{x7nDt`4JL+NA%;yy zi22zixo2n3!6wdJ7K(^phtAH>!^uvZq&so4ZM;~_{O>DZ=J=?($TitUV1|; z)pUE?ejPm2VyR|$WEN^;y4gpte?Be-H4yxoY*3N8;?Fi+aQQhm}-!r ztll40q(=Z*qR_LCeot67pJocXF2Ggfs znIY&9@rjVTHEbCHEIuI3gnR)=K2*lXJgr9pmtOX;ihg;(92%Bc$*0JHoF4a8^QhOt)e$>^lH+kg-2&(t@!0h;tjUnqEEc~& z7mEp~Hk2(~aKMpfaLR*Fm~4EEDLGY6$cdS9?(4PwKWY5C^`8RPzwr6vm{an1?{_ zfj$tLbxy-_W4z4Cqe8W(b7XwB?_`D3$t+gn%s)e)$(Er*^lZse!`i`L(uy{$$Oq@L zpX|<6F!JcIi!jWN4y?ZDIBFm~Up9VGjMA1rayt#jebBw2yt_ z_OV?broauu@JEa|!*n=IM%0s#Y*EWlH&XEuV?+!29Wa`}(VcQW# zv8l)(whg0>mdAT6GQ^^9MR|qk4>ZJpW7_=G?@k6xsEHgCqo=$2LpZFIXOrz}S-v2{62$xtD^LpE&2Sz7QebqwU~ zFefIvkM(LgDheX(DZ{O{?(pH>0In6bz|VA`ImC`h$1kTBDS%2*qKFJTS-=|T%q8O* zTIGE>79$duDRADFDS)|6K~=aHDCr&Xs2aUp8oDE}{wHNz9CrL;L!<{ClJm$#KOnmM zhy_ZwQv*Uz>$5=>)^;jTU#10EcCL?gG-H@iA?cU>C=Z%XG5r!&{xq_iQj^m4pyu51^mI~|13klRm`9-*UETJruScKR1F~h|^pc*MJe*57WsQ3^N|cdl)#xgXBP&nCo+E z9+Z@e`;?CHqEY&qNBv%kvG1InJY?2pC1GNKHq;z$$e9sd3?%%w=Y3~Xe?IcaG`<+Q zl_<*T{&tA`?kxzT#E)1V*GkB}_RTc#>{$nkv(>Zk2GfE!f=^;ve3D*UmZhM5hV@O! zFh;&yRTp`tu0cze!6y1p z+T(FT@PV?UY(}M4N4UF_EQe>PX(5Or6Ro%(-w{efQtHUGtArtmj-7oAHou-A+Eg z3q_M&!VFG#P-Zy8+GRj)m=KFoaiO-J8CrCR76t@W`~oul8a*P)7TfUb2INi2Ju9TC zN$*4SCw2si4KWCgg(#Oo-s{raM7qeb(hoF`LnRzUAW_nw@xZ$f`{C0o_8}dFh z)D~%ig!9oIok^ssTS9ardMEgE@7 z`q`;k15vx#k{!NjBKL54A2yp9NQjduYFr`YEYSn^>MkGQ>EX3oKN`Rfp)FIn@zj7f zi&qww03!BGB%X1n{Kn4)vv}uGR737{?vF#c30`V?!F#;x^cbqGN?54e*2h6@b=HFD zdL~Tc#v9YAt}|!`T0}5l?dov3Q$usqxC}Hz*xyDKj!=4JsYB)5wsz|A;g~cpWhUgk zr?!W@hd7XeN#G77J<3rn3^k}kFK;hfeU}E;B%3t__=5FRoYE@=dL+-lvvO1Nczp5x z9wg=#yq>|m;fU?vVF_SAW%RI6>n}%!>4vhRcu<^xkKL5%sYH}*r2Z}Zc>6xsvc~iU zBGhVZ;M0A|>BOwPy)PEp4%I5VWmpH!SYiqWHybaD7H{8ufM_JP^kC$zToXK@> z-&}Dov9nCuNHm>_-D^3l-KW~-p&Y4@1I4(H%cS2#?+x?Mtk7173^e} z;78ik3TvX{k)lzW3Wtl^r5k)v2{;gr=g7NlxVhp|`xz8IV@lOZe!bX$BNn|^wY+R2 zm|-Z#kp=I_3YWYob%H6)cZKoi#O80j@v~%@@Y_6ub>|=a!*28$CcG8y!O>Up9Q)P_ zNZ~glTXw2RW-|;GK!W88t_n6VVhBj+AiDQXyLpM<5@|Z80!VBz@(^ySmJs@Qik>o0 z-yBgIBNr|ocG3J);zA!&`?To`T`NVsJUr-yfP6wnq!@l@&ArUMEr1&_4B05X(Z*0m zfhK*W1IpXke1l!LkVLebg!O7NqEu|t#*cJ%JuAV`kY2jksq}_;+q$DVY;9suc=y9H+c8$AGV?6Q;%0z5^%Fb0&UP-^e!>*}UU*S-0rxL4$sMa5A>TZRlbqLHS z5`#hcACX9~jd1C+_SrcJGy@l+104CyQ+@&G)&9N+RI<~jsH&E*?q0A_8NTq;a=M<` z&H@usVd$_nPO5MisvqTj8>%J9l7VJ;w?`M-^-0KXYJ9--OA?4S8cF!{Z&m*@5;wMy zt-PsiHW67_lyGQ0wfZxxI>N=j6`KZOAfR4T>!q^8);khD5=^Ni$GZWhJben? zkUosG08LLO8)9IHPG%L@*zV#m*j2Y{Rsuc27){q_!9#VIT)&i?W&%OhBO%mWIjmyF zak2x*LoBAh8IU4rI9n}E;)!j;l<{F$Q5W6WHRu6q8oS@EekghXN!3CIb1HPmLt#FA zb+EKz6dT8|WH(>NRoKER6x}r-3#XePSNSho&(BGPjt#c|unO6~g?%XasHpPim6F9t zodgNiF&~tc$ydviq_jlVCBws_)NNC{Wd@E}4QcDRVPzM;Pt@#!tcfSR%={3W!1Do% z^io);ZVerx>y!M}L4GvEMUN3h^`*m{A5k!)TXLiAQZMANgt*3HCtd5y!0?D-ZlCg@h%l`HEUDe!Rl$c2Ww^{_J#)|u3w1vHRtZacuhd7E zWx&E`49**IRB8XgIKR`WALh=)w7l@`Wr-yZoJo#M!c!x#A^abi`k8ZsAse~a&pKey zG6SoaLvcZtgKOx=6`$n2xh%^~0lc>BH&wNAD1G;>ENbgQ_`<<59b!N~#=kq34u8jo z7D^{@9zm=MR_#H0B8a;!GCCAE8sL@A;4zm%I?8ttGR3qwl)m|vGy0-_^XnFC+aR zcnhh!JQ4Ze=t9{_AyoYm>G17sV%!b%rI|yCl1uH*dxq)6-NCB6Z)3-Rl;ON1c&vF} zmHf@vm{WCGc`SOwh-=%mOq>F(h0p#*1V;YD?aNZdo$H7XYYp z6DwDLzk18t094a=lb}^T(M0d}3j7r3PY~KB`6?-GHKYeH93|B^JYEWWVxATms)m=f zre6jsf=W4hf=mL@h~C<=S)T5!4D>ApgtTMg-Yc8#3#wM&Qi2v~lqfC{Ycfv1!_P4w zjaCTs5JIFtfPd?nr;iXD0+WK26hCpJ7VxsL^cO^FVi1MSI7?2i&dE)ip6--xf>$2y7>vc zQvX}ZXurhx_Mi-<1W8Uef@qTc{CcKequWdx+I%s4uptH{Hx7e5)nz#--l)JnHOjff z#>>CcC(W>6t_pW4A;=XDoeQt)9+xwKrFmjHrvu;YpXk*lmB6&Q=z3`Frfetxm7tu= z;(5M4VQIVNU7MJ0kHJneoV3uNY8S+D2mo2O&o5?zkvf6EY)T(~C@Azo%VBH_w?y#Wp|b$@+RQhWrPU zpXetVjKID!8HbBTjEsBe+>FYEZCOtFL{X(!XJ7*(s0iBEJZ1JgG}cy!=qVHSy>rp- z0#AVd2@>e&Lc8CJIg}H;jcGHZ9leWg-hA6ICnqp5Zv%jUwWwlyr7J zNGudebu{D6a-iryok${gNW2tvsJKgu%j>?0p51I#t$cV>s`01`_A^v|M@Efih}TkOL+>{0F3xTZ7G%LwRkmdk^JF-|cKHZ%E3A}mJj03z%GXm`Dl)jqNKVr{!(d&Z>RzYMfk?5EKkKSv*Qc-9!m_0@YEhmk zgxR;~=?BV3rLVVvi;aU!X)zCSl;ml@U_Q*evT~-!7(MM!vob0*Za2T*IwM4R$>(mo zj(Z7inHTpGJB4{;-ZWy14nP&jf)Kp&p!oRL?KTZSURD@LY!A8?X7OO%-7)D%UXT&0 zWWN+_T ziDtf5w4UC4_JI!{UvR%~F0|bgvJEP zFz1VxE0_L5prV@~uoEj@{A}$lKcWCP z`Ul-Q&aVN78Ocv!6Vcx}1Vv|b4Q2Ki&6o`COaCtep`93Nly=9`mQ>$U?(&ah?$7Mq z@kOIkLjR0(=!_gQS5Z5hTvdn<@95*wz4j z4Gz=N_z32xv*3hf-o)fJM*WT6P=2$Gxlm;No#QklPVQvffH{oA{kOJ5RwdoIZf5x; zhK>Fm>-X|Aj}T9ZKwp_wW2Jb&AeKn_;#5;gwH?<@Oikd{%v7HDJ4jq=l9qNoV+#yOZbo8IQnrraU4DCM6|hE%2Er ziUxU1Evk+eZF`xj=I2E0-(`w8I={pUYq%zW-_9{F-IZ?t5Q_n+HN{UtBxdrwz%%6v z(3B#J+tLQX%^zyF4-mz10|b^QIVz{f`gFodG_aNpM%^_XH=- z*SFm|IKz{!V|#mf9Y*{1$8hHtfA`I2YyTr=Px|{W!`J=ar}`cG1w1L{Pj$XmT%goA zV$b~%;>;1-^FGBtjk_p`W6PTQ@v*7--M9@%{mdH}{qLpDeG-(%9<>ysOGi1dCHEuW zFgkPY8?k3_-X4;_OUrpxCPnbb<(^?a$LCOuwzo!ep>HodE{uo`WxvLuL9DfC$fG^( znFifHE(0~TlQq`5@bnIz{XLJxo>RB($>=7rjC7LCjs8K_zKs_zb#}+zDDvGI&{%dc z3(WJmHm{-)m>Uy8Jj&zrL5OuY;hojwg~=9KALH!YXDh82#;I#dtz>O}FsbiY$6t@) zW@Jybc#mFeR5nFG)&~f7WnX{QV#&{NfoSy6)`XE{;84cbhu)i`+xQpUYW)B4D&a z;|qpguK3U2$>Ib~>X;dD@+yFe4S+2W8B^L{Ik8_5eZ)@d(Sb#9DKYO&VI4A$DL1)~ zC2zh0=1495#+>OIado*yx#TrY(yarZ4zaXpe(sM#`3dS4BxG)fJF}&u?S|RZH~B{& z^>tFkVwaLmO{WqagRE_|xQWi01KI<3=WDx~FI~?%jXHiHQ~gNBWPZ3N#Mv9NL2=V+ ze!=R=c(In_;nQbUl_i0;-rRu!jKqqm}$a!OftkqR46e#azWBaq)TYxo9byI zbtb>s`6PA#Jn>I#GI!pKD@?vfD^T%C zFFIrXtGagd&-*{PXKBb2<`kG@pL@#h{N0EF{%-rs2sC!yQ8GTaK4o|8h0;N*>p8zz z`G0TR_&qzPZdQV^v*0$CcC7LlVL$#xHMaR~iu*?qH$Gnp zrlAPT-SfPur<}bKH!!n*Bhlw{e$iq9=auQqgi0K1> zxDr9eG^%1oM5+<5s98U zadzapu&tLe$7>u~|)%5(IzFp-=^mHtxO6pz^Mc+W5IfByq{qq1_UgrA& z@16FN9Ak3M5wZZLZS0|5?0TDqV{2n}Z}05c`!|a1-&W9nY0|n$GB31Fc;;rQ#wP6! z{l|!5T?4i9@&XhX-!mVf!6!1NjLQ15FE;@=SK|hG=fQm>=ga!T$j(t7vfZ;+OHmO9 z-EZGyixTF*h}1*N%w+5Mk9bA;No_xGPBPI-(+x`s|oll{58@p}W{o&8j&l?rVKC|>NE6?2JS&eU53fh8{Fc${#(-&Q;|~NL|kI_acg&RnuTvxKB3dSj^LtiO_Kdh4gBg zU0@F_M}4gB;N_hYWn91Min8=oqyQ{6|MCh}mjglEG|#vDE-T^Y=?^Ve)^~*sL%^vz z>?CLNs7{8}0{8gBDP+THT@;V&eOG1Qp zB+Zm>Y+BV;EojX~oC@%Ky`auWWSb)7L ziJ7kTQoF-FeyZb~Q15O;!oY~wisxm*!pof|h`@;^J6+bBif)8T&{y@Le1miioG`QR^}Vaw`gJ;Qrdvg=CkJr+8Zjs+1oS>@)4z5OxC zBC7I9XR=<^oniEvqaP)F=z@|ud3b5pJGZ%ouHmx#B1L405aDgE1wt>*O_9C}-^rB*yjdqeva#G`t-bi9l{iC&# z6i_krcAxw)2E0F4C1S8~z!Y`|Me`{s=e^Hmb()jwVZOryIwB(!X{8Ig6lc$0XN_~6 zjXe3U6)+OqnnWdS2!Q@boklQ(b8p5mv#~=zo#Nz8D-dvB$^lv$=&h1CGV7)g}0w*bF%5_e3-1)`C&3U~JPOS-T)&#S(HCGKkcA5I=@;KE z=7&c>6*_%sQo6M4(GVk(U)1W4)us+2AWis15$o@70ySFGsPcBJP3G(DK@8$gcY{O^eoq(UPM>tAPSXQU-!!^_Z$|## zUC3xXf!KO4-5eKnpYGRMk|GDR)Rl#7`Ms9J8z1+7k=x7g$WeP=%RuxiKFERq_MTcq zk9vR0faBbODD3jM#JxaQkk$DAoQYBf9wA*GJlNzM#(^x1f-&T<>SjA4DCDjpW*cjkw>;f4nm&(pH0Qj zH}-r%Y(jb>c3QIF=epWo-@5w0;NYPAas|qaf^O~{>ED_hw*0@D9HBX_- z0-jb(*MB#Vec&Hxh5ZcF-%hC({ANDK0YD1r&GP#IaFU?Nw^&@4fjv5b{*(FX-x!P$ zrt?3WFZD?&>QBEEE&)*#(TWp|*y-=9Og}~r9)-rta)}(hHHVY~Rq!uI0Z`$;B8#7I zs1*?9RbzXZ{~$~Mls-_10)oH>E^F;qR(Q#I8JF7E_ zAsPhwWQQ2~6waoD|0IdW(DtLzWb2>Av=j*bKDE5f#R`-htt0yt5WwZ1<^K;w7x@}D z4Uyo~x~+0F{~sBM>B9^q@`G+0i9T#gA87^a>+v)?iI8ragSOh z4bpsXI#ZWdxKoH#(@dv^OcR!e{<&Fs?1M}CfTEU092MICaxFCmBe(`bT4O#Bmp?3$ zIqwmZqITxfwIFleYmGC!2MY}usV?lhQ`%Jz;+yldx4aX-rR69%m)=-9!;E=AQ2~3+ zcCVCPNU6^#dPO~~u#vdCkNxX2a!0W0OS#?-_l4^ixnv>a1r4qkrXBn|GIy&_3p0^NW7})4u?_Y7xK{r77krK?xBS z6_t}6{SQ~NCAIpI^d{mi7hi}clFtgO_b>Ym0Ru zOvbDB57k7QB)i|PQ#E`LKux4xQmz?g!aR&=H~VlG{a6r8Tsj~~ zP=*5+Dz6tad1bu1Rv^jFbMZ-c%6WhN&#bxN)iY3Gb0eb}y?V7tuQN6?GuF;oo{k^dp!eDQ3Bx_i`7nPo8 z*G=m7U3W>2`EX^Mzn@Fg+Ct%QbacbzkM7;z^&VoV5yA3`GI+MtO+x0`=&&VCk&xc%mN;~N_5N$s#?>e`DBOV>S)`a^4pWMg^>l_%Mym{@u zKfOXMYj|`@d4WhT-~E@nyJzAt37fh_`$63926iFqb??lxTVpx=OD$9a6|Q|j-;N%r z(+w+m2CF;O*Ty$P*7K9nwnE~JesNSou_RY{TMvkgetXo zU{^Go-A&KkJY!lK*Q${=)!#SZzgz852MUm;t}IMZHj7;4Oc)I68i}P4qxE(#u5sJZ z7j})Uso6aE(9FG_kdW}mfK_AK)HIJJ{*>MWHF%g7CvALKblgm#7}#S3;qHuT%WNp( zq!NR&=-1kdUfjU@>n1hyV>_kc;{TL%R%(z)qf}l2&kzr%A$U`HeRlAt+WFD#sozRfO@=?GJV z&3<+NZ23fV_MC^x`Ot*x30==41pp`Za~FS8$U*MmvHM=ddwX;<6>^mT&*dZa z`^{I+WAZZjX+|?^-@S{p-|qBVSx0(_)voVpHBEFH-A~)uTW$H~SruwpA2{%NqFlCl zsN&#oWsY3Mzg@2~m&$on^9gN?lH)YGGR{gWVxhj5a;$&Vd-Hwx`$);NQw;T_n0s7j z>S2*@vOP7TFFJ%8uYNT%J_u-^O!lOCrCa~R{qvxyHig+2{k)GwMOk8ARqLZG&%cby zc)CP~WtflgH#z56nkK4Z?7FN?&WaQ z<@{w}CgJ9dH_p($k8D`yaK|H4Q`v*7Hp|PSVjK9SG&i}0=czD~`DDKP{OJmnOQ+{$ zcD)}NXpmPXwUrEz_jNi7=a{u$8vn|xIo){_-7S~0?UD-FCix>2*@gle)L|7{#l1{P zk+1&6&K%>#x#!b-4t)6Xqen6u5=IK^chA~ozG!?EbLaXu>fS_z9h|!`r$c@59-GK3 zos89%g>98)0`#eWhqs9eAm4Eb&T0%b7WM0TY>C%<;~*UNV4 z$1pwVD7lVmK2uSaCXi%yhw~WB)}wtt`8b8&3}Yy&t8x7z%d70_^0GsCGcm+Ej4OsJ zwVEN1N&l4bfTvwxtoKgO%1|q3K+{>gzn|YBWz4%zzVy`%-c<`pE4{PzS48*xIRm5f zVggfAtj~aGnm<)tsODQ9uMzs@g)i8~6Z6oNOBA$O(bVEKwsBumm%ZgKL?!ffCnqOkXsCKHtjc;SW+mRDo-BEd&xa-WK&$xUbPOJ)lYTTd6(bsI8I_$)2jQO znMy1SwCk1%uZNZuK#lJkdLmBIERr-kH@rpVqm-a%|D}d}B87~E>Y)!*uH;uY_7jv` zK%E%VSS@7P8wX8+lI*1nkY@AKJhbqL5|y99ANhb0j8K@#4d|NNkxNlVw+8-sqRWVb z{Lzac8_SNxi#zY!>vr#4ky*gM2v0_?NJgaV(MJeB#T~Xt6(ri*AJyA$l|7HLO3Vh6 zmQ*ianH?(2l`NPUAHjd07LEuI-iOLoUp)ybG;<7_=OzpDsukP+>Q58UWTjqNRu>Ex9!h@!ydqn>e=`5}db z5xl-KtTx{)?V?69*7czpsXQ@yuGewep>~;s`MGZMYE#NOw%C4q>eQ|+*M1k$%Yghq zfj2uYRN>VpE1X*x82+#dZD63=dn)AFdr>#v)uE8Oa1+S~?i)q#{QT@?Gpo}ANk2U` zCKWq0-EI=w;hXGHfr82`Z}hT6a~js0=9H9PdZQcZoNJb@3~wiyLG%<)&FSH)!b(h% zZ=KkDmNZhq@4V)AUvRrmA>4aMrQy=jP?dl*mf42Fapkt9{#W%=S<|>7_thIp^K(81 zGBQl;MQJGSx9f{c@nSWTwi_RijY6!iU&dCyg(kBlKyO9YsX6u1Ksex`m@zQ?o+tC_y&F!!B1gxxS=6 zLgw{2tIa?R8nR%Q;RaxQlmvUvNM2y!t|SsHbLHMht6e2d0y!wAdZhzfe?NKzL-7a5 zWy%^o95#&bpm!yL=r{choUy*m57wT2OnO&8W-mwDj>qF$75m5%balVx(G)t>2;Cg~ zdSZB_pk1FMzWIFJ$;QvJ_s+a>(;^l2sBg}JcBQ_xJ;>aCqoTJ5`&`$kKddjB6h-_u zs4B)cZt^6mjU`ck6@F{(H|mE<{cMzd9=0(phxHG>)=b}VpPHI+{toCA;oYML zRSp??k4Z@ap)^Yusi~VEG^Ad0R_wJtIcIzI_Ur8_IiFn^kG<&U%RoIl zmhei1R6zDksiA5a1rt#lPo0qKy5Ji=<}Wnx)4`M3OU@u-p`CL*eb)Vf&OzKJl5uG@ zQH|>-d$FV029lz>wba5st*^GXHr8Jr1+=?1zuxmt z&@SpOv2>RTsBv1YRWzkx7Hq#Ig%Y^RIF)qNe0Ix!`5?C8oRShdw?5S1g|@S9LOc1Y zT+oYGw@$Ot)0kk^qFH)8ZXhVXLPzjtkH)9*mwR6X9lAVjUZ{7vL!wB6^ZvU0#WKD% z)^I;Q`J@OZt$4%T)Pv&Gsj?O~mpTcNp%dzMG{b1lcJ>0}k1f2qYfd`%do!eY9XDY_ zq5;GgubDYjr2|Rytj$Qpw%z!FP;{D#LeXKMJzhjll58eVA%oi`9L z&8oSe-8Te!eW)A7{Ny29-Npmt889ac%jh)lPqp=+vmWQ1##MZ_(w0AUoZW4pPIjBu z8C8Rl$e*yA{D=o5&LI-??vfsbL9Fr%`izRa6_iJib_85C03RxqH?n_vTlzFVbggPh zAYMt2z3QRy0dkI(5tm(4nUEnUY7e$#d@#g-ve><45AHgx%p{1Q zEt;m1@QB=nRVxc2-56KJssofk^mx%5V{5Nh_uiv;x{O>kAAg0s0++ev#JnH7w2ar_ zezU-lnLicD;7L^RN5R4`hx~ds2gP5BDFH!v&Gx%+(a^p`Ax}+G74It?*`rHWUHkJ5 zia_O>9$hbDH$5J$?#j^4N=(l@xztDftjN^Jh%0em8f_i~t7%|Be#>J&XQi*`0RL>~9%Xf*Lj5JHm7L;Y=7gLeDG@1V@~E^z z=o@;map*#M-X~s8gPrU(j|Z)$wwP)Wwr(S%Bt`8ob#r(^EP0)igo3XX8a-aO#>y_V zqk9=AjURrohkwa8?j{xa{+4no9B0|Ss;cJb=(f_O5;%J}k!RdB&l^N=;)M7KxrY+! zu#7G`iskX4chaY1#7iR^&5Io^I$nlvEuOb)Shc$EyyNL}I5u3=x~)kQ(?W<}NkewQ zY{9p@YH1TJIHbE**D^^(*Km(mdlJu%f6Y!jES|h`bJq0sI(OD`?T)V7L7!oB#+-fy z3GravMyn6;aK(7huFaK&`SkXCpYGUb+t(%Uev2)`&dt&`-MKLdi=t|M0R^cK-gnDf zHw~%Y3QIki6pMO~mVT#Y=y6BdK(7QM6~cf}E5qmo)n|HKBxOF2jM4O&FZCfKehDHF zWEmgDk7SL%N$vgQ(z6H=&S*dk>jYpGSAh}&7b(%4sDJB_(Ib?EOLP?_o}y zbiEF6t{&$Hyv!4FP?^gp7A0x72VbQXj6cdU!`YhMl8{~QA(f+Uf$=SeX_eHR9XT<^ zwO$OnBcIB>Lg?)2^_S|er}gp&g{a-v$!?k>0&%puOWmB?RCP?KbtOzFo8kcS`LQStx8zouNQqYb$CMPW z#D{p;u!t7yl{Hwo=S*f1bX_dKLhQ($y`eKZgP;1`qOSK=hsFSem{fS*Uc2CNK&!?Aa7kJ1jfLu`n}j{b+Ow~T5t>ejWf0&S5NDmWBxad&HrTU)HSJHg$hg<`>j zyIX?0yE_C3?jAfqki)z8*x$F$80YWvb3JRUDfcz+dFIw_%c5z34vQx9YhLl;xxA0z^{cD2pM=6byMdH}{8~oyavTxKH;Pa$V}eBmfzydW@&3V?dT-b#WgNMPV#YP zutT>_*Mt134LS8b8R{CGP&&82^vFmip(kd%52^>8pOP9}f=n^&;M0 zr{fdeJ4B`-cwTEToRqFcMtzx);(V~73LfJry3pbZPcp#LvKzk^)*8QWFJm~vj?_aS#Fo9_YQo|^>D=||bR=`X9< z4GFPq!{ek{Nw1+K$B!$@kJg1hrCT4zpH-T(%oFlL3~n2BUmV1nsiSE?+CRs^vc|`36n2Hjw2ikwC%yhAJqNra)i;N7Xkp9J!CSHx3xN z?WtWQb#HIT2B%v2Y&~QA=KY})#oNWzqC}`i37ecil&X%gR=*;u(3x z>#ILQEi}q`1!qxX5F8<^0uMhv}y68vsvrz@-y*}7I%lc?$2~dg*K`F zrlywS=bx1TT~)IVodm$#2IgzE3QFhLZ=SrRf395j9ah4bfGBQHK~cjug{CT|djAqH zF2#*o?i!vaQFYA+9GAT*E~73y*4B~TrvrT5(#>vv>&My&4ih#0K9}_WU@ut|D}N(6 zaW^7kjNYqKa-BJBR8HM_AkPI4d}kQZdugKlw0_EIgar2mpSJql!|Zo12OD(zLvz>F zh%qSD^O_Q@LDF%Ozod7MEgWv$$;?v|40E;6!$ej6p2T%9XED3el$sK)c;rcYb@~?< z?|x`#e5`jN?aq95#u>2XZOqpXHE`>il={Z{XYFdEz-LOLdP}QVZ1S>x zU!fX4Af4)3?mB93yaE4X?A*sLs3tkB2c%wU3?i)C3_4H&5b`kr4uz*iH}#t8^8NZ1 zhPg;KAJl^MK8xvp8&+BL{b%~CFLzkI4RDLh8!>&DuezT;zvc1B^iQ){+3qKP@^Ork z9g4BpzHs140Foy!e{YSATU(Xc_wI>^b4t&M+w#7tf4izH!fp55WrRs}Xaw+D7Mq}_ zext}tnE65bUvNr?4$YdY&eV-d5EH4ks;~VNJ{J%Nx0L0C*JNr(ezE3J1y&HLs8KYd z9`3QIxbRTmBs)5l%SzG5;6bd<|cHwE$*ELA=xM8SQ!a2~G^HFG@lNKKP zrC?U!LP38T(C67YC&dTD(71IyN$C-!)tC0&UcLBV6vEorGd*?enx7y9AZJ21*M|~LZWJ z&|$jlIS4H1<0lp?^X}um-Xl1mYLJm}OxH@7op` z9sT*8(6)Ea@Mg;)7QIQ7SLL>4CjP*^mN9pw0eE$_P;mlwS&wSF!%c2w`$Or2fE!=_ zBJFJ*S!xq$es1Eya$e@j6|mI{c-x80ItaJj!RfS0oUfW(vNhiNUVB$?k%=}aZ=Xyy zTH#qk?c!gaiaO}f8^f{`+!?xiFLvJzmg~zG&4tWzI@TQUO_b*!E4h+|w?}=El)EB; zl)fu30<3r4c*!h^KsBgZ85X@=cbp4{oE3Im`AK$K$X3GCtc^_gNJrA1eN!wSvvnK2 zCFOycvB2H|*WKFqjKisdA~$(Zd4&8jD->SUSS$H9rpl0ZrQ@lm8~TIcc-e{fjHY$vSLZNM&!EMX03eI4MbDh zn`fi=;7G90%9H%HXy)kY|AZ<#M=ZogM9RX4}8mfHkXQ!kg@TH+WUWCZO-Ct zI)sEbZX{G(zquw7lA7UNoy}58YCeWw*H3C`AKnaN^k_Gl^dW+>3ix_Qt{%)Uj!eFt z@`-b#&Foj~yOZ`&c;W<%D1ncMCN=vv>4j(|KhOU3bC^0ce!Ay)6j z67~CK;Ik`YtZ^qArUcfhWX@&G3|E@W?H3cRp8)+mZN^96?x?=_>u)&d@KHh`FWaY} z4ylt*{B{?Pc+jL8P1pfen=|)lA4J?3at-A{CDc?(?6iOpfAYk9n2ksGYo+)`B zP=uSr7Dab9Bj+@XJtEo_o1$rG$*(iF*#M3S&2inbUo#o4 zQhzpfZ26M{+nY_PA8cOD&50Aeag4iW$eZKCjb%+sWD= zs>HUv2Fb50keU=AdH>clfI6G`A0Y(z>zB3$rMxJAQISy!Z7tZ@T2?`E z4r+jKpfi!o=EW%fPM=?I`|<`ncB zl#m8ESU|7E{u7zleI+%V3>bF=ehN#l?g}2&Hhndr^5Y9awzAc+Q7CN=e zURup(_|6+`!e`_P5 z5@+kCiCaNuW(lj1Ck1_CdRNVfU5>9T{GikF0iSWG09Oei zdioEvWi!<_$e##_t&bJv=i_Ib))XaFFV}pV;dvdt9=ZCcg@_08+w7N!&|we|OrK&q2fYLdUII4imLkak3>vTWd2mD<-v$)XCtqFQmctHMFw5Jqszv zYtw3;3Q~D!Ik-ukxHxNT(1qlrd1^*rDG7t0jf|}*Z&s2+gHJP`w)DF?qg+eV)C>== zy}Da7J!z%7z4kvokpL+L-sK{1D4MK}n@)SoML&u@h3L;d)brjy)~^2H(|-c+yInhT zr#&s`dkYDl@2RYhB&)24tUiUDR_4G2&KHK>mv&`(Kdc7#XL@ov@+3|UA~f+y+z!%O z?o##pEZo~xJuPRY-;a(5#}t??ce~(`1Ii0{X;gIm=9|6T+z%sHOYXe_6|MD*mfmWO z=1Qi)YDY?GXclS^1Dnc!j}a?O*RL$6#6MyV z)FHU=)(0tf-S&@AE=U(FqTcinc)G=<#e20s{x^R(u^8Ymy2jpQdygBCO0_ne0e1o( zZ4|NUjVusuHhqaTOYpZ-Ys%O%C2?wAoy_t9gvHEV>MB855t-iU%C=5|E=_fJi$=Jm zs}8ckD}fS(%@0&tppZApG2z9X| zr(g2HuKQXu5Ya-0^`%{b65pA?0?@plm8BY`6tY=to32xrk#cy4cNGh4kqj5TQp2n< zi50tWftu+{r#KUn82imaBAcRy(+YjFkOC*>$=!Qfbi-J)iRUhejlWI@#wB89?aNrx zc$H#Dj0rA7!*36k+HI`0i`vXoioJ6kXB@nl5l-oL`ub4^s~QLO2dy{wC4SpY8c;#~ zt4@-XcsuI|@qOHkv2&W@Lkk55;5 zv80d8?C25TE3=(+gOV*4|Ek37ZWZX#_0l0T&RulI&xLaC>e{YU>gv4l6kYcOmYNEa zJb?((71YbB{#ATVNRz47Bywh2=1LGJArxt$=rPaURqV$YOKr9>-rtTBS!HV=FV@Et zfD>SgUMpop5w7>$OsKuWCMkJ8um|haaz=AmopZ<{6xT`&zqougxAMM@H&UcWAf#!Y zm8C)VpfQ-DHlK%b?Uu5>cbV_>#vj&V3vVhvbS=_froSQ!+GV-d<#cm+5SK~o|KVr& z<9LT+l*I$wg?}aV^rKV>Y@F&%W3t6Iy&MYY2JCK z^YZoX*k3$J=KWva*$=xKSzW^JxFuM*5B#l2(d) zXgv^iaY+Xf=Ja!)tqn%jUAroRz|eCu>rE5;$Q-{s@~9eMhTtjTeFiOm;bw-gFSC@u z*##>TwCuGTphd{!9U!k*Aer55<{ew}KCrhqF5~it2hW+qqm9OqzlUS49{MrwLTw%E ztKr(CBnBIiQ23y~r;CxW!+PlTp&+YefbEW2!l_9ZO^vfNS>#(CVdBY|8Is)QC1gjp zLrzEBq&bh_rfd+Gxl(XloKb^)=upE!Q!af(%ZR2QfBqnxD-7F7(&c(JTB(ymcg1VFNHPbQe~0Us5nlu@ znbiCbFswqAm7P4nSe__+I{%40@s2hM^DHx-YQ$tpr)@~-d*8@T*R0IyVW%#c- zeAp@$s0tk|g_RK;VhO3V(0RnZDxyvIxeHEb1U=VaIHAuK=p4UAkIVg4P0RC|o%*}#f^aov6j4Xc!s;sP< zeke$Ye3RZJg7tC6k5o0| z1j~bZ2!DrJuS&Q~vtHC@v4w?swEUI8RVx`?C*jxhgJ$yE8}S;WK&aBIWlbL=WU?Rjp7y>L*seaocj9L7b@p^zqEg`3`Ip3>1z-m+bj(sSUQ2IaIQ|*_?9h;e* zmYnMvSkB`L{+y6Z0Bp+ko88p>(2&|;PRyuJbQvry_m>g-;m_vVuQuPjY`XFHZv%+x zt)+vdH@;o@{>aHMXJJ-Ft^!7D4|)G2H1cF?;VZ?z=h->ot-|%oz`t1PH>`aE++VbW z?MjbyI!XIRn>F^ed|=Tcp=7@d;y z`Twz^&&w+6xpQ{P%Ly!o2)+1*9&|QHqzt+%c2OQ-o5JYeRHS9rJqZoPB{>2=18ng! ziuM#3VyBZK2iW9M=(8D1ryVt!71wcd$x|lfUU(V&P#b^DV=N zRN3jRP(WYKc;n#Q5EHQ8K$|lB?2+w247vBzBb+-ixRK2!5qqnz|54FOR8K?0JdOQ# zospX5gtwYSxgHTsUk^n;cN&IUhp@PxEFoc`QpP__G$G;-EP4fG9!+9Cr7dL zbY!Swm?CT*P?sI&qol>HotNmTD~p|)j1owyFRQH5G%tII=32jUh>)-1O!r%y9IxpJB7W6v~DE?{^IE2z9| zny|9rP)f3cIrz6IT;^n%hm#p&mGOD5au`N*c7-Ny;NCi-sbl0k zql3z9NQxszkLc?}F{Ggldivp=+d7lqHEw$N3r@W~=niFCY<>9ETxlc)y#2x}`JlvD zt;!wSJzz}wmU6`>hnFY!Jg4X=|2`F^LA@x{O}GZCdN0S%&d1a{SyC7OyH-feE$;8{gb zral|B;#z-XrO%AolUkRt0A?MxIXBhp)mAB=jzJwH|Jn<6_;_YA)xU0uxYAUdXgs1o zx7WN=71O_2esp-6^ywSsL?6odfCWPGwe)Z#*&;CFnC78xgEs#FB;_0#8TzuMSK}FLsv)%BoJVXW zJUas|V5rbNkJtq?DtBzkkUqOdQocI#8#bqlqG|LwcA%Bd#mu`^pgL|w8oo{)S-KX< ztxJa`7S4jN=i71%5q((O%9c1X#y>$M7kV+hPW(XjC774M7>j$$-bCkv1Gp1lS>K=A zsBo+?F5OUm^jv%ZmcfW_DheS7p$VU0!CE)+H{z*%SbHLEMk;BPbEiQ;l~LIogsGcL zc*iP2UK1DcL#vx)xRJOVT@Y9vaA?P(Uw#&(2j=yXGUI?kk1eCr8pc{hd{GL%T~JEP zq2dGk>dIkNzEs`AxqVn12P8Qjqo!?H0>D7bk^3LTutt|VR;ftBkBQyeii-nI1dI{6`u!aYHmGX`d%&-4z;HV+@CA&DMd?BAJ z>xS6Dq!p)gW=`!9teHva)U_zvR}vu+cbJ+*ER&Q;;@ykwpHl2@1h@-DDH{QcZCIJlX@P$WlcZHu0WH0+&m@Gb%xdq#@zRVH&mGWB#gg62cexay= zAMelpL2Oj${T!TQLW{ah6VoVsnSRZ0dHj+}TDA0-v_*FTi-VC+aQZdvPeCm3(L-Jv z0}7cgtJLqqqXw>5~|t2v`~27KQgsXoI&_ ztDw!wT-?vWDgND@fYCUflbc)Nl-67Z_o`(=nUffgjZVVV)-TQVBDwdX5*h2nYG%HY?tlpDVM8J5pLtGt#OEnMuk^<)kgX+IQ%2#=yuV^I@eU=z?{ z@6i4tc+aBg$lU*>xvK=6v9EV!x`VtYzW|ttDk?)XTRqE@iyag-+%(i+tck;*gfj6L zI~WASRH)SKR3RQ2J|~|f{^c@Fde*X#XKSM+b16E(=A5tDOh zr71nLvGvFp|BFJpN-L``h6uUjN?=C={Y`(_5dNy4&^()%;5p2|jH{?QKww#8K~@X4 zXy5WPg*YoNiFIKZ1lkYu$N0EV?y=VQM?3wP-ZN~?9fb$%8aR2s?vT@vDv zFWtw8*S%QU=R)=TARb-IO~_-K(}*J!zQ!+aJ%nLZO}nJQ7}I1u{%9X2wLfFCi*;Fo z(^($U{lm3hYbePSK+sm}L;X>hx%=B4*FMit}l&(nCK}-~+genKCwAMf&?r4&fM|^`A zG8^Y=eAOWEw+W8$#uzeMAAEKVAmv22?FcB3Yo@Hz zu1FF^`41TT=tihIPZNVhNZRN(r5ob+{k25CVD={`%oQp7e-pj{ull(_ff7jN_3==}@aAugY z=!5rsd6m@|%3X%p$WUWu$3{P8%j=+B?M||jB^&mi6~76ui$@kUAZYA_BYXk6!I7qG z<5^k)Sz_b*D!Qbu;HpNp^qdMJcjeX$d=z#!!aP;J24J~`xKi_2N8V;Kra!~Q5{4t! zzvQZ7{CtI6|9%#ZW7owoC%&DovCD;+(Tn+hcwaT`VQ)%K&AS=TBdO#CA6pDP(%)%+ zOu@Tde`Q7pEJUl#zc_zJbsFmGb4;w)L2JbDeIodQO8+YJ-;tU}OZZg>pT{s=wEUW3 zjDNezgN)bS`1FJJH{DY$Hr)p;{a83;0$wdZZZNWQydLlGStY2q{$3Q(#Xp<>pcISJ z&bl|Zmzr&yD>gJz;OlYO?RMsu)>CK?e8rnMaXf|b&REQ}M^(;HRJ&u6+tu_BQRk1n znD=|*RovECd9wy{SRJDWOq?C#V@Bv}h?_T=3l6{Iz1UnGz~M3Xg%q|6H;aOqEZ)EO zmO1HDQ8+wr9p5lIOXO8#Hhhg0{&+?RculrW`@^gV!j$aOx8uQO?BqK5S|!>v6CLtPUo_P8cOfF~9~B+ZwHx`@xo z@8$c#(wolPS_9lMIs?80TU2%N5MKJJuR8)!LtO5TyNebDQMZG)6 zh)<{y#OPh18WPfGG8<}m%Y&BI7(wrf!n9d-#$R7Idd57UA29OL?>X1D8I#F zXsAD`_6NV#{|Ou0Wz?6Pn6aS>LdE1IA(=B|J_<#Vz({@#ExuSr`KQT`mV>jA@*eC! zwWcVpMXuzMeu#Rru|&dT*LppSt%XI$ZCpbZ5dh$tBZrbL@SyEZ#ipP%u2 z0RgaF9oZL{gFXUJa0lf}$ZM3ivyoryB-SLHBtJRfNAs=t>M7;%#g;glNiMd;q{*mY zMd11kTeuHS2cIHD8P!-Tn<(o%aWLmY2Y*BbM+6I-;MPr@af}BUjTmu%K z+FDA4^OWAOpj)WJOWrqoa~PwwC}QxqtXEujxDnXD-!;%n)=6Ln!LCq_O-il*vSj!9 zzrf6};RfyR_YKr4L^FGU^it7KKBWe1R-^%sPy2z7`Ty2$~92=@wBDG-oXXM7yt0>BYJvjwq&P~bn-&>$n<3zXLF0v){vtbH_`L~wsCtcd1&OC@Lo8)E~ zKM;p|=&r5swOw>?MLiw0qi40oT^)xvEA=vj13PN%sXt&c<_B&_T=Xkvb_2ib8Hcn= zu|LEtE;f$-^W_ZG)KA8*50`L@`BQwZu&>-i|0Ui)`7q|#M;N7q1Kde?Oe=hTlLU%f0|^YJl2ro*x* z;^dD0MBxK(fDPjas+Lo^kW?azkw0q#i}t1t+?&zPs|1GnCX=*bfb6$8uu5X(ga!2l zV0{6_v9gL)*oiwc+l-1p7D%-E=B5E{N%U{E?txbZI+8s8(CEo#oq?>EA30APSAF zRCt`}k$l^9oglhOVgF-N$GmV=F&q0plz|2Q=K6^7-b0hg^|k%Oh9X^T0!6A1)NA7)lVvh$-5muBh!t?M{$weoed!oN|VQR7nB+Q2(jzTwiAJq+PR97yG51ZG1$pv40m0oF1Qe(Qq{ZnKl&|^Ok+yV!98#Pp=d5OYuoy{q!n)!v|L^7U&v0cdV)kY(;)i{++OF)aog9~!jheBU@!Jw1#_ky zEP^+4bKkDjco+5k9cuRH4MJt%f526ck!G1!$d&Hy82Ok!8<@i_hau*{9>joY?DB(@ zj4!oUfh3ZqxWw9;ltY~tQ#+;18k9@?f{1s(!Q@@FLJ^e^`M!|rN%&MM<_GRz=U`Io z#-3{)YYrfP6y9~IMt}Fe#vy(%;h)MH3ywZeaEzubN5!~3KE_L;)4GUxc4Co-3Me+p zbo5U%5|vb;I%%qIYxblZmG*QS%IeWpJqa(+c%^fM1OfR2+BMOyFO*v*r=9*l8#c5B zjCYLY!$SjORp#(ccJ9&!HoahD_M2r==f!1IxrzD1zhES_#A{+KZf^284VH?pZ(z*g zDE(@$iXe@%4zBd+6|~qB_BOpH`osq8!?Hv6op=;%EKgkTY_Lh(U4!-#(l)DyF1h}R zt#T)JZFsuB#Xg$;yLOs#-!lxZa z!4o{)GE56SOd6Ko-xu~xRC!XOYzDr110B@pTznfYzWxvV5qv00Hgl4EB zlQ+pZ{CyOGUE=}26q&p@`Y6id3IiV9>f>W3Ju<^R)}=nyWr?Tt$1CAFPx~i+YVsh# zUp}KL2qO^BaBSJ6y1s$ox2qUC6dfeg??H_Iuz>+2?sMIn`1NB+dFhM$4t$wEOAIM@ zg9O&5Zv}svSG}m?gqmNGWCqw?h7HGySzFj(D4-ltK;etWZ;+bPQL(+HkH*uX_dT86 zvJ6cLYtG7rEg1e!2OpnU#Q)f8;1g|WuOfdw&uZP;#hm$~P**J3dpyQ7eHo4=_j0B4 zQ@?^%Dm{hxA=-P+*gAueBq$Aq1{%v#m2`cM{iI_OCV>we{f~WaZ68fpF|@He5E(

;8OHA#Cz-o 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߫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�F$b69Z|#yWt#L{HT8!vLz?92jJG$u9qv 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 z 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 literal 0 HcmV?d00001 diff --git a/docs-cn/14-reference/04-taosadapter.md b/docs-cn/14-reference/04-taosadapter.md new file mode 100644 index 0000000000..90a31ec94c --- /dev/null +++ b/docs-cn/14-reference/04-taosadapter.md @@ -0,0 +1,338 @@ +--- +title: "taosAdapter" +description: "taosAdapter 是一个 TDengine 的配套工具,是 TDengine 集群和应用程序之间的桥梁和适配器。它提供了一种易于使用和高效的方式来直接从数据收集代理软件(如 Telegraf、StatsD、collectd 等)摄取数据。它还提供了 InfluxDB/OpenTSDB 兼容的数据摄取接口,允许 InfluxDB/OpenTSDB 应用程序无缝移植到 TDengine" +sidebar_label: "taosAdapter" +--- + +import Prometheus from "./_prometheus.mdx" +import CollectD from "./_collectd.mdx" +import StatsD from "./_statsd.mdx" +import Icinga2 from "./_icinga2.mdx" +import TCollector from "./_tcollector.mdx" + +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.png) + +## taosAdapter 部署方法 + +### 安装 taosAdapter + +taosAdapter 从 TDengine v2.4.0.0 版本开始成为 TDengine 服务端软件 的一部分,如果您使用 TDengine server 您不需要任何额外的步骤来安装 taosAdapter。您可以从[涛思数据官方网站](https://taosdata.com/cn/all-downloads/)下载 TDengine server(taosAdapter 包含在 v2.4.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 中查询数据。细节请参考[官方文档](/reference/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 + + + +### StatsD + + + +### icinga2 OpenTSDB writer + + + +### TCollector + + + +### node_exporter + +Prometheus 使用的由\*NIX 内核暴露的硬件和操作系统指标的输出器 + +- 启用 taosAdapter 的配置 node_exporter.enable +- 设置 node_exporter 的相关配置 +- 重新启动 taosAdapter + +### prometheus + + + +## 内存使用优化方法 + +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..f34d12a546 --- /dev/null +++ b/docs-cn/14-reference/05-taosbenchmark.md @@ -0,0 +1,434 @@ +--- +title: taosBenchmark +sidebar_label: taosBenchmark +toc_max_heading_level: 4 +description: 'taosBenchmark (曾用名 taosdemo ) 是一个用于测试 TDengine 产品性能的工具' +--- + +## 简介 + +taosBenchmark (曾用名 taosdemo ) 是一个用于测试 TDengine 产品性能的工具。taosBenchmark 可以测试 TDengine 的插入、查询和订阅等功能的性能,它可以模拟由大量设备产生的大量数据,还可以灵活地控制数据库、超级表、标签列的数量和类型、数据列的数量和类型、子表的数量、每张子表的数据量、插入数据的时间间隔、taosBenchmark 的工作线程数量、是否以及如何插入乱序数据等。为了兼容过往用户的使用习惯,安装包提供 了 taosdemo 作为 taosBenchmark 的软链接。 + +## 安装 + +taosBenchmark 有两种安装方式: + +- 安装 TDengine 官方安装包的同时会自动安装 taosBenchmark, 详情请参考[ TDengine 安装](/operation/pkg-install)。 + +- 单独编译 taos-tools 并安装, 详情请参考 [taos-tools](https://github.com/taosdata/taos-tools) 仓库。 + +## 运行 + +### 配置和运行方式 + +taosBenchmark 支持两种配置方式:[命令行参数](#命令行参数详解) 和 [JSON 配置文件](#配置文件参数详解)。这两种方式是互斥的,在使用配置文件时只能使用一个命令行参数 `-f ` 指定配置文件。在使用命令行参数运行 taosBenchmark 并控制其行为时则不能使用 `-f` 参数而要用其它参数来进行配置。除此之外,taosBenchmark 还提供了一种特殊的运行方式,即无参数运行。 + +taosBenchmark 支持对 TDengine 做完备的性能测试,其所支持的 TDengine 功能分为三大类:写入、查询和订阅。这三种功能之间是互斥的,每次运行 taosBenchmark 只能选择其中之一。值得注意的是,所要测试的功能类型在使用命令行配置方式时是不可配置的,命令行配置方式只能测试写入性能。若要测试 TDengine 的查询和订阅性能,必须使用配置文件的方式,通过配置文件中的参数 `filetype` 指定所要测试的功能类型。 + +**在运行 taosBenchmark 之前要确保 TDengine 集群已经在正确运行。** + +### 无命令行参数运行 + +执行下列命令即可快速体验 taosBenchmark 对 TDengine 进行基于默认配置的写入性能测试。 + +```bash +taosBenchmark +``` + +在无参数运行时,taosBenchmark 默认连接 `/etc/taos` 下指定的 TDengine 集群,并在 TDengine 中创建一个名为 test 的数据库,test 数据库下创建名为 meters 的一张超级表,超级表下创建 10000 张表,每张表中写入 10000 条记录。注意,如果已有 test 数据库,这个命令会先删除该数据库后建立一个全新的 test 数据库。 + +### 使用命令行配置参数运行 + +在使用命令行参数运行 taosBenchmark 并控制其行为时,`-f ` 参数不能使用。所有配置参数都必须通过命令行指定。以下是使用命令行方式测试 taosBenchmark 写入性能的一个示例。 + +```bash +taosBenchmark -I stmt -n 200 -t 100 +``` + +上面的命令 `taosBenchmark` 将创建一个名为`test`的数据库,在其中建立一张超级表`meters`,在该超级表中建立 100 张子表并使用参数绑定的方式为每张子表插入 200 条记录。 + +### 使用配置文件运行 + +taosBenchmark 安装包中提供了配置文件的示例,位于 `/examples/taosbenchmark-json` 下 + +使用如下命令行即可运行 taosBenchmark 并通过配置文件控制其行为。 + +```bash +taosBenchmark -f +``` + +**下面是几个配置文件的示例:** + +#### 插入场景 JSON 配置文件示例 + +

+insert.json + +```json +{{#include /taos-tools/example/insert.json}} +``` + +
+ +#### 查询场景 JSON 配置文件示例 + +
+query.json + +```json +{{#include /taos-tools/example/query.json}} +``` + +
+ +#### 订阅场景 JSON 配置文件示例 + +
+subscribe.json + +```json +{{#include /taos-tools/example/subscribe.json}} +``` + +
+ +## 命令行参数详解 + +- **-f/--file ** : + 要使用的 JSON 配置文件,由该文件指定所有参数,本参数与命令行其他参数不能同时使用。没有默认值。 + +- **-c/--config-dir ** : + TDengine 集群配置文件所在的目录,默认路径是 /etc/taos 。 + +- **-h/--host ** : + 指定要连接的 TDengine 服务端的 FQDN,默认值为 localhost 。 + +- **-P/--port ** : + 要连接的 TDengine 服务器的端口号,默认值为 6030 。 + +- **-I/--interface ** : + 插入模式,可选项有 taosc, rest, stmt, sml, sml-rest, 分别对应普通写入、restful 接口写入、参数绑定接口写入、schemaless 接口写入、restful schemaless 接口写入 (由 taosAdapter 提供)。默认值为 taosc。 + +- **-u/--user ** : + 用于连接 TDengine 服务端的用户名,默认为 root 。 + +- **-p/--password ** : + 用于连接 TDengine 服务端的密码,默认值为 taosdata。 + +- **-o/--output ** : + 结果输出文件的路径,默认值为 ./output.txt。 + +- **-T/--thread ** : + 插入数据的线程数量,默认为 8 。 + +- **-B/--interlace-rows ** : + 启用交错插入模式并同时指定向每个子表每次插入的数据行数。交错插入模式是指依次向每张子表插入由本参数所指定的行数并重复这个过程,直到所有子表的数据都插入完成。默认值为 0, 即向一张子表完成数据插入后才会向下一张子表进行数据插入。 + +- **-i/--insert-interval ** : + 指定交错插入模式的插入间隔,单位为 ms,默认值为 0。 只有当 `-B/--interlace-rows` 大于 0 时才起作用。意味着数据插入线程在为每个子表插入隔行扫描记录后,会等待该值指定的时间间隔后再进行下一轮写入。 + +- **-r/--rec-per-req ** : + 每次向 TDengine 请求写入的数据行数,默认值为 30000 。 + +- **-t/--tables ** : + 指定子表的数量,默认为 10000 。 + +- **-S/--timestampstep ** : + 每个子表中插入数据的时间戳步长,单位是 ms,默认值是 1。 + +- **-n/--records ** : + 每个子表插入的记录数,默认值为 10000 。 + +- **-d/--database ** : + 所使用的数据库的名称,默认值为 test 。 + +- **-b/--data-type ** : + 超级表的数据列的类型。如果不使用则默认为有三个数据列,其类型分别为 FLOAT, INT, FLOAT 。 + +- **-l/--columns ** : + 超级表的数据列的总数量。如果同时设置了该参数和 `-b/--data-type`,则最后的结果列数为两者取大。如果本参数指定的数量大于 `-b/--data-type` 指定的列数,则未指定的列类型默认为 INT, 例如: `-l 5 -b float,double`, 那么最后的列为 `FLOAT,DOUBLE,INT,INT,INT`。如果 columns 指定的数量小于或等于 `-b/--data-type` 指定的列数,则结果为 `-b/--data-type` 指定的列和类型,例如: `-l 3 -b float,double,float,bigint`,那么最后的列为 `FLOAT,DOUBLE,FLOAT,BIGINT` 。 + +- **-A/--tag-type ** : + 超级表的标签列类型。nchar 和 binary 类型可以同时设置长度,例如: + +``` +taosBenchmark -A INT,DOUBLE,NCHAR,BINARY(16) +``` + +如果没有设置标签类型,默认是两个标签,其类型分别为 INT 和 BINARY(16)。 +注意:在有的 shell 比如 bash 命令里面 “()” 需要转义,则上述指令应为: + +``` +taosBenchmark -A INT,DOUBLE,NCHAR,BINARY\(16\) +``` + +- **-w/--binwidth **: + nchar 和 binary 类型的默认长度,默认值为 64。 + +- **-m/--table-prefix ** : + 子表名称的前缀,默认值为 "d"。 + +- **-E/--escape-character** : + 开关参数,指定在超级表和子表名称中是否使用转义字符。默认值为不使用。 + +- **-C/--chinese** : + 开关参数,指定 nchar 和 binary 是否使用 Unicode 中文字符。默认值为不使用。 + +- **-N/--normal-table** : + 开关参数,指定只创建普通表,不创建超级表。默认值为 false。仅当插入模式为 taosc, stmt, rest 模式下可以使用。 + +- **-M/--random** : + 开关参数,插入数据为生成的随机值。默认值为 false。若配置此参数,则随机生成要插入的数据。对于数值类型的 标签列/数据列,其值为该类型取值范围内的随机值。对于 NCHAR 和 BINARY 类型的 标签列/数据列,其值为指定长度范围内的随机字符串。 + +- **-x/--aggr-func** : + 开关参数,指示插入后查询聚合函数。默认值为 false。 + +- **-y/--answer-yes** : + 开关参数,要求用户在提示后确认才能继续。默认值为 false 。 + +- **-O/--disorder ** : + 指定乱序数据的百分比概率,其值域为 [0,50]。默认为 0,即没有乱序数据。 + +- **-R/--disorder-range ** : + 指定乱序数据的时间戳回退范围。所生成的乱序时间戳为非乱序情况下应该使用的时间戳减去这个范围内的一个随机值。仅在 `-O/--disorder` 指定的乱序数据百分比大于 0 时有效。 + +- **-F/--prepare_rand ** : + 生成的随机数据中唯一值的数量。若为 1 则表示所有数据都相同。默认值为 10000 。 + +- **-a/--replica ** : + 创建数据库时指定其副本数,默认值为 1 。 + +- **-V/--version** : + 显示版本信息并退出。不能与其它参数混用。 + +- **-?/--help** : + 显示帮助信息并退出。不能与其它参数混用。 + +## 配置文件参数详解 + +### 通用配置参数 + +本节所列参数适用于所有功能模式。 + +- **filetype** : 要测试的功能,可选值为 `insert`, `query` 和 `subscribe`。分别对应插入、查询和订阅功能。每个配置文件中只能指定其中之一。 +- **cfgdir** : TDengine 集群配置文件所在的目录,默认路径是 /etc/taos 。 + +- **host** : 指定要连接的 TDengine 服务端的 FQDN,默认值为 localhost。 + +- **port** : 要连接的 TDengine 服务器的端口号,默认值为 6030。 + +- **user** : 用于连接 TDengine 服务端的用户名,默认为 root。 + +- **password** : 用于连接 TDengine 服务端的密码,默认值为 taosdata。 + +### 插入场景配置参数 + +插入场景下 `filetype` 必须设置为 `insert`,该参数及其它通用参数详见[通用配置参数](#通用配置参数) + +#### 数据库相关配置参数 + +创建数据库时的相关参数在 json 配置文件中的 `dbinfo` 中配置,具体参数如下。这些参数与 TDengine 中 `create database` 时所指定的数据库参数相对应。 + +- **name** : 数据库名。 + +- **drop** : 插入前是否删除数据库,默认为 true。 + +- **replica** : 创建数据库时指定的副本数。 + +- **days** : 单个数据文件中存储数据的时间跨度,默认值为 10。 + +- **cache** : 缓存块的大小,单位是 MB,默认值是 16。 + +- **blocks** : 每个 vnode 中缓存块的数量,默认为 6。 + +- **precision** : 数据库时间精度,默认值为 "ms"。 + +- **keep** : 保留数据的天数,默认值为 3650。 + +- **minRows** : 文件块中的最小记录数,默认值为 100。 + +- **maxRows** : 文件块中的最大记录数,默认值为 4096。 + +- **comp** : 文件压缩标志,默认值为 2。 + +- **walLevel** : WAL 级别,默认为 1。 + +- **cacheLast** : 是否允许将每个表的最后一条记录保留在内存中,默认值为 0,可选值为 0,1,2,3。 + +- **quorum** : 多副本模式下的写确认数量,默认值为 1。 + +- **fsync** : 当 wal 设置为 2 时,fsync 的间隔时间,单位为 ms,默认值为 3000。 + +- **update** : 是否支持数据更新,默认值为 0, 可选值为 0, 1, 2。 + +#### 超级表相关配置参数 + +创建超级表时的相关参数在 json 配置文件中的 `super_tables` 中配置,具体参数如下表。 + +- **name**: 超级表名,必须配置,没有默认值。 +- **child_table_exists** : 子表是否已经存在,默认值为 "no",可选值为 "yes" 或 "no"。 + +- **child_table_count** : 子表的数量,默认值为 10。 + +- **child_table_prefix** : 子表名称的前缀,必选配置项,没有默认值。 + +- **escape_character** : 超级表和子表名称中是否包含转义字符,默认值为 "no",可选值为 "yes" 或 "no"。 + +- **auto_create_table** : 仅当 insert_mode 为 taosc, rest, stmt 并且 childtable_exists 为 "no" 时生效,该参数为 "yes" 表示 taosBenchmark 在插入数据时会自动创建不存在的表;为 "no" 则表示先提前建好所有表再进行插入。 + +- **batch_create_tbl_num** : 创建子表时每批次的建表数量,默认为 10。注:实际的批数不一定与该值相同,当执行的 SQL 语句大于支持的最大长度时,会自动截断再执行,继续创建。 + +- **data_source** : 数据的来源,默认为 taosBenchmark 随机产生,可以配置为 "rand" 和 "sample"。为 "sample" 时使用 sample_file 参数指定的文件内的数据。 + +- **insert_mode** : 插入模式,可选项有 taosc, rest, stmt, sml, sml-rest, 分别对应普通写入、restful 接口写入、参数绑定接口写入、schemaless 接口写入、restful schemaless 接口写入 (由 taosAdapter 提供)。默认值为 taosc 。 + +- **non_stop_mode** : 指定是否持续写入,若为 "yes" 则 insert_rows 失效,直到 Ctrl + C 停止程序,写入才会停止。默认值为 "no",即写入指定数量的记录后停止。注:即使在持续写入模式下 insert_rows 失效,但其也必须被配置为一个非零正整数。 + +- **line_protocol** : 使用行协议插入数据,仅当 insert_mode 为 sml 或 sml-rest 时生效,可选项为 line, telnet, json。 + +- **tcp_transfer** : telnet 模式下的通信协议,仅当 insert_mode 为 sml-rest 并且 line_protocol 为 telnet 时生效。如果不配置,则默认为 http 协议。 + +- **insert_rows** : 每个子表插入的记录数,默认为 0 。 + +- **childtable_offset** : 仅当 childtable_exists 为 yes 时生效,指定从超级表获取子表列表时的偏移量,即从第几个子表开始。 + +- **childtable_limit** : 仅当 childtable_exists 为 yes 时生效,指定从超级表获取子表列表的上限。 + +- **interlace_rows** : 启用交错插入模式并同时指定向每个子表每次插入的数据行数。交错插入模式是指依次向每张子表插入由本参数所指定的行数并重复这个过程,直到所有子表的数据都插入完成。默认值为 0, 即向一张子表完成数据插入后才会向下一张子表进行数据插入。 + +- **insert_interval** : 指定交错插入模式的插入间隔,单位为 ms,默认值为 0。 只有当 `-B/--interlace-rows` 大于 0 时才起作用。意味着数据插入线程在为每个子表插入隔行扫描记录后,会等待该值指定的时间间隔后再进行下一轮写入。 + +- **partial_col_num** : 若该值为正数 n 时, 则仅向前 n 列写入,仅当 insert_mode 为 taosc 和 rest 时生效,如果 n 为 0 则是向全部列写入。 + +- **disorder_ratio** : 指定乱序数据的百分比概率,其值域为 [0,50]。默认为 0,即没有乱序数据。 + +- **disorder_range** : 指定乱序数据的时间戳回退范围。所生成的乱序时间戳为非乱序情况下应该使用的时间戳减去这个范围内的一个随机值。仅在 `-O/--disorder` 指定的乱序数据百分比大于 0 时有效。 + +- **timestamp_step** : 每个子表中插入数据的时间戳步长,单位与数据库的 `precision` 一致,默认值是 1。 + +- **start_timestamp** : 每个子表的时间戳起始值,默认值是 now。 + +- **sample_format** : 样本数据文件的类型,现在只支持 "csv" 。 + +- **sample_file** : 指定 csv 格式的文件作为数据源,仅当 data_source 为 sample 时生效。若 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 为 taosc, rest 的模式下生效。 最终的 tag 的数值与 childtable_count 有关,如果 csv 文件内的 tag 数据行小于给定的子表数量,那么会循环读取 csv 文件数据直到生成 childtable_count 指定的子表数量;否则则只会读取 childtable_count 行 tag 数据。也即最终生成的子表数量为二者取小。 + +#### 标签列与数据列配置参数 + +指定超级表标签列与数据列的配置参数分别在 `super_tables` 中的 `columns` 和 `tag` 中。 + +- **type** : 指定列类型,可选值请参考 TDengine 支持的数据类型。 + 注:JSON 数据类型比较特殊,只能用于标签,当使用 JSON 类型作为 tag 时有且只能有这一个标签,此时 count 和 len 代表的意义分别是 JSON tag 内的 key-value pair 的个数和每个 KV pair 的 value 的值的长度,value 默认为 string。 + +- **len** : 指定该数据类型的长度,对 NCHAR,BINARY 和 JSON 数据类型有效。如果对其他数据类型配置了该参数,若为 0 , 则代表该列始终都是以 null 值写入;如果不为 0 则被忽略。 + +- **count** : 指定该类型列连续出现的数量,例如 "count": 4096 即可生成 4096 个指定类型的列。 + +- **name** : 列的名字,若与 count 同时使用,比如 "name":"current", "count":3, 则 3 个列的名字分别为 current, current_2. current_3。 + +- **min** : 数据类型的 列/标签 的最小值。 + +- **max** : 数据类型的 列/标签 的最大值。 + +- **values** : nchar/binary 列/标签的值域,将从值中随机选择。 + +#### 插入行为配置参数 + +- **thread_count** : 插入数据的线程数量,默认为 8。 + +- **create_table_thread_count** : 建表的线程数量,默认为 8。 + +- **connection_pool_size** : 预先建立的与 TDengine 服务端之间的连接的数量。若不配置,则与所指定的线程数相同。 + +- **result_file** : 结果输出文件的路径,默认值为 ./output.txt。 + +- **confirm_parameter_prompt** : 开关参数,要求用户在提示后确认才能继续。默认值为 false 。 + +- **interlace_rows** : 启用交错插入模式并同时指定向每个子表每次插入的数据行数。交错插入模式是指依次向每张子表插入由本参数所指定的行数并重复这个过程,直到所有子表的数据都插入完成。默认值为 0, 即向一张子表完成数据插入后才会向下一张子表进行数据插入。 + 在 `super_tables` 中也可以配置该参数,若配置则以 `super_tables` 中的配置为高优先级,覆盖全局设置。 + +- **insert_interval** : + 指定交错插入模式的插入间隔,单位为 ms,默认值为 0。 只有当 `-B/--interlace-rows` 大于 0 时才起作用。意味着数据插入线程在为每个子表插入隔行扫描记录后,会等待该值指定的时间间隔后再进行下一轮写入。 + 在 `super_tables` 中也可以配置该参数,若配置则以 `super_tables` 中的配置为高优先级,覆盖全局设置。 + +- **num_of_records_per_req** : + 每次向 TDengine 请求写入的数据行数,默认值为 30000 。当其设置过大时,TDengine 客户端驱动会返回相应的错误信息,此时需要调低这个参数的设置以满足写入要求。 + +- **prepare_rand** : 生成的随机数据中唯一值的数量。若为 1 则表示所有数据都相同。默认值为 10000 。 + +### 查询场景配置参数 + +查询场景下 `filetype` 必须设置为 `query`,该参数及其它通用参数详见[通用配置参数](#通用配置参数) + +#### 执行指定查询语句的配置参数 + +查询子表或者普通表的配置参数在 `specified_table_query` 中设置。 + +- **query_interval** : 查询时间间隔,单位是秒,默认值为 0。 + +- **threads** : 执行查询 SQL 的线程数,默认值为 1。 + +- **sqls**: + - **sql**: 执行的 SQL 命令,必填。 + - **result**: 保存查询结果的文件,未指定则不保存。 + +#### 查询超级表的配置参数 + +查询超级表的配置参数在 `super_table_query` 中设置。 + +- **stblname** : 指定要查询的超级表的名称,必填。 + +- **query_interval** : 查询时间间隔,单位是秒,默认值为 0。 + +- **threads** : 执行查询 SQL 的线程数,默认值为 1。 + +- **sqls** : + - **sql** : 执行的 SQL 命令,必填;对于超级表的查询 SQL,在 SQL 命令中保留 "xxxx",程序会自动将其替换为超级表的所有子表名。 + 替换为超级表中所有的子表名。 + - **result** : 保存查询结果的文件,未指定则不保存。 + +### 订阅场景配置参数 + +订阅场景下 `filetype` 必须设置为 `subscribe`,该参数及其它通用参数详见[通用配置参数](#通用配置参数) + +#### 执行指定订阅语句的配置参数 + +订阅子表或者普通表的配置参数在 `specified_table_query` 中设置。 + +- **threads** : 执行 SQL 的线程数,默认为 1。 + +- **interval** : 执行订阅的时间间隔,单位为秒,默认为 0。 + +- **restart** : "yes" 表示开始新的订阅,"no" 表示继续之前的订阅,默认值为 "no"。 + +- **keepProgress** : "yes" 表示保留订阅进度,"no" 表示不保留,默认值为 "no"。 + +- **resubAfterConsume** : "yes" 表示取消之前的订阅然后再次订阅, "no" 表示继续之前的订阅,默认值为 "no"。 + +- **sqls** : + - **sql** : 执行的 SQL 命令,必填。 + - **result** : 保存查询结果的文件,未指定则不保存。 + +#### 订阅超级表的配置参数 + +订阅超级表的配置参数在 `super_table_query` 中设置。 + +- **stblname** : 要订阅的超级表名称,必填。 + +- **threads** : 执行 SQL 的线程数,默认为 1。 + +- **interval** : 执行订阅的时间间隔,单位为秒,默认为 0。 + +- **restart** : "yes" 表示开始新的订阅,"no" 表示继续之前的订阅,默认值为 "no"。 + +- **keepProgress** : "yes" 表示保留订阅进度,"no" 表示不保留,默认值为 "no"。 + +- **resubAfterConsume** : "yes" 表示取消之前的订阅然后再次订阅, "no" 表示继续之前的订阅,默认值为 "no"。 + +- **sqls** : + - **sql** : 执行的 SQL 命令,必填;对于超级表的查询 SQL,在 SQL 命令中保留 "xxxx",程序会自动将其替换为超级表的所有子表名。 + 替换为超级表中所有的子表名。 + - **result** : 保存查询结果的文件,未指定则不保存。 diff --git a/docs-cn/14-reference/06-taosdump.md b/docs-cn/14-reference/06-taosdump.md new file mode 100644 index 0000000000..7131493ec9 --- /dev/null +++ b/docs-cn/14-reference/06-taosdump.md @@ -0,0 +1,118 @@ +--- +title: taosdump +description: "taosdump 是一个支持从运行中的 TDengine 集群备份数据并将备份的数据恢复到相同或另一个运行中的 TDengine 集群中的工具应用程序" +--- + +## 简介 + +taosdump 是一个支持从运行中的 TDengine 集群备份数据并将备份的数据恢复到相同或另一个运行中的 TDengine 集群中的工具应用程序。 + +taosdump 可以用数据库、超级表或普通表作为逻辑数据单元进行备份,也可以对数据库、超级 +表和普通表中指定时间段内的数据记录进行备份。使用时可以指定数据备份的目录路径,如果 +不指定位置,taosdump 默认会将数据备份到当前目录。 + +如果指定的位置已经有数据文件,taosdump 会提示用户并立即退出,避免数据被覆盖。这意味着同一路径只能被用于一次备份。 +如果看到相关提示,请小心操作。 + +taosdump 是一个逻辑备份工具,它不应被用于备份任何原始数据、环境设置、 +硬件信息、服务端配置或集群的拓扑结构。taosdump 使用 +[ Apache AVRO ](https://avro.apache.org/)作为数据文件格式来存储备份数据。 + +## 安装 + +taosdump 有两种安装方式: + +- 安装 taosTools 官方安装包, 请从[所有下载链接](https://www.taosdata.com/all-downloads)页面找到 taosTools 并下载安装。 + +- 单独编译 taos-tools 并安装, 详情请参考 [taos-tools](https://github.com/taosdata/taos-tools) 仓库。 + +## 常用使用场景 + +### taosdump 备份数据 + +1. 备份所有数据库:指定 `-A` 或 `--all-databases` 参数; +2. 备份多个指定数据库:使用 `-D db1,db2,...` 参数; +3. 备份指定数据库中的某些超级表或普通表:使用 `dbname stbname1 stbname2 tbname1 tbname2 ...` 参数,注意这种输入序列第一个参数为数据库名称,且只支持一个数据库,第二个和之后的参数为该数据库中的超级表或普通表名称,中间以空格分隔; +4. 备份系统 log 库:TDengine 集群通常会包含一个系统数据库,名为 `log`,这个数据库内的数据为 TDengine 自我运行的数据,taosdump 默认不会对 log 库进行备份。如果有特定需求对 log 库进行备份,可以使用 `-a` 或 `--allow-sys` 命令行参数。 +5. “宽容”模式备份:taosdump 1.4.1 之后的版本提供 `-n` 参数和 `-L` 参数,用于备份数据时不使用转义字符和“宽容”模式,可以在表名、列名、标签名没使用转义字符的情况下减少备份数据时间和备份数据占用空间。如果不确定符合使用 `-n` 和 `-L` 条件时请使用默认参数进行“严格”模式进行备份。转义字符的说明请参考[官方文档](/taos-sql/escape)。 + +:::tip +- taosdump 1.4.1 之后的版本提供 `-I` 参数,用于解析 avro 文件 schema 和数据,如果指定 `-s` 参数将只解析 schema。 +- taosdump 1.4.2 之后的备份使用 `-B` 参数指定的批次数,默认值为 16384,如果在某些环境下由于网络速度或磁盘性能不足导致 "Error actual dump .. batch .." 可以通过 `-B` 参数挑战为更小的值进行尝试。 + +::: + +### taosdump 恢复数据 + +恢复指定路径下的数据文件:使用 `-i` 参数加上数据文件所在路径。如前面提及,不应该使用同一个目录备份不同数据集合,也不应该在同一路径多次备份同一数据集,否则备份数据会造成覆盖或多次备份。 + +:::tip +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/assets/15146-tdengine-monitor-dashboard.json b/docs-cn/14-reference/07-tdinsight/assets/15146-tdengine-monitor-dashboard.json new file mode 100644 index 0000000000..f651983528 --- /dev/null +++ b/docs-cn/14-reference/07-tdinsight/assets/15146-tdengine-monitor-dashboard.json @@ -0,0 +1,3191 @@ +{ + "__inputs": [ + { + "name": "DS_TDENGINE", + "label": "TDengine", + "description": "", + "type": "datasource", + "pluginId": "tdengine-datasource", + "pluginName": "TDengine" + } + ], + "__requires": [ + { + "type": "panel", + "id": "gauge", + "name": "Gauge", + "version": "" + }, + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "7.5.10" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "panel", + "id": "piechart", + "name": "Pie chart v2", + "version": "" + }, + { + "type": "panel", + "id": "stat", + "name": "Stat", + "version": "" + }, + { + "type": "panel", + "id": "table", + "name": "Table", + "version": "" + }, + { + "type": "datasource", + "id": "tdengine-datasource", + "name": "TDengine", + "version": "3.1.0" + }, + { + "type": "panel", + "id": "text", + "name": "Text", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "TDengine nodes metrics.", + "editable": true, + "gnetId": 15146, + "graphTooltip": 0, + "id": null, + "iteration": 1635263227798, + "links": [], + "panels": [ + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 57, + "panels": [], + "title": "Cluster Status", + "type": "row" + }, + { + "datasource": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 24, + "x": 0, + "y": 1 + }, + "id": 32, + "options": { + "content": "

TDengine Cluster Dashboard

>\n", + "mode": "markdown" + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "mnodes", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show mnodes", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "-- OVERVIEW --", + "transformations": [ + { + "id": "calculateField", + "options": { + "binary": { + "left": "Time", + "operator": "+", + "reducer": "sum", + "right": "" + }, + "mode": "binary", + "reduce": { + "reducer": "sum" + } + } + } + ], + "type": "text" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 8, + "x": 0, + "y": 4 + }, + "id": 28, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": true + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "dnodes", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show mnodes", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Master MNode", + "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "regex", + "options": { + "value": "master" + } + }, + "fieldName": "role" + } + ], + "match": "all", + "type": "include" + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["dnodes"] + } + } + } + ], + "type": "stat" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 7, + "x": 8, + "y": 4 + }, + "id": 70, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "/^Time$/", + "values": true + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "dnodes", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show mnodes", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Master MNode Create Time", + "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "regex", + "options": { + "value": "master" + } + }, + "fieldName": "role" + } + ], + "match": "all", + "type": "include" + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["Time"] + } + } + }, + { + "id": "calculateField", + "options": { + "mode": "reduceRow", + "reduce": { + "reducer": "min" + } + } + } + ], + "type": "stat" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": null, + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 9, + "x": 15, + "y": 4 + }, + "id": 29, + "options": { + "showHeader": true + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "dnodes", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show variables", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Variables", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["value", "name"] + } + } + }, + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "regex", + "options": { + "value": ".*" + } + }, + "fieldName": "name" + } + ], + "match": "all", + "type": "include" + } + } + ], + "type": "table" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 0, + "y": 7 + }, + "id": 33, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "/.*/", + "values": true + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "dnodes", + "formatType": "Table", + "queryType": "SQL", + "refId": "A", + "sql": "select server_version()", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Server Version", + "transformations": [], + "type": "stat" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 2, + "y": 7 + }, + "id": 27, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": true + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "dnodes", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show mnodes", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Number of MNodes", + "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "greater", + "options": { + "value": 0 + } + }, + "fieldName": "id" + } + ], + "match": "any", + "type": "include" + } + }, + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": ["count"] + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["id"] + } + } + } + ], + "type": "stat" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 5, + "y": 7 + }, + "id": 41, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show dnodes", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Total Dnodes", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": ["count"] + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["id"] + } + } + } + ], + "type": "stat" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 1 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 7, + "y": 7 + }, + "id": 31, + "options": { + "colorMode": "value", + "graphMode": "none", + "justifyMode": "auto", + "orientation": "horizontal", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "text": {}, + "textMode": "value" + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show dnodes", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Offline Dnodes", + "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "regex", + "options": { + "value": "ready" + } + }, + "fieldName": "status" + } + ], + "match": "all", + "type": "exclude" + } + }, + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": ["count"] + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["id"] + } + } + } + ], + "type": "stat" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 9, + "y": 7 + }, + "id": 65, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": true + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "dnodes", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show databases;", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Number of Databases", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": ["count"] + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["name"] + } + } + } + ], + "type": "stat" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 12, + "y": 7 + }, + "id": 69, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": true + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "dnodes", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show databases;", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Total Number of Vgroups", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["vgroups"] + } + } + }, + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": ["sum"] + } + } + ], + "type": "stat" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "filterable": true + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "role" + }, + "properties": [ + { + "id": "mappings", + "value": [ + { + "from": "", + "id": 1, + "text": "", + "to": "", + "type": 2, + "value": "" + } + ] + } + ] + } + ] + }, + "gridPos": { + "h": 3, + "w": 9, + "x": 0, + "y": 10 + }, + "id": 67, + "options": { + "showHeader": true + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "formatType": "Table", + "queryType": "SQL", + "refId": "A", + "sql": "show dnodes", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Number of DNodes for each Role", + "transformations": [ + { + "id": "groupBy", + "options": { + "fields": { + "end_point": { + "aggregations": ["count"], + "operation": "aggregate" + }, + "role": { + "aggregations": [], + "operation": "groupby" + } + } + } + }, + { + "id": "filterFieldsByName", + "options": {} + }, + { + "id": "organize", + "options": { + "excludeByName": {}, + "indexByName": {}, + "renameByName": { + "end_point (count)": "Number of DNodes", + "role": "Dnode Role" + } + } + } + ], + "type": "table" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 9, + "y": 10 + }, + "id": 55, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": true + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "dnodes", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show connections", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Number of Connections", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": ["count"] + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["connId"] + } + } + } + ], + "type": "stat" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 3, + "w": 3, + "x": 12, + "y": 10 + }, + "id": 68, + "options": { + "colorMode": "value", + "graphMode": "area", + "justifyMode": "auto", + "orientation": "auto", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "", + "values": true + }, + "text": {}, + "textMode": "auto" + }, + "pluginVersion": "7.5.10", + "repeatDirection": "h", + "targets": [ + { + "alias": "dnodes", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "show databases;", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Total Number of Tables", + "transformations": [ + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["ntables"] + } + } + }, + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": ["sum"] + } + } + ], + "type": "stat" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 13 + }, + "id": 24, + "panels": [], + "title": "Dnodes Status", + "type": "row" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "displayMode": "auto", + "filterable": true + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "status" + }, + "properties": [ + { + "id": "custom.width", + "value": 86 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "vnodes" + }, + "properties": [ + { + "id": "custom.width", + "value": 77 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "role" + }, + "properties": [ + { + "id": "custom.width", + "value": 84 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "cores" + }, + "properties": [ + { + "id": "custom.width", + "value": 75 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "end_point" + }, + "properties": [ + { + "id": "custom.width", + "value": 205 + } + ] + }, + { + "matcher": { + "id": "byName", + "options": "id" + }, + "properties": [ + { + "id": "custom.width", + "value": 78 + } + ] + } + ] + }, + "gridPos": { + "h": 5, + "w": 16, + "x": 0, + "y": 14 + }, + "id": 36, + "options": { + "showHeader": true, + "sortBy": [] + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "formatType": "Table", + "queryType": "SQL", + "refId": "A", + "sql": "show dnodes", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "DNodes Status", + "type": "table" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 8, + "x": 16, + "y": 14 + }, + "id": 40, + "options": { + "displayLabels": [], + "legend": { + "displayMode": "table", + "placement": "right", + "values": ["value"] + }, + "pieType": "pie", + "reduceOptions": { + "calcs": ["lastNotNull"], + "fields": "/.*/", + "values": false + }, + "text": { + "titleSize": 6 + } + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "formatType": "Table", + "queryType": "SQL", + "refId": "A", + "sql": "show dnodes", + "target": "select metric", + "type": "timeserie" + } + ], + "title": "Offline Reasons", + "transformations": [ + { + "id": "filterByValue", + "options": { + "filters": [ + { + "config": { + "id": "regex", + "options": { + "value": "ready" + } + }, + "fieldName": "status" + } + ], + "match": "all", + "type": "exclude" + } + }, + { + "id": "filterFieldsByName", + "options": { + "include": { + "names": ["offline reason", "end_point"] + } + } + }, + { + "id": "groupBy", + "options": { + "fields": { + "Time": { + "aggregations": ["count"], + "operation": "aggregate" + }, + "end_point": { + "aggregations": ["count"], + "operation": "aggregate" + }, + "offline reason": { + "aggregations": [], + "operation": "groupby" + } + } + } + } + ], + "type": "piechart" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 19 + }, + "id": 22, + "panels": [], + "title": "Mnodes Status", + "type": "row" + }, + { + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "custom": { + "align": "center", + "filterable": false + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + }, + "overrides": [] + }, + "gridPos": { + "h": 5, + "w": 24, + "x": 0, + "y": 20 + }, + "id": 38, + "options": { + "showHeader": true + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "formatType": "Table", + "queryType": "SQL", + "refId": "A", + "sql": "show mnodes;", + "target": "select metric", + "type": "timeserie" + } + ], + "title": "Mnodes Status", + "type": "table" + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 25 + }, + "id": 20, + "panels": [], + "repeat": "fqdn", + "title": "节点资源占用 [ $fqdn ]", + "type": "row" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decmbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 0, + "y": 26 + }, + "id": 66, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["mean"], + "fields": "/^taosd$/", + "values": false + }, + "showThresholdLabels": true, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "memory", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select last(mem_taosd) as taosd, last(mem_total) as total from log.dn where fqdn = '$fqdn' and ts >= now -5m and ts < now", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Current Memory Usage of taosd", + "type": "gauge" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "taosd max memery last 10 minutes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 0.5 + }, + { + "color": "red", + "value": 0.8 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "last(cpu_taosd)" + }, + "properties": [ + { + "id": "thresholds", + "value": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + } + } + ] + } + ] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 5, + "y": 26 + }, + "id": 45, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["mean"], + "fields": "/^last\\(cpu_taosd\\)$/", + "values": false + }, + "showThresholdLabels": true, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "mem_taosd", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select last(cpu_taosd) from log.dn where fqdn = '$fqdn'", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Current CPU Usage of taosd", + "type": "gauge" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "avg band speed last one minute", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 8192, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 4916 + }, + { + "color": "red", + "value": 6554 + } + ] + }, + "unit": "Kbits" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 10, + "y": 26 + }, + "id": 14, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "band_speed", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select avg(band_speed) from log.dn where fqdn='$fqdn' and ts >= now-5m and ts < now interval(1m)", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "band speed", + "type": "gauge" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "io read/write rate", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 8192, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 4916 + }, + { + "color": "red", + "value": 6554 + } + ] + }, + "unit": "Kbits" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 14, + "y": 26 + }, + "id": 48, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select last(io_read) as io_read, last(io_write) as io_write from log.dn where fqdn='$fqdn' and ts >= now-1h and ts < now interval(1m)", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "IO Rate", + "type": "gauge" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "percentage", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 75 + }, + { + "color": "red", + "value": 80 + }, + { + "color": "dark-red", + "value": 95 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 19, + "y": 26 + }, + "id": 51, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "/^disk_used_percent$/", + "values": false + }, + "showThresholdLabels": true, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "disk_used", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select last(disk_used) as used from log.dn where fqdn = '$fqdn' and ts >= $from and ts < $to interval(1m)", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + }, + { + "alias": "disk_total", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "B", + "sql": "select last(disk_total) as total from log.dn where fqdn = '$fqdn' and ts >= $from and ts < $to interval(1m)", + "target": "select metric", + "type": "timeserie" + }, + { + "alias": "disk_used_percent", + "expression": "A/B", + "formatType": "Time series", + "hide": false, + "queryType": "Arithmetic", + "refId": "C", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Disk Used", + "transformations": [ + { + "id": "reduce", + "options": { + "includeTimeField": false, + "mode": "reduceFields", + "reducers": ["lastNotNull"] + } + } + ], + "type": "gauge" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "taosd max memery last 10 minutes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "mappings": [], + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + } + ] + }, + "unit": "decmbytes" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 0, + "y": 32 + }, + "id": 12, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["mean"], + "fields": "/^taosd$/", + "values": false + }, + "showThresholdLabels": true, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "memory", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select max(mem_taosd) as taosd, max(mem_total) as total from log.dn where fqdn = '$fqdn' and ts >= now -5m and ts < now", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Max Memory Usage of taosd in Last 5 minute", + "type": "gauge" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "taosd max memery last 10 minutes", + "fieldConfig": { + "defaults": { + "color": { + "mode": "continuous-GrYlRd" + }, + "mappings": [], + "max": 1, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 0.5 + }, + { + "color": "red", + "value": 0.8 + } + ] + }, + "unit": "percentunit" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 5, + "y": 32 + }, + "id": 43, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["mean"], + "fields": "", + "values": false + }, + "showThresholdLabels": true, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "mem_taosd", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select max(cpu_taosd) from log.dn where fqdn = '$fqdn' and ts >= now -5m and ts < now", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Max CPU Usage of taosd in Last 5 minute", + "type": "gauge" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "avg band speed last one minute", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 8192, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 4916 + }, + { + "color": "red", + "value": 6554 + } + ] + }, + "unit": "Kbits" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 4, + "x": 10, + "y": 32 + }, + "id": 50, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "band_speed", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select max(band_speed) from log.dn where fqdn = '$fqdn' and ts >= now-1h", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Max band speed in last hour", + "type": "gauge" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "io read/write rate", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 8192, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 4916 + }, + { + "color": "red", + "value": 6554 + } + ] + }, + "unit": "Kbits" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 14, + "y": 32 + }, + "id": 49, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select max(io_read) as io_read, max(io_write) as io_write from log.dn where fqdn = '$fqdn'", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Max IO Rate in last hour", + "type": "gauge" + }, + { + "datasource": "${DS_TDENGINE}", + "description": "io read/write rate", + "fieldConfig": { + "defaults": { + "color": { + "mode": "thresholds" + }, + "mappings": [], + "max": 8192, + "min": 0, + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "#EAB839", + "value": 4916 + }, + { + "color": "red", + "value": 6554 + } + ] + }, + "unit": "cpm" + }, + "overrides": [] + }, + "gridPos": { + "h": 6, + "w": 5, + "x": 19, + "y": 32 + }, + "id": 52, + "options": { + "orientation": "auto", + "reduceOptions": { + "calcs": ["last"], + "fields": "", + "values": false + }, + "showThresholdLabels": false, + "showThresholdMarkers": true, + "text": {} + }, + "pluginVersion": "7.5.10", + "targets": [ + { + "alias": "req-http", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select sum(req_http) as req_http from log.dn where fqdn = '$fqdn' and ts >= now - 1h interval(1m)", + "target": "select metric", + "type": "timeserie" + }, + { + "alias": "req-inserts", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "B", + "sql": "select sum(req_insert) as req_insert from log.dn where fqdn = '$fqdn' and ts >= now - 1h interval(1m)", + "target": "select metric", + "type": "timeserie" + }, + { + "alias": "req-selects", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "C", + "sql": "select sum(req_select) as req_select from log.dn where fqdn = '$fqdn' and ts >= now - 1h interval(1m)", + "target": "select metric", + "type": "timeserie" + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Requests in last Minute", + "type": "gauge" + }, + { + "aliasColors": {}, + "bars": false, + "cacheTimeout": null, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TDENGINE}", + "description": "monitor system cpu", + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 0, + "y": 38 + }, + "hiddenSeries": false, + "hideTimeOverride": true, + "id": 2, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.10", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "cpu_system", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "A", + "sql": "select avg(cpu_system) from log.dn where fqdn='$fqdn' and ts >= now-1h and ts < now interval(30s)", + "target": "select metric", + "type": "timeserie" + }, + { + "alias": "cpu_taosd", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "B", + "sql": "select avg(cpu_taosd) from log.dn where fqdn='$fqdn' and ts >= now-1h and ts < now interval(30s)", + "target": "select metric", + "type": "timeserie" + } + ], + "thresholds": [], + "timeFrom": "1h", + "timeRegions": [], + "timeShift": "30s", + "title": "CPU 资源占用情况", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "percent", + "label": "使用占比", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "cacheTimeout": null, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TDENGINE}", + "description": "monitor system cpu", + "fieldConfig": { + "defaults": { + "links": [] + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 11, + "w": 12, + "x": 12, + "y": 38 + }, + "hiddenSeries": false, + "hideTimeOverride": true, + "id": 42, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.10", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "system", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "A", + "sql": "select avg(mem_system) from log.dn where fqdn = '$fqdn' and ts >= now-1h and ts < now interval(30s)", + "target": "select metric", + "type": "timeserie" + }, + { + "alias": "taosd", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "B", + "sql": "select avg(mem_taosd) from log.dn where fqdn = '$fqdn' and ts >= now-1h and ts < now interval(30s)", + "target": "select metric", + "type": "timeserie" + }, + { + "alias": "total", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "C", + "sql": "select avg(mem_total) from log.dn where fqdn = '$fqdn' and ts >= now-1h and ts < now interval(30s)", + "target": "select metric", + "type": "timeserie" + } + ], + "thresholds": [], + "timeFrom": "1h", + "timeRegions": [], + "timeShift": "30s", + "title": "内存资源占用情况", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "decmbytes", + "label": "使用占比", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 49 + }, + "hiddenSeries": false, + "id": 54, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.10", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "percent", + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "disk_used", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "A", + "sql": "select avg(disk_used) from log.dn where fqdn = '$fqdn' and ts >= $from and ts < $to interval(30s)", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + }, + { + "alias": "disk_total", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "B", + "sql": "select avg(disk_total) from log.dn where fqdn = '$fqdn' and ts >= $from and ts < $to interval(30s)", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + }, + { + "alias": "percent", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "D", + "sql": "select avg(disk_used)/avg(disk_total) * 100 from log.dn where fqdn = '$fqdn' and ts >= $from and ts < $to interval(30s)", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Disk Used Percent", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "gbytes", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "percent", + "label": "Disk Used", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 49 + }, + "hiddenSeries": false, + "id": 64, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.10", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "disk_used", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "A", + "sql": "select derivative(value, 1m, 0) from (select avg(disk_used) as value from log.dn where fqdn = '$fqdn' and ts >= $from and ts < $to interval(1m))", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Disk Used Increasing Rate per Minute", + "tooltip": { + "shared": false, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percentunit", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "percentunit", + "label": "Disk Used", + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "cacheTimeout": null, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TDENGINE}", + "description": "total select request per minute last hour", + "fieldConfig": { + "defaults": { + "unit": "cpm" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 58 + }, + "hiddenSeries": false, + "id": 8, + "interval": null, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "maxDataPoints": 100, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.10", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "req_select", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select sum(req_select) from log.dn where fqdn = '$fqdn' and ts >= $from and ts < $to interval(1m)", + "target": "select metric", + "timeshift": { + "period": null + }, + "type": "timeserie" + }, + { + "alias": "req_insert", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "B", + "sql": "select sum(req_insert) from log.dn where fqdn = '$fqdn' and ts >= $from and ts < $to interval(1m)", + "target": "select metric", + "type": "timeserie" + }, + { + "alias": "req_http", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "C", + "sql": "select sum(req_http) from log.dn where fqdn = '$fqdn' and ts >= $from and ts < $to interval(1m)", + "target": "select metric", + "type": "timeserie" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Requets Count per Minutes $fqdn", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "cpm", + "label": null, + "logBase": 1, + "max": null, + "min": "0", + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": false, + "cacheTimeout": null, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TDENGINE}", + "description": "io", + "fieldConfig": { + "defaults": { + "links": [], + "unit": "Kbits" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 58 + }, + "hiddenSeries": false, + "hideTimeOverride": true, + "id": 47, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "links": [], + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.10", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "io-read", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "A", + "sql": "select avg(io_read) from log.dn where fqdn = '$fqdn' and ts >= now-1h and ts < now interval(1m)", + "target": "select metric", + "type": "timeserie" + }, + { + "alias": "io-write", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "B", + "sql": "select avg(io_write) from log.dn where fqdn = '$fqdn' and ts >= now-1h and ts < now interval(1m)", + "target": "select metric", + "type": "timeserie" + }, + { + "alias": "io-read-last-hour", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "C", + "sql": "select avg(io_read) from log.dn where fqdn = '$fqdn' and ts >= now-2h and ts < now - 1h interval(1m)", + "target": "select metric", + "timeshift": { + "period": 1, + "unit": "hours" + }, + "type": "timeserie" + }, + { + "alias": "io-write-last-hour", + "formatType": "Time series", + "hide": false, + "queryType": "SQL", + "refId": "D", + "sql": "select avg(io_write) from log.dn where fqdn = '$fqdn' and ts >= now-1h and ts < now interval(1m)", + "target": "select metric", + "timeshift": { + "period": 1, + "unit": "hours" + }, + "type": "timeserie" + } + ], + "thresholds": [], + "timeFrom": "1h", + "timeRegions": [], + "timeShift": "30s", + "title": "IO", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "Kbits", + "label": "IO Rate", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "collapsed": false, + "datasource": null, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 67 + }, + "id": 63, + "panels": [], + "title": "Login History", + "type": "row" + }, + { + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TDENGINE}", + "fieldConfig": { + "defaults": { + "displayName": "Logins Per Minute", + "unit": "cpm" + }, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 68 + }, + "hiddenSeries": false, + "id": 61, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.10", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "logins", + "nullPointMode": "null as zero" + } + ], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "logins", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select count(*) from log.log where ts >= $from and ts < $to interval (1m)", + "target": "select metric", + "type": "timeserie" + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Login Counts per Minute", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "cpm", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "refresh": "1m", + "schemaVersion": 27, + "style": "dark", + "tags": ["TDengine", "multiple"], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "TDengine", + "value": "TDengine" + }, + "description": "TDengine Data Source Selector", + "error": null, + "hide": 0, + "includeAll": false, + "label": "Datasource", + "multi": false, + "name": "ds", + "options": [], + "query": "tdengine-datasource", + "queryValue": "", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "type": "datasource" + }, + { + "allValue": null, + "current": {}, + "datasource": "${DS_TDENGINE}", + "definition": "select fqdn from log.dn", + "description": "TDengine Nodes FQDN (Hostname)", + "error": null, + "hide": 0, + "includeAll": false, + "label": null, + "multi": false, + "name": "fqdn", + "options": [], + "query": "select fqdn from log.dn", + "refresh": 1, + "regex": "", + "skipUrlSync": false, + "sort": 0, + "tagValuesQuery": "", + "tags": [], + "tagsQuery": "", + "type": "query", + "useTags": false + } + ] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": ["5s", "10s", "30s", "1m", "5m", "15m", "30m", "1h", "2h", "1d"] + }, + "timezone": "", + "title": "Multiple TDengines Monitoring", + "uid": "tdengine-multiple", + "version": 4 +} diff --git a/docs-cn/14-reference/07-tdinsight/assets/15155-tdengine-alert-demo.json b/docs-cn/14-reference/07-tdinsight/assets/15155-tdengine-alert-demo.json new file mode 100644 index 0000000000..ab98354aae --- /dev/null +++ b/docs-cn/14-reference/07-tdinsight/assets/15155-tdengine-alert-demo.json @@ -0,0 +1,212 @@ +{ + "__inputs": [ + { + "name": "DS_TDENGINE", + "label": "TDengine", + "description": "", + "type": "datasource", + "pluginId": "tdengine-datasource", + "pluginName": "TDengine" + } + ], + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "7.5.10" + }, + { + "type": "panel", + "id": "graph", + "name": "Graph", + "version": "" + }, + { + "type": "datasource", + "id": "tdengine-datasource", + "name": "TDengine", + "version": "3.1.0" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "description": "", + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": null, + "links": [], + "panels": [ + { + "alert": { + "alertRuleTags": {}, + "conditions": [ + { + "evaluator": { + "params": [5], + "type": "gt" + }, + "operator": { + "type": "and" + }, + "query": { + "params": ["A", "10s", "now"] + }, + "reducer": { + "params": [], + "type": "avg" + }, + "type": "query" + } + ], + "executionErrorState": "alerting", + "for": "5m", + "frequency": "10s", + "handler": 1, + "message": "This is a alert demo!", + "name": "cpu load average alert", + "noDataState": "no_data", + "notifications": [ + { + "uid": "4sKm9jd7k" + }, + { + "uid": "QyczKkFnk" + } + ] + }, + "aliasColors": {}, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "${DS_TDENGINE}", + "decimals": null, + "fieldConfig": { + "defaults": {}, + "overrides": [] + }, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 0 + }, + "hiddenSeries": false, + "id": 2, + "legend": { + "avg": true, + "current": true, + "hideEmpty": false, + "max": true, + "min": true, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "alertThreshold": true + }, + "percentage": false, + "pluginVersion": "7.5.10", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [], + "spaceLength": 10, + "stack": false, + "steppedLine": false, + "targets": [ + { + "alias": "system", + "formatType": "Time series", + "queryType": "SQL", + "refId": "A", + "sql": "select avg(cpu_system) as system from log.dn where ts >= now-1h and ts < now interval(30s)", + "target": "select metric", + "type": "timeserie" + } + ], + "thresholds": [ + { + "colorMode": "critical", + "fill": true, + "line": true, + "op": "gt", + "value": 5, + "visible": true + } + ], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "CPU Load Average", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "percent", + "label": "Percent", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": "", + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + } + ], + "schemaVersion": 27, + "style": "dark", + "tags": ["TDengine"], + "templating": { + "list": [] + }, + "time": { + "from": "now-1h", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "TDengine Alert Demo", + "uid": "aLN9ujOnk", + "version": 15 +} diff --git a/docs-cn/14-reference/07-tdinsight/assets/TDinsight-1-cluster-status.png b/docs-cn/14-reference/07-tdinsight/assets/TDinsight-1-cluster-status.png new file mode 100644 index 0000000000000000000000000000000000000000..4708f836feb21980f2db7fed4a55f799b23a6ec1 GIT binary patch literal 34238 zcmaI61yEc~^Dn%(OK^8t+#&elu-FC`hv06(-2(&-l}`=S9SKBu9==+_i33k-L)|g4J8~*3QPb1fTN-;uLS@g!vFxpQgo!}5x{V3 zF#v!BfT%$gfE+yR65JlW_RQ?O%s?(ic2)*v4l+vG7X+lDg1pjV!gNePd1)~T5dq1U zf{HSd49q|Ukc6z1n5Yn+$V(A+EMCE;d<7QE_2@sh9k$Y+UK-MR|FZaCpna#G0QlD_BuBE4wT?IbT^$nvs*$+Qyab zStSN$7gs+%7B)sEc3%sn=y$2Yd_3Zka(=epgoJEvUZIlGdKo!zgr9mufTolPKkrMy z+S*QAJ2z$^r|)Zprlx)&5lMHCAh4>ofVfC(Y`TwccyLGzGbN3T(94%%AZZyTT|HA4 zUUq&?<|uah!opgA|42@50UstxmsV>wAcw9eMBQF_d}1y@0 zmnJ=J|G*c1L9v(Av?Id{WR#q5ebl?U#+6hwC&mYY8=MV|EbZz{t;>z5Ss0C9+h~S@ zZOWG971h->byePti>Zqmq-nqPR(q*00*%##Xc>^wg2_2;%?k7*nwRBWWHa^I$S9d; z)G{W7hoU&ZPiXs$(zSTa`0uaGq5Um!} z7^-6;Pfg=hy)=?<*ELweFD*C>_lr?w)>Kn!3R8V4B`)IK(VeKrs8Z>dq99I*T(@DlE&`=l^4Uw$Q&PZy5>6){fgq6~AfjDU8Da0h=f^3pY2Vk)Y zuNcKMoHaT|I|5vFUGpto@`{#Z_$zW+*y@0d#2n1U6kZT;S5yv(iwMe6i40@bcmn`* z02O%|sPD>2zNsDMDD?;)g$s(G6v@LIv@*RVsgBGj#{zI14@Nuuiozck&G8t@k-}T7 ziP&9>pV5{6miO~pFe)WI)_4X-1oQj8DwGyVW4WDgIk#JF)x=8CNN#TsHT=21ZcdyY zB{_?JZOrr7x~EUxU0yz)&=fCxoLu_onhgMaGj>0mH9|)O0K%Vt=+CJug8=x1#{xeX zHbF)QyqQ9_ULXCu>ZpX$ypb*iy_8`-UTMuu+YDUpzy$!BG$Cm1+i}wA6_!fK>6AjW z{03b=9VA~`B)Y^m*F0yvGZ7R@&6~~8!bbo#d-J&o`i^>S`t$v-JSO#}^jK$?*)-1d z?01}f=Bf5xAv)=$VG;LLj&l=7ft0i&Y<+BV4vGava_Q!wsUQfnCM8KgDujoPCqMcZ%;d z5dbUnz}1CnVHUWAL5!^yPWXy|^pVxLNQTKPKX-=m+H-H9b3^VD!P%(hXYfFch6&`C zQX%~yAk>@lT&jLq%G^}DZZ#U&>tjlCtEHM$SPhfPMHW2}TpTY6ESu#~NeJh!&0sdf zK?Hy(k*zy|O$fCIXyz|h)gYR|dB0Fc<~So(^iNMug^$BypTdxv1eZpTce4)!_<!u@d$t)_-V;RyQm?Re@J+$T!Ii8-DV6w*d&^UNlk0dMm%NM`r7r5 zd+a+!y6mTkU$5&$t3yZBTzwj}XC{6|yF9z`i>^#|T7K~384UNdx-9mWk%Zy40)L#h zDzlNX2Bm5j8gcr9L{=tRm+5jg&c1(uweGRVHSOWiFMWNxZA#2aEX3(>v6+pDyY9 zg9Td{`OF2xli*@ADFwm41YO^JHBL>3)Y1n~ph}Jhi5Tft8L3kO>nBOTA!_iP1Dz z$N0_4#2jsTZsh+Rhe9DRMyc%A=fUw09}?=GEYV*f;S;@7)e>H{EQ}yvl!NRPWj6Yt z7*4FW?|P2Y+xOLx+N*Ynx5}TLH2=sn-83zAV$X47l1LF%5&ypWJ*#>W>#JD+J1F{$-B?)@4w8I9_p|b*P5VK26Zk z>#u*esr`H_5P?81{Zf9rBMyduj@k7}-AgdY$0;B8ska1a2Jx?(pPlmWP9XEY`5PJK z-x&Ds;> z9W)ZhZk_ADPJQ*7nUS~}Pr*OeBdAyD%&@=4W9YGYQK#m-Z>8j5iBb27v}?;gpf=yw zTpiu&I+W;l+O7QNsI4QtWqe#$Ns81O$+`Y|vnaS(#Sw$T7EeR#SBn;|=-v~vaP;&L z?eG`hlRlCmDAq{PFW+c~@0wt&y}6*kJXUe?-!mrHE^JUUN2;CVaDP|r7V8&{*x|o7 zd;|Og0(w*S4w`)uxKACZm>1>$FKs{(R5z7x2l>^ce}~ao}Rx@ zt*o=n98W1IkC{r>B&T()B;@NmOa^xwk^3#f*Uj@QeqQqu-W`t&Dlh4D8;tUoQ9C3R zrr{;nZL&%Eop{m~{#w4>X)oYvmSXAtq;tHV93HhHCaECt12tTXS#|cKu<6=~Hy8Wm zhxY5mw%eReODQ9>xpB(Um=G;gVjZPPRx1cf#;|YD#XPw!WP_K|hfZAUc!G4qJFaU% zk1$QOFtix%1C6&a=3-&azZEFg{_Y$7gxw9wkP3Y#Mr|1cBy&!!Z~5A!G%85dIBY;Q z1kL#E-zMpd@6X9F;a~ClR&e?AfDJ|J3_B0(ve>-jnh`~v@f=m@N0vE0R29@_Z*D$5eCFJmYu~((}MYo`kju*1K3{m;0+7D5|<=iSsOi=<=c}q`RTN` zzB=|c_TE(3c^HCvJ*lC{&6S%C%kbe(oPEto`@&0|5|L89*Vx}-zgc?I+9M}vXH!Uq zO^W+1s*D?9a-Z&);Mb+s@o_6!fq`>&2OnV$)Vo~3bBuxP;Mfvlivh#gwHG?M#AJ|B zfvLPj0xWjvWm7%p?|kbH#*mSg%(06+g;1^~(OkvMBPlWCFKb3pF99;m+Rx74B*aMfZ-sgzU45n{E;Jea*p15v8(ZM8 zR%&5^M_IBp-=4Uax0uvJr4->MFS5Reloxcj-`Ao4@ue+uc>rrSeLXM$($RSw4T406onzZmS?IyZ?aEbbS`iq^*DVWi_ifgot z!z5fPHn zn3L@5Fw@5KLH$6bKv5mjUu;lQH!!j&;A@?vpa|_8r3vP%U{B)!`$twZsIKz(q=RO5 zf+1~G>X11HBkRP%+}RHY%;T`MnL_mn!IF_XEmwd%_2cwqIs+g z#F;oPa`(FO1iiC=dFU*eW${WKrGqcEK5rDF4!O3xY8alw4RV+|_~D>^^afREfPg{T z6!-Z<5H+W*CjOMTH^?L#6YkX`I66&1LP0^d`vu2Tjj!3aR3Rh3-eq=2_2XkIcC)gI zar|RWqN1{x>YIS`;_t}VH;tn|@zS+L>0*MaqC zO!{khDnkqrtxif%vZ9tpGtBBI+?!toT=G$@jfnVAa}J2MNJ{@XzY=3Upwyl6tE#|@ z*bI<&eNVANOFP{v-*5)fCW>UuA6}#&oex=V@A(2u5#)CI`0KOOn(D!-&8xzA>WLR? z(W27_sym9iDfHZ`*i>o+tmL-t-@gqMpR*C7vf^g*q9?;*4!AAfs7;|~enKV4v&3nj zTq0W=l6V)g%?@WWzm}0q?EL+8T}M)Kl!CRM_2%)*sG67Ff}W|)8D*yqSG1b2Zax@JsT!i@kV1N%f zN&*xH_r4LnBTN?U%sw7O7l1iUWcVWJ(i!|}3Hp(Y5j%o$#gPZIm@dSLTU5-!NIUh^ zTp`}CkV_9-vO#GQL=vvuDSPYWn>PKiHl{Z8<+PXvk0TzS_UN|E4ZjQF=5~Pf*^OTu%AeQ$tPjbpHf5B zk&mpu-C!bH8#_{;&r*5(9&@qBVviwpbPZN2n2CoCo)oX@HNceZAbFA0*(e8Ta*zCh zv%fq|uipFFTQT*Jq|DI4)4mWewHaLTM%;zUL)2fLEmdo#ak7fy3EX1gEirtSAWAzX z%oAcMdbq)LIo!WNLEzLSF#q9dQ1Wx2-~Y#kW{?%LlO{x}p;`n~f}^!U_Ue#P&A?yi zyP*wDYE6HjyjXN>0CkRsPh?XfXmY3}+{+n^VhadAZ3-BnD{M%*rd*atV z3Eg6EJlB|jd!JsQJIJ#FtMOvj@%@C~wnx9*{E^{{iMQn7loi70_o>~apWNuf1sFr*Lue5-}U#O`Fs3K76&-eqgLbz4Dhhc9NrGG3nQ z`Y#m|n#Xb*EW$at@QLuD8yF6BZUc;obDK6YcQusad)yo0>)bR3_7WvqfqAfr6K@it z>#8@lT$`p}u|77!w&%mOJh3qU;Ju0E<%G-lc^A%S`WkfloSLbVoPaeuYl96;$!*C^oRgA&m3q06SBbV8x!OZ-Ud9zh#Y z8;V!3C>A>bj`Rx}LE6WJ#&2ec$nDG=QZmnnug@}&WT}TQ16Yy#l;KD5xdoJ1GDWHG zK<1bT3x#+V?#7ouDYSQtgI^!UB3Q9Zg869E-ggVEhHKd)GsGblBQq13$wAt{+xWtP z5TuEm)Idnuj#@a_Ab$s(ssgF?$vI<8Z*R_siAd?=V)82AqThDZV0(LSvt6U#ZT<$n zhHt(hd1;v91#o_A;#VVZly&}jTR}d&qxp|8cW#b&I3Bxd9E0?=A1g|9S`Sb#oUVaj zC!>i*I*7QO>~BuTDy1Z`a1;0(-83R3za8x6;gDhe?#anQdKa9?FZMv4#B^q@snvBs zW)%>+?gV2&HavKu7orQ`O5U_Fkeq+9E{9c= z&DZ%Y7VYz5=o=hISf)V*Lgz&rLKR;l%FQB^36`{A(BoiAsm0tWW!Wq3KvGRQU=S(Z z-SDI~_svu37jC9ln8xjhY4HhiCG+5(a~K;cayo7J1^1%hk?ncl_mx45?}+4I5HZ+f zuimG_EJUIQ;&41p0AX`He zUuDBn)Z^3I@NWo7KN^HO$t(}amG#OR3{UzK%$^f zHp?$`HTbO>5Y%O+n!$!BK#PMq3C1r|s4qCB{3(k>2bXw3B%UxW*JospEO?HI#U%gz z!vN>g3K-%P=WOw-=k>?pjclys?~nd4mRNOl%P2q9h#MD4ido;RoB1Zv)QD`87TD4% z_9?^qh&bQ9{}yRIAO_a2S%70Nkz$Q z3}V*xS2lhGGW8JP3Xo<4`Kt;h8TJpp6pV zxxpOM17n(VA$;_mDXK3yp@ubSMEHcNq}sDP;vC?|i2mc5v&3Z8SciGfqy~yx zZiqq9%Ignq^P!;lNkMMQ)DDDxN-ebAIzEl#mNSiLqMC{;)#%GXenxx!D^6pDs2xG|J?e zv16b2i2#m$$|5wGaZn=dDkegAP_K}k@a6aa4|-&392f#`Ah2?p;$8VKevjV<8+{*$5(yP7Ea8I~gwyQ| zZrNbWltQcSIxuByUIp{MhA17}hgN%`&L2%pVn24!s><^Rk9=^-m4CmN6Y&aUM}iAM zrj|9_;lBSr>l2%a8KhPG*L_aDu{RZc_A^8bH&jCQvYMu0{SU%=V^qC zj>6O&0sg!g16(-sV*xvAvO7LBvby6hgJVS7Ajj(j3aa9=YZZvPNly8-rineD76b9k zxh;R<98c=>1fg|8^K}Du(afCf&Y*_O;DcD8QxtS80JCT@`Azow;VX-$cw7e0hE2JU zrj7PEEfI-oHUhL`Qs&Ipb&kjp9LYn8uoiUfY2ETD_qQt%8G{iyvC@Pt53j0!7}oVi zxE$Yt?Q%M^LpG1MO?$+$&}fP5y#qsbY^Xn&*ggz^J^hfv#yUa zjoduv1$S4;&R_sKv&t@v6QpSc&gZ1uR$YKTCOpE|MzwwPY9Jr>jRRB%g$4D}snDrg zGJZ=TH$UqP(b8JcZ`eX2ggS4pNa-!i-k3c?7lX~h#S~};NDVb?qPW%7?`=(e?5$q2 z@Ze)b1vZE;&W4Nm&B(Fm=ipAEde$TA4<@4Z)vqQW%oVAf-4j4N$93ul--E4#Uq&bj zNT(?be<#$$ue{}&8OKhB?eG6F#y%h+dV2A&`|xg$x@+T4_`8-6F^S4A_ZJ)~8jw=c zz!9F_gL@mgi0f^>4qT2nKC+%Iv5e{|>0j}7N3CNPN{~rzz10+cw{WqeU!B4wv5*4_ zcJ^ymYvks8e66)kpQ16b5c|;z9C{z|CyJVCr2x4i%zpF*qvX!{O%nNp)z-VDqLnJh zov?$9xqDUh)5(XYDoH!(o)KdAuNjUjLK*iaogFi~E37R=_ch> zJ@%^kApscL{C^}~?YcswR(CfQn-c>-UAy_n6Ie2S5l^aCX{L8KaR=WfB2?GHW6>fi zKBOG-UVEHT5E2zw=95l6B<0^H*fUIi2jF^ad#Idov8jDd-?1n{)n?4Z*BZ-pKt=x+ z>r$0X8vc-NsLkz-sXN!?ps=3WG9&!2$Zvcw&g%Surf`J!zd#*Yi_C(uZ&| zUv%2aqkYHyMq*lRm90rX$<1!u^)={^nz(7f{^7{x<-`(~UP$1_GY!GTup$xhatD@r zMD1gZyn-7d*VB|htJ}f57V1A)uY-5IMy?x2eAFMEE{I$*Tc|67b0mUxw!WJlT7n*L z*LzAz!x=#NbrR=8CCQC{%I+&ERaX3WgR_H!TfR@|?c++Ob)F*F*F3s`*4qPqTJ0uQ zwm=)15RdY+tzd1x)XTgU>T)z}7Zi=uBar7E^kjruXTrh4E`~E}^Ld*`vXjHi*Ufk3 zEIh69sAKvD(P0e>^X{Jw&)y-XpB+^-6MyhNM2B1tF;DJny8k}%^ASR*T7fR{W(}Ay zopp2v#+1>xkwllN>Af$RQ74CpxuO8WZq!Gr--Issr@0Bs@)X6TR5(mVcQURc=`15 z!R1o=s{{5t&B^R&Ga9#O{*?BNK3ddMWCS&Ze}r^&re+liG=*M>KQQ3-^s2Wav$cDh zJ8;JBe(zYxe~sG+cUyTtseF295%xKMoRtyuL#ea;>LuH{=DPB$E|p%V)#f`~F^^;4 z)tC~kuPO;;$PT}$FofLQRjUm45ZUcn$l*3d0en?nnBk3c9b)`Lm+l+GQHrWXD)`9a ztB|p5%*9TMocDdY?r!#_;VT-k(HS=LY-B&v-jpGyY4aLj-zMl+WCEIL7V79|iT|3g z85^ptQVG%65E{`sYDkUn#I);dp5Li`Vd&k6nv=5ut=zZKgIw8J#o9Xm@C2h-*{|AR zKwr50VyOtDWJ*vA@mEPPbmU;KN)FXJy$D9o$=}XjEOGWJ?0tlyf^)XJgLJz{?7NjW zI*(QJjsPlOC=;$_LczkNicz%2K~i2;{&&|#BX~GoS5m%STR@l0ZawJ%O)$uu@=XU5 zXy$P~fv6H@*Xz{Tau+B8k&Cw6wWWdtXixH^J@=xV2d{_xNoNBnBgYImOq%Ty06Ka` zTZUgP__97T%z0}l(bTc|Y=ueN?ZoaOR#jut&eS1~DeJ`@RPV3WA;Z(DwmTr?C{@LX z&Ep`QY^o)lv^hieigJ;KzagWd6o;UIr|&!Qnb-jd%6QuO@SCbQDdxzs6hz)#E?Y^P zEQ2@(TifkMtiqnps4m>TSDUz4T?j z256pp=NXm(j&S}7ln}e#t{^GT+X>d`p5$`hifb)PX2c`cN*fj2d8%x0adB#I)gs3m zEu+k@O`SH8E+yS|W!Py0R&rbu4%gs3+PJ;Ers3rM z1f6DlyW%I|3q?FW<`mu?Z7II6#bsk}M{Ia8W>~e{D_Fn3xUaSO#fCz;7e>=BAfMVetprN;DR!S+lEj{60Js3cwSU%Mny@4RiW2sEzf z24fIitmJXTdXf;Jwc-VX+Mk;l(0$oSK6eMbScywbGa>Q)>=UM@*DoAt+z1jX+oR{R zUem_#l8nQYYNT9-RhwoRcRBTWh6?HnDUxjjaSFDXbApl%OK?c-o6;JXkid-`c*i*x z<2eg{)y%XHuKEp-+N(ZkcUn3EW0j{r@oYL#^I66kT%gj|i1TQU%wD-{WjLAU!+BRf z9BPvQATAdeltRn$kW@tn#((gq)o!KkaMc3<)diGAC|3f~v!(36)Y(g90pOefFj}$} z0=rz$EX-uimlpw&jhBYYP=*LC|6MB6S$=MY2;-*#Qp-l*O)fJ0ycs0 z`XsqWY`;%U6L<_w_n7Et-P+*YC%k1;ao{?@HMqs58-!*PnhoSLq&ejlLx=KEN;05;DPS zUnb*%EVuzEpfFT?6>(TYlZiPm7ZD_iV;h;V#~6zm{n;nHS+h8BQW3I_$6#Vlx@X=# zGcXTqiDQYSmzLwYmPQOe)UXuf*ZSay$qT&F6R=3s~EY6NvE?=Jj zJoJKuKsEyry!CLL}}riO(+zqHAkC4D^VTIh-j;pw*;N5KKWYY^Fw z2+xiWFQd~nM+JJnyL7I5h)O-IZsDEelZ* zKDtN?Q(<^fF5I# zBJYubEvy|^Q#V82wn~;97Z!gy!(!j7%&&b^4z);cZ0CE{-dYs${em@6u+Jm-u+q+N zmS_E@V$CBcCd>i}BXeD{r!D{S46(}8FMLL_=awD**7p4BIp)$8Kq|nWTk_M_gPL%B zcUB`7P4~AD3)oJXPeBq=QhLz#`q)t2NAcjeZgm>k)=oQpD$uAS{~IxLQL@&i-I3j#mIQqmZ9;fT!rB!7voO_xSMUpJ@+ zVpB<0F6yY%Vp5NF-e6JBSxE2gxHLqW$UDLw1xmge{3lU*il>F zr#UHyJIE>v=&)BW*GPr*xaS8vuJN3upd{|ZimAV!&PZ7AbDATHze#3`8*b%^s3ou# z7Y%5)76SmsFroeM9CSdP?-E>N3WmM)*AdJ=-x=A_z5b%@z43l21VZV9!OH43zqNQ8 z8g8V?GZo$6V#%t1UAUOP^D#6$Oz2(EI*>+K((r2w2ITiydbHhz`0;YV#?vLy<19*F z4hNkIq1W0)4`%~*wF|iFBi}*;*ZZcgS}yj1yde6Uw*VpAcHdLRke*sw2VUw*8lq3T zyBrn8ZyDID3*;?eR(4v)gbx)~q zKVtc5AOY2%jeryQA&d?e(~tW#^s^0df0f1d%K^KS{Kn>&Q2pGRPKJqz|{7(Lks!$ndS9a2e(#5>a8EMolj_y9LP%9RRqq@wLzfN_&hBV{O4 zKb5SlW;D>qOdC zlUi5=U{WJPGA!E`k%9IBJwK)>UN?*m=8i=AGc=#Z8!_L0gM?mCO_qK@4vW3ci$JPk zE8ODNdEU~!N`~Jfm~mFrQ=!zXx}m&5++Ge`~i2Y$w^dsq`CKsiDu1YI_=?i zSqs`~fiL8(x2Ooj{maQ%1vtOk-f|~?W6m{WttW#Wyfeuw3n%zxWX7hOW;IC~bkDP1 zE8rH=C4>q&DNd|)J~=rE#l&{f40UJ~p_{S*8Aw)6WMA@nEUN_LqYyh%A@U<KI?r%wPWFMbzD6FWA`t z>{aesjDdQoIj>Vb2j#pL4z2F|5(~E7Zs)#goX6&E;B8hASjj+?*ETbTfOge~N-m+0 z(&Kj-s5Ay<7(PTTK1XV9IITF1zIUKs4HTa%bBDy`vk+dITCTLyP1OOr8qtX4Lw%4q z$2l!cv9%ZqPB<-#QQ2g)!Bq2w_XyCsX%{>CZ`T!}Bokl&ZQ7777XwQ_H%hihm5NOA$O2Kqy>II{O3-(BV}W-kKm zAic}5r=MuQ<-tyW9zqe5=hg2WBU6iE6kDZ0Ac=~U$PKB=)Oh&ro*1Y|3dXJf0WT|N z-&XYGPBA9JZ8JqEehf~N5VYW}j|E?Y=e2zj_#OD}GwoNR1$w!0vh6c+0Q(^lyFX+XG>Y1-CZec-AH z$gDPU83c>`g!=hi<(l^x5e%xCubnFDHh{)D@ zm6n5XQrxovcgw}e?U`dr1C@}pFPL!7!Wlx@+kx^>_D?6`s-U4qhVNQ^;VyM=O41wM ze7La=ZLZZMgh;3L^2`JzzD2iEu_-swv(fxi{(%P}fUo6m+KP-#Vf%b3Yvc0VkDle`hyMeT;%R-B1rA0dbMUg z#{q2P&u_fGMUR+4{jU5}%++1Dm^7ydA(b=Z;ps%s6)w0z=Dga-y68d%<8=XMYI%-~ zBt>1N6xGedoR+uoPz5@i!Q=!6yG7*Ex&DeI2dMr{Y?r8$l3dVF_iDfadI{tz(#+mS%{G8t%> z$PwX$fbr)YUA|M+0Obr5$9oRQ^n70+4vfIXlHk|;%V5wwzmEB!&&QE|?6p2v zK$8&zInp^kES$%O&O|>T(zx!WOY3kEXH-VRTO%I;vysR-d%R;Ypx^AwUaUUT6osgrGA6K1G|@yz58glohVY>$ISwUUolk+w9hysGV23}f@^?EsgSham8|Ub`^mJVpMVRU+J~Y$GLuN^cTP&4 z^X`HjF1?{EeyWy^KA_r{#`Dcab<6V7Is!MQl6-xdNp49~&FI{+KN4-WG6{XJH1HsP zyi_=%2uCG=bf2z`4373m=MqYt_t;$V6}{oi3?Ql+?l{)%Icnw$!t+#Wy%h`5UvyvI z0_$~5ZYw$um=K72kRTb~5<5QK9U%liU7B@)er)EBd}wxes1a*(VDjzSeA=O!GR= z*i%6~>K#1|MMR$r2<1;^HQrI|*0RNc?@hEu+u%fCK=!|sZjwOS^N^ef<<8^2%7E5( zTb0jSuzYfKjohrwFt+x7Qh+8sF)=u&gps*FPR=1iZ*f5wEyng_9w=exNt-pcB+m0F z^TbH=lix`nb6F|WLVt6H@*gV%P|4=Je^5&hg%M!{O2e}WoW1r>7~b%Dt5hBB#|ge! z7;pqApr)o(Gr4~xWL|!gDn$nQvrG+f#Z%Wgld_Z#6b?PA{{Z8*C4Wo4_6L`RCI{d( z6?(BIbd8yjXtRF48?)Yy5ON#?V88J}ftCbC5zX^#DTQR;0EYI)>iQ&~?ugTb5RUQ? ztXF1o{MoIOAR2e0zi-fxjy~igTwjI_k!AgkY0D9zYb5D5kT^`6%Nxp7>%xFAB3K*7 zj<4uk5(F@ZNO~a73mY{HzzQfwwFi`Otqo(mux?%1aF25Ds8B$a98bdQ|Gu+CXtCI+ z>w~KO^tLHWLackr#idty5^9>DUpFzVXH#g|A>ZuHI3Z83a2fK`^hWoveENnP6ddArAMw&zInQ#>OB)wTIl$j-f0Cb`RUZ|`G1 z-age7xh*$MVNc4#(S!-tT;Z%QvnT~1jRVBCJAFb)dnkVY4ohY+?+0bQR3{79_zpj$ zL=!Wud;h!YgBZ_o;O5fA4^JN+4HH?H-r(K*TO6NX>Ohf^-`vBlF7+Ga$71BYvFwMr zGXU7^3%G5Y8aGn<^1T)4hjgl^*rDPD7C~N?-)LJkYwGKUZ((`@w5)n-M14BmX~^b8`5;c0N~%U2F&J%2s>4`&*j|B*>D z%)M=|{cv%V0~PkXdVY2z;>ffy3si)__!kNXVu}D%LUCTXh_QJ^`$JIJD}rmqtz^1`Oq!Y|9k46;M9-LgB4+5(WQu$WjQsh%$PJ9TfC}! z5mVNhNU?=i1}bGFsO-k=iCH9^glv+ikOIeAuN!p7B8_Y06x?&=!&jhMocV@bE`1g) z{~Aen$Ql}Zx_L7-Z*fSclVn$`N_U8pKp~XS1%$n~OJmjVd!z?rC|riLs$u z=_?(_E@m6v-@Z#1h_^KV_@pYWfds=mLNq7Uy1*enPOEAT<6oHrz`erC-AOHCb=Y)T zz|J-3J|h%6NV{6#ssV28nyL---Eut0cwKW#Fwa0_{$vZ%{rtw)U(M9-sMl=}ns4&% zs(axatrNwJMjzH<3wpX9XEj6ahFIA0R|wH-yeu;V;l(OaY&XA~sZ4FSrsw2WCj%O2GYeWTcp~hYXIfv}7w;-7Y zwoM}*?bAwzY<=!K3%+VrRXXjyI1QQVzA4!ZzV<1kCGvQ;uL15Us#@|$FJi%hmoIRU zKo`sy4GyIxOU-nKk;#oKT14e;3)5U3E9A# zSo@NZSDaI3!cx{kRC$ESQCncwjsmrWwb!)quJmy0=GdMdqEm|zan!JrU_q+}z<}nn z{(9AsQ)(%fpq;bm%?8}_r<(ygLH{e$5BX1v!pt~uXocWB^-*1QZDZI9el1@=vCbIn zkCqL?Xy1fabMgKW)zY7JXG6hWYDG~UIt~#tCow}~?(sI(D5b^e0-)w9W5zXtHLafc z@lwqAy>EuqVjB%987UISp&z6S37hfGJ8U>t~;!nMiesM!~IcjRfFiw~jt@a~i{&$iKr=cE10%abm=GRQT>qP=l z*e$C+St>aBHpTp!<(SMB-Y-S>t4gaxzSd=Oxow58_WUiladPD%<7dY9Nv0fsm^*#V zpf&1Z@&=EF^LoIkZA|LUW-qa1(BqKmhu*6S_2N))5O|R$jeUh5Db0TweII24qW-|- zS51C&p5F0LeH|U14MkUSSCTwJD^5# z%5xtnhTk4F_G6xBY#J4Uf?Ar20Oaj%sdniOI?$q6EhmhDlQo}#Sqb#wc}K-2G8a80 zViQG-%>t&{z|hB4DwIcGK%Tc?Pb38f4KW3R#83vzF=U7*CJaajsy`qCyjM-j0I~cCMEQt75^F@D7z!i}1Ew68_B>c70D0=PWk}wXX8o=S_aN)eUy)w%3QkR9 zu%l9BgfJQ`QnVIY+a$D6C-0d{tY{y#(kexkmfB}2ARPS~67T6GJ^Q?L;fY&94F6$^ z>OTZ1^B_wA;ozNZbc=63@y(IDQ3EFje48o+zt~Ft{_o1hT-^oDwY2)fLn_5Hnt2y5 zohh2MVe=~lp&98Ec+iJc&>_umiDa2c>gkR04R0J`+GGEg$kc=3a7Kj(*?I*IGC~;> zVi_u`H_lQd?T-l`YT){SuV=;1^wGM|8(n2^N0dQL>dyVMjK&}Kl&CN*?0MhKy?W)s zu`?>2Q=~FKvZP;WQPVCoNlqjwDdpxQ81P5dsSvsMnjKLHa3Un@4}HPMSzrtR_>z}q zolTwV1)n{Y>1CF$nQ_x_OoR*7r|NjWSb8`ogh`R7do=ora2f;xqqWZRZ=02tj53 zcn*4nhI7lTR$oE?qA2n&*jZ^Q{?(2Mc3RLBGWM{A@?tfA${fHx1XBN{=&f$zSNiZ@ z{f1E)kSw*dW(6lKUaLr5e??R^FpS#T-h7siY{9_%^{;n_iq-z>-8c;dqW^d|=GqQ+ z@zu7iXK|lHW?*80O)E!)S%!~UD@8CqIxVk45DT78j<^VBw##Z4QvhFWZ- z-#>q%Z4h*TML#0JUwA#d{syKF5Xh`D;Hy092W|`<`}|(pC!OA9jiPDcD>m&f z@i%5c$ppDH&j-Bc*nNikzu>F=3;Ms|{}1_J{O4%@zvDk!>>2;>@$orVo&{LUA^op+ z(fUW?bFw@OaPt2vg_#x}#);qv!l1CJ(@37}VpEA_D@Z9dmnhwlC^G)g6b?BEK{ITj#cIOsvdwGm=BelbJ?W4+9GMl77XekTq5ewQ z9i{ntpY^ov{6^bd?nrx`Gg<^Q`1~u3N+)sfCBMn6(z{<*?KCu%bt3|Fc-YhHB4*Xg zW27rX2eYMgm-LjzNV`w?&+mP&9;E1J9fPWTmpjwiBdXBS!Cxo^oAT*dd&|1}mK%4D z$F1Wx&bi&6C~3M?O;a<5c|N4fjj)&g!-@txa=Ar{nVY0>#DI^Jbl2bCB<`k2_*46s z)+}{lv`Z{N$wU8Q;&<}|D=9k)By4=(5i)h+w!EVFGtc=aMd`zJDiYTY+?YLG7aeva zQ`QY2@%HkWTej<^Jn000F|7@7MC}xXiZb!Jt?v+q+^`zPFyqGmM=6gPYV0L4*zqZt5t$ z|9WA?=<^HxE@M#*DN)tBhlD&1+p84{N^X*3pUTeUz!f#zKm&|_eF!wMl65X#?H#0{ z=C5mdJg%z^><*L6bO`njS4w&<|8=sZxt4?dleSm!Z0FegmsvH#vWsz?o!rw!@5+aV z8GgkxTIbnRy`h8V{;IOA2IHr%U;s294y6ELgoBXJndwP^$GatcPY7z@%|G&oG0xGJn`yZa68-W3oP(flSX-Q!K=^9$PYv`0_ zq(eZuVd$X+1SFMiP`VKWl$I`m|KRg{pWpLa@Be+*d)IlqPzR6rZ zI7kI_kPjX{Fw{yCl2`HoYQuy9qVU1{FPa?#)#2YXlraf+Bm^$0N{9HWbc&9YA_Na) zmal%5(A=ujl1%@qRK%NzgRGJI{KdzdmP$laC2r`SX8|Hu+v*j-WErLIR;0jC)S>k@ z6yDj{DG4z}9dw8fA3i*QzD6B5V0QKjVQi4maW`9+S0*r{)Xa(ZOH1$XW#LvevJuT1 z0ayS$VORSJ4eIV*@17w|Vcyk)wbg3XBkPIlM~s!o;PQ)UgwQF6Qcwi{e>24!+G!RU6etu@`~?!3!fq*rJwP+qCa@*8=xF0g|6DQ0CJ&J*4&aM|ILNLLvG8Q$mn^UTkP?M0)`ExRz6mg z53LtREYm_Mg2$P^u8;T`uMd^tA14ZUx+q&{s_nrOI_T1>&=FAZ+p1vnL$$3ja z#@T9Pe09jyZ50ce=GAbv-$7EkwwnJHxhivn8BJ>ZrD9R6548L7e>Ty}e2c zlAFW!();r>pPo`H+R67bMK#tzZfne3X!>)r_JQ=fH9tR=!fPQFDrzlD6WB*1W&)q>btGzB*~wM!vlxWN00!x)I-9vOaj6oD zZG4tREuyN%2)u;z?T$A;(s`SVONn3wnviAn0xZX7*QzLe#uDXogBVqR655Z?wcU2> zwR8y_{5}z)tW9U?iaCEKAOk}Ly9!CN-aG?&Wa%2&(W&W<+Pw1~m6*K{a3KWDGLW;`q}{LYWPiNT7CZR?aFK zXH|s789d6|abEyMvEFb~qnS|Kc~3QQtK%;FPwB>gVpf{Izlka{)E{-kC~pr=fGjGh z32-SKphHd_*sYHp#eFSGy;W3Uj?37}Eqs2Fl)2;G-B1@uI+oQ7~g^CTwvWcUwOD zoa`km*vV(|qXtuzo*yj4=U1Px%hV?&En=|QnCP1jcK}k(+U)hmpsC9=bXKwu*#hr1 z^JyB$_djn*h`@J~w9e!>E+aDMh>yX8rz>@M}-9^m!o-{ z{?)JLcSW86{wFtdw$|``o;?6)X7$!Sc0bB#;8k4nSQCgfA(Hrp2D;9rF{1tqUW=rO z1FIWEo$r2|L035ZkxlNC#`^NR-qt338{n{Y^x*^c6!NK`%}+Qzp#eWYQx+x6;U}+; zaNT1lc$E8_vEBK)Ed9>i!geVJCuga#A0m|s{RK%l$U4ME+|UpkB0 zNB;b%YZ@u5QTND$dW0@2C$H-H!6beFxs%TWrB2D7R4;eVXd;%|EmF=I{rB(C6b^*c zc;Tc@ya3~eBL$K#DhwxIulXg4Rv2iFt$!qw@X;D${!$u{gg$74r8hzQHMDa5aPGT8 zr@(;I={bAO_kp@Ff0R|mtdwrT&3GIf67JU3xo9`F9gC+MhF$@stD7hXy4z7d=rcGb z1jAO`(uIe$+T~T)&jNflM>nxFcJO@O85G+K#usF+3+PO-!tk5vWI3&S`ve{#>GFY_zc-f4@0 zhE=wh-G&2*T)l79yFEcy+=-C3XI~T}0e!6TI8JnAnTB2?E0kwyMgGm0@jK7NI(|IV zufpAZ-8d)tfB~}(2Q!9!jafv~ls-xu1pA^SZ7wLv{rp6})_;C>M~c6}96XozeOVvx zfT3Dcbt*#P&ivqy52<{e;BM$vVy}4ptdu67$uHYM0uXm}AQj18Y@sa9d$oj~_KAme zM(&wjre?xV40u1l;LAJLh}YZ#!Xx^=zuPdTpH9W1szd%+RFtBJ zdNh}OLfk+CIPnIHEGVe-;1%>s2*M=lEdq`!(E$oVV^Wuda)thzG6SIcuwY;+;oucN z#KB8@08*j@n@BYiq3i%=sRZefD2N{Lh4z(9fcU)~FKkjo& zE3isbE;31%{Y-f0Rc>GSLM@VXLXaiqh+@ou8dP1Bb*kMobY+?RLAZKQUjCSZ)6B?c zfKt8fNV})5AlgoS8y=a2MXq@kY@pRPW#8bA`hLm~^M24dJ9Spy^26-`ZaNQ25DVcO zOh(rLwX&F|1%Va+#mkF9!{y6V+PebU^TW};H%~^mHY$zxKSf{HInCKRdGxhtH_d%I z9&Eh4wYlqDYP+P|6n8q`w>n_|GgQtQ&fn8s*W|Xg8M*s0=l!{xoo({nUJ0-oAy+y^ zKH1R7qq0(!oZtOQ0sYIQGIT#C+0Kda@JHRmVr0vWvD0$J=egFFio1pYr|44Mz=_8@ zm4P1|2VX3C{50#OUYT8V7pM=cS*~1OxV+l5@;x_rE?lOwh5OPODWEqqd#VzuJw8>+Hk=P!MM(I{b)S)QZc+$&kqVnxxCbF`EQ6<=54r7I{zV_X`F|-hrMam7F z=;}b(AD#s8ucb*V-CN_oDU>Dthoa~Bmsz3Ck&vzVDr^*r!B1q&fF(m;9bVDAqZYA`eUqIe7s7kbwRD~+h3 zP+xI{kxlmIydqv_*s;Tgpo}>ILA7~GaoRq6W+wtKh5nS|vzCmCx6#X}{c540l`3bc zX_=n1jij~Y)Jax~PmqEBXz1UqVBh!`V@X^dFNpXN{hKBl~8zkc>#%9-Ja3 ze#?o-cV*9$YBVjiWi%R0iuN6JwQj#XV~j!M4;@?i|6=HCuSb8%q(0u+EsDH$ipb^m zu>6=5ft&W2hzX--vH9+V58Vu{I6MbiTJOIc{J$_miG=Jw81R2n$Y0fUW85RkUuBKo zWnhw*T|@KmeDC;ugg%@(%lZ2 zB&WX%(U0jRMN%P^u+MnlW69^nKGaQ@tMNsBD*!7$`}cm5#mlHt}JD z#sij846W99*j!tS>)wuzB+8f+7FeoVLar~9s*Q1-BIIq;e4%8qbtEy`S$>N%)gfte zk={@xL(|89G5EivN(UbQ2g6Yk|3}6s`O8J7{R8?K|8MFHqGv+k{l8QlPjQd_SGgpU z8Hw(j8_rG80$Rwwq#Gb?2;)yfpvb=vuCkL*iSj6%JwQCuH*b>(Td)Gnvu0;U9NhTTKhG zsdcl+m@YJXJ(5&By!-qV-cqNoJf$Wwn6uOVGbhPCXkNANzpo{m%yKi)4xM}5HAD22 zZ$hY3*-nPLbL3sIeed{`ZYj!Pn|Ttamk1+QJnl8ik@Irdk!$s@#}|f_W+I*kdS+Ej zPMp2?XZfr{vB!B+CPAKnwW* zxhEA#Q5f+0<$7;RapuS5<}?J?ffg3F1|6NIpg>2x69h&w?=O+Zch$roXRxfd!k<4o z4TkyM7P4L!mS3M-*6kyuQrZ^Bd8HM$eW5e$%cQ7~rjT~!7sY(7{_<%5?zzf4uG^yz z9?!C)&CY`ZqGT2P#e3Y2T#TJ?QHctp(zZ7=&Iegi_5MEcNOk^Ctik936yF6Q%-DwZ zf9h-{oD2DGyjmY@7|Qmro+7NPT9~nF+hoYTJR{6pwoJCUVwNRQJn@Nz`Rr%y(0
0Uv(Q@Gx@$XSaIb{GwR3H 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 literal 0 HcmV?d00001 diff --git a/docs-cn/14-reference/07-tdinsight/assets/TDinsight-2-dnodes.png b/docs-cn/14-reference/07-tdinsight/assets/TDinsight-2-dnodes.png new file mode 100644 index 0000000000000000000000000000000000000000..f2684e6eed70e8f56697eae42b495d6bd62815e8 GIT binary patch literal 17122 zcmb8WWmH>T*ER~27I$~IV#Oh7aSiTP+@+A<6j~@&T#E#EcZxd{3+_?@N(jpVf%j+N^p#hMPkg?F8Aihb* z^JGCnLb=mY*Hgg4#w8*lADvmnz`*kEb;7{JmXMU8XJjQNr4STirek2HqGiz6mnS8o zBqpUNqoA(IRb*gi%Ziumn^}c_5~CocFtML{pauu42HtZZ=g@UnGugA`Z$ zhrCy$pkODXprED;m7^YTMNoz4K9)4cPxAqcwLSg|TN{tuf z6x@){bkD(wBddZUVvNLOt!-`EdPaj)aR~e(ZU9TM9elFLsKsbcRj!Xw2TT|xwDmx9brAIpZY$F zhxB$2L-nXho-1;VPS%dj%=L~hx_3GB4-SyMaCM*g`liv1@%`7n&$G<7)pTNs^epB? zv_{^dl*7eNhQ{UszMtZ>=trlAvvUheYg;JjsM(zxpBpGt_01N{tnwviRM*s*=NNkX zh59zH^7^^a3YpR=mD!e=Q8S3ya*+^V${Q4_%S~;P zq!X;o7n;{=ZDX$}O4F63)8E@l@3H7zqe^Ep%Io!t&$fUyW@(~MuB$Hig-`uUSLxhn zTb<+~MrCD70CP}KP)ZY2H6VYuBZkqdUxom8q$`b>hMW1#6r+(ot&}pYRAxzXNOVuP zu_~Khj7D#38Ka1xXNG8Y)7T5kSczzT_86{QI$wRNLp--cgzijUl=;{jqyOzS9bz8RG9v#oiHLu*`q4wsoE~k2PDN>`W7jU zuN3sl+WEh$pkL&7-gQ5OqFOvQkoP*^WB@oIWnCFwoj77v32^g<>^|Av5ns>R=-Ool zv!>fmG~H&F7+3hO6@Yr2wU?}df7gqA&OV!C|IrTpiAI~OKe(q9c~vi4lUh8F)lPt7 zZi7+q9=P)TP4#z#`=Va@Wre_(uvaQNdqyma31$#`BB~YY5)VQFS9q1SqJ?WxDbJ}x zkk`dzc&ofAC&#Evz?=FEpnur?(^Ks8=J3GDG_K#v`gy79%<4UdR! zRlG-rTFJuMruN>VghDNi15O;I17=sWdtM* z+lDVbE)ClC6?q062GTj-<5GOV7xYYD;HkL-($tlRs8AiTTGqtJ!SHgUgI zxaF}vVTxf-G?8nWE@84*QI+;*ts(CFHyaMWIZUj|50EkjKvj#5eyR zmq_IZaF9X)2=xCimj!=?{OuO`|HtLmd2I*nF^5qkq+THw1@S9my?1`})=?|pVpc4` zq{05Os7SXza@Yxc?S*Vv7$c%+YNNUfpo9(R-;lp***ft^@>MY)$gMuCP{D7j(c!3H zwLG~pa!2v8oGZ zeDc_E)JSA+78{Z$yx4{0cC^>)Z2CXNEDwZxiZbr6suyzhmHxb_p;1qpTv-4~hx}S& zh zZu=*1&0GuU``GsH9v32wq>9N`i)k0ZD3iF`J5Ry1LSU0N=%gb1Ng>z9X+frm$C#8! z(jJ;AL=#7x=}L`2v-}hXj0^S{|A2GWw|SWk7!F(X|7jPt!iZC|mnC20Rv2vEGP8Ka zhjDe>nimT=r_s6Kop2puSqsxO#nU)ezdUS@$OBScVt8Jbfp8rEz8#v?7Zg)3Tcz#pm_f+uNtdXo2zl_~QRY4vK5r*sVos-J_`-tfb* zMRmgpr{tHF=p~(gjp*WDON&tBzw-8&Li>{C7jkQ z_G4?L^0@a5KQ49FFI*|g!ZQ#zgYAerBCj%Tj-u2;3pYI68X=81MBDqw8W%3YJyiv6 zf5Y7{0m*%*+R!8%d!J>f80 z6Jc+1^J)T`RxahVG!FQkuN{1`)=EyW^|E zbBzb7mTFRDebR$7^DsY~B2MrI30TuZluRvec#xM`3>;x?O11Fx