TDengine 数据订阅基本概念,可参考之前写的文章 数据订阅, 本文重点从实操方面介绍数据订阅的使用。
TDengine 订阅功能依赖于 TDengine 数据库,因此首先需要搭建 TDengine 集群。 以下是搭建 TDengine 集群的基本步骤:
install.sh
脚本进行安装。taos.cfg
,配置集群节点信息、数据存储路径等参数。 关键参数(详见官方文档)firstEp node1:6030
secondEp node2:6030
fqdn node1
logDir /data/taos/log
dataDir /data/taos/data
tempDir /data/taos/tmp
timezone UTC-8
locale en_US.UTF-8
charset UTF-8
/usr/loca/taos/bin/start-all.sh
,启动 TDengine 服务所有组件。[root@ ~]# taos
taos> show dnodes;
taos
会使用 root 账号和默认密码(taosdata)登录。taos -u USERNAME -p'PASSWORD'
-p
参数和密码直接没有空格。具体安装步骤见官方集群部署文档。
为了方便操作 TDengine 数据库和订阅功能,建议安装以下客户端工具:
Python 连接器需要 Python 3.6.2 以上版本。
pip3 uninstall taos taospy
pip3 uninstall taos taos-ws-py
安装最新版本的 Python 连接器。
pip3 install taospy
pip3 install taos-ws-py
安装验证,在 Python 交互模式执行:
import taosws
使用 TDengine CLI 或 WEB 工具连接到 TDengine 数据库。
taos.cfg
(配置参数除 fqnd 外其他可与服务端一致)。创建测试用数据库用于数据的存储,下面是示例语句:
CREATE DATABASE `test1` VGROUPS 4 WAL_RETENTION_PERIOD 3600 WAL_RETENTION_SIZE 0 ;
在上述语句中,VGROUPS、WAL_RETENTION_PERIOD、WAL_RETENTION_SIZE 3个参数与订阅息息相关。
USE test1;
CREATE STABLE `stb1` (`ts` TIMESTAMP, `v1` INT ') TAGS (`t1` INT);
CREATE TABLE `t1` USING `stb1` (`t1`) TAGS (1);
CREATE TABLE `t2` USING `stb1` (`t2`) TAGS (2);
CREATE TABLE `t3` USING `stb1` (`t3`) TAGS (3);
CREATE TABLE `t4` USING `stb1` (`t4`) TAGS (4);
CREATE TABLE `t5` USING `stb1` (`t5`) TAGS (5);
vi insert.sh
#!/bin/sh
i=0
while true
do
echo "insert into test1.t1 values(now,$i);" >i.sql
echo "insert into test1.t2 values(now,$i);" >>i.sql
echo "insert into test1.t3 values(now,$i);" >>i.sql
echo "insert into test1.t4 values(now,$i);" >>i.sql
echo "insert into test1.t5 values(now,$i);" >>i.sql
taos -f i.sql
sleep 1
i=$(($i+1))
done
create user test pass 'Tbase_125#' host '0.0.0.0/0';
TDengine 支持 3 种主题类型:
创建查询主题的步骤如下:
使用 TDengine CLI 或 WEB 工具连接到 TDengine 数据库。
使用 SQL 语句 CREATE TOPIC
创建主题,例如:
CREATE TOPIC tp_test2 as select ts,v1 from test1.stb1;
该语句创建了一个名为 tp_test2
的主题。
为测试用户 test
授予主题 tp_test2
订阅权限。
GRANT SUBSCRIBE ON tp_test2 TO test;
如果不再需要订阅数据,可以删除 topic,需要注意正在订阅中的 topic 无法被删除。
DROP TOPIC IF EXISTS tp_test2;
查询当前集群存在的主题:
show topics;
查询主题详细信息:
select * from information_schema.ins_topics;
ins_topics 表结构说明:
# | 列名 | 数据类型 | 说明 |
---|---|---|---|
1 | topic_name | VARCHAR(192) | topic 名称 |
2 | db_name | VARCHAR(64) | topic 相关的 DB |
3 | create_time | TIMESTAMP | topic 的 创建时间 |
4 | sql | VARCHAR(1024) | 创建该 topic 时所用的 SQL 语句 |
示例:
taos> select * from information_schema.ins_topics\G;
*************************** 1.row ***************************
topic_name: tp_test2
db_name: test1
create_time: 2025-02-13 21:24:54.704
sql: create topic tp_test2 as select ts,v1 from test1.stb1;
schema: [{"name":"ts","type":"TIMESTAMP","length":8},{"name":"v1","type":"INT","length":4}]
meta: no
type: column
消费者的创建只能通过 TDengine 客户端驱动或者连接器所提供的 API 创建,以 Python为例:
consumer = taosws.Consumer(conf={
"td.connect.websocket.scheme": "ws",
"group.id": "test_ws_group01",
"client.id": "test_ws_client01",
"td.connect.user": "test",
"td.connect.pass": "Tbase_125#",
"td.connect.ip": "10.7.7.14",
"td.connect.port": "6041",
"enable.auto.commit": "true", # 自动提交偏移量
}
)
参数说明:
参数名称 | 类型 | 参数说明 | 备注 |
---|---|---|---|
td.connect.ip | string | 服务端的 FQDN | 可以是ip或者host name |
td.connect.user | string | 用户名 | |
td.connect.pass | string | 密码 | |
td.connect.port | integer | 服务端的端口号 | |
group.id | string | 消费组 ID,同一消费组共享消费进度 | 必填项。最大长度:192,超长将截断。每个topic最多可建立 100 个 consumer group |
client.id | string | 客户端 ID | 最大长度:255,超长将截断。 |
auto.offset.reset | enum | 消费组订阅的初始位置 | earliest: default(version < 3.2.0.0);从头开始订阅;latest: default(version >= 3.2.0.0);仅从最新数据开始订阅;none: 没有提交的 offset 无法订阅 |
enable.auto.commit | boolean | 是否启用消费位点自动提交,true: 自动提交,客户端应用无需commit;false:客户端应用需要自行commit | 默认值为 true |
auto.commit.interval.ms | integer | 消费记录自动提交消费位点时间间隔,单位为毫秒 | 默认值为 5000 |
msg.with.table.name | boolean | 是否允许从消息中解析表名, 不适用于列订阅(列订阅时可将 tbname 作为列写入 subquery 语句)(从3.2.0.0版本该参数废弃,恒为true) | 默认关闭 |
enable.replay | boolean | 是否开启数据回放功能 | 默认关闭 |
session.timeout.ms | integer | consumer 心跳丢失后超时时间,超时后会触发 rebalance 逻辑,成功后该 consumer 会被删除(从3.3.3.0版本开始支持) | 默认值为 12000,取值范围 [6000, 1800000] |
max.poll.interval.ms | integer | consumer poll 拉取数据间隔的最长时间,超过该时间,会认为该 consumer 离线,触发rebalance 逻辑,成功后该 consumer 会被删除(从3.3.3.0版本开始支持) | 默认值为 300000,[1000,INT32_MAX] |
查询当前数据库下所有消费者的信息,会显示消费者的状态,创建时间等信息。
SHOW CONSUMERS;
select * from performance_schema.perf_consumers
以上两个命令输出的信息一致。
# | 列名 | 数据类型 | 说明 |
---|---|---|---|
1 | consumer_id | BIGINT | 消费者的唯一 ID |
2 | consumer_group | BINARY(192) | 消费者组 |
3 | client_id | BINARY(192) | 用户自定义字符串,通过创建 consumer 时指定 client_id 来展示 |
4 | status | BINARY(20) | 消费者当前状态。消费者状态包括:ready(正常可用)、 lost(连接已丢失)、 rebalancing(消费者所属 vgroup 正在分配中)、unknown(未知状 态) |
5 | topics | BINARY(204) | 被订阅的 topic。若订阅多个 topic,则展示为多行 |
6 | up_time | TIMESTAMP | 第一次连接 taosd 的时间 |
7 | subscribe_time | TIMESTAMP | 上一次发起订阅的时间 |
8 | rebalance_time | TIMESTAMP | 上一次触发 rebalance 的时间 |
示例:
taos> select * from performance_schema.perf_consumers\G;
*************************** 1.row ***************************
consumer_id: 0x157f10288388e37d
consumer_group: test_ws_group01
client_id: test_ws_client01
user: test
fqdn: test4
status: ready
topics: tp_test2
up_time: 2025-02-14 11:37:16.738
subscribe_time: 2025-02-14 11:37:16.738
rebalance_time: 2025-02-14 11:37:18.620
parameters: tbname:1,commit:0,interval:5000ms,reset:latest
消费者创建的时候,会给消费者指定一个消费者组,消费者不能显式的删除,但是消费者组在组内没有消费者时可以通过下面语句删除:
DROP CONSUMER GROUP IF EXISTS test_ws_group01 ON tp_test2;
注意 正在消费的消费组无法被删除,而停止消费的消费组无法使用上述语句查看,仅可通过2.2.5中查询数据订阅查询到。
通过以下命令可以查询 topic 在不同 vgroup 上的消费信息,可用于查看消费进度。
SHOW SUBSCRIPTIONS;
select * from information_schema.ins_subscriptions
以上两个命令输出的信息相同,表结构描述如下:
# | 列名 | 数据类型 | 说明 |
---|---|---|---|
1 | topic_name | VARCHAR(204) | 被订阅的 topic |
2 | consumer_group | VARCHAR(193) | 订阅者的消费者组 |
3 | vgroup_id | INT | 消费者被分配的 vgroup id |
4 | consumer_id | BIGINT | 消费组的唯一 id |
5 | user | VARCHAR(24) | 消费者的登录的用户名 |
6 | fqdn | VARCHAR(128) | 消费者的所在机器的 fqdn |
7 | offset | VARCHAR(64) | 消费者的消费进度 |
8 | rows | BIGINT | 消费者的消费的数据条数 |
示例:
taos> select * from information_schema.ins_subscriptions where topic_name='tp_test2' and consumer_id is not null\G;
*************************** 1.row ***************************
topic_name: tp_test2
consumer_group: test_ws_group01
vgroup_id: 31
consumer_id: 0x157f102d329911e7
user: test
fqdn: test4
offset: wal:11831/11831
rows: 84
*************************** 2.row ***************************
topic_name: tp_test2
consumer_group: test_ws_group01
vgroup_id: 32
consumer_id: 0x157f102d329911e7
user: test
fqdn: test4
offset: wal:6939/6939
rows: 42
*************************** 3.row ***************************
topic_name: tp_test2
consumer_group: test_ws_group01
vgroup_id: 33
consumer_id: 0x157f102d329911e7
user: test
fqdn: test4
offset: wal:7099/7099
rows: 42
*************************** 4.row ***************************
topic_name: tp_test2
consumer_group: test_ws_group01
vgroup_id: 34
consumer_id: 0x157f102d329911e7
user: test
fqdn: test4
offset: wal:6991/6991
rows: 42
TDengine 订阅功能的数据发布是通过预写日志 WAL (Write-Ahead Logging) 文件实现的。当数据写入 TDengine 数据库时,会同时写入 WAL 文件。订阅功能会从 WAL 文件中读取数据,并将其推送给订阅端。
在我们的测试环境中,insert.sh
即为数据发布端。
运行脚本:
sh insert.sh
TDengine 支持多种语言和连接方式订阅数据,订阅功能支持列表如下:
连接方式 | Java | Python | Go | C# | Node.js | Rust | C/C++ |
---|---|---|---|---|---|---|---|
原生方式 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 |
Websocket | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 | 支持 |
以下为Python 消费示例:
import time
import logging
import taosws
# 配置日志
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
def main():
try:
consumer = taosws.Consumer(conf={
"td.connect.websocket.scheme": "ws",
"group.id": "test_ws_group01",
"client.id": "test_ws_client01",
"td.connect.user": "test",
"td.connect.pass": "Tbase_125#",
"td.connect.ip": "10.7.7.14",
"td.connect.port": "6041",
"enable.auto.commit": "true", # 自动提交偏移量
}
)
# 订阅主题
topics = ["tp_test2"]
consumer.subscribe(topics)
logging.info(f"Subscribed to topics: {topics}")
while True:
try:
# 拉取数据
res = consumer.poll(1) # 超时时间 1 秒
if not res:
continue # 没有数据,继续轮询
# 处理数据
for block in res:
for row in block:
logging.info(f"Fetched data: {row}")
except Exception as e:
logging.error(f"Error while consuming messages: {e}")
time.sleep(5) # 出现错误后暂停一段时间重试
except KeyboardInterrupt:
logging.info("Consumer interrupted by user. Exiting...")
consumer.unsubscribe()
print("Consumer unsubscribed successfully.");
except Exception as e:
logging.critical(f"Failed to initialize consumer: {e}")
finally:
if consumer:
consumer.close()
print("Consumer closed successfully.");
logging.info("Shutting down consumer.")
if __name__ == "__main__":
main()
代码说明:
poll
每次调用获取一个消息,一个消息中可能包含多个记录。records
包含了多个 block 块, 每个块中可能包含多个记录。运行示例(Ctrl+C 中止):
# python3 test_sub_ws.py
2025-02-14 13:45:33,623 - INFO - Subscribed to topics: ['tp_test2']
2025-02-14 13:45:43,649 - INFO - Fetched data: (datetime.datetime(2025, 2, 14, 13, 45, 43, 164000), 0)
2025-02-14 13:45:43,651 - INFO - Fetched data: (datetime.datetime(2025, 2, 14, 13, 45, 43, 166000), 0)
2025-02-14 13:45:43,653 - INFO - Fetched data: (datetime.datetime(2025, 2, 14, 13, 45, 43, 166000), 0)
2025-02-14 13:45:43,655 - INFO - Fetched data: (datetime.datetime(2025, 2, 14, 13, 45, 43, 167000), 0)
2025-02-14 13:45:43,657 - INFO - Fetched data: (datetime.datetime(2025, 2, 14, 13, 45, 43, 165000), 0)
^C
2025-02-14 13:45:47,665 - INFO - Consumer interrupted by user. Exiting...
Consumer unsubscribed successfully.
Consumer closed successfully.
2025-02-14 13:45:47,666 - INFO - Shutting down consumer.
更多内容欢迎访问 TDengine 官网