数据订阅

为了帮助应用实时获取写入 TDengine 的数据,或者以事件到达顺序处理数据,TDengine 提供了类似消息队列产品的数据订阅、消费接口。这样在很多场景下,采用 TDengine 的时序数据处理系统不再需要集成消息队列产品,比如 kafka, 从而简化系统设计的复杂度,降低运营维护成本。

与 kafka 一样,你需要定义 topic, 但 TDengine 的 topic 是基于一个已经存在的超级表、子表或普通表的查询条件,即一个 SELECT 语句。你可以使用 SQL 对标签、表名、列、表达式等条件进行过滤,以及对数据进行标量函数与 UDF 计算(不包括数据聚合)。与其他消息队列软件相比,这是 TDengine 数据订阅功能的最大的优势,它提供了更大的灵活性,数据的颗粒度可以由应用随时调整,而且数据的过滤与预处理交给 TDengine,而不是应用完成,有效的减少传输的数据量与应用的复杂度。

消费者订阅 topic 后,可以实时获得最新的数据。多个消费者可以组成一个消费者组 (consumer group), 一个消费者组里的多个消费者共享消费进度,便于多线程、分布式地消费数据,提高消费速度。但不同消费者组中的消费者即使消费同一个 topic, 并不共享消费进度。一个消费者可以订阅多个 topic。如果订阅的是超级表,数据可能会分布在多个不同的 vnode 上,也就是多个 shard 上,这样一个消费组里有多个消费者可以提高消费效率。TDengine 的消息队列提供了消息的 ACK 机制,在宕机、重启等复杂环境下确保 at least once 消费。

为了实现上述功能,TDengine 会为 WAL (Write-Ahead-Log) 文件自动创建索引以支持快速随机访问,并提供了灵活可配置的文件切换与保留机制:用户可以按需指定 WAL 文件保留的时间以及大小(详见 create database 语句)。通过以上方式将 WAL 改造成了一个保留事件到达顺序的、可持久化的存储引擎(但由于 TSDB 具有远比 WAL 更高的压缩率,我们不推荐保留太长时间,一般来说,不超过几天)。 对于以 topic 形式创建的查询,TDengine 将对接 WAL 而不是 TSDB 作为其存储引擎。在消费时,TDengine 根据当前消费进度从 WAL 直接读取数据,并使用统一的查询引擎实现过滤、变换等操作,将数据推送给消费者。

本文档不对消息队列本身的基础知识做介绍,如果需要了解,请自行搜索。

主要数据结构和 API

不同语言下, TMQ 订阅相关的 API 及数据结构如下:

  • C
  • Java
  • Python
  • Go
  • Rust
  • Node.JS
  • C#
  1. typedef struct tmq_t tmq_t;
  2. typedef struct tmq_conf_t tmq_conf_t;
  3. typedef struct tmq_list_t tmq_list_t;
  4. typedef void(tmq_commit_cb(tmq_t *, int32_t code, void *param));
  5. DLL_EXPORT tmq_list_t *tmq_list_new();
  6. DLL_EXPORT int32_t tmq_list_append(tmq_list_t *, const char *);
  7. DLL_EXPORT void tmq_list_destroy(tmq_list_t *);
  8. DLL_EXPORT tmq_t *tmq_consumer_new(tmq_conf_t *conf, char *errstr, int32_t errstrLen);
  9. DLL_EXPORT const char *tmq_err2str(int32_t code);
  10. DLL_EXPORT int32_t tmq_subscribe(tmq_t *tmq, const tmq_list_t *topic_list);
  11. DLL_EXPORT int32_t tmq_unsubscribe(tmq_t *tmq);
  12. DLL_EXPORT TAOS_RES *tmq_consumer_poll(tmq_t *tmq, int64_t timeout);
  13. DLL_EXPORT int32_t tmq_consumer_close(tmq_t *tmq);
  14. DLL_EXPORT int32_t tmq_commit_sync(tmq_t *tmq, const TAOS_RES *msg);
  15. DLL_EXPORT void tmq_commit_async(tmq_t *tmq, const TAOS_RES *msg, tmq_commit_cb *cb, void *param);
  16. enum tmq_conf_res_t {
  17. TMQ_CONF_UNKNOWN = -2,
  18. TMQ_CONF_INVALID = -1,
  19. TMQ_CONF_OK = 0,
  20. };
  21. typedef enum tmq_conf_res_t tmq_conf_res_t;
  22. DLL_EXPORT tmq_conf_t *tmq_conf_new();
  23. DLL_EXPORT tmq_conf_res_t tmq_conf_set(tmq_conf_t *conf, const char *key, const char *value);
  24. DLL_EXPORT void tmq_conf_destroy(tmq_conf_t *conf);
  25. DLL_EXPORT void tmq_conf_set_auto_commit_cb(tmq_conf_t *conf, tmq_commit_cb *cb, void *param);

这些 API 的文档请见 C/C++ Connector,下面介绍一下它们的具体用法(超级表和子表结构请参考“数据建模”一节),完整的示例代码请见下面 C 语言的示例代码。

  1. void subscribe(Collection<String> topics) throws SQLException;
  2. void unsubscribe() throws SQLException;
  3. Set<String> subscription() throws SQLException;
  4. ConsumerRecords<V> poll(Duration timeout) throws SQLException;
  5. void commitAsync();
  6. void commitAsync(OffsetCommitCallback callback);
  7. void commitSync() throws SQLException;
  8. void close() throws SQLException;
  1. class Consumer:
  2. def subscribe(self, topics):
  3. pass
  4. def unsubscribe(self):
  5. pass
  6. def poll(self, timeout: float = 1.0):
  7. pass
  8. def close(self):
  9. pass
  10. def commit(self, message):
  11. pass
  1. func NewConsumer(conf *tmq.ConfigMap) (*Consumer, error)
  2. // 出于兼容目的保留 rebalanceCb 参数,当前未使用
  3. func (c *Consumer) Subscribe(topic string, rebalanceCb RebalanceCb) error
  4. // 出于兼容目的保留 rebalanceCb 参数,当前未使用
  5. func (c *Consumer) SubscribeTopics(topics []string, rebalanceCb RebalanceCb) error
  6. func (c *Consumer) Poll(timeoutMs int) tmq.Event
  7. // 出于兼容目的保留 tmq.TopicPartition 参数,当前未使用
  8. func (c *Consumer) Commit() ([]tmq.TopicPartition, error)
  9. func (c *Consumer) Unsubscribe() error
  10. func (c *Consumer) Close() error
  1. impl TBuilder for TmqBuilder
  2. fn from_dsn<D: IntoDsn>(dsn: D) -> Result<Self, Self::Error>
  3. fn build(&self) -> Result<Self::Target, Self::Error>
  4. impl AsAsyncConsumer for Consumer
  5. async fn subscribe<T: Into<String>, I: IntoIterator<Item = T> + Send>(
  6. &mut self,
  7. topics: I,
  8. ) -> Result<(), Self::Error>;
  9. fn stream(
  10. &self,
  11. ) -> Pin<
  12. Box<
  13. dyn '_
  14. + Send
  15. + futures::Stream<
  16. Item = Result<(Self::Offset, MessageSet<Self::Meta, Self::Data>), Self::Error>,
  17. >,
  18. >,
  19. >;
  20. async fn commit(&self, offset: Self::Offset) -> Result<(), Self::Error>;
  21. async fn unsubscribe(self);

