TDengine中訂閱的用途和用法

TDengine中訂閱的用途和用法 - TDengine | 濤思數據 (taosdata.com)

 

默認創建的數據庫在create topic語句時會出錯,表示:WAL_RETENTION_PERIOD爲0

文檔中說創建數據庫時WAL_RETENTION_PERIOD默認爲3600秒,和實際不符合,因爲topic需要WAL_RETENTION_PERIOD支持,因此修改數據庫即可:

alter database ttt WAL_RETENTION_PERIOD 3600

 

本文將介紹TDengine Database訂閱功能的使用場景、使用方法和一些限制,並與InfluxDB的訂閱功能進行簡單的對比。本文的預期讀者是基於TDengine開發各種應用的軟件開發人員。

什麼是訂閱?

訂閱,是一種數據查詢方式,其特點爲:客戶端執行一個查詢語句後,可以增量形式,不斷收到新到達服務端的、符合查詢條件的數據。訂閱的實現模型有兩種,一種是“推”,即服務器主動將數據發到客戶端;另一種是“拉”,即客戶端主動向服務器請求數據。兩種方式各有優缺點,這裏不做詳細的對比,只是說明一下,TDengine Database使用的是“拉”模型。

什麼時候需要使用訂閱?

爲了便於用戶程序消費TDengine Database中的數據,TDengine實現了基於SQL的數據查詢語法,並提供了豐富的聚合函數,這種方式的優勢已在多個實際案例中得到了體現。但由於時序數據的特點,單純的直接數據查詢並不能滿足用戶程序的需求,比如:我們管理着一批溫度測量設備,希望當某個設備檢測到的溫度超過限制(比如80°C)後能得到通知並進行一些處理時,肯定會先爲所有的設備建立一張超級表:

  1. create database test;
  2. use test;
  3. create table devices (ts timestamp, temperature float) tags(id int);

併爲每個設備創建一張子表:

  1. create table device1 using devices tags(1);
  2. create table device2 using devices tags(2);
  3. ...

這種設計滿足了設備管理的需求,但如何滿足溫度監測的需求呢?如果僅使用普通的查詢,有兩種方法:一是分別對每張子表進行查詢,每次查詢後記錄最後一條數據的時間戳,後續只查詢這個時間戳之後的數據:

  1. select * from device1 where ts > last_timestamp1 and temperature > 80;
  2. select * from device2 where ts > last_timestamp2 and temperature > 80;
  3. ...

這確實可行,但隨着設備數量的增加,查詢數量也會增加,客戶端和服務端的性能都會受到影響,當設備數增長到一定的程度,系統就無法承受了。

另一種方法是對超級表進行查詢。這樣,無論有多少設備,都只需一次查詢:

  1. select * from devices where ts > last_timestamp and temperature > 80;

但是,如何選擇 last_timestamp 就成了一個新的問題。因爲,一方面數據的產生時間(也就是數據時間戳)和數據入庫的時間一般並不相同,有時偏差還很大;另一方面,不同設備的數據到達TDengine的時間也會有差異。所以,如果我們在查詢中使用最慢的那臺設備的數據的時間戳作爲 last_timestamp ,就可能重複讀入其它設備的數據;如果使用最快的設備的時間戳,其它設備的數據就可能被漏掉。

TDengine的訂閱功能爲上面這個問題提供了一個徹底的解決方案。

如何使用TDengine中的訂閱功能?

TDengine的API中,與訂閱相關的主要有以下三個:

  • taos_subscribe
  • taos_consume
  • taos_unsubscribe

這三個API的具體說明請見《C/C++數據訂閱接口》,下面結合一個示例,介紹下其使用方法,完整的示例代碼可以在這裏找到。

首先是創建訂閱:

  1. TAOS_SUB* tsub = NULL;
  2. if (async) {
  3.   // create an asynchronized subscription, the callback function will be called every 1s
  4.   tsub = taos_subscribe(taos, restart, topic, sql, subscribe_callback, &blockFetch, 1000);
  5. } else {
  6.   // create an synchronized subscription, need to call 'taos_consume' manually
  7.   tsub = taos_subscribe(taos, restart, topic, sql, NULL, NULL, 0);
  8. }

