TDengine 技术内幕 第六章 数据订阅

为了帮助应用实时获取写入 TDengine 的数据,或者以事件到达顺序处理数据,TDengine 提供了类似消息队列产品的数据订阅、消费接口。在很多场景下,采用 TDengine 的时序数据处理系统不再需要集成消息队列产品。

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

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

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

数据订阅语法

创建 topic

TDengine 使用 SQL 创建一个 topic,共有三种类型的 topic:

查询 topic

语法:

CREATE TOPIC [IF NOT EXISTS] topic_name as subquery

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

  • 该类型 TOPIC 一旦创建则订阅数据的结构确定。

  • 被订阅或用于计算的列或标签不可被删除(ALTER table DROP)、修改(ALTER table MODIFY)。

  • 若发生表结构变更,新增的列不出现在结果中。

  • 对于 select *,则订阅展开为创建时所有的列(子表、普通表为数据列,超级表为数据列加标签列)

超级表 topic

语法:

CREATE TOPIC [IF NOT EXISTS] topic_name [with meta] AS STABLE stb_name [where_condition]

SELECT * from stbName 订阅的区别是:

  • 不会限制用户的表结构变更。

  • 返回的是非结构化的数据,返回数据的结构会随之超级表的表结构变化而变化。

  • with meta 参数可选,选择时将返回创建超级表,子表等语句,主要用于taosx做超级表迁移。

  • where_condition 参数可选,选择时将用来过滤符合条件的子表,订阅这些子表。where 条件里不能有普通列,只能是tag或tbname,where条件里可以用函数,用来过滤tag,但是不能是聚合函数,因为子表tag值无法做聚合。也可以是常量表达式,比如 2 > 1(订阅全部子表),或者 false(订阅0个子表)。

  • 返回数据不包含标签。

数据库 topic

语法:

CREATE TOPIC [IF NOT EXISTS] topic_name [with meta] AS DATABASE db_name;

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

  • with meta 参数可选,选择时将返回创建数据库里所有超级表,子表的语句,主要用于taosx做数据库迁移。

  • 超级表订阅和库订阅属于高级订阅模式,容易出错,如确实要使用,请咨询专业人员。

删除 topic

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

DROP TOPIC [IF EXISTS] topic_name;

此时如果该订阅主题上存在 consumer,则此 consumer 会收到一个错误。

查看 topic

SHOW TOPICS;

显示当前数据库下的所有主题的信息。

创建消费组

消费组的创建只能通过 TDengine 客户端驱动或者连接器所提供的 API 创建。

删除消费组

DROP CONSUMER GROUP [IF EXISTS] cgroup_name ON topic_name;

删除主题 topic_name 上的消费组 cgroup_name,只有在消费组内没有消费者时才可删除成功。

查看消费者

SHOW CONSUMERS;

显示当前数据库下所有活跃的消费者的信息。

查看订阅信息

SHOW SUBSCRIPTIONS;

显示 consumer 与 vgroup 之间的分配关系和消费信息,可用于查看消费进度。

数据订阅参数

消费参数主要用于消费者创建时指定,需要根据需求设置合理的消费参数,基础配置项如下表所示:

参数名称类型参数说明备注
td.connect.ipstring服务端的 IP 地址
td.connect.userstring用户名
td.connect.passstring密码
td.connect.portinteger服务端的端口号
group.idstring消费组 ID,同一消费组共享消费进度
必填项。最大长度:192。
每个topic最多可建立100个 consumer group
client.idstring客户端 ID最大长度:192。
auto.offset.resetenum消费组订阅的初始位置
earliest: 从头开始订阅;
latest: default;仅从最新数据开始订阅;
none: 没有提交的 offset 无法订阅
enable.auto.commitboolean是否启用消费位点自动提交,true: 自动提交,客户端应用无需commit;false:客户端应用需要自行commit默认值为 true
auto.commit.interval.msinteger消费记录自动提交消费位点时间间隔,单位为毫秒默认值为 5000
enable.replayboolean是否开启数据回放功能默认关闭

数据订阅 API

数据订阅提供了丰富的 API 接口,可以根据接口开发相应的程序,目前支持 Java/go/rust/python/c# 等多种语言,具体可参考官网链接器部分。

下面主要列举些主要的 C 语言相关的 API :

tmq_t *tmq_consumer_new(tmq_conf_t *conf, char *errstr, int32_t errstrLen);

int32_t tmq_subscribe(tmq_t *tmq, const tmq_list_t *topic_list);

int32_t tmq_unsubscribe(tmq_t *tmq); int32_t tmq_subscription(tmq_t *tmq, tmq_list_t **topics);

TAOS_RES *tmq_consumer_poll(tmq_t *tmq, int64_t timeout);

int32_t tmq_consumer_close(tmq_t *tmq);

int32_t tmq_commit_sync(tmq_t *tmq, const TAOS_RES *msg); //Commit the msg’s offset + 1

void tmq_commit_async(tmq_t *tmq, const TAOS_RES *msg, tmq_commit_cb *cb, void *param);

int32_t tmq_commit_offset_sync(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset);

void tmq_commit_offset_async(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset, tmq_commit_cb *cb, void *param);

int32_t tmq_get_topic_assignment(tmq_t *tmq, const char *pTopicName, tmq_topic_assignment **assignment,int32_t *numOfAssignment);

void tmq_free_assignment(tmq_topic_assignment* pAssignment);

int32_t tmq_offset_seek(tmq_t *tmq, const char *pTopicName, int32_t vgId, int64_t offset);

int64_t tmq_position(tmq_t *tmq, const char *pTopicName, int32_t vgId);

// The current offset is the offset of the last consumed message + 1 int64_t tmq_committed(tmq_t *tmq, const char *pTopicName, int32_t vgId);

数据订阅示例

写入数据

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

DROP DATABASE IF EXISTS tmqdb;

CREATE DATABASE tmqdb WAL_RETENTION_PERIOD 3600;

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

使用 SQL 创建一个 topic,可以使查询超级表中的数据并带过滤条件。

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

创建消费者 consumer

设置消费参数,创建消费者。如果多个 consumer 指定的 consumer group ID 一样,则自动形成一个 consumer group,共享消费进度。

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", "latest");

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);

订阅 topics

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

// 创建订阅 topics 列表

tmq_list_t* topicList = tmq_list_new();

tmq_list_append(topicList, "topicName");

// 启动订阅

tmq_subscribe(tmq, topicList);

tmq_list_destroy(topicList);

消费数据

// 消费数据

while (running) {

   TAOS_RES* msg = tmq_consumer_poll(tmq, timeOut);

   msg_process(msg);

}

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

结束消费,

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

/* 取消订阅 */ tmq_unsubscribe(tmq); /* 关闭消费者对象 */ tmq_consumer_close(tmq);

完整的订阅消费代码可以参考官网数据订阅部分。

数据订阅高级功能

数据回放

  • 订阅支持 replay 功能,按照数据写入的时间回放。 比如,如下时间写入三条数据,则订阅出第一条数据 5s 后返回第二条数据,获取第二条数据 3s 后返回第三条数据。

2023/09/22 00:00:00.000 2023/09/22 00:00:05.000 2023/09/22 00:00:08.000

  • 仅查询订阅支持数据回放,超级表和库订阅不支持回放

  • 回放不支持进度保存

  • 因为数据回放本身需要处理时间,所以回放的精度存在几十ms的误差

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

TDengine (老段)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值