可在 https://docs.rs/taos 上查看详细 API 说明。

  1. function TMQConsumer(config)
  2. function subscribe(topic)
  3. function consume(timeout)
  4. function subscription()
  5. function unsubscribe()
  6. function commit(msg)
  7. function close()
  1. ConsumerBuilder(IEnumerable<KeyValuePair<string, string>> config)
  2. virtual IConsumer Build()
  3. Consumer(ConsumerBuilder builder)
  4. void Subscribe(IEnumerable<string> topics)
  5. void Subscribe(string topic)
  6. ConsumeResult Consume(int millisecondsTimeout)
  7. List<string> Subscription()
  8. void Unsubscribe()
  9. void Commit(ConsumeResult consumerResult)
  10. void Close()

写入数据

首先完成建库、建一张超级表和多张子表操作,然后就可以写入数据了,比如:

  1. DROP DATABASE IF EXISTS tmqdb;
  2. CREATE DATABASE tmqdb;
  3. CREATE TABLE tmqdb.stb (ts TIMESTAMP, c1 INT, c2 FLOAT, c3 VARCHAR(16)) TAGS(t1 INT, t3 VARCHAR(16));
  4. CREATE TABLE tmqdb.ctb0 USING tmqdb.stb TAGS(0, "subtable0");
  5. CREATE TABLE tmqdb.ctb1 USING tmqdb.stb TAGS(1, "subtable1");
  6. INSERT INTO tmqdb.ctb0 VALUES(now, 0, 0, 'a0')(now+1s, 0, 0, 'a00');
  7. INSERT INTO tmqdb.ctb1 VALUES(now, 1, 1, 'a1')(now+1s, 11, 11, 'a11');

创建 topic

TDengine 使用 SQL 创建一个 topic:

  1. CREATE TOPIC topic_name AS SELECT ts, c1, c2, c3 FROM tmqdb.stb WHERE c1 > 1;

TMQ 支持多种订阅类型:

列订阅

语法:

  1. CREATE TOPIC topic_name as subquery

通过 SELECT 语句订阅(包括 SELECT *,或 SELECT ts, c1 等指定列订阅,可以带条件过滤、标量函数计算,但不支持聚合函数、不支持时间窗口聚合)。需要注意的是:

  • 该类型 TOPIC 一旦创建则订阅数据的结构确定。
  • 被订阅或用于计算的列或标签不可被删除(ALTER table DROP)、修改(ALTER table MODIFY)。
  • 若发生表结构变更,新增的列不出现在结果中。

超级表订阅

语法:

  1. CREATE TOPIC topic_name AS STABLE stb_name

SELECT * from stbName 订阅的区别是:

  • 不会限制用户的表结构变更。
  • 返回的是非结构化的数据:返回数据的结构会随之超级表的表结构变化而变化。
  • 用户对于要处理的每一个数据块都可能有不同的表结构。
  • 返回数据不包含标签。

数据库订阅

语法:

  1. CREATE TOPIC topic_name AS DATABASE db_name;

通过该语句可创建一个包含数据库所有表数据的订阅

创建消费者 consumer

消费者需要通过一系列配置选项创建,基础配置项如下表所示:

参数名称类型参数说明备注
td.connect.ipstring用于创建连接,同 taos_connect
td.connect.userstring用于创建连接,同 taos_connect
td.connect.passstring用于创建连接,同 taos_connect
td.connect.portinteger用于创建连接,同 taos_connect
group.idstring消费组 ID,同一消费组共享消费进度必填项。最大长度:192。
client.idstring客户端 ID最大长度:192。
auto.offset.resetenum消费组订阅的初始位置
earliest: default;从头开始订阅;
latest: 仅从最新数据开始订阅;
none: 没有提交的 offset 无法订阅
enable.auto.commitboolean是否启用消费位点自动提交合法值:true, false
auto.commit.interval.msinteger以毫秒为单位的消费记录自动提交消费位点时间间隔默认 5000 m
enable.heartbeat.backgroundboolean启用后台心跳,启用后即使长时间不 poll 消息也不会造成离线默认开启
experimental.snapshot.enableboolean是否允许从 TSDB 消费数据实验功能,默认关闭
msg.with.table.nameboolean是否允许从消息中解析表名, 不适用于列订阅(列订阅时可将 tbname 作为列写入 subquery 语句)

对于不同编程语言,其设置方式如下:

  • C
  • Java
  • Go
  • Rust
  • Python
  • Node.JS
  • C#
  1. /* 根据需要,设置消费组 (group.id)、自动提交 (enable.auto.commit)、
  2. 自动提交时间间隔 (auto.commit.interval.ms)、用户名 (td.connect.user)、密码 (td.connect.pass) 等参数 */
  3. tmq_conf_t* conf = tmq_conf_new();
  4. tmq_conf_set(conf, "enable.auto.commit", "true");
  5. tmq_conf_set(conf, "auto.commit.interval.ms", "1000");
  6. tmq_conf_set(conf, "group.id", "cgrpName");
  7. tmq_conf_set(conf, "td.connect.user", "root");
  8. tmq_conf_set(conf, "td.connect.pass", "taosdata");
  9. tmq_conf_set(conf, "auto.offset.reset", "earliest");
  10. tmq_conf_set(conf, "experimental.snapshot.enable", "true");
  11. tmq_conf_set(conf, "msg.with.table.name", "true");
  12. tmq_conf_set_auto_commit_cb(conf, tmq_commit_cb_print, NULL);
  13. tmq_t* tmq = tmq_consumer_new(conf, NULL, 0);
  14. tmq_conf_destroy(conf);

对于 Java 程序,使用如下配置项:

参数名称类型参数说明
bootstrap.serversstring连接地址,如 localhost:6030
value.deserializerstring值解析方法,使用此方法应实现 com.taosdata.jdbc.tmq.Deserializer 接口或继承 com.taosdata.jdbc.tmq.ReferenceDeserializer
value.deserializer.encodingstring指定字符串解析的字符集

需要注意:此处使用 bootstrap.servers 替代 td.connect.iptd.connect.port,以提供与 Kafka 一致的接口。

  1. Properties properties = new Properties();
  2. properties.setProperty("enable.auto.commit", "true");
  3. properties.setProperty("auto.commit.interval.ms", "1000");
  4. properties.setProperty("group.id", "cgrpName");
  5. properties.setProperty("bootstrap.servers", "127.0.0.1:6030");
  6. properties.setProperty("td.connect.user", "root");
  7. properties.setProperty("td.connect.pass", "taosdata");
  8. properties.setProperty("auto.offset.reset", "earliest");
  9. properties.setProperty("msg.with.table.name", "true");
  10. properties.setProperty("value.deserializer", "com.taos.example.MetersDeserializer");
  11. TaosConsumer<Meters> consumer = new TaosConsumer<>(properties);
  12. /* value deserializer definition. */
  13. import com.taosdata.jdbc.tmq.ReferenceDeserializer;
  14. public class MetersDeserializer extends ReferenceDeserializer<Meters> {
  15. }
  1. conf := &tmq.ConfigMap{
  2. "group.id": "test",
  3. "auto.offset.reset": "earliest",
  4. "td.connect.ip": "127.0.0.1",
  5. "td.connect.user": "root",
  6. "td.connect.pass": "taosdata",
  7. "td.connect.port": "6030",
  8. "client.id": "test_tmq_c",
  9. "enable.auto.commit": "false",
  10. "enable.heartbeat.background": "true",
  11. "experimental.snapshot.enable": "true",
  12. "msg.with.table.name": "true",
  13. }
  14. consumer, err := NewConsumer(conf)
  1. let mut dsn: Dsn = "taos://".parse()?;
  2. dsn.set("group.id", "group1");
  3. dsn.set("client.id", "test");
  4. dsn.set("auto.offset.reset", "earliest");
  5. let tmq = TmqBuilder::from_dsn(dsn)?;
  6. let mut consumer = tmq.build()?;