TDengine中的訂閱既可以是同步的,也可以是異步的,上面的代碼會根據從命令行獲取的參數async的值來決定使用哪種方式。這裏,同步的意思是用戶程序要直接調用 taos_consume來拉取數據,而異步則由API在內部的另一個線程中調用taos_consume,然後把拉取到的數據交給回調函數 subscribe_callback去處理。

參數taos是一個已經建立好的數據庫連接,在同步模式下無特殊要求。但在異步模式下,需要注意它不會被其它線程使用,否則可能導致不可預計的錯誤,因爲回調函數在API的內部線程中被調用,而TDengine的部分API不是線程安全的。

參數sql是查詢語句,可以在其中使用where子句指定過濾條件。回到開頭的例子,如果我們只想訂閱設備溫度超過 80°C 時的數據,可以這樣寫:

  1. select * from devices where temperature > 80;

注意,這裏沒有指定起始時間,所以會讀到所有時間的數據。如果只想從一天前的數據開始訂閱,而不需要更早的歷史數據,可以再加上一個時間條件:

  1. select * from devices where ts > now - 1d and temperature > 80;

訂閱的topic實際上是它的名字,因爲訂閱功能是在客戶端API中實現的,所以沒必要保證它全局唯一,但需要它在一臺客戶端機器上唯一。

如果名topic的訂閱不存在,參數restart沒有意義;但如果用戶程序創建這個訂閱後退出,當它再次啓動並重新使用這個topic時,restart就會被用於決定是從頭開始讀取數據,還是接續上次的位置進行讀取。本例中,如果restarttrue(非零值),用戶程序肯定會讀到所有數據。但如果這個訂閱之前就存在了,並且已經讀取了一部分數據,且restartfalse(0),用戶程序就不會讀到之前已經讀取的數據了。

taos_subscribe的最後一個參數是以毫秒爲單位的輪詢週期。在同步模式下,如過前後兩次調用taos_consume的時間間隔小於此時間,taos_consume會阻塞,直到間隔超過此時間。異步模式下,這個時間是兩次調用回調函數的最小時間間隔。

taos_subscribe的倒數第二個參數用於用戶程序向回調函數傳遞附加參數,訂閱API不對其做任何處理,只原樣傳遞給回調函數。此參數在同步模式下無意義。

訂閱創建以後,就可以消費其數據了,同步模式下,示例代碼是下面的 else 部分:

  1. if (async) {
  2.   getchar();
  3. } else while(1) {
  4.   TAOS_RES* res = taos_consume(tsub);
  5.   if (res == NULL) {
  6.     printf("failed to consume data.");
  7.     break;
  8.   } else {
  9.     print_result(res, blockFetch);
  10.     getchar();
  11.   }
  12. }

這裏是一個while循環,用戶每按一次回車鍵就調用一次taos_consume,而taos_consume的返回值是查詢到的結果集,與taos_use_result完全相同,例子中使用這個結果集的代碼是函數print_result

  1. void print_result(TAOS_RES* res, int blockFetch) {
  2.   TAOS_ROW row = NULL;
  3.   int num_fields = taos_num_fields(res);
  4.   TAOS_FIELD* fields = taos_fetch_fields(res);
  5.   int nRows = 0;
  6.   if (blockFetch) {
  7.     nRows = taos_fetch_block(res, &row);
  8.     for (int i = 0; i < nRows; i++) {
  9.       char temp[256];
  10.       taos_print_row(temp, row + i, fields, num_fields);
  11.       puts(temp);
  12.     }
  13.   } else {
  14.     while ((row = taos_fetch_row(res))) {
  15.       char temp[256];
  16.       taos_print_row(temp, row, fields, num_fields);puts(temp);
  17.       nRows++;
  18.     }
  19.   }
  20.   printf("%d rows consumed.\n", nRows);
  21. }

