07-tmq.mdx 31.7 KB
Newer Older
H
Huo Linhe 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
---
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 的数据,或者以事件到达顺序处理数据,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 及数据结构如下:

<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">

```c
typedef struct tmq_t      tmq_t;
typedef struct tmq_conf_t tmq_conf_t;
typedef struct tmq_list_t tmq_list_t;

typedef void(tmq_commit_cb(tmq_t *, int32_t code, void *param));

DLL_EXPORT tmq_list_t *tmq_list_new();
DLL_EXPORT int32_t     tmq_list_append(tmq_list_t *, const char *);
DLL_EXPORT void        tmq_list_destroy(tmq_list_t *);
DLL_EXPORT tmq_t *tmq_consumer_new(tmq_conf_t *conf, char *errstr, int32_t errstrLen);
DLL_EXPORT const char *tmq_err2str(int32_t code);

DLL_EXPORT int32_t   tmq_subscribe(tmq_t *tmq, const tmq_list_t *topic_list);
DLL_EXPORT int32_t   tmq_unsubscribe(tmq_t *tmq);
DLL_EXPORT TAOS_RES *tmq_consumer_poll(tmq_t *tmq, int64_t timeout);
DLL_EXPORT int32_t   tmq_consumer_close(tmq_t *tmq);
DLL_EXPORT int32_t   tmq_commit_sync(tmq_t *tmq, const TAOS_RES *msg);
DLL_EXPORT void      tmq_commit_async(tmq_t *tmq, const TAOS_RES *msg, tmq_commit_cb *cb, void *param);

enum tmq_conf_res_t {
  TMQ_CONF_UNKNOWN = -2,
  TMQ_CONF_INVALID = -1,
  TMQ_CONF_OK = 0,
};
typedef enum tmq_conf_res_t tmq_conf_res_t;

DLL_EXPORT tmq_conf_t    *tmq_conf_new();
DLL_EXPORT tmq_conf_res_t tmq_conf_set(tmq_conf_t *conf, const char *key, const char *value);
DLL_EXPORT void           tmq_conf_destroy(tmq_conf_t *conf);
DLL_EXPORT void           tmq_conf_set_auto_commit_cb(tmq_conf_t *conf, tmq_commit_cb *cb, void *param);
```

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

</TabItem>
<TabItem value="java" label="Java">

```java
void subscribe(Collection<String> topics) throws SQLException;

void unsubscribe() throws SQLException;

Set<String> subscription() throws SQLException;

ConsumerRecords<V> poll(Duration timeout) throws SQLException;

void commitAsync();

void commitAsync(OffsetCommitCallback callback);

void commitSync() throws SQLException;

void close() throws SQLException;
```

</TabItem>
Z
zhaoyanggh 已提交
91

92
<TabItem value="Python" label="Python">
S
Shuaiqiang Chang 已提交
93

Z
zhaoyanggh 已提交
94 95
```python
class TaosConsumer():
Y
Yang Zhao 已提交
96 97 98 99 100 101 102
    def __init__(self, *topics, **configs)

    def __iter__(self)

    def __next__(self)

    def sync_next(self)
Z
zhaoyanggh 已提交
103
    
Y
Yang Zhao 已提交
104 105 106 107 108
    def subscription(self)

    def unsubscribe(self)

    def close(self)
Z
zhaoyanggh 已提交
109
    
Y
Yang Zhao 已提交
110
    def __del__(self)
Z
zhaoyanggh 已提交
111
```
S
Shuaiqiang Chang 已提交
112

113
</TabItem>
S
Shuaiqiang Chang 已提交
114

115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
<TabItem label="Go" value="Go">

```go
func NewConsumer(conf *Config) (*Consumer, error)

func (c *Consumer) Close() error

func (c *Consumer) Commit(ctx context.Context, message unsafe.Pointer) error

func (c *Consumer) FreeMessage(message unsafe.Pointer)

func (c *Consumer) Poll(timeout time.Duration) (*Result, error)

func (c *Consumer) Subscribe(topics []string) error

func (c *Consumer) Unsubscribe() error
```
S
Shuaiqiang Chang 已提交
132