Python 语言下引入 taos 库的 Consumer 类,创建一个 Consumer 示例:

  1. from taos.tmq import Consumer
  2. # Syntax: `consumer = Consumer(configs)`
  3. #
  4. # Example:
  5. consumer = Consumer({"group.id": "local", "td.connect.ip": "127.0.0.1"})

其中,configs 为 dict 类型,传递创建 Consumer 的参数。可以配置的参数有:

参数名称类型参数说明备注
td.connect.ipstring用于创建连接
td.connect.userstring用于创建连接
td.connect.passstring用于创建连接
td.connect.portstring用于创建连接
group.idstring消费组 ID,同一消费组共享消费进度必填项。最大长度:192
client.idstring客户端 ID最大长度:192
msg.with.table.namestring是否允许从消息中解析表名,不适用于列订阅合法值:true, false
enable.auto.commitstring启用自动提交合法值:true, false
auto.commit.interval.msstring以毫秒为单位的自动提交时间间隔默认值:5000 ms
auto.offset.resetstring消费组订阅的初始位置可选:earliest(default), latest, none
experimental.snapshot.enablestring是否允许从 TSDB 消费数据合法值:true, false
enable.heartbeat.backgroundstring启用后台心跳,启用后即使长时间不 poll 消息也不会造成离线合法值:true, false
  1. // 根据需要,设置消费组 (group.id)、自动提交 (enable.auto.commit)、
  2. // 自动提交时间间隔 (auto.commit.interval.ms)、用户名 (td.connect.user)、密码 (td.connect.pass) 等参数
  3. let consumer = taos.consumer({
  4. 'enable.auto.commit': 'true',
  5. 'auto.commit.interval.ms','1000',
  6. 'group.id': 'tg2',
  7. 'td.connect.user': 'root',
  8. 'td.connect.pass': 'taosdata',
  9. 'auto.offset.reset','earliest',
  10. 'msg.with.table.name': 'true',
  11. 'td.connect.ip','127.0.0.1',
  12. 'td.connect.port','6030'
  13. });
  1. using TDengineTMQ;
  2. // 根据需要,设置消费组 (GourpId)、自动提交 (EnableAutoCommit)、
  3. // 自动提交时间间隔 (AutoCommitIntervalMs)、用户名 (TDConnectUser)、密码 (TDConnectPasswd) 等参数
  4. var cfg = new ConsumerConfig
  5. {
  6. EnableAutoCommit = "true"
  7. AutoCommitIntervalMs = "1000"
  8. GourpId = "TDengine-TMQ-C#",
  9. TDConnectUser = "root",
  10. TDConnectPasswd = "taosdata",
  11. AutoOffsetReset = "earliest"
  12. MsgWithTableName = "true",
  13. TDConnectIp = "127.0.0.1",
  14. TDConnectPort = "6030"
  15. };
  16. var consumer = new ConsumerBuilder(cfg).Build();

上述配置中包括 consumer group ID,如果多个 consumer 指定的 consumer group ID 一样,则自动形成一个 consumer group,共享消费进度。

订阅 topics

一个 consumer 支持同时订阅多个 topic。

  • C
  • Java
  • Go
  • Rust
  • Python
  • Node.JS
  • C#
  1. // 创建订阅 topics 列表
  2. tmq_list_t* topicList = tmq_list_new();
  3. tmq_list_append(topicList, "topicName");
  4. // 启动订阅
  5. tmq_subscribe(tmq, topicList);
  6. tmq_list_destroy(topicList);
  1. List<String> topics = new ArrayList<>();
  2. topics.add("tmq_topic");
  3. consumer.subscribe(topics);
  1. err = consumer.Subscribe("example_tmq_topic", nil)
  2. if err != nil {
  3. panic(err)
  4. }
  1. consumer.subscribe(["tmq_meters"]).await?;
  1. consumer.subscribe(['topic1', 'topic2'])
  1. // 创建订阅 topics 列表
  2. let topics = ['topic_test']
  3. // 启动订阅
  4. consumer.subscribe(topics);
  1. // 创建订阅 topics 列表
  2. List<String> topics = new List<string>();
  3. topics.add("tmq_topic");
  4. // 启动订阅
  5. consumer.Subscribe(topics);

消费

以下代码展示了不同语言下如何对 TMQ 消息进行消费。

  • C
  • Java
  • Go
  • Rust
  • Python
  • Node.JS
  • C#
  1. // 消费数据
  2. while (running) {
  3. TAOS_RES* msg = tmq_consumer_poll(tmq, timeOut);
  4. msg_process(msg);
  5. }

这里是一个 while 循环,每调用一次 tmq_consumer_poll(),获取一个消息,该消息与普通查询返回的结果集完全相同,可以使用相同的解析 API 完成消息内容的解析。

  1. while(running){
  2. ConsumerRecords<Meters> meters = consumer.poll(Duration.ofMillis(100));
  3. for (Meters meter : meters) {
  4. processMsg(meter);
  5. }
  6. }
  1. for {
  2. ev := consumer.Poll(0)
  3. if ev != nil {
  4. switch e := ev.(type) {
  5. case *tmqcommon.DataMessage:
  6. fmt.Println(e.Value())
  7. case tmqcommon.Error:
  8. fmt.Fprintf(os.Stderr, "%% Error: %v: %v\n", e.Code(), e)
  9. panic(e)
  10. }
  11. consumer.Commit()
  12. }
  13. }
  1. {
  2. let mut stream = consumer.stream();
  3. while let Some((offset, message)) = stream.try_next().await? {
  4. // get information from offset
  5. // the topic
  6. let topic = offset.topic();
  7. // the vgroup id, like partition id in kafka.
  8. let vgroup_id = offset.vgroup_id();
  9. println!("* in vgroup id {vgroup_id} of topic {topic}\n");
  10. if let Some(data) = message.into_data() {
  11. while let Some(block) = data.fetch_raw_block().await? {
  12. // one block for one table, get table name if needed
  13. let name = block.table_name();
  14. let records: Vec<Record> = block.deserialize().try_collect()?;
  15. println!(
  16. "** table: {}, got {} records: {:#?}\n",
  17. name.unwrap(),
  18. records.len(),
  19. records
  20. );
  21. }
  22. }
  23. consumer.commit(offset).await?;
  24. }
  25. }
  1. while True:
  2. res = consumer.poll(100)
  3. if not res:
  4. continue
  5. err = res.error()
  6. if err is not None:
  7. raise err
  8. val = res.value()
  9. for block in val:
  10. print(block.fetchall())
  1. while(true){
  2. msg = consumer.consume(200);
  3. // process message(consumeResult)
  4. console.log(msg.topicPartition);
  5. console.log(msg.block);
  6. console.log(msg.fields)
  7. }
  1. // 消费数据
  2. while (true)
  3. {
  4. var consumerRes = consumer.Consume(100);
  5. // process ConsumeResult
  6. ProcessMsg(consumerRes);
  7. consumer.Commit(consumerRes);
  8. }