其中的 taos_print_row 用於處理訂閱到數據,在我們的例子中,它會打印出所有符合條件的記錄。而異步模式下,消費訂閱到的數據則顯得更爲簡單:

  1. void subscribe_callback(TAOS_SUB* tsub, TAOS_RES *res, void* param, int code) {
  2.   print_result(res, *(int*)param);
  3. }

當要結束一次數據訂閱時,需要調用taos_unsubscribe:

  1. taos_unsubscribe(tsub, keep);

其第二個參數,用於決定是否在客戶端保留訂閱的進度信息,如果大家還記得前面說過“訂閱功能是在客戶端API中實現的”的話,應該可以猜到,如果這個參數是false(0),那無論下次調用taos_subscribe的時的restart參數是什麼,訂閱都只能重新開始了。另外,進度信息的保存位置是{DataDir}/subscribe/,這個目錄下,每個訂閱有一個與其topic同名的文件,刪掉某個文件,同樣會導致下次創建其對應的訂閱時只能重新開始。

代碼介紹完畢,我們來看一下實際的運行效果。假設:

  • 示例代碼已經下載到本地
  • TDengine 也已經在同一臺機器上安裝好
  • 已經按照本文開頭的腳本創建數據庫、超級表和一些子表

則可以在示例代碼所在目錄執行以下命令來編譯並啓動示例程序:

  1. $ make
  2. $ ./subscribe -sql='select * from devices where temperature > 80;'

示例程序啓動後,打開另一個終端窗口,啓動 TDengine 的 shell 向 device1 插入一條溫度爲 90 °C 的數據:

  1. $ taos
  2. > use test;
  3. > insert into device1 values(0, 90);

這時,因爲溫度超過了 80 °C ,您應該可以看到示例程序將它輸出到了屏幕上。您可以繼續插入一些數據觀察示例程序的輸出。

用作消息隊列

本文開頭的例子,是用訂閱實現了一個報警監控的功能,但其實訂閱也可以用在其它場景中,比如:消息隊列。

應用程序可以訂閱數據庫某些表的內容,同一個表也可以被多個應用訂閱,一旦表有新的記錄,應用將立即得到通知。這樣,再把數據插入看做Publish操作,用戶完全可以把TDengine作爲一個消息隊列中間件來使用。

所以,當下次面對需要使用Kafka的場景時,不妨先考慮下TDengine,因爲TDengine除了安裝包超小、運維超簡單的優點外,還有一個Kafka不具備的功能——數據過濾:可以在查詢語句中指定過濾條件,保證讀到的數據都是有用的,不用再在代碼中手寫過濾邏輯了。

與InfluxDB的對比

概念上說,InfluxDB的訂閱和TDengine的訂閱區別很大,我們可以認爲訂閱在InfluxDB中更像一種數據同步機制,而TDengine中的訂閱則是一種數據查詢機制:

  • InfluxDB將收到的數據實時推送給其它節點,TDengine通過輪詢的方式拉取數據,InfluxDB具有更好的實時性。
  • InfluxDB中只能訂閱全部數據,TDengine中可以指定數據過濾條件。
  • InfluxDB中只能訂閱當前時間之後的數據,TDengine中可以在訂閱中讀到歷史數據。

所以,兩相對比,InfluxDB的優勢是實時性,而TDengine則以稍微犧牲實時性爲代價提供了更強大的功能。

限制條件

下面是一些TDengine訂閱功能的侷限,大家需要在使用中注意。

  • 訂閱的查詢語句只能是 select 語句,只能查詢原始數據(不支持聚合函數),只能按時間正序查詢數據。
  • 在滿足應用需求的情況下,請儘量將輪詢週期設置的大一些,否則會對系統性能造成影響。
  • 暫不支持亂序數據,用戶程序可能讀不到使用import方式插入的數據。
  • 如果用戶程序異常退出或沒有正確調用taos_unsubscribe,進度信息可能會有錯誤,這時,後續的同名訂閱可能讀到之前已經讀過的數據。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章