133 134
</TabItem>

135 136
<TabItem value="C#" label="C#">

X
xleili 已提交
137
```csharp
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
ConsumerBuilder(IEnumerable<KeyValuePair<string, string>> config)

virtual IConsumer Build()

Consumer(ConsumerBuilder builder)

void Subscribe(IEnumerable<string> topics)

void Subscribe(string topic) 

ConsumeResult Consume(int millisecondsTimeout)

List<string> Subscription()

void Unsubscribe()
 
void Commit(ConsumeResult consumerResult)

void Close()
157
```
S
Shuaiqiang Chang 已提交
158

H
Huo Linhe 已提交
159
</TabItem>
160

X
xleili 已提交
161 162
<TabItem label="Node.JS" value="Node.JS">

X
xleili 已提交
163
```node
X
xleili 已提交
164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
function TMQConsumer(config)

function subscribe(topic)

function consume(timeout)

function subscription()

function unsubscribe()

function commit(msg)

function close()
```

</TabItem>

H
Huo Linhe 已提交
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290
</Tabs>

## 写入数据

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

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

## 创建 *topic*

TDengine 使用 SQL 创建一个 topic:

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

TMQ 支持多种订阅类型:

### 列订阅

语法:

```sql
CREATE TOPIC topic_name as subquery
```

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

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

### 超级表订阅

语法:

```sql
CREATE TOPIC topic_name AS STABLE stb_name
```

与 `SELECT * from stbName` 订阅的区别是:

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

### 数据库订阅

语法:

```sql
CREATE TOPIC topic_name [WITH META] AS DATABASE db_name;
```

通过该语句可创建一个包含数据库所有表数据的订阅,`WITH META` 可选择将数据库结构变动信息加入到订阅消息流,TMQ 将消费当前数据库下所有表结构的变动,包括超级表的创建与删除,列添加、删除或修改,子表的创建、删除及 TAG 变动信息等等。消费者可通过 API 来判断具体的消息类型。这一点也是与 Kafka 不同的地方。

## 创建消费者 *consumer*

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

|            参数名称            |  类型   | 参数说明                                                 | 备注                                        |
| :----------------------------: | :-----: | -------------------------------------------------------- | ------------------------------------------- |
|        `td.connect.ip`         | string  | 用于创建连接,同 `taos_connect`                          |                                             |
|       `td.connect.user`        | string  | 用于创建连接,同 `taos_connect`                          |                                             |
|       `td.connect.pass`        | string  | 用于创建连接,同 `taos_connect`                          |
|       `td.connect.port`        | integer | 用于创建连接,同 `taos_connect`                          |
|           `group.id`           | string  | 消费组 ID,同一消费组共享消费进度                        | **必填项**。最大长度:192。                 |
|          `client.id`           | string  | 客户端 ID                                                | 最大长度:192。                             |
|      `auto.offset.reset`       |  enum   | 消费组订阅的初始位置                                     | 可选:`earliest`, `latest`, `none`(default) |
|      `enable.auto.commit`      | boolean | 启用自动提交                                             | 合法值:`true`, `false`。                   |
|   `auto.commit.interval.ms`    | integer | 以毫秒为单位的自动提交时间间隔                           |
| `enable.heartbeat.background`  | boolean | 启用后台心跳,启用后即使长时间不 poll 消息也不会造成离线 |                                             |
| `experimental.snapshot.enable` | boolean | 从 WAL 开始消费,还是从 TSBS 开始消费                    |                                             |
|     `msg.with.table.name`      | boolean | 是否允许从消息中解析表名                                 |

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

<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">