结束消费

消费结束后,应当取消订阅。

  • C
  • Java
  • Go
  • Rust
  • Python
  • Node.JS
  • C#
  1. /* 取消订阅 */
  2. tmq_unsubscribe(tmq);
  3. /* 关闭消费者对象 */
  4. tmq_consumer_close(tmq);
  1. /* 取消订阅 */
  2. consumer.unsubscribe();
  3. /* 关闭消费 */
  4. consumer.close();
  1. /* Unsubscribe */
  2. _ = consumer.Unsubscribe()
  3. /* Close consumer */
  4. _ = consumer.Close()
  1. consumer.unsubscribe().await;
  1. # 取消订阅
  2. consumer.unsubscribe()
  3. # 关闭消费
  4. consumer.close()
  1. consumer.unsubscribe();
  2. consumer.close();
  1. // 取消订阅
  2. consumer.Unsubscribe();
  3. // 关闭消费
  4. consumer.Close();

删除 topic

如果不再需要订阅数据,可以删除 topic,需要注意:只有当前未在订阅中的 TOPIC 才能被删除。

  1. /* 删除 topic */
  2. DROP TOPIC topic_name;

状态查看

1、topics:查询已经创建的 topic

  1. SHOW TOPICS;

2、consumers:查询 consumer 的状态及其订阅的 topic

  1. SHOW CONSUMERS;

3、subscriptions:查询 consumer 与 vgroup 之间的分配关系

  1. SHOW SUBSCRIPTIONS;

示例代码

