高級(jí)功能
連續(xù)查詢(xún)(Continuous Query)
連續(xù)查詢(xún)是 TDengine 定期自動(dòng)執(zhí)行的查詢(xún),采用滑動(dòng)窗口的方式進(jìn)行計(jì)算,是一種簡(jiǎn)化的時(shí)間驅(qū)動(dòng)的流式計(jì)算。針對(duì)庫(kù)中的表或超級(jí)表,TDengine 可提供定期自動(dòng)執(zhí)行的連續(xù)查詢(xún),用戶(hù)可讓 TDengine 推送查詢(xún)的結(jié)果,也可以將結(jié)果再寫(xiě)回到 TDengine 中。每次執(zhí)行的查詢(xún)是一個(gè)時(shí)間窗口,時(shí)間窗口隨著時(shí)間流動(dòng)向前滑動(dòng)。在定義連續(xù)查詢(xún)的時(shí)候需要指定時(shí)間窗口(time window, 參數(shù)interval)大小和每次前向增量時(shí)間(forward sliding times, 參數(shù)sliding)。
TDengine 的連續(xù)查詢(xún)采用時(shí)間驅(qū)動(dòng)模式,可以直接使用 TAOS SQL 進(jìn)行定義,不需要額外的操作。使用連續(xù)查詢(xún),可以方便快捷地按照時(shí)間窗口生成結(jié)果,從而對(duì)原始采集數(shù)據(jù)進(jìn)行降采樣(down sampling)。用戶(hù)通過(guò) TAOS SQL 定義連續(xù)查詢(xún)以后,TDengine 自動(dòng)在最后的一個(gè)完整的時(shí)間周期末端拉起查詢(xún),并將計(jì)算獲得的結(jié)果推送給用戶(hù)或者寫(xiě)回 TDengine。
TDengine 提供的連續(xù)查詢(xún)與普通流計(jì)算中的時(shí)間窗口計(jì)算具有以下區(qū)別:
- 不同于流計(jì)算的實(shí)時(shí)反饋計(jì)算結(jié)果,連續(xù)查詢(xún)只在時(shí)間窗口關(guān)閉以后才開(kāi)始計(jì)算。例如時(shí)間周期是 1 天,那么當(dāng)天的結(jié)果只會(huì)在 23:59:59 以后才會(huì)生成。
- 如果有歷史記錄寫(xiě)入到已經(jīng)計(jì)算完成的時(shí)間區(qū)間,連續(xù)查詢(xún)并不會(huì)重新進(jìn)行計(jì)算,也不會(huì)重新將結(jié)果推送給用戶(hù)。對(duì)于寫(xiě)回 TDengine 的模式,也不會(huì)更新已經(jīng)存在的計(jì)算結(jié)果。
- 使用連續(xù)查詢(xún)推送結(jié)果的模式,服務(wù)端并不緩存客戶(hù)端計(jì)算狀態(tài),也不提供 Exactly-Once 的語(yǔ)意保證。如果用戶(hù)的應(yīng)用端崩潰,再次拉起的連續(xù)查詢(xún)將只會(huì)從再次拉起的時(shí)間開(kāi)始重新計(jì)算最近的一個(gè)完整的時(shí)間窗口。如果使用寫(xiě)回模式,TDengine 可確保數(shù)據(jù)寫(xiě)回的有效性和連續(xù)性。
使用連續(xù)查詢(xún)
下面以智能電表場(chǎng)景為例介紹連續(xù)查詢(xún)的具體使用方法。假設(shè)我們通過(guò)下列 SQL 語(yǔ)句創(chuàng)建了超級(jí)表和子表:
create table meters (ts timestamp, current float, voltage int, phase float) tags (location binary(64), groupId int);
create table D1001 using meters tags ("Beijing.Chaoyang", 2);
create table D1002 using meters tags ("Beijing.Haidian", 2);
...
我們已經(jīng)知道,可以通過(guò)下面這條 SQL 語(yǔ)句以一分鐘為時(shí)間窗口、30秒為前向增量統(tǒng)計(jì)這些電表的平均電壓。
select avg(voltage) from meters interval(1m) sliding(30s);
每次執(zhí)行這條語(yǔ)句,都會(huì)重新計(jì)算所有數(shù)據(jù)。 如果需要每隔 30 秒執(zhí)行一次來(lái)增量計(jì)算最近一分鐘的數(shù)據(jù),可以把上面的語(yǔ)句改進(jìn)成下面的樣子,每次使用不同的 startTime 并定期執(zhí)行:
select avg(voltage) from meters where ts > {startTime} interval(1m) sliding(30s);
這樣做沒(méi)有問(wèn)題,但 TDengine 提供了更簡(jiǎn)單的方法,只要在最初的查詢(xún)語(yǔ)句前面加上 create table {tableName} as 就可以了,例如:
create table avg_vol as select avg(voltage) from meters interval(1m) sliding(30s);
會(huì)自動(dòng)創(chuàng)建一個(gè)名為 avg_vol 的新表,然后每隔 30 秒,TDengine 會(huì)增量執(zhí)行 as 后面的 SQL 語(yǔ)句,并將查詢(xún)結(jié)果寫(xiě)入這個(gè)表中,用戶(hù)程序后續(xù)只要從 avg_vol 中查詢(xún)數(shù)據(jù)即可。例如:
taos> select * from avg_vol;
ts | avg_voltage_ |
===================================================
2020-07-29 13:37:30.000 | 222.0000000 |
2020-07-29 13:38:00.000 | 221.3500000 |
2020-07-29 13:38:30.000 | 220.1700000 |
2020-07-29 13:39:00.000 | 223.0800000 |
需要注意,查詢(xún)時(shí)間窗口的最小值是10毫秒,沒(méi)有時(shí)間窗口范圍的上限。
此外,TDengine 還支持用戶(hù)指定連續(xù)查詢(xún)的起止時(shí)間。如果不輸入開(kāi)始時(shí)間,連續(xù)查詢(xún)將從第一條原始數(shù)據(jù)所在的時(shí)間窗口開(kāi)始;如果沒(méi)有輸入結(jié)束時(shí)間,連續(xù)查詢(xún)將永久運(yùn)行;如果用戶(hù)指定了結(jié)束時(shí)間,連續(xù)查詢(xún)?cè)谙到y(tǒng)時(shí)間達(dá)到指定的時(shí)間以后停止運(yùn)行。比如使用下面的SQL創(chuàng)建的連續(xù)查詢(xún)將運(yùn)行一小時(shí),之后會(huì)自動(dòng)停止。
create table avg_vol as select avg(voltage) from meters where ts > now and ts <= now + 1h interval(1m) sliding(30s);
需要說(shuō)明的是,上面例子中的 now 是指創(chuàng)建連續(xù)查詢(xún)的時(shí)間,而不是查詢(xún)執(zhí)行的時(shí)間,否則,查詢(xún)就無(wú)法自動(dòng)停止了。另外,為了盡量避免原始數(shù)據(jù)延遲寫(xiě)入導(dǎo)致的問(wèn)題,TDengine 中連續(xù)查詢(xún)的計(jì)算有一定的延遲。也就是說(shuō),一個(gè)時(shí)間窗口過(guò)去后,TDengine 并不會(huì)立即計(jì)算這個(gè)窗口的數(shù)據(jù),所以要稍等一會(huì)(一般不會(huì)超過(guò) 1 分鐘)才能查到計(jì)算結(jié)果。
管理連續(xù)查詢(xún)
用戶(hù)可在控制臺(tái)中通過(guò) show streams 命令來(lái)查看系統(tǒng)中全部運(yùn)行的連續(xù)查詢(xún),并可以通過(guò) kill stream 命令殺掉對(duì)應(yīng)的連續(xù)查詢(xún)。后續(xù)版本會(huì)提供更細(xì)粒度和便捷的連續(xù)查詢(xún)管理命令。
數(shù)據(jù)訂閱(Publisher/Subscriber)
基于數(shù)據(jù)天然的時(shí)間序列特性,TDengine 的數(shù)據(jù)寫(xiě)入(insert)與消息系統(tǒng)的數(shù)據(jù)發(fā)布(pub)邏輯上一致,均可視為系統(tǒng)中插入一條帶時(shí)間戳的新記錄。同時(shí),TDengine 在內(nèi)部嚴(yán)格按照數(shù)據(jù)時(shí)間序列單調(diào)遞增的方式保存數(shù)據(jù)。本質(zhì)上來(lái)說(shuō),TDengine 中里每一張表均可視為一個(gè)標(biāo)準(zhǔn)的消息隊(duì)列。
TDengine 內(nèi)嵌支持輕量級(jí)的消息訂閱與推送服務(wù)。使用系統(tǒng)提供的 API,用戶(hù)可使用普通查詢(xún)語(yǔ)句訂閱數(shù)據(jù)庫(kù)中的一張或多張表。訂閱的邏輯和操作狀態(tài)的維護(hù)均是由客戶(hù)端完成,客戶(hù)端定時(shí)輪詢(xún)服務(wù)器是否有新的記錄到達(dá),有新的記錄到達(dá)就會(huì)將結(jié)果反饋到客戶(hù)。
TDengine 的訂閱與推送服務(wù)的狀態(tài)是客戶(hù)端維持,TDengine 服務(wù)器并不維持。因此如果應(yīng)用重啟,從哪個(gè)時(shí)間點(diǎn)開(kāi)始獲取最新數(shù)據(jù),由應(yīng)用決定。
TDengine 的 API 中,與訂閱相關(guān)的主要有以下三個(gè):
taos_subscribe
taos_consume
taos_unsubscribe
這些API的文檔請(qǐng)見(jiàn) C/C++ Connector,下面仍以智能電表場(chǎng)景為例介紹一下它們的具體用法(超級(jí)表和子表結(jié)構(gòu)請(qǐng)參考上一節(jié)“連續(xù)查詢(xún)”),完整的示例代碼可以在 這里 找到。
如果我們希望當(dāng)某個(gè)電表的電流超過(guò)一定限制(比如 10A)后能得到通知并進(jìn)行一些處理, 有兩種方法:一是分別對(duì)每張子表進(jìn)行查詢(xún),每次查詢(xún)后記錄最后一條數(shù)據(jù)的時(shí)間戳,后續(xù)只查詢(xún)這個(gè)時(shí)間戳之后的數(shù)據(jù):
select * from D1001 where ts > {last_timestamp1} and current > 10;
select * from D1002 where ts > {last_timestamp2} and current > 10;
...
這確實(shí)可行,但隨著電表數(shù)量的增加,查詢(xún)數(shù)量也會(huì)增加,客戶(hù)端和服務(wù)端的性能都會(huì)受到影響,當(dāng)電表數(shù)增長(zhǎng)到一定的程度,系統(tǒng)就無(wú)法承受了。
另一種方法是對(duì)超級(jí)表進(jìn)行查詢(xún)。這樣,無(wú)論有多少電表,都只需一次查詢(xún):
select * from meters where ts > {last_timestamp} and current > 10;
但是,如何選擇 last_timestamp 就成了一個(gè)新的問(wèn)題。因?yàn)?,一方面?shù)據(jù)的產(chǎn)生時(shí)間(也就是數(shù)據(jù)時(shí)間戳)和數(shù)據(jù)入庫(kù)的時(shí)間一般并不相同,有時(shí)偏差還很大;另一方面,不同電表的數(shù)據(jù)到達(dá) TDengine 的時(shí)間也會(huì)有差異。所以,如果我們?cè)诓樵?xún)中使用最慢的那臺(tái)電表的數(shù)據(jù)的時(shí)間戳作為 last_timestamp,就可能重復(fù)讀入其它電表的數(shù)據(jù);如果使用最快的電表的時(shí)間戳,其它電表的數(shù)據(jù)就可能被漏掉。
TDengine 的訂閱功能為上面這個(gè)問(wèn)題提供了一個(gè)徹底的解決方案。
首先是使用 taos_subscribe 創(chuàng)建訂閱:
TAOS_SUB* tsub = NULL;
if (async) {
// create an asynchronized subscription, the callback function will be called every 1s
tsub = taos_subscribe(taos, restart, topic, sql, subscribe_callback, &blockFetch, 1000);
} else {
// create an synchronized subscription, need to call 'taos_consume' manually
tsub = taos_subscribe(taos, restart, topic, sql, NULL, NULL, 0);
}
TDengine 中的訂閱既可以是同步的,也可以是異步的,上面的代碼會(huì)根據(jù)從命令行獲取的參數(shù) async 的值來(lái)決定使用哪種方式。這里,同步的意思是用戶(hù)程序要直接調(diào)用 taos_consume 來(lái)拉取數(shù)據(jù),而異步則由 API 在內(nèi)部的另一個(gè)線程中調(diào)用 taos_consume,然后把拉取到的數(shù)據(jù)交給回調(diào)函數(shù) subscribe_callback去處理。(注意,subscribe_callback 中不宜做較為耗時(shí)的操作,否則有可能導(dǎo)致客戶(hù)端阻塞等不可控的問(wèn)題。)
參數(shù) taos 是一個(gè)已經(jīng)建立好的數(shù)據(jù)庫(kù)連接,在同步模式下無(wú)特殊要求。但在異步模式下,需要注意它不會(huì)被其它線程使用,否則可能導(dǎo)致不可預(yù)計(jì)的錯(cuò)誤,因?yàn)榛卣{(diào)函數(shù)在API的內(nèi)部線程中被調(diào)用,而 TDengine 的部分 API 不是線程安全的。
參數(shù) sql 是查詢(xún)語(yǔ)句,可以在其中使用where子句指定過(guò)濾條件。在我們的例子中,如果只想訂閱電流超過(guò) 10A 時(shí)的數(shù)據(jù),可以這樣寫(xiě):
select * from meters where current > 10;
注意,這里沒(méi)有指定起始時(shí)間,所以會(huì)讀到所有時(shí)間的數(shù)據(jù)。如果只想從一天前的數(shù)據(jù)開(kāi)始訂閱,而不需要更早的歷史數(shù)據(jù),可以再加上一個(gè)時(shí)間條件:
select * from meters where ts > now - 1d and current > 10;
訂閱的 topic 實(shí)際上是它的名字,因?yàn)橛嗛喒δ苁窃诳蛻?hù)端API中實(shí)現(xiàn)的,所以沒(méi)必要保證它全局唯一,但需要它在一臺(tái)客戶(hù)端機(jī)器上唯一。
如果名為 topic 的訂閱不存在,參數(shù) restart 沒(méi)有意義;但如果用戶(hù)程序創(chuàng)建這個(gè)訂閱后退出,當(dāng)它再次啟動(dòng)并重新使用這個(gè) topic 時(shí),restart 就會(huì)被用于決定是從頭開(kāi)始讀取數(shù)據(jù),還是接續(xù)上次的位置進(jìn)行讀取。本例中,如果 restart 是 true(非零值),用戶(hù)程序肯定會(huì)讀到所有數(shù)據(jù)。但如果這個(gè)訂閱之前就存在了,并且已經(jīng)讀取了一部分?jǐn)?shù)據(jù),且 restart 是 false(0),用戶(hù)程序就不會(huì)讀到之前已經(jīng)讀取的數(shù)據(jù)了。
taos_subscribe的最后一個(gè)參數(shù)是以毫秒為單位的輪詢(xún)周期。在同步模式下,如果前后兩次調(diào)用 taos_consume 的時(shí)間間隔小于此時(shí)間,taos_consume 會(huì)阻塞,直到間隔超過(guò)此時(shí)間。異步模式下,這個(gè)時(shí)間是兩次調(diào)用回調(diào)函數(shù)的最小時(shí)間間隔。
taos_subscribe 的倒數(shù)第二個(gè)參數(shù)用于用戶(hù)程序向回調(diào)函數(shù)傳遞附加參數(shù),訂閱 API 不對(duì)其做任何處理,只原樣傳遞給回調(diào)函數(shù)。此參數(shù)在同步模式下無(wú)意義。
訂閱創(chuàng)建以后,就可以消費(fèi)其數(shù)據(jù)了,同步模式下,示例代碼是下面的 else 部分:
if (async) {
getchar();
} else while(1) {
TAOS_RES* res = taos_consume(tsub);
if (res == NULL) {
printf("failed to consume data.");
break;
} else {
print_result(res, blockFetch);
getchar();
}
}
這里是一個(gè) while 循環(huán),用戶(hù)每按一次回車(chē)鍵就調(diào)用一次 taos_consume,而 taos_consume 的返回值是查詢(xún)到的結(jié)果集,與 taos_use_result 完全相同,例子中使用這個(gè)結(jié)果集的代碼是函數(shù) print_result:
void print_result(TAOS_RES* res, int blockFetch) {
TAOS_ROW row = NULL;
int num_fields = taos_num_fields(res);
TAOS_FIELD* fields = taos_fetch_fields(res);
int nRows = 0;
if (blockFetch) {
nRows = taos_fetch_block(res, &row);
for (int i = 0; i < nRows; i++) {
char temp[256];
taos_print_row(temp, row + i, fields, num_fields);
puts(temp);
}
} else {
while ((row = taos_fetch_row(res))) {
char temp[256];
taos_print_row(temp, row, fields, num_fields);
puts(temp);
nRows++;
}
}
printf("%d rows consumed.\n", nRows);
}
其中的 taos_print_row 用于處理訂閱到數(shù)據(jù),在我們的例子中,它會(huì)打印出所有符合條件的記錄。而異步模式下,消費(fèi)訂閱到的數(shù)據(jù)則顯得更為簡(jiǎn)單:
void subscribe_callback(TAOS_SUB* tsub, TAOS_RES *res, void* param, int code) {
print_result(res, *(int*)param);
}
當(dāng)要結(jié)束一次數(shù)據(jù)訂閱時(shí),需要調(diào)用 taos_unsubscribe:
taos_unsubscribe(tsub, keep);
其第二個(gè)參數(shù),用于決定是否在客戶(hù)端保留訂閱的進(jìn)度信息。如果這個(gè)參數(shù)是false(0),那無(wú)論下次調(diào)用 taos_subscribe 時(shí)的 restart 參數(shù)是什么,訂閱都只能重新開(kāi)始。另外,進(jìn)度信息的保存位置是 {DataDir}/subscribe/ 這個(gè)目錄下,每個(gè)訂閱有一個(gè)與其 topic 同名的文件,刪掉某個(gè)文件,同樣會(huì)導(dǎo)致下次創(chuàng)建其對(duì)應(yīng)的訂閱時(shí)只能重新開(kāi)始。
代碼介紹完畢,我們來(lái)看一下實(shí)際的運(yùn)行效果。假設(shè):
- 示例代碼已經(jīng)下載到本地
- TDengine 也已經(jīng)在同一臺(tái)機(jī)器上安裝好
- 示例所需的數(shù)據(jù)庫(kù)、超級(jí)表、子表已經(jīng)全部創(chuàng)建好
則可以在示例代碼所在目錄執(zhí)行以下命令來(lái)編譯并啟動(dòng)示例程序:
make
./subscribe -sql='select * from meters where current > 10;'
示例程序啟動(dòng)后,打開(kāi)另一個(gè)終端窗口,啟動(dòng) TDengine 的 shell 向 D1001 插入一條電流為 12A 的數(shù)據(jù):
$ taos
> use test;
> insert into D1001 values(now, 12, 220, 1);
這時(shí),因?yàn)殡娏鞒^(guò)了 10A,您應(yīng)該可以看到示例程序?qū)⑺敵龅搅似聊簧?。您可以繼續(xù)插入一些數(shù)據(jù)觀察示例程序的輸出。
Java 使用數(shù)據(jù)訂閱功能
訂閱功能也提供了 Java 開(kāi)發(fā)接口,相關(guān)說(shuō)明請(qǐng)見(jiàn) Java Connector。需要注意的是,目前 Java 接口沒(méi)有提供異步訂閱模式,但用戶(hù)程序可以通過(guò)創(chuàng)建 TimerTask 等方式達(dá)到同樣的效果。
下面以一個(gè)示例程序介紹其具體使用方法。它所完成的功能與前面介紹的 C 語(yǔ)言示例基本相同,也是訂閱數(shù)據(jù)庫(kù)中所有電流超過(guò) 10A 的記錄。
準(zhǔn)備數(shù)據(jù)
# 創(chuàng)建 power 庫(kù)
taos> create database power;
# 切換庫(kù)
taos> use power;
# 創(chuàng)建超級(jí)表
taos> create table meters(ts timestamp, current float, voltage int, phase int) tags(location binary(64), groupId int);
# 創(chuàng)建表
taos> create table d1001 using meters tags ("Beijing.Chaoyang", 2);
taos> create table d1002 using meters tags ("Beijing.Haidian", 2);
# 插入測(cè)試數(shù)據(jù)
taos> insert into d1001 values("2020-08-15 12:00:00.000", 12, 220, 1),("2020-08-15 12:10:00.000", 12.3, 220, 2),("2020-08-15 12:20:00.000", 12.2, 220, 1);
taos> insert into d1002 values("2020-08-15 12:00:00.000", 9.9, 220, 1),("2020-08-15 12:10:00.000", 10.3, 220, 1),("2020-08-15 12:20:00.000", 11.2, 220, 1);
# 從超級(jí)表 meters 查詢(xún)電流大于 10A 的記錄
taos> select * from meters where current > 10;
ts | current | voltage | phase | location | groupid |
===========================================================================================================
2020-08-15 12:10:00.000 | 10.30000 | 220 | 1 | Beijing.Haidian | 2 |
2020-08-15 12:20:00.000 | 11.20000 | 220 | 1 | Beijing.Haidian | 2 |
2020-08-15 12:00:00.000 | 12.00000 | 220 | 1 | Beijing.Chaoyang | 2 |
2020-08-15 12:10:00.000 | 12.30000 | 220 | 2 | Beijing.Chaoyang | 2 |
2020-08-15 12:20:00.000 | 12.20000 | 220 | 1 | Beijing.Chaoyang | 2 |
Query OK, 5 row(s) in set (0.004896s)
示例程序
public class SubscribeDemo {
private static final String topic = "topic-meter-current-bg-10";
private static final String sql = "select * from meters where current > 10";
public static void main(String[] args) {
Connection connection = null;
TSDBSubscribe subscribe = null;
try {
Class.forName("com.taosdata.jdbc.TSDBDriver");
Properties properties = new Properties();
properties.setProperty(TSDBDriver.PROPERTY_KEY_CHARSET, "UTF-8");
properties.setProperty(TSDBDriver.PROPERTY_KEY_TIME_ZONE, "UTC-8");
String jdbcUrl = "jdbc:TAOS://127.0.0.1:6030/power?user=root&password=taosdata";
connection = DriverManager.getConnection(jdbcUrl, properties);
subscribe = ((TSDBConnection) connection).subscribe(topic, sql, true); // 創(chuàng)建訂閱
int count = 0;
while (count < 10) {
TimeUnit.SECONDS.sleep(1); // 等待1秒,避免頻繁調(diào)用 consume,給服務(wù)端造成壓力
TSDBResultSet resultSet = subscribe.consume(); // 消費(fèi)數(shù)據(jù)
if (resultSet == null) {
continue;
}
ResultSetMetaData metaData = resultSet.getMetaData();
while (resultSet.next()) {
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
System.out.print(metaData.getColumnLabel(i) + ": " + resultSet.getString(i) + "\t");
}
System.out.println();
count++;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (null != subscribe)
subscribe.close(true); // 關(guān)閉訂閱
if (connection != null)
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
運(yùn)行示例程序,首先,它會(huì)消費(fèi)符合查詢(xún)條件的所有歷史數(shù)據(jù):
# java -jar subscribe.jar
ts: 1597464000000 current: 12.0 voltage: 220 phase: 1 location: Beijing.Chaoyang groupid : 2
ts: 1597464600000 current: 12.3 voltage: 220 phase: 2 location: Beijing.Chaoyang groupid : 2
ts: 1597465200000 current: 12.2 voltage: 220 phase: 1 location: Beijing.Chaoyang groupid : 2
ts: 1597464600000 current: 10.3 voltage: 220 phase: 1 location: Beijing.Haidian groupid : 2
ts: 1597465200000 current: 11.2 voltage: 220 phase: 1 location: Beijing.Haidian groupid : 2
接著,使用 taos 客戶(hù)端向表中新增一條數(shù)據(jù):
# taos
taos> use power;
taos> insert into d1001 values("2020-08-15 12:40:00.000", 12.4, 220, 1);
因?yàn)檫@條數(shù)據(jù)的電流大于10A,示例程序會(huì)將其消費(fèi):
ts: 1597466400000 current: 12.4 voltage: 220 phase: 1 location: Beijing.Chaoyang groupid: 2
緩存(Cache)
TDengine 采用時(shí)間驅(qū)動(dòng)緩存管理策略(First-In-First-Out,F(xiàn)IFO),又稱(chēng)為寫(xiě)驅(qū)動(dòng)的緩存管理機(jī)制。這種策略有別于讀驅(qū)動(dòng)的數(shù)據(jù)緩存模式(Least-Recent-Used,LRU),直接將最近寫(xiě)入的數(shù)據(jù)保存在系統(tǒng)的緩存中。當(dāng)緩存達(dá)到臨界值的時(shí)候,將最早的數(shù)據(jù)批量寫(xiě)入磁盤(pán)。一般意義上來(lái)說(shuō),對(duì)于物聯(lián)網(wǎng)數(shù)據(jù)的使用,用戶(hù)最為關(guān)心最近產(chǎn)生的數(shù)據(jù),即當(dāng)前狀態(tài)。TDengine 充分利用了這一特性,將最近到達(dá)的(當(dāng)前狀態(tài))數(shù)據(jù)保存在緩存中。
TDengine 通過(guò)查詢(xún)函數(shù)向用戶(hù)提供毫秒級(jí)的數(shù)據(jù)獲取能力。直接將最近到達(dá)的數(shù)據(jù)保存在緩存中,可以更加快速地響應(yīng)用戶(hù)針對(duì)最近一條或一批數(shù)據(jù)的查詢(xún)分析,整體上提供更快的數(shù)據(jù)庫(kù)查詢(xún)響應(yīng)能力。從這個(gè)意義上來(lái)說(shuō),可通過(guò)設(shè)置合適的配置參數(shù)將 TDengine 作為數(shù)據(jù)緩存來(lái)使用,而不需要再部署額外的緩存系統(tǒng),可有效地簡(jiǎn)化系統(tǒng)架構(gòu),降低運(yùn)維的成本。需要注意的是,TDengine 重啟以后系統(tǒng)的緩存將被清空,之前緩存的數(shù)據(jù)均會(huì)被批量寫(xiě)入磁盤(pán),緩存的數(shù)據(jù)將不會(huì)像專(zhuān)門(mén)的 key-value 緩存系統(tǒng)再將之前緩存的數(shù)據(jù)重新加載到緩存中。
TDengine 分配固定大小的內(nèi)存空間作為緩存空間,緩存空間可根據(jù)應(yīng)用的需求和硬件資源配置。通過(guò)適當(dāng)?shù)脑O(shè)置緩存空間,TDengine 可以提供極高性能的寫(xiě)入和查詢(xún)的支持。TDengine 中每個(gè)虛擬節(jié)點(diǎn)(virtual node)創(chuàng)建時(shí)分配獨(dú)立的緩存池。每個(gè)虛擬節(jié)點(diǎn)管理自己的緩存池,不同虛擬節(jié)點(diǎn)間不共享緩存池。每個(gè)虛擬節(jié)點(diǎn)內(nèi)部所屬的全部表共享該虛擬節(jié)點(diǎn)的緩存池。
TDengine 將內(nèi)存池按塊劃分進(jìn)行管理,數(shù)據(jù)在內(nèi)存塊里是以行(row)的形式存儲(chǔ)。一個(gè) vnode 的內(nèi)存池是在 vnode 創(chuàng)建時(shí)按塊分配好,而且每個(gè)內(nèi)存塊按照先進(jìn)先出的原則進(jìn)行管理。在創(chuàng)建內(nèi)存池時(shí),塊的大小由系統(tǒng)配置參數(shù) cache 決定;每個(gè) vnode 中內(nèi)存塊的數(shù)目則由配置參數(shù)blocks決定。因此對(duì)于一個(gè) vnode,總的內(nèi)存大小為:cache * blocks。一個(gè) cache block 需要保證每張表能存儲(chǔ)至少幾十條以上記錄,才會(huì)有效率。
你可以通過(guò)函數(shù) last_row() 快速獲取一張表或一張超級(jí)表的最后一條記錄,這樣很便于在大屏顯示各設(shè)備的實(shí)時(shí)狀態(tài)或采集值。例如:
select last_row(voltage) from meters where location='Beijing.Chaoyang';
該 SQL 語(yǔ)句將獲取所有位于北京朝陽(yáng)區(qū)的電表最后記錄的電壓值。
支持和反饋
感謝您成為我們社區(qū)的一員!歡迎您對(duì)本文檔或其他任何 TDengine 錯(cuò)誤進(jìn)行修改或報(bào)告。