```c
/* 根据需要,设置消费组 (group.id)、自动提交 (enable.auto.commit)、
   自动提交时间间隔 (auto.commit.interval.ms)、用户名 (td.connect.user)、密码 (td.connect.pass) 等参数 */
tmq_conf_t* conf = tmq_conf_new();
tmq_conf_set(conf, "enable.auto.commit", "true");
tmq_conf_set(conf, "auto.commit.interval.ms", "1000");
tmq_conf_set(conf, "group.id", "cgrpName");
tmq_conf_set(conf, "td.connect.user", "root");
tmq_conf_set(conf, "td.connect.pass", "taosdata");
tmq_conf_set(conf, "auto.offset.reset", "earliest");
tmq_conf_set(conf, "experimental.snapshot.enable", "true");
tmq_conf_set(conf, "msg.with.table.name", "true");
tmq_conf_set_auto_commit_cb(conf, tmq_commit_cb_print, NULL);

tmq_t* tmq = tmq_consumer_new(conf, NULL, 0);
tmq_conf_destroy(conf);
```

</TabItem>
<TabItem value="java" label="Java">

H
Huo Linhe 已提交
291
对于 Java 程序,使用如下配置项:
H
Huo Linhe 已提交
292

293 294 295 296 297
| 参数名称                      | 类型   | 参数说明                                                                                                                      |
| ----------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------- |
| `bootstrap.servers`           | string | 连接地址,如 `localhost:6030`                                                                                                 |
| `value.deserializer`          | string | 值解析方法,使用此方法应实现 `com.taosdata.jdbc.tmq.Deserializer` 接口或继承 `com.taosdata.jdbc.tmq.ReferenceDeserializer` 类 |
| `value.deserializer.encoding` | string | 指定字符串解析的字符集                                                                                                        |  |
H
Huo Linhe 已提交
298

H
Huo Linhe 已提交
299
需要注意:此处使用 `bootstrap.servers` 替代 `td.connect.ip` 和 `td.connect.port`,以提供与 Kafka 一致的接口。
H
Huo Linhe 已提交
300 301 302 303 304 305 306 307 308 309 310

```java
Properties properties = new Properties();
properties.setProperty("enable.auto.commit", "true");
properties.setProperty("auto.commit.interval.ms", "1000");
properties.setProperty("group.id", "cgrpName");
properties.setProperty("bootstrap.servers", "127.0.0.1:6030");
properties.setProperty("td.connect.user", "root");
properties.setProperty("td.connect.pass", "taosdata");
properties.setProperty("auto.offset.reset", "earliest");
properties.setProperty("msg.with.table.name", "true");
H
huolibo 已提交
311
properties.setProperty("value.deserializer", "com.taos.example.MetersDeserializer");
H
Huo Linhe 已提交
312 313

TaosConsumer<Meters> consumer = new TaosConsumer<>(properties);
H
Huo Linhe 已提交
314 315 316 317 318 319

/* value deserializer definition. */
import com.taosdata.jdbc.tmq.ReferenceDeserializer;

public class MetersDeserializer extends ReferenceDeserializer<Meters> {
}
H
Huo Linhe 已提交
320 321
```

322
</TabItem>
Z
zhaoyanggh 已提交
323

324
<TabItem value="Python" label="Python">
Z
zhaoyanggh 已提交
325

Y
Yang Zhao 已提交
326 327
Python 使用以下配置项创建一个 Consumer 实例。

Z
zhaoyanggh 已提交
328 329 330 331 332 333 334 335 336 337 338 339 340 341
|            参数名称            |  类型   | 参数说明                                                 | 备注                                        |
| :----------------------------: | :-----: | -------------------------------------------------------- | ------------------------------------------- |
|        `td_connect_ip`         | string  | 用于创建连接,同 `taos_connect`                          |                                             |
|       `td_connect_user`        | string  | 用于创建连接,同 `taos_connect`                          |                                             |
|       `td_connect_pass`        | string  | 用于创建连接,同 `taos_connect`                          |                                             |
|       `td_connect_port`        | string  | 用于创建连接,同 `taos_connect`                          |                                             |
|           `group_id`           | string  | 消费组 ID,同一消费组共享消费进度                        | **必填项**。最大长度:192。                 |
|          `client_id`           | string  | 客户端 ID                                                | 最大长度:192。                             |
|      `auto_offset_reset`       | string  | 消费组订阅的初始位置                                     | 可选:`earliest`, `latest`, `none`(default) |
|      `enable_auto_commit`      | string  | 启用自动提交                                             | 合法值:`true`, `false`。                   |
|   `auto_commit_interval_ms`    | string  | 以毫秒为单位的自动提交时间间隔                           |                                             |
| `enable_heartbeat_background`  | string  | 启用后台心跳,启用后即使长时间不 poll 消息也不会造成离线 | 合法值:`true`, `false`                     |
| `experimental_snapshot_enable` | string  | 从 WAL 开始消费,还是从 TSBS 开始消费                    | 合法值:`true`, `false`                     |
|     `msg_with_table_name`      | string  | 是否允许从消息中解析表名                                 | 合法值:`true`, `false`                     |
Y
Yang Zhao 已提交
342
|      `timeout`                 | int     | 消费者拉去的超时时间                                    |                                            |
Z
zhaoyanggh 已提交
343 344 345