以下是各语言的完整示例代码。

  • C
  • Java
  • Go
  • Rust
  • Python
  • Node.JS
  • C#
  1. /*
  2. * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
  3. *
  4. * This program is free software: you can use, redistribute, and/or modify
  5. * it under the terms of the GNU Affero General Public License, version 3
  6. * or later ("AGPL"), as published by the Free Software Foundation.
  7. *
  8. * This program is distributed in the hope that it will be useful, but WITHOUT
  9. * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  10. * FITNESS FOR A PARTICULAR PURPOSE.
  11. *
  12. * You should have received a copy of the GNU Affero General Public License
  13. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. #include <assert.h>
  16. #include <stdio.h>
  17. #include <stdlib.h>
  18. #include <string.h>
  19. #include <time.h>
  20. #include "taos.h"
  21. static int running = 1;
  22. static char dbName[64] = "tmqdb";
  23. static char stbName[64] = "stb";
  24. static char topicName[64] = "topicname";
  25. static int32_t msg_process(TAOS_RES* msg) {
  26. char buf[1024];
  27. int32_t rows = 0;
  28. const char* topicName = tmq_get_topic_name(msg);
  29. const char* dbName = tmq_get_db_name(msg);
  30. int32_t vgroupId = tmq_get_vgroup_id(msg);
  31. printf("topic: %s\n", topicName);
  32. printf("db: %s\n", dbName);
  33. printf("vgroup id: %d\n", vgroupId);
  34. while (1) {
  35. TAOS_ROW row = taos_fetch_row(msg);
  36. if (row == NULL) break;
  37. TAOS_FIELD* fields = taos_fetch_fields(msg);
  38. int32_t numOfFields = taos_field_count(msg);
  39. int32_t* length = taos_fetch_lengths(msg);
  40. int32_t precision = taos_result_precision(msg);
  41. rows++;
  42. taos_print_row(buf, row, fields, numOfFields);
  43. printf("row content: %s\n", buf);
  44. }
  45. return rows;
  46. }
  47. static int32_t init_env() {
  48. TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0);
  49. if (pConn == NULL) {
  50. return -1;
  51. }
  52. TAOS_RES* pRes;
  53. // drop database if exists
  54. printf("create database\n");
  55. pRes = taos_query(pConn, "drop database if exists tmqdb");
  56. if (taos_errno(pRes) != 0) {
  57. printf("error in drop tmqdb, reason:%s\n", taos_errstr(pRes));
  58. return -1;
  59. }
  60. taos_free_result(pRes);
  61. // create database
  62. pRes = taos_query(pConn, "create database tmqdb");
  63. if (taos_errno(pRes) != 0) {
  64. printf("error in create tmqdb, reason:%s\n", taos_errstr(pRes));
  65. return -1;
  66. }
  67. taos_free_result(pRes);
  68. // create super table
  69. printf("create super table\n");
  70. pRes = taos_query(
  71. pConn, "create table tmqdb.stb (ts timestamp, c1 int, c2 float, c3 varchar(16)) tags(t1 int, t3 varchar(16))");
  72. if (taos_errno(pRes) != 0) {
  73. printf("failed to create super table stb, reason:%s\n", taos_errstr(pRes));
  74. return -1;
  75. }
  76. taos_free_result(pRes);
  77. // create sub tables
  78. printf("create sub tables\n");
  79. pRes = taos_query(pConn, "create table tmqdb.ctb0 using tmqdb.stb tags(0, 'subtable0')");
  80. if (taos_errno(pRes) != 0) {
  81. printf("failed to create super table ctb0, reason:%s\n", taos_errstr(pRes));
  82. return -1;
  83. }
  84. taos_free_result(pRes);
  85. pRes = taos_query(pConn, "create table tmqdb.ctb1 using tmqdb.stb tags(1, 'subtable1')");
  86. if (taos_errno(pRes) != 0) {
  87. printf("failed to create super table ctb1, reason:%s\n", taos_errstr(pRes));
  88. return -1;
  89. }
  90. taos_free_result(pRes);
  91. pRes = taos_query(pConn, "create table tmqdb.ctb2 using tmqdb.stb tags(2, 'subtable2')");
  92. if (taos_errno(pRes) != 0) {
  93. printf("failed to create super table ctb2, reason:%s\n", taos_errstr(pRes));
  94. return -1;
  95. }
  96. taos_free_result(pRes);
  97. pRes = taos_query(pConn, "create table tmqdb.ctb3 using tmqdb.stb tags(3, 'subtable3')");
  98. if (taos_errno(pRes) != 0) {
  99. printf("failed to create super table ctb3, reason:%s\n", taos_errstr(pRes));
  100. return -1;
  101. }
  102. taos_free_result(pRes);
  103. // insert data
  104. printf("insert data into sub tables\n");
  105. pRes = taos_query(pConn, "insert into tmqdb.ctb0 values(now, 0, 0, 'a0')(now+1s, 0, 0, 'a00')");
  106. if (taos_errno(pRes) != 0) {
  107. printf("failed to insert into ctb0, reason:%s\n", taos_errstr(pRes));
  108. return -1;
  109. }
  110. taos_free_result(pRes);
  111. pRes = taos_query(pConn, "insert into tmqdb.ctb1 values(now, 1, 1, 'a1')(now+1s, 11, 11, 'a11')");
  112. if (taos_errno(pRes) != 0) {
  113. printf("failed to insert into ctb0, reason:%s\n", taos_errstr(pRes));
  114. return -1;
  115. }
  116. taos_free_result(pRes);
  117. pRes = taos_query(pConn, "insert into tmqdb.ctb2 values(now, 2, 2, 'a1')(now+1s, 22, 22, 'a22')");
  118. if (taos_errno(pRes) != 0) {
  119. printf("failed to insert into ctb0, reason:%s\n", taos_errstr(pRes));
  120. return -1;
  121. }
  122. taos_free_result(pRes);
  123. pRes = taos_query(pConn, "insert into tmqdb.ctb3 values(now, 3, 3, 'a1')(now+1s, 33, 33, 'a33')");
  124. if (taos_errno(pRes) != 0) {
  125. printf("failed to insert into ctb0, reason:%s\n", taos_errstr(pRes));
  126. return -1;
  127. }
  128. taos_free_result(pRes);
  129. taos_close(pConn);
  130. return 0;
  131. }
  132. int32_t create_topic() {
  133. printf("create topic\n");
  134. TAOS_RES* pRes;
  135. TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0);
  136. if (pConn == NULL) {
  137. return -1;
  138. }
  139. pRes = taos_query(pConn, "use tmqdb");
  140. if (taos_errno(pRes) != 0) {
  141. printf("error in use tmqdb, reason:%s\n", taos_errstr(pRes));
  142. return -1;
  143. }
  144. taos_free_result(pRes);
  145. pRes = taos_query(pConn, "create topic topicname as select ts, c1, c2, c3, tbname from tmqdb.stb where c1 > 1");
  146. if (taos_errno(pRes) != 0) {
  147. printf("failed to create topic topicname, reason:%s\n", taos_errstr(pRes));
  148. return -1;
  149. }
  150. taos_free_result(pRes);
  151. taos_close(pConn);
  152. return 0;
  153. }
  154. void tmq_commit_cb_print(tmq_t* tmq, int32_t code, void* param) {
  155. printf("tmq_commit_cb_print() code: %d, tmq: %p, param: %p\n", code, tmq, param);
  156. }
  157. tmq_t* build_consumer() {
  158. tmq_conf_res_t code;
  159. tmq_conf_t* conf = tmq_conf_new();
  160. code = tmq_conf_set(conf, "enable.auto.commit", "true");
  161. if (TMQ_CONF_OK != code) {
  162. tmq_conf_destroy(conf);
  163. return NULL;
  164. }
  165. code = tmq_conf_set(conf, "auto.commit.interval.ms", "1000");
  166. if (TMQ_CONF_OK != code) {
  167. tmq_conf_destroy(conf);
  168. return NULL;
  169. }
  170. code = tmq_conf_set(conf, "group.id", "cgrpName");
  171. if (TMQ_CONF_OK != code) {
  172. tmq_conf_destroy(conf);
  173. return NULL;
  174. }
  175. code = tmq_conf_set(conf, "client.id", "user defined name");
  176. if (TMQ_CONF_OK != code) {
  177. tmq_conf_destroy(conf);
  178. return NULL;
  179. }
  180. code = tmq_conf_set(conf, "td.connect.user", "root");
  181. if (TMQ_CONF_OK != code) {
  182. tmq_conf_destroy(conf);
  183. return NULL;
  184. }
  185. code = tmq_conf_set(conf, "td.connect.pass", "taosdata");
  186. if (TMQ_CONF_OK != code) {
  187. tmq_conf_destroy(conf);
  188. return NULL;
  189. }
  190. code = tmq_conf_set(conf, "auto.offset.reset", "earliest");
  191. if (TMQ_CONF_OK != code) {
  192. tmq_conf_destroy(conf);
  193. return NULL;
  194. }
  195. code = tmq_conf_set(conf, "experimental.snapshot.enable", "false");
  196. if (TMQ_CONF_OK != code) {
  197. tmq_conf_destroy(conf);
  198. return NULL;
  199. }
  200. tmq_conf_set_auto_commit_cb(conf, tmq_commit_cb_print, NULL);
  201. tmq_t* tmq = tmq_consumer_new(conf, NULL, 0);
  202. tmq_conf_destroy(conf);
  203. return tmq;
  204. }
  205. tmq_list_t* build_topic_list() {
  206. tmq_list_t* topicList = tmq_list_new();
  207. int32_t code = tmq_list_append(topicList, "topicname");
  208. if (code) {
  209. return NULL;
  210. }
  211. return topicList;
  212. }
  213. void basic_consume_loop(tmq_t* tmq) {
  214. int32_t totalRows = 0;
  215. int32_t msgCnt = 0;
  216. int32_t timeout = 5000;
  217. while (running) {
  218. TAOS_RES* tmqmsg = tmq_consumer_poll(tmq, timeout);
  219. if (tmqmsg) {
  220. msgCnt++;
  221. totalRows += msg_process(tmqmsg);
  222. taos_free_result(tmqmsg);
  223. } else {
  224. break;
  225. }
  226. }
  227. fprintf(stderr, "%d msg consumed, include %d rows\n", msgCnt, totalRows);
  228. }
  229. int main(int argc, char* argv[]) {
  230. int32_t code;
  231. if (init_env() < 0) {
  232. return -1;
  233. }
  234. if (create_topic() < 0) {
  235. return -1;
  236. }
  237. tmq_t* tmq = build_consumer();
  238. if (NULL == tmq) {
  239. fprintf(stderr, "%% build_consumer() fail!\n");
  240. return -1;
  241. }
  242. tmq_list_t* topic_list = build_topic_list();
  243. if (NULL == topic_list) {
  244. return -1;
  245. }
  246. if ((code = tmq_subscribe(tmq, topic_list))) {
  247. fprintf(stderr, "%% Failed to tmq_subscribe(): %s\n", tmq_err2str(code));
  248. }
  249. tmq_list_destroy(topic_list);
  250. basic_consume_loop(tmq);
  251. code = tmq_consumer_close(tmq);
  252. if (code) {
  253. fprintf(stderr, "%% Failed to close consumer: %s\n", tmq_err2str(code));
  254. } else {
  255. fprintf(stderr, "%% Consumer closed\n");
  256. }
  257. return 0;
  258. }

查看源码

  • 本地连接
  • WebSocket 连接
  1. package com.taos.example;
  2. import com.taosdata.jdbc.tmq.ConsumerRecords;
  3. import com.taosdata.jdbc.tmq.TMQConstants;
  4. import com.taosdata.jdbc.tmq.TaosConsumer;
  5. import java.sql.Connection;
  6. import java.sql.DriverManager;
  7. import java.sql.SQLException;
  8. import java.sql.Statement;
  9. import java.time.Duration;
  10. import java.util.Collections;
  11. import java.util.Properties;
  12. import java.util.Timer;
  13. import java.util.TimerTask;
  14. import java.util.concurrent.atomic.AtomicBoolean;
  15. public class SubscribeDemo {
  16. private static final String TOPIC = "tmq_topic";
  17. private static final String DB_NAME = "meters";
  18. private static final AtomicBoolean shutdown = new AtomicBoolean(false);
  19. public static void main(String[] args) {
  20. Timer timer = new Timer();
  21. timer.schedule(new TimerTask() {
  22. public void run() {
  23. shutdown.set(true);
  24. }
  25. }, 3_000);
  26. try {
  27. // prepare
  28. Class.forName("com.taosdata.jdbc.TSDBDriver");
  29. String jdbcUrl = "jdbc:TAOS://127.0.0.1:6030/?user=root&password=taosdata";
  30. Connection connection = DriverManager.getConnection(jdbcUrl);
  31. try (Statement statement = connection.createStatement()) {
  32. statement.executeUpdate("drop topic if exists " + TOPIC);
  33. statement.executeUpdate("drop database if exists " + DB_NAME);
  34. statement.executeUpdate("create database " + DB_NAME);
  35. statement.executeUpdate("use " + DB_NAME);
  36. statement.executeUpdate(
  37. "CREATE TABLE `meters` (`ts` TIMESTAMP, `current` FLOAT, `voltage` INT) TAGS (`groupid` INT, `location` BINARY(24))");
  38. statement.executeUpdate("CREATE TABLE `d0` USING `meters` TAGS(0, 'California.LosAngles')");
  39. statement.executeUpdate("INSERT INTO `d0` values(now - 10s, 0.32, 116)");
  40. statement.executeUpdate("INSERT INTO `d0` values(now - 8s, NULL, NULL)");
  41. statement.executeUpdate(
  42. "INSERT INTO `d1` USING `meters` TAGS(1, 'California.SanFrancisco') values(now - 9s, 10.1, 119)");
  43. statement.executeUpdate(
  44. "INSERT INTO `d1` values (now-8s, 10, 120) (now - 6s, 10, 119) (now - 4s, 11.2, 118)");
  45. // create topic
  46. statement.executeUpdate("create topic " + TOPIC + " as select * from meters");
  47. }
  48. // create consumer
  49. Properties properties = new Properties();
  50. properties.setProperty(TMQConstants.BOOTSTRAP_SERVERS, "127.0.0.1:6030");
  51. properties.setProperty(TMQConstants.MSG_WITH_TABLE_NAME, "true");
  52. properties.setProperty(TMQConstants.ENABLE_AUTO_COMMIT, "true");
  53. properties.setProperty(TMQConstants.GROUP_ID, "test");
  54. properties.setProperty(TMQConstants.VALUE_DESERIALIZER,
  55. "com.taos.example.MetersDeserializer");
  56. // poll data
  57. try (TaosConsumer<Meters> consumer = new TaosConsumer<>(properties)) {
  58. consumer.subscribe(Collections.singletonList(TOPIC));
  59. while (!shutdown.get()) {
  60. ConsumerRecords<Meters> meters = consumer.poll(Duration.ofMillis(100));
  61. for (Meters meter : meters) {
  62. System.out.println(meter);
  63. }
  64. }
  65. consumer.unsubscribe();
  66. }
  67. } catch (ClassNotFoundException | SQLException e) {
  68. e.printStackTrace();
  69. }
  70. timer.cancel();
  71. }
  72. }

查看源码

  1. package com.taos.example;
  2. import com.taosdata.jdbc.tmq.ReferenceDeserializer;
  3. public class MetersDeserializer extends ReferenceDeserializer<Meters> {
  4. }

查看源码

  1. package com.taos.example;
  2. import java.sql.Timestamp;
  3. public class Meters {
  4. private Timestamp ts;
  5. private float current;
  6. private int voltage;
  7. private int groupid;
  8. private String location;
  9. public Timestamp getTs() {
  10. return ts;
  11. }
  12. public void setTs(Timestamp ts) {
  13. this.ts = ts;
  14. }
  15. public float getCurrent() {
  16. return current;
  17. }
  18. public void setCurrent(float current) {
  19. this.current = current;
  20. }
  21. public int getVoltage() {
  22. return voltage;
  23. }
  24. public void setVoltage(int voltage) {
  25. this.voltage = voltage;
  26. }
  27. public int getGroupid() {
  28. return groupid;
  29. }
  30. public void setGroupid(int groupid) {
  31. this.groupid = groupid;
  32. }
  33. public String getLocation() {
  34. return location;
  35. }
  36. public void setLocation(String location) {
  37. this.location = location;
  38. }
  39. @Override
  40. public String toString() {
  41. return "Meters{" +
  42. "ts=" + ts +
  43. ", current=" + current +
  44. ", voltage=" + voltage +
  45. ", groupid=" + groupid +
  46. ", location='" + location + '\'' +
  47. '}';
  48. }
  49. }
  50. [查看源码](https://github.com/taosdata/TDengine/blob/3.0/docs/examples/java/src/main/java/com/taos/example/Meters.java)
  1. package com.taos.example;
  2. import com.taosdata.jdbc.tmq.ConsumerRecords;
  3. import com.taosdata.jdbc.tmq.TMQConstants;
  4. import com.taosdata.jdbc.tmq.TaosConsumer;
  5. import java.sql.Connection;
  6. import java.sql.DriverManager;
  7. import java.sql.SQLException;
  8. import java.sql.Statement;
  9. import java.time.Duration;
  10. import java.util.Collections;
  11. import java.util.Properties;
  12. import java.util.Timer;
  13. import java.util.TimerTask;
  14. import java.util.concurrent.atomic.AtomicBoolean;
  15. public class WebsocketSubscribeDemo {
  16. private static final String TOPIC = "tmq_topic_ws";
  17. private static final String DB_NAME = "meters_ws";
  18. private static final AtomicBoolean shutdown = new AtomicBoolean(false);
  19. public static void main(String[] args) {
  20. Timer timer = new Timer();
  21. timer.schedule(new TimerTask() {
  22. public void run() {
  23. shutdown.set(true);
  24. }
  25. }, 3_000);
  26. try {
  27. // prepare
  28. Class.forName("com.taosdata.jdbc.rs.RestfulDriver");
  29. String jdbcUrl = "jdbc:TAOS-RS://127.0.0.1:6041/?user=root&password=taosdata&batchfetch=true";
  30. try (Connection connection = DriverManager.getConnection(jdbcUrl);
  31. Statement statement = connection.createStatement()) {
  32. statement.executeUpdate("drop topic if exists " + TOPIC);
  33. statement.executeUpdate("drop database if exists " + DB_NAME);
  34. statement.executeUpdate("create database " + DB_NAME);
  35. statement.executeUpdate("use " + DB_NAME);
  36. statement.executeUpdate(
  37. "CREATE TABLE `meters` (`ts` TIMESTAMP, `current` FLOAT, `voltage` INT) TAGS (`groupid` INT, `location` BINARY(24))");
  38. statement.executeUpdate("CREATE TABLE `d0` USING `meters` TAGS(0, 'California.LosAngles')");
  39. statement.executeUpdate("INSERT INTO `d0` values(now - 10s, 0.32, 116)");
  40. statement.executeUpdate("INSERT INTO `d0` values(now - 8s, NULL, NULL)");
  41. statement.executeUpdate(
  42. "INSERT INTO `d1` USING `meters` TAGS(1, 'California.SanFrancisco') values(now - 9s, 10.1, 119)");
  43. statement.executeUpdate(
  44. "INSERT INTO `d1` values (now-8s, 10, 120) (now - 6s, 10, 119) (now - 4s, 11.2, 118)");
  45. // create topic
  46. statement.executeUpdate("create topic " + TOPIC + " as select * from meters");
  47. }
  48. // create consumer
  49. Properties properties = new Properties();
  50. properties.setProperty(TMQConstants.BOOTSTRAP_SERVERS, "127.0.0.1:6041");
  51. properties.setProperty(TMQConstants.CONNECT_TYPE, "ws");
  52. properties.setProperty(TMQConstants.MSG_WITH_TABLE_NAME, "true");
  53. properties.setProperty(TMQConstants.ENABLE_AUTO_COMMIT, "true");
  54. properties.setProperty(TMQConstants.GROUP_ID, "test");
  55. properties.setProperty(TMQConstants.VALUE_DESERIALIZER,
  56. "com.taos.example.MetersDeserializer");
  57. // poll data
  58. try (TaosConsumer<Meters> consumer = new TaosConsumer<>(properties)) {
  59. consumer.subscribe(Collections.singletonList(TOPIC));
  60. while (!shutdown.get()) {
  61. ConsumerRecords<Meters> meters = consumer.poll(Duration.ofMillis(100));
  62. for (Meters meter : meters) {
  63. System.out.println(meter);
  64. }
  65. }
  66. consumer.unsubscribe();
  67. }
  68. } catch (ClassNotFoundException | SQLException e) {
  69. e.printStackTrace();
  70. }
  71. timer.cancel();
  72. }
  73. }

查看源码

  1. package com.taos.example;
  2. import com.taosdata.jdbc.tmq.ReferenceDeserializer;
  3. public class MetersDeserializer extends ReferenceDeserializer<Meters> {
  4. }

查看源码

  1. package com.taos.example;
  2. import java.sql.Timestamp;
  3. public class Meters {
  4. private Timestamp ts;
  5. private float current;
  6. private int voltage;
  7. private int groupid;
  8. private String location;
  9. public Timestamp getTs() {
  10. return ts;
  11. }
  12. public void setTs(Timestamp ts) {
  13. this.ts = ts;
  14. }
  15. public float getCurrent() {
  16. return current;
  17. }
  18. public void setCurrent(float current) {
  19. this.current = current;
  20. }
  21. public int getVoltage() {
  22. return voltage;
  23. }
  24. public void setVoltage(int voltage) {
  25. this.voltage = voltage;
  26. }
  27. public int getGroupid() {
  28. return groupid;
  29. }
  30. public void setGroupid(int groupid) {
  31. this.groupid = groupid;
  32. }
  33. public String getLocation() {
  34. return location;
  35. }
  36. public void setLocation(String location) {
  37. this.location = location;
  38. }
  39. @Override
  40. public String toString() {
  41. return "Meters{" +
  42. "ts=" + ts +
  43. ", current=" + current +
  44. ", voltage=" + voltage +
  45. ", groupid=" + groupid +
  46. ", location='" + location + '\'' +
  47. '}';
  48. }
  49. }

查看源码

  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "github.com/taosdata/driver-go/v3/af"
  6. "github.com/taosdata/driver-go/v3/af/tmq"
  7. tmqcommon "github.com/taosdata/driver-go/v3/common/tmq"
  8. )
  9. func main() {
  10. db, err := af.Open("", "root", "taosdata", "", 0)
  11. if err != nil {
  12. panic(err)
  13. }
  14. defer db.Close()
  15. _, err = db.Exec("create database if not exists example_tmq")
  16. if err != nil {
  17. panic(err)
  18. }
  19. _, err = db.Exec("create topic if not exists example_tmq_topic as DATABASE example_tmq")
  20. if err != nil {
  21. panic(err)
  22. }
  23. if err != nil {
  24. panic(err)
  25. }
  26. consumer, err := tmq.NewConsumer(&tmqcommon.ConfigMap{
  27. "group.id": "test",
  28. "auto.offset.reset": "earliest",
  29. "td.connect.ip": "127.0.0.1",
  30. "td.connect.user": "root",
  31. "td.connect.pass": "taosdata",
  32. "td.connect.port": "6030",
  33. "client.id": "test_tmq_client",
  34. "enable.auto.commit": "false",
  35. "enable.heartbeat.background": "true",
  36. "experimental.snapshot.enable": "true",
  37. "msg.with.table.name": "true",
  38. })
  39. if err != nil {
  40. panic(err)
  41. }
  42. err = consumer.Subscribe("example_tmq_topic", nil)
  43. if err != nil {
  44. panic(err)
  45. }
  46. _, err = db.Exec("create table example_tmq.t1 (ts timestamp,v int)")
  47. if err != nil {
  48. panic(err)
  49. }
  50. _, err = db.Exec("insert into example_tmq.t1 values(now,1)")
  51. if err != nil {
  52. panic(err)
  53. }
  54. for i := 0; i < 5; i++ {
  55. ev := consumer.Poll(0)
  56. if ev != nil {
  57. switch e := ev.(type) {
  58. case *tmqcommon.DataMessage:
  59. fmt.Println(e.String())
  60. case tmqcommon.Error:
  61. fmt.Fprintf(os.Stderr, "%% Error: %v: %v\n", e.Code(), e)
  62. panic(e)
  63. }
  64. consumer.Commit()
  65. }
  66. }
  67. err = consumer.Unsubscribe()
  68. if err != nil {
  69. panic(err)
  70. }
  71. err = consumer.Close()
  72. if err != nil {
  73. panic(err)
  74. }
  75. }

查看源码

  1. use std::time::Duration;
  2. use chrono::{DateTime, Local};
  3. use taos::*;
  4. // Query options 2, use deserialization with serde.
  5. #[derive(Debug, serde::Deserialize)]
  6. #[allow(dead_code)]
  7. struct Record {
  8. // deserialize timestamp to chrono::DateTime<Local>
  9. ts: DateTime<Local>,
  10. // float to f32
  11. current: Option<f32>,
  12. // int to i32
  13. voltage: Option<i32>,
  14. phase: Option<f32>,
  15. }
  16. async fn prepare(taos: Taos) -> anyhow::Result<()> {
  17. let inserted = taos.exec_many([
  18. // create child table
  19. "CREATE TABLE `d0` USING `meters` TAGS(0, 'California.LosAngles')",
  20. // insert into child table
  21. "INSERT INTO `d0` values(now - 10s, 10, 116, 0.32)",
  22. // insert with NULL values
  23. "INSERT INTO `d0` values(now - 8s, NULL, NULL, NULL)",
  24. // insert and automatically create table with tags if not exists
  25. "INSERT INTO `d1` USING `meters` TAGS(1, 'California.SanFrancisco') values(now - 9s, 10.1, 119, 0.33)",
  26. // insert many records in a single sql
  27. "INSERT INTO `d1` values (now-8s, 10, 120, 0.33) (now - 6s, 10, 119, 0.34) (now - 4s, 11.2, 118, 0.322)",
  28. ]).await?;
  29. assert_eq!(inserted, 6);
  30. Ok(())
  31. }
  32. #[tokio::main]
  33. async fn main() -> anyhow::Result<()> {
  34. let dsn = "taos://localhost:6030";
  35. let builder = TaosBuilder::from_dsn(dsn)?;
  36. let taos = builder.build()?;
  37. let db = "tmq";
  38. // prepare database
  39. taos.exec_many([
  40. format!("DROP TOPIC IF EXISTS tmq_meters"),
  41. format!("DROP DATABASE IF EXISTS `{db}`"),
  42. format!("CREATE DATABASE `{db}`"),
  43. format!("USE `{db}`"),
  44. // create super table
  45. format!("CREATE TABLE `meters` (`ts` TIMESTAMP, `current` FLOAT, `voltage` INT, `phase` FLOAT) TAGS (`groupid` INT, `location` BINARY(24))"),
  46. // create topic for subscription
  47. format!("CREATE TOPIC tmq_meters AS SELECT * FROM `meters`")
  48. ])
  49. .await?;
  50. let task = tokio::spawn(prepare(taos));
  51. tokio::time::sleep(Duration::from_secs(1)).await;
  52. // subscribe
  53. let tmq = TmqBuilder::from_dsn("taos://localhost:6030/?group.id=test")?;
  54. let mut consumer = tmq.build()?;
  55. consumer.subscribe(["tmq_meters"]).await?;
  56. consumer
  57. .stream()
  58. .try_for_each(|(offset, message)| async {
  59. let topic = offset.topic();
  60. // the vgroup id, like partition id in kafka.
  61. let vgroup_id = offset.vgroup_id();
  62. println!("* in vgroup id {vgroup_id} of topic {topic}\n");
  63. if let Some(data) = message.into_data() {
  64. while let Some(block) = data.fetch_raw_block().await? {
  65. let records: Vec<Record> = block.deserialize().try_collect()?;
  66. println!("** read {} records: {:#?}\n", records.len(), records);
  67. }
  68. }
  69. consumer.commit(offset).await?;
  70. Ok(())
  71. })
  72. .await?;
  73. consumer.unsubscribe().await;
  74. task.await??;
  75. Ok(())
  76. }

查看源码

  1. from taos.tmq import Consumer
  2. import taos
  3. def init_tmq_env(db, topic):
  4. conn = taos.connect()
  5. conn.execute("drop topic if exists {}".format(topic))
  6. conn.execute("drop database if exists {}".format(db))
  7. conn.execute("create database if not exists {}".format(db))
  8. conn.select_db(db)
  9. conn.execute(
  10. "create stable if not exists stb1 (ts timestamp, c1 int, c2 float, c3 varchar(16)) tags(t1 int, t3 varchar(16))")
  11. conn.execute("create table if not exists tb1 using stb1 tags(1, 't1')")
  12. conn.execute("create table if not exists tb2 using stb1 tags(2, 't2')")
  13. conn.execute("create table if not exists tb3 using stb1 tags(3, 't3')")
  14. conn.execute("create topic if not exists {} as select ts, c1, c2, c3 from stb1".format(topic))
  15. conn.execute("insert into tb1 values (now, 1, 1.0, 'tmq test')")
  16. conn.execute("insert into tb2 values (now, 2, 2.0, 'tmq test')")
  17. conn.execute("insert into tb3 values (now, 3, 3.0, 'tmq test')")
  18. def cleanup(db, topic):
  19. conn = taos.connect()
  20. conn.execute("drop topic if exists {}".format(topic))
  21. conn.execute("drop database if exists {}".format(db))
  22. if __name__ == '__main__':
  23. init_tmq_env("tmq_test", "tmq_test_topic") # init env
  24. consumer = Consumer(
  25. {
  26. "group.id": "tg2",
  27. "td.connect.user": "root",
  28. "td.connect.pass": "taosdata",
  29. "enable.auto.commit": "true",
  30. }
  31. )
  32. consumer.subscribe(["tmq_test_topic"])
  33. try:
  34. while True:
  35. res = consumer.poll(1)
  36. if not res:
  37. break
  38. err = res.error()
  39. if err is not None:
  40. raise err
  41. val = res.value()
  42. for block in val:
  43. print(block.fetchall())
  44. finally:
  45. consumer.unsubscribe()
  46. consumer.close()
  47. cleanup("tmq_test", "tmq_test_topic")

查看源码

  1. const taos = require("@tdengine/client");
  2. const conn = taos.connect({ host: "localhost", database: "power" });
  3. var cursor = conn.cursor();
  4. function runConsumer() {
  5. // create topic
  6. cursor.execute("create topic topic_name_example as select * from meters");
  7. let consumer = taos.consumer({
  8. 'group.id': 'tg2',
  9. 'td.connect.user': 'root',
  10. 'td.connect.pass': 'taosdata',
  11. 'msg.with.table.name': 'true',
  12. 'enable.auto.commit': 'true'
  13. });
  14. // subscribe the topic just created.
  15. consumer.subscribe("topic_name_example");
  16. // get subscribe topic list
  17. let topicList = consumer.subscription();
  18. console.log(topicList);
  19. for (let i = 0; i < 5; i++) {
  20. let msg = consumer.consume(100);
  21. console.log(msg.topicPartition);
  22. console.log(msg.block);
  23. console.log(msg.fields)
  24. consumer.commit(msg);
  25. console.log(`=======consumer ${i} done`)
  26. }
  27. consumer.unsubscribe();
  28. consumer.close();
  29. // drop topic
  30. cursor.execute("drop topic topic_name_example");
  31. }
  32. try {
  33. runConsumer();
  34. } finally {
  35. setTimeout(() => {
  36. cursor.close();
  37. conn.close();
  38. }, 2000);
  39. }

查看源码

  1. using System;
  2. using TDengineTMQ;
  3. using TDengineDriver;
  4. using System.Runtime.InteropServices;
  5. namespace TMQExample
  6. {
  7. internal class SubscribeDemo
  8. {
  9. static void Main(string[] args)
  10. {
  11. IntPtr conn = GetConnection();
  12. string topic = "topic_example";
  13. //create topic
  14. IntPtr res = TDengine.Query(conn, $"create topic if not exists {topic} as select * from meters");
  15. if (TDengine.ErrorNo(res) != 0 )
  16. {
  17. throw new Exception($"create topic failed, reason:{TDengine.Error(res)}");
  18. }
  19. var cfg = new ConsumerConfig
  20. {
  21. GourpId = "group_1",
  22. TDConnectUser = "root",
  23. TDConnectPasswd = "taosdata",
  24. MsgWithTableName = "true",
  25. TDConnectIp = "127.0.0.1",
  26. };
  27. // create consumer
  28. var consumer = new ConsumerBuilder(cfg)
  29. .Build();
  30. // subscribe
  31. consumer.Subscribe(topic);
  32. // consume
  33. for (int i = 0; i < 5; i++)
  34. {
  35. var consumeRes = consumer.Consume(300);
  36. // print consumeResult
  37. foreach (KeyValuePair<TopicPartition, TaosResult> kv in consumeRes.Message)
  38. {
  39. Console.WriteLine("topic partitions:\n{0}", kv.Key.ToString());
  40. kv.Value.Metas.ForEach(meta =>
  41. {
  42. Console.Write("{0} {1}({2}) \t|", meta.name, meta.TypeName(), meta.size);
  43. });
  44. Console.WriteLine("");
  45. kv.Value.Datas.ForEach(data =>
  46. {
  47. Console.WriteLine(data.ToString());
  48. });
  49. }
  50. consumer.Commit(consumeRes);
  51. Console.WriteLine("\n================ {0} done ", i);
  52. }
  53. // retrieve topic list
  54. List<string> topics = consumer.Subscription();
  55. topics.ForEach(t => Console.WriteLine("topic name:{0}", t));
  56. // unsubscribe
  57. consumer.Unsubscribe();
  58. // close consumer after use.Otherwise will lead memory leak.
  59. consumer.Close();
  60. TDengine.Close(conn);
  61. }
  62. static IntPtr GetConnection()
  63. {
  64. string host = "localhost";
  65. short port = 6030;
  66. string username = "root";
  67. string password = "taosdata";
  68. string dbname = "power";
  69. var conn = TDengine.Connect(host, username, password, dbname, port);
  70. if (conn == IntPtr.Zero)
  71. {
  72. throw new Exception("Connect to TDengine failed");
  73. }
  74. else
  75. {
  76. Console.WriteLine("Connect to TDengine success");
  77. }
  78. return conn;
  79. }
  80. }
  81. }

查看源码