</TabItem>

T
t_max 已提交
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392
<TabItem label="Go" value="Go">

```go
config := tmq.NewConfig()
defer config.Destroy()
err = config.SetGroupID("test")
if err != nil {
  panic(err)
}
err = config.SetAutoOffsetReset("earliest")
if err != nil {
  panic(err)
}
err = config.SetConnectIP("127.0.0.1")
if err != nil {
  panic(err)
}
err = config.SetConnectUser("root")
if err != nil {
  panic(err)
}
err = config.SetConnectPass("taosdata")
if err != nil {
  panic(err)
}
err = config.SetConnectPort("6030")
if err != nil {
  panic(err)
}
err = config.SetMsgWithTableName(true)
if err != nil {
  panic(err)
}
err = config.EnableHeartBeat()
if err != nil {
  panic(err)
}
err = config.EnableAutoCommit(func(result *wrapper.TMQCommitCallbackResult) {
  if result.ErrCode != 0 {
    errStr := wrapper.TMQErr2Str(result.ErrCode)
    err := errors.NewError(int(result.ErrCode), errStr)
    panic(err)
  }
})
if err != nil {
  panic(err)
}
393 394
```

H
Huo Linhe 已提交
395
</TabItem>
396 397 398

<TabItem value="C#" label="C#">

X
xleili 已提交
399
```csharp
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419
using TDengineTMQ;

// 根据需要,设置消费组 (GourpId)、自动提交 (EnableAutoCommit)、
// 自动提交时间间隔 (AutoCommitIntervalMs)、用户名 (TDConnectUser)、密码 (TDConnectPasswd) 等参数
var cfg = new ConsumerConfig
 {
    EnableAutoCommit = "true"
    AutoCommitIntervalMs = "1000"
    GourpId = "TDengine-TMQ-C#",
    TDConnectUser = "root",
    TDConnectPasswd = "taosdata",
    AutoOffsetReset = "earliest"
    MsgWithTableName = "true",
    TDConnectIp = "127.0.0.1",
    TDConnectPort = "6030"
 };

var consumer = new ConsumerBuilder(cfg).Build();

```
S
Shuaiqiang Chang 已提交
420

421 422
</TabItem>

X
xleili 已提交
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444
<TabItem label="Node.JS" value="Node.JS">

``` node
// 根据需要,设置消费组 (group.id)、自动提交 (enable.auto.commit)、
// 自动提交时间间隔 (auto.commit.interval.ms)、用户名 (td.connect.user)、密码 (td.connect.pass) 等参数 

let consumer = taos.consumer({
  'enable.auto.commit': 'true',
  'auto.commit.interval.ms','1000',
  'group.id': 'tg2',
  'td.connect.user': 'root',
  'td.connect.pass': 'taosdata',
  'auto.offset.reset','earliest',
  'msg.with.table.name': 'true',
  'td.connect.ip','127.0.0.1',
  'td.connect.port','6030'  
  });

```

</TabItem>

H
Huo Linhe 已提交
445 446 447 448 449 450 451 452
</Tabs>

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

## 订阅 *topics*

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

H
Huo Linhe 已提交
453 454 455
<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">

H
Huo Linhe 已提交
456 457 458 459 460 461 462 463 464 465
```c
// 创建订阅 topics 列表
tmq_list_t* topicList = tmq_list_new();
tmq_list_append(topicList, "topicName");
// 启动订阅
tmq_subscribe(tmq, topicList);
tmq_list_destroy(topicList);
  
```

H
Huo Linhe 已提交
466 467 468 469 470 471 472 473 474
</TabItem>
<TabItem value="java" label="Java">

```java
List<String> topics = new ArrayList<>();
topics.add("tmq_topic");
consumer.subscribe(topics);
```

T
t_max 已提交
475 476 477 478 479 480 481 482 483 484 485 486 487 488
</TabItem>
<TabItem value="Go" label="Go">

```go
consumer, err := tmq.NewConsumer(config)
if err != nil {
 panic(err)
}
err = consumer.Subscribe([]string{"example_tmq_topic"})
if err != nil {
 panic(err)
}
```

H
Huo Linhe 已提交
489 490
</TabItem>

491 492
<TabItem value="C#" label="C#">

X
xleili 已提交
493
```csharp
494 495 496 497 498 499 500 501 502
// 创建订阅 topics 列表
List<String> topics = new List<string>();
topics.add("tmq_topic");
// 启动订阅
consumer.Subscribe(topics);
```

</TabItem>

503
<TabItem value="Python" label="Python">
Z
zhaoyanggh 已提交
504 505 506 507 508
```python
consumer = TaosConsumer('topic_ctb_column', group_id='vg2')
```
</TabItem>

X
xleili 已提交
509 510 511 512 513 514 515 516 517 518 519 520
<TabItem label="Node.JS" value="Node.JS">

```node
// 创建订阅 topics 列表
let topics = ['topic_test']

// 启动订阅
consumer.subscribe(topics);
```

</TabItem>

H
Huo Linhe 已提交
521 522
</Tabs>

H
Huo Linhe 已提交
523 524
## 消费

H
Huo Linhe 已提交
525 526
以下代码展示了不同语言下如何对 TMQ 消息进行消费。

H
Huo Linhe 已提交
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">

```c
// 消费数据
while (running) {
  TAOS_RES* msg = tmq_consumer_poll(tmq, timeOut);
  msg_process(msg);
}  
```

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

</TabItem>
<TabItem value="java" label="Java">

```java
while(running){
  ConsumerRecords<Meters> meters = consumer.poll(Duration.ofMillis(100));
    for (Meters meter : meters) {
      processMsg(meter);
    }    
}
```

T
t_max 已提交
552
</TabItem>
Z
zhaoyanggh 已提交
553

554
<TabItem value="Python" label="Python">
Z
zhaoyanggh 已提交
555 556 557 558 559 560 561
```python
for msg in consumer:
    for row in msg:
        print(row)
```
</TabItem>

T
t_max 已提交
562 563 564 565 566 567 568 569 570 571 572 573 574 575
<TabItem value="Go" label="Go">

```go
for {
 result, err := consumer.Poll(time.Second)
 if err != nil {
  panic(err)
 }
 fmt.Println(result)
 consumer.Commit(context.Background(), result.Message)
 consumer.FreeMessage(result.Message)
}
```

H
Huo Linhe 已提交
576 577
</TabItem>

578 579
<TabItem value="C#" label="C#">

X
xleili 已提交
580
```csharp
581 582 583 584 585 586 587 588 589 590 591
// 消费数据
while (true)
{
    var consumerRes = consumer.Consume(100);
    // process ConsumeResult
    ProcessMsg(consumerRes);
    consumer.Commit(consumerRes);
}
```

</TabItem>
X
xleili 已提交
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606

<TabItem label="Node.JS" value="Node.JS">

```node
while(true){
   msg = consumer.consume(200);
   // process message(consumeResult)
   console.log(msg.topicPartition);
   console.log(msg.block);
   console.log(msg.fields)
   }
```

</TabItem>

607 608
</Tabs>

H
Huo Linhe 已提交
609 610
## 结束消费

H
Huo Linhe 已提交
611 612
消费结束后,应当取消订阅。

H
Huo Linhe 已提交
613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634
<Tabs defaultValue="java" groupId="lang">
<TabItem value="c" label="C">

```c
/* 取消订阅 */
tmq_unsubscribe(tmq);

/* 关闭消费者对象 */
tmq_consumer_close(tmq);
```

</TabItem>
<TabItem value="java" label="Java">

```java
/* 取消订阅 */
consumer.unsubscribe();

/* 关闭消费 */
consumer.close();
```

635
</TabItem>
Z
zhaoyanggh 已提交
636

637
<TabItem value="Python" label="Python">
Z
zhaoyanggh 已提交
638 639 640 641 642 643 644

```python
/* 取消订阅 */
consumer.unsubscribe();

/* 关闭消费 */
consumer.close();
S
Shuaiqiang Chang 已提交
645 646 647

</TabItem>

648 649 650 651 652 653 654
<TabItem value="Go" label="Go">

```go
consumer.Close()
```

</TabItem>
655 656
<TabItem value="C#" label="C#">

X
xleili 已提交
657
```csharp
658 659 660 661 662
// 取消订阅
consumer.Unsubscribe();

// 关闭消费
consumer.Close();
S
Shuaiqiang Chang 已提交
663
```
664 665
</TabItem>

X
xleili 已提交
666 667 668 669 670 671 672 673 674
<TabItem label="Node.JS" value="Node.JS">

```node
consumer.unsubscribe();
consumer.close();
```

</TabItem>

H
Huo Linhe 已提交
675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022
</Tabs>

## 删除 *topic*

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

```sql
/* 删除 topic */
DROP TOPIC topic_name;
```

## 状态查看

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

```sql
SHOW TOPICS;
```

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

```sql
SHOW CONSUMERS;
```

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

```sql
SHOW SUBSCRIPTIONS;
```

## 示例代码

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

<Tabs defaultValue="java" groupId="lang">
<TabItem label="C" value="c">

```c
/*
 * Copyright (c) 2019 TAOS Data, Inc. <jhtao@taosdata.com>
 *
 * This program is free software: you can use, redistribute, and/or modify
 * it under the terms of the GNU Affero General Public License, version 3
 * or later ("AGPL"), as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "taos.h"

static int  running = 1;
static char dbName[64] = "tmqdb";
static char stbName[64] = "stb";
static char topicName[64] = "topicname";

static int32_t msg_process(TAOS_RES* msg) {
  char    buf[1024];
  int32_t rows = 0;

  const char* topicName = tmq_get_topic_name(msg);
  const char* dbName = tmq_get_db_name(msg);
  int32_t     vgroupId = tmq_get_vgroup_id(msg);

  printf("topic: %s\n", topicName);
  printf("db: %s\n", dbName);
  printf("vgroup id: %d\n", vgroupId);

  while (1) {
    TAOS_ROW row = taos_fetch_row(msg);
    if (row == NULL) break;

    TAOS_FIELD* fields = taos_fetch_fields(msg);
    int32_t     numOfFields = taos_field_count(msg);
    int32_t*    length = taos_fetch_lengths(msg);
    int32_t     precision = taos_result_precision(msg);
    const char* tbName = tmq_get_table_name(msg);
    rows++;
    taos_print_row(buf, row, fields, numOfFields);
    printf("row content from %s: %s\n", (tbName != NULL ? tbName : "table null"), buf);
  }

  return rows;
}

static int32_t init_env() {
  TAOS* pConn = taos_connect("localhost", "root", "taosdata", NULL, 0);
  if (pConn == NULL) {
    return -1;
  }

  TAOS_RES* pRes;
  // drop database if exists
  printf("create database\n");
  pRes = taos_query(pConn, "drop database if exists tmqdb");
  if (taos_errno(pRes) != 0) {
    printf("error in drop tmqdb, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  // create database
  pRes = taos_query(pConn, "create database tmqdb");
  if (taos_errno(pRes) != 0) {
    printf("error in create tmqdb, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  // create super table
  printf("create super table\n");
  pRes = taos_query(
      pConn, "create table tmqdb.stb (ts timestamp, c1 int, c2 float, c3 varchar(16)) tags(t1 int, t3 varchar(16))");
  if (taos_errno(pRes) != 0) {
    printf("failed to create super table stb, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  // create sub tables
  printf("create sub tables\n");
  pRes = taos_query(pConn, "create table tmqdb.ctb0 using tmqdb.stb tags(0, 'subtable0')");
  if (taos_errno(pRes) != 0) {
    printf("failed to create super table ctb0, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  pRes = taos_query(pConn, "create table tmqdb.ctb1 using tmqdb.stb tags(1, 'subtable1')");
  if (taos_errno(pRes) != 0) {
    printf("failed to create super table ctb1, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  pRes = taos_query(pConn, "create table tmqdb.ctb2 using tmqdb.stb tags(2, 'subtable2')");
  if (taos_errno(pRes) != 0) {
    printf("failed to create super table ctb2, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  pRes = taos_query(pConn, "create table tmqdb.ctb3 using tmqdb.stb tags(3, 'subtable3')");
  if (taos_errno(pRes) != 0) {
    printf("failed to create super table ctb3, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  // insert data
  printf("insert data into sub tables\n");
  pRes = taos_query(pConn, "insert into tmqdb.ctb0 values(now, 0, 0, 'a0')(now+1s, 0, 0, 'a00')");
  if (taos_errno(pRes) != 0) {
    printf("failed to insert into ctb0, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  pRes = taos_query(pConn, "insert into tmqdb.ctb1 values(now, 1, 1, 'a1')(now+1s, 11, 11, 'a11')");
  if (taos_errno(pRes) != 0) {
    printf("failed to insert into ctb0, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  pRes = taos_query(pConn, "insert into tmqdb.ctb2 values(now, 2, 2, 'a1')(now+1s, 22, 22, 'a22')");
  if (taos_errno(pRes) != 0) {
    printf("failed to insert into ctb0, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  pRes = taos_query(pConn, "insert into tmqdb.ctb3 values(now, 3, 3, 'a1')(now+1s, 33, 33, 'a33')");
  if (taos_errno(pRes) != 0) {
    printf("failed to insert into ctb0, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  taos_close(pConn);
  return 0;
}

int32_t create_topic() {
  printf("create topic\n");
  TAOS_RES* pRes;
  TAOS*     pConn = taos_connect("localhost", "root", "taosdata", NULL, 0);
  if (pConn == NULL) {
    return -1;
  }

  pRes = taos_query(pConn, "use tmqdb");
  if (taos_errno(pRes) != 0) {
    printf("error in use tmqdb, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  pRes = taos_query(pConn, "create topic topicname as select ts, c1, c2, c3 from tmqdb.stb where c1 > 1");
  if (taos_errno(pRes) != 0) {
    printf("failed to create topic topicname, reason:%s\n", taos_errstr(pRes));
    return -1;
  }
  taos_free_result(pRes);

  taos_close(pConn);
  return 0;
}

void tmq_commit_cb_print(tmq_t* tmq, int32_t code, void* param) {
  printf("tmq_commit_cb_print() code: %d, tmq: %p, param: %p\n", code, tmq, param);
}

tmq_t* build_consumer() {
  tmq_conf_res_t code;
  tmq_conf_t*    conf = tmq_conf_new();
  code = tmq_conf_set(conf, "enable.auto.commit", "true");
  if (TMQ_CONF_OK != code) return NULL;
  code = tmq_conf_set(conf, "auto.commit.interval.ms", "1000");
  if (TMQ_CONF_OK != code) return NULL;
  code = tmq_conf_set(conf, "group.id", "cgrpName");
  if (TMQ_CONF_OK != code) return NULL;
  code = tmq_conf_set(conf, "client.id", "user defined name");
  if (TMQ_CONF_OK != code) return NULL;
  code = tmq_conf_set(conf, "td.connect.user", "root");
  if (TMQ_CONF_OK != code) return NULL;
  code = tmq_conf_set(conf, "td.connect.pass", "taosdata");
  if (TMQ_CONF_OK != code) return NULL;
  code = tmq_conf_set(conf, "auto.offset.reset", "earliest");
  if (TMQ_CONF_OK != code) return NULL;
  code = tmq_conf_set(conf, "experimental.snapshot.enable", "true");
  if (TMQ_CONF_OK != code) return NULL;
  code = tmq_conf_set(conf, "msg.with.table.name", "true");
  if (TMQ_CONF_OK != code) return NULL;

  tmq_conf_set_auto_commit_cb(conf, tmq_commit_cb_print, NULL);

  tmq_t* tmq = tmq_consumer_new(conf, NULL, 0);
  tmq_conf_destroy(conf);
  return tmq;
}

tmq_list_t* build_topic_list() {
  tmq_list_t* topicList = tmq_list_new();
  int32_t     code = tmq_list_append(topicList, "topicname");
  if (code) {
    return NULL;
  }
  return topicList;
}

void basic_consume_loop(tmq_t* tmq, tmq_list_t* topicList) {
  int32_t code;

  if ((code = tmq_subscribe(tmq, topicList))) {
    fprintf(stderr, "%% Failed to tmq_subscribe(): %s\n", tmq_err2str(code));
    return;
  }

  int32_t totalRows = 0;
  int32_t msgCnt = 0;
  int32_t timeout = 5000;
  while (running) {
    TAOS_RES* tmqmsg = tmq_consumer_poll(tmq, timeout);
    if (tmqmsg) {
      msgCnt++;
      totalRows += msg_process(tmqmsg);
      taos_free_result(tmqmsg);
      /*} else {*/
      /*break;*/
    }
  }

  fprintf(stderr, "%d msg consumed, include %d rows\n", msgCnt, totalRows);
}

int main(int argc, char* argv[]) {
  int32_t code;

  if (init_env() < 0) {
    return -1;
  }

  if (create_topic() < 0) {
    return -1;
  }

  tmq_t* tmq = build_consumer();
  if (NULL == tmq) {
    fprintf(stderr, "%% build_consumer() fail!\n");
    return -1;
  }

  tmq_list_t* topic_list = build_topic_list();
  if (NULL == topic_list) {
    return -1;
  }

  basic_consume_loop(tmq, topic_list);

  code = tmq_unsubscribe(tmq);
  if (code) {
    fprintf(stderr, "%% Failed to unsubscribe: %s\n", tmq_err2str(code));
  } else {
    fprintf(stderr, "%% unsubscribe\n");
  }

  code = tmq_consumer_close(tmq);
  if (code) {
    fprintf(stderr, "%% Failed to close consumer: %s\n", tmq_err2str(code));
  } else {
    fprintf(stderr, "%% Consumer closed\n");
  }

  return 0;
}

```

[查看源码](https://github.com/taosdata/TDengine/blob/develop/examples/c/tmq.c)
</TabItem>

<TabItem label="Java" value="java">
    <Java />
</TabItem>

<TabItem label="Go" value="Go">
   <Go/>
</TabItem>

<TabItem label="Rust" value="Rust">
    <Rust />
</TabItem>

<TabItem label="Python" value="Python">

```python
import taos
1023
from taos.tmq import TaosConsumer
H
Huo Linhe 已提交
1024

1025 1026 1027 1028 1029 1030
import taos
from taos.tmq import *
consumer = TaosConsumer('topic_ctb_column', group_id='vg2')
for msg in consumer:
    for row in msg:
        print(row)
H
Huo Linhe 已提交
1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046

```

[查看源码](https://github.com/taosdata/TDengine/blob/develop/docs/examples/python/tmq_example.py)

</TabItem>

<TabItem label="Node.JS" value="Node.JS">
   <Node/>
</TabItem>

<TabItem label="C#" value="C#">
   <CSharp/>
</TabItem>

</Tabs>