高級功能
連續查詢(Continuous Query)
連續(xu)(xu)查(cha)(cha)(cha)詢(xun)是(shi)(shi) TDengine 定期自(zi)動(dong)(dong)執(zhi)行(xing)的(de)(de)(de)(de)(de)(de)查(cha)(cha)(cha)詢(xun),采(cai)用滑(hua)動(dong)(dong)窗口(kou)(kou)(kou)的(de)(de)(de)(de)(de)(de)方式進行(xing)計算,是(shi)(shi)一種簡化的(de)(de)(de)(de)(de)(de)時(shi)(shi)間(jian)(jian)(jian)(jian)驅(qu)動(dong)(dong)的(de)(de)(de)(de)(de)(de)流(liu)式計算。針對(dui)庫中的(de)(de)(de)(de)(de)(de)表或超級(ji)表,TDengine 可提供定期自(zi)動(dong)(dong)執(zhi)行(xing)的(de)(de)(de)(de)(de)(de)連續(xu)(xu)查(cha)(cha)(cha)詢(xun),用戶(hu)可讓 TDengine 推(tui)送查(cha)(cha)(cha)詢(xun)的(de)(de)(de)(de)(de)(de)結果,也可以(yi)將結果再寫(xie)回到 TDengine 中。每次執(zhi)行(xing)的(de)(de)(de)(de)(de)(de)查(cha)(cha)(cha)詢(xun)是(shi)(shi)一個(ge)時(shi)(shi)間(jian)(jian)(jian)(jian)窗口(kou)(kou)(kou),時(shi)(shi)間(jian)(jian)(jian)(jian)窗口(kou)(kou)(kou)隨著時(shi)(shi)間(jian)(jian)(jian)(jian)流(liu)動(dong)(dong)向(xiang)前(qian)滑(hua)動(dong)(dong)。在定義連續(xu)(xu)查(cha)(cha)(cha)詢(xun)的(de)(de)(de)(de)(de)(de)時(shi)(shi)候需要(yao)指定時(shi)(shi)間(jian)(jian)(jian)(jian)窗口(kou)(kou)(kou)(time window, 參數interval)大小和每次前(qian)向(xiang)增量時(shi)(shi)間(jian)(jian)(jian)(jian)(forward sliding times, 參數sliding)。
TDengine 的連續查詢(xun)(xun)采(cai)用時間驅動模(mo)式,可(ke)以直接使用 TAOS SQL 進行(xing)定義,不需要(yao)額外的操作。使用連續查詢(xun)(xun),可(ke)以方便快捷地(di)按照時間窗(chuang)口生成(cheng)結果(guo)(guo),從而對原始采(cai)集數據進行(xing)降采(cai)樣(down sampling)。用戶通過 TAOS SQL 定義連續查詢(xun)(xun)以后,TDengine 自動在最后的一個完整的時間周期末端拉起查詢(xun)(xun),并將(jiang)計算獲得的結果(guo)(guo)推送給用戶或者寫回 TDengine。
TDengine 提(ti)供的連續查詢與普(pu)通流(liu)計(ji)(ji)算中的時(shi)間窗口(kou)計(ji)(ji)算具有以(yi)下(xia)區別:
- 不同于流計算的實時反饋計算結果,連續查詢只在時間窗口關閉以后才開始計算。例如時間周期是 1 天,那么當天的結果只會在 23:59:59 以后才會生成。
- 如果有歷史記錄寫入到已經計算完成的時間區間,連續查詢并不會重新進行計算,也不會重新將結果推送給用戶。對于寫回 TDengine 的模式,也不會更新已經存在的計算結果。
- 使用連續查詢推送結果的模式,服務端并不緩存客戶端計算狀態,也不提供 Exactly-Once 的語意保證。如果用戶的應用端崩潰,再次拉起的連續查詢將只會從再次拉起的時間開始重新計算最近的一個完整的時間窗口。如果使用寫回模式,TDengine 可確保數據寫回的有效性和連續性。
使用連續查詢
下(xia)(xia)面(mian)以智(zhi)能電表(biao)(biao)場景為例介紹連續查詢的具體使用方法。假設我們通(tong)過下(xia)(xia)列 SQL 語句創建(jian)了(le)超級表(biao)(biao)和子表(biao)(biao):
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);
...
我們已經知道,可以通過下面這(zhe)條 SQL 語句以一分鐘為時(shi)間窗(chuang)口(kou)、30秒為前(qian)向增量統計這(zhe)些電表(biao)的(de)平均(jun)電壓(ya)。
select avg(voltage) from meters interval(1m) sliding(30s);
每次執行這條語句,都會重新計算所有數據。 如果需要每隔 30 秒執行一次來增量計算最近一分鐘的數據,可以把上面的語句改進成下面的樣子,每次使用不同的 startTime 并定期執行:
select avg(voltage) from meters where ts > {startTime} interval(1m) sliding(30s);
這樣做沒有問題,但 TDengine 提供了更簡單的方法,只要在最初的查詢語句前面加上 create table {tableName} as 就可以了,例如:
create table avg_vol as select avg(voltage) from meters interval(1m) sliding(30s);
會自動創建一個名為 avg_vol 的新表,然后每隔 30 秒,TDengine 會增量執行 as 后面的 SQL 語句,并將查詢結果寫入這個表中,用戶程序后續只要從 avg_vol 中查詢數據(ju)即可。例(li)如:
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 |
需要(yao)注(zhu)意,查詢時(shi)(shi)間窗口的(de)最(zui)小值是10毫(hao)秒,沒有時(shi)(shi)間窗口范圍(wei)的(de)上限。
此外,TDengine 還支持用戶指(zhi)(zhi)定連續(xu)查(cha)詢(xun)的起止時(shi)(shi)間。如(ru)果不輸入開(kai)始(shi)時(shi)(shi)間,連續(xu)查(cha)詢(xun)將(jiang)(jiang)從第一條(tiao)原(yuan)始(shi)數據所在的時(shi)(shi)間窗口開(kai)始(shi);如(ru)果沒(mei)有(you)輸入結束(shu)時(shi)(shi)間,連續(xu)查(cha)詢(xun)將(jiang)(jiang)永久運行;如(ru)果用戶指(zhi)(zhi)定了結束(shu)時(shi)(shi)間,連續(xu)查(cha)詢(xun)在系統時(shi)(shi)間達到指(zhi)(zhi)定的時(shi)(shi)間以后(hou)停止運行。比如(ru)使用下面的SQL創(chuang)建的連續(xu)查(cha)詢(xun)將(jiang)(jiang)運行一小時(shi)(shi),之后(hou)會(hui)自動停止。
create table avg_vol as select avg(voltage) from meters where ts > now and ts <= now + 1h interval(1m) sliding(30s);
需要說明的是,上面例子中的 now 是(shi)指(zhi)創建連續(xu)查(cha)詢(xun)的時(shi)間,而不(bu)是(shi)查(cha)詢(xun)執行的時(shi)間,否(fou)則,查(cha)詢(xun)就無法自動停止了。另外,為(wei)了盡(jin)量避免原始數據(ju)(ju)延遲寫入導致(zhi)的問題,TDengine 中連續(xu)查(cha)詢(xun)的計算有(you)一(yi)定的延遲。也就是(shi)說,一(yi)個時(shi)間窗口過去后,TDengine 并不(bu)會(hui)(hui)(hui)立即計算這個窗口的數據(ju)(ju),所以(yi)要(yao)稍(shao)等一(yi)會(hui)(hui)(hui)(一(yi)般不(bu)會(hui)(hui)(hui)超過 1 分鐘(zhong))才能(neng)查(cha)到計算結果。
管理連續查詢
用戶可在控制臺中通過 show streams 命令來查看系統中全部運行的連續查詢,并可以通過 kill stream 命令(ling)殺掉對應的連續查詢(xun)。后續版(ban)本會提供更細粒度和便捷的連續查詢(xun)管理命令(ling)。
數據訂閱(Publisher/Subscriber)
基于數(shu)據(ju)天然的時(shi)間序列(lie)特(te)性,TDengine 的數(shu)據(ju)寫入(insert)與(yu)消(xiao)息系(xi)統(tong)的數(shu)據(ju)發布(pub)邏輯上(shang)一(yi)致,均可(ke)視(shi)為系(xi)統(tong)中插(cha)入一(yi)條帶時(shi)間戳(chuo)的新記(ji)錄。同(tong)時(shi),TDengine 在(zai)內部嚴格按照數(shu)據(ju)時(shi)間序列(lie)單調遞增的方式保存數(shu)據(ju)。本質上(shang)來說(shuo),TDengine 中里每(mei)一(yi)張表(biao)均可(ke)視(shi)為一(yi)個(ge)標準的消(xiao)息隊列(lie)。
TDengine 內嵌支(zhi)持輕量級的消息訂閱(yue)與(yu)推送服務。使用系統提供的 API,用戶可使用普通查(cha)詢(xun)語句訂閱(yue)數據庫(ku)中(zhong)的一張(zhang)或多張(zhang)表。訂閱(yue)的邏(luo)輯和(he)操(cao)作狀態的維護均是由客戶端(duan)完成,客戶端(duan)定(ding)時輪詢(xun)服務器是否有新(xin)(xin)的記錄到(dao)達(da),有新(xin)(xin)的記錄到(dao)達(da)就會將結果反(fan)饋到(dao)客戶。
TDengine 的訂閱(yue)與推送服務的狀態是(shi)客戶端維持,TDengine 服務器并不維持。因此如果應用(yong)重(zhong)啟,從哪個時間點開(kai)始獲(huo)取最新數(shu)據,由應用(yong)決(jue)定。
TDengine 的 API 中,與訂(ding)閱相關的主(zhu)要有以下三個:
taos_subscribe
taos_consume
taos_unsubscribe
這些API的文檔請見 C/C++ Connector,下面(mian)仍(reng)以智能電(dian)表場(chang)景為例介紹一下它們的具體用法(超級表和子表結構請參考上(shang)一節“連(lian)續查(cha)詢”),完整的示(shi)例代(dai)碼可以在 找到(dao)。
如果我們希(xi)望(wang)當某個(ge)電(dian)表的電(dian)流超過一定限制(比如 10A)后(hou)能得到通知并進行一些(xie)處(chu)理, 有兩種方法:一是分(fen)別(bie)對每張(zhang)子表進行查詢,每次查詢后(hou)記錄最后(hou)一條數據的時間戳,后(hou)續只查詢這個(ge)時間戳之后(hou)的數據:
select * from D1001 where ts > {last_timestamp1} and current > 10;
select * from D1002 where ts > {last_timestamp2} and current > 10;
...
這確實可行,但隨著(zhu)電(dian)表(biao)數(shu)量(liang)的增(zeng)加,查詢數(shu)量(liang)也會(hui)增(zeng)加,客(ke)戶端和服務端的性能都會(hui)受(shou)到影(ying)響,當電(dian)表(biao)數(shu)增(zeng)長到一定(ding)的程度,系(xi)統就無法承受(shou)了(le)。
另(ling)一(yi)種方(fang)法(fa)是對超級表進行查(cha)詢(xun)。這樣,無論有多(duo)少電表,都只需一(yi)次查(cha)詢(xun):
select * from meters where ts > {last_timestamp} and current > 10;
但是,如何選擇 last_timestamp 就成了一個新的問題。因為,一方面數據的產生時間(也就是數據時間戳)和數據入庫的時間一般并不相同,有時偏差還很大;另一方面,不同電表的數據到達 TDengine 的時間也會有差異。所以,如果我們在查詢中使用最慢的那臺電表的數據的時間戳作為 last_timestamp,就(jiu)可(ke)能(neng)重復讀入其(qi)(qi)它(ta)電(dian)(dian)表(biao)的數據;如果使用最(zui)快(kuai)的電(dian)(dian)表(biao)的時間戳,其(qi)(qi)它(ta)電(dian)(dian)表(biao)的數據就(jiu)可(ke)能(neng)被漏掉。
TDengine 的訂閱功能為上面這(zhe)個問(wen)題(ti)提供了一個徹(che)底的解決方案。
首先是使用 taos_subscribe 創建訂閱:
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 中的訂閱既可以是同步的,也可以是異步的,上面的代碼會根據從命令行獲取的參數 async 的值來決定使用哪種方式。這里,同步的意思是用戶程序要直接調用 taos_consume 來拉取數據,而異步則由 API 在內部的另一個線程中調用 taos_consume,然后把拉取到的數據交給回調函數 subscribe_callback去處理。(注意,subscribe_callback 中不(bu)宜做較為耗時(shi)的操作,否則有可能導致客戶端阻塞等不(bu)可控的問題。)
參數 taos 是(shi)一個已經建立好的數據庫連接,在(zai)同步模式(shi)下無特殊(shu)要求。但在(zai)異步模式(shi)下,需要注意它不(bu)會被其它線程使用,否(fou)則可能導致不(bu)可預計(ji)的錯誤,因為回(hui)調(diao)函(han)數在(zai)API的內部(bu)(bu)線程中被調(diao)用,而 TDengine 的部(bu)(bu)分(fen) API 不(bu)是(shi)線程安(an)全(quan)的。
參數 sql 是查詢語句,可以在其中使(shi)用(yong)where子句指定過(guo)濾條(tiao)件。在我(wo)們的例(li)子中,如果(guo)只想訂閱電流超過(guo) 10A 時的數據(ju),可以這樣寫(xie):
select * from meters where current > 10;
注意(yi),這里沒有指定起始時間(jian)(jian),所以會讀到(dao)所有時間(jian)(jian)的(de)數(shu)據。如果(guo)只想從一(yi)天(tian)前的(de)數(shu)據開始訂閱,而不(bu)需要(yao)更早的(de)歷(li)史數(shu)據,可以再加(jia)上一(yi)個(ge)時間(jian)(jian)條件(jian):
select * from meters where ts > now - 1d and current > 10;
訂閱的 topic 實際上是它的名字(zi),因為(wei)訂(ding)閱功(gong)能是在(zai)客戶端API中實現(xian)的,所以沒必(bi)要(yao)保證(zheng)它全局唯(wei)一(yi)(yi),但(dan)需要(yao)它在(zai)一(yi)(yi)臺客戶端機器上唯(wei)一(yi)(yi)。
如果名為 topic 的訂閱不存在,參數 restart 沒有意義;但如果用戶程序創建這個訂閱后退出,當它再次啟動并重新使用這個 topic 時,restart 就會被用于決定是從頭開始讀取數據,還是接續上次的位置進行讀取。本例中,如果 restart 是 true(非零值),用戶程序肯定會讀到所有數據。但如果這個訂閱之前就存在了,并且已經讀取了一部分數據,且 restart 是 false(0),用戶程序就不會(hui)讀(du)到之前(qian)已經讀(du)取(qu)的數據了(le)。
taos_subscribe的最后一個參數是以毫秒為單位的輪詢周期。在同步模式下,如果前后兩次調用 taos_consume 的時間間隔小于此時間,taos_consume 會阻塞,直到(dao)間(jian)隔(ge)超過此時(shi)間(jian)。異步模式下(xia),這個時(shi)間(jian)是兩次調(diao)用回調(diao)函數的最小時(shi)間(jian)間(jian)隔(ge)。
taos_subscribe 的倒數(shu)第二個(ge)參(can)數(shu)用(yong)于用(yong)戶程序向回(hui)調函(han)數(shu)傳(chuan)遞(di)附加參(can)數(shu),訂閱 API 不對其做(zuo)任何(he)處(chu)理,只原樣傳(chuan)遞(di)給回(hui)調函(han)數(shu)。此參(can)數(shu)在(zai)同步模(mo)式下無意義。
訂閱創建以(yi)后,就可以(yi)消費(fei)其數據了,同步模式下,示例代碼(ma)是下面的 else 部分(fen):
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();
}
}
這里是一個 while 循環,用戶每按一次回車鍵就調用一次 taos_consume,而 taos_consume 的返回值是查詢到的結果集,與 taos_use_result 完全相同,例子中使用這個結果集的代碼是函數 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 用于處理訂(ding)閱(yue)到數據(ju),在我們的(de)例(li)子中,它會打(da)印出所有符合條件(jian)的(de)記錄。而異(yi)步模式下(xia),消(xiao)費訂(ding)閱(yue)到的(de)數據(ju)則顯(xian)得(de)更為簡單(dan):
void subscribe_callback(TAOS_SUB* tsub, TAOS_RES *res, void* param, int code) {
print_result(res, *(int*)param);
}
當要結束一次數據訂閱時,需要調用 taos_unsubscribe:
taos_unsubscribe(tsub, keep);
其第二個參數,用于決定是否在客戶端保留訂閱的進度信息。如果這個參數是false(0),那無論下次調用 taos_subscribe 時的 restart 參數是什么,訂閱都只能重新開始。另外,進度信息的保存位置是 {DataDir}/subscribe/ 這個目錄下,每個訂閱有一個與其 topic 同名的文(wen)件,刪(shan)掉某個(ge)文(wen)件,同樣會(hui)導致下次創建其對應的訂閱時只能重新(xin)開始。
代碼介紹完畢,我(wo)們來(lai)看一下實(shi)際(ji)的運行效(xiao)果。假設:
- 示例代碼已經下載到本地
- TDengine 也已經在同一臺機器上安裝好
- 示例所需的數據庫、超級表、子表已經全部創建好
則可(ke)以在示例代碼所在目錄(lu)執行以下(xia)命令(ling)來編譯并啟動示例程序:
make
./subscribe -sql='select * from meters where current > 10;'
示例程序啟動后,打開另一個終端窗口,啟動 TDengine 的 shell 向 D1001 插入一條電流為(wei) 12A 的數據:
$ taos
> use test;
> insert into D1001 values(now, 12, 220, 1);
這時,因(yin)為電(dian)流超過了 10A,您應該(gai)可(ke)以看(kan)到示(shi)例程(cheng)序將它輸(shu)出到了屏幕上。您可(ke)以繼續插入一些數(shu)據觀(guan)察示(shi)例程(cheng)序的(de)輸(shu)出。
Java 使用數據訂閱功能
訂閱功能也提供了 Java 開發接口,相關說明請見 Java Connector。需要注意的是,目前 Java 接口沒有提供異步訂閱模式,但用戶程序可以通過創建 TimerTask 等方(fang)式達到同樣的效果。
下面(mian)以一個示例程序介(jie)紹(shao)(shao)其具體使(shi)用方法(fa)。它(ta)所完(wan)成的功能與前面(mian)介(jie)紹(shao)(shao)的 C 語言(yan)示例基本(ben)相(xiang)同,也是訂閱數據庫中所有電流超過 10A 的記錄(lu)。
準備數據
# 創建 power 庫
taos> create database power;
# 切換庫
taos> use power;
# 創建超級表
taos> create table meters(ts timestamp, current float, voltage int, phase int) tags(location binary(64), groupId int);
# 創建表
taos> create table d1001 using meters tags ("Beijing.Chaoyang", 2);
taos> create table d1002 using meters tags ("Beijing.Haidian", 2);
# 插入測試數據
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);
# 從超級表 meters 查詢電流大于 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); // 創建訂閱
int count = 0;
while (count < 10) {
TimeUnit.SECONDS.sleep(1); // 等待1秒,避免頻繁調用 consume,給服務端造成壓力
TSDBResultSet resultSet = subscribe.consume(); // 消費數據
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); // 關閉訂閱
if (connection != null)
connection.close();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
}
運行示例程序(xu),首先,它會消費符合查詢(xun)條件的所有歷史數據:
# 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 客戶端向表中(zhong)新增一條數據(ju):
# taos
taos> use power;
taos> insert into d1001 values("2020-08-15 12:40:00.000", 12.4, 220, 1);
因為(wei)這條數據的(de)電流(liu)大于10A,示例(li)程序會將其消費:
ts: 1597466400000 current: 12.4 voltage: 220 phase: 1 location: Beijing.Chaoyang groupid: 2
緩存(Cache)
TDengine 采(cai)用時(shi)間(jian)驅(qu)動(dong)緩存(cun)管理策略(First-In-First-Out,FIFO),又稱為(wei)寫(xie)驅(qu)動(dong)的(de)(de)緩存(cun)管理機制。這種策略有別于讀驅(qu)動(dong)的(de)(de)數(shu)(shu)據(ju)緩存(cun)模式(Least-Recent-Used,LRU),直接將(jiang)最(zui)(zui)(zui)近寫(xie)入的(de)(de)數(shu)(shu)據(ju)保(bao)存(cun)在系統的(de)(de)緩存(cun)中。當緩存(cun)達到(dao)臨界(jie)值的(de)(de)時(shi)候(hou),將(jiang)最(zui)(zui)(zui)早(zao)的(de)(de)數(shu)(shu)據(ju)批量寫(xie)入磁盤(pan)。一(yi)般(ban)意義(yi)上來說,對于物聯網(wang)數(shu)(shu)據(ju)的(de)(de)使用,用戶(hu)最(zui)(zui)(zui)為(wei)關(guan)心最(zui)(zui)(zui)近產(chan)生(sheng)的(de)(de)數(shu)(shu)據(ju),即當前狀(zhuang)態。TDengine 充分利用了這一(yi)特性(xing),將(jiang)最(zui)(zui)(zui)近到(dao)達的(de)(de)(當前狀(zhuang)態)數(shu)(shu)據(ju)保(bao)存(cun)在緩存(cun)中。
TDengine 通(tong)過查(cha)詢(xun)(xun)函(han)數向(xiang)用戶(hu)提供毫(hao)秒(miao)級的(de)(de)(de)(de)數據獲取能力(li)。直(zhi)接(jie)將(jiang)最近到(dao)達(da)的(de)(de)(de)(de)數據保存(cun)(cun)(cun)在緩(huan)存(cun)(cun)(cun)中,可以更(geng)加快速(su)地響應(ying)用戶(hu)針對最近一(yi)條或一(yi)批數據的(de)(de)(de)(de)查(cha)詢(xun)(xun)分(fen)析,整體上提供更(geng)快的(de)(de)(de)(de)數據庫查(cha)詢(xun)(xun)響應(ying)能力(li)。從這個意義(yi)上來說(shuo),可通(tong)過設(she)置合適的(de)(de)(de)(de)配置參數將(jiang) TDengine 作為數據緩(huan)存(cun)(cun)(cun)來使用,而不需要再部(bu)署額(e)外的(de)(de)(de)(de)緩(huan)存(cun)(cun)(cun)系統,可有效(xiao)地簡化系統架(jia)構,降(jiang)低運(yun)維的(de)(de)(de)(de)成本(ben)。需要注意的(de)(de)(de)(de)是,TDengine 重啟以后系統的(de)(de)(de)(de)緩(huan)存(cun)(cun)(cun)將(jiang)被清(qing)空,之前緩(huan)存(cun)(cun)(cun)的(de)(de)(de)(de)數據均會被批量寫入磁盤,緩(huan)存(cun)(cun)(cun)的(de)(de)(de)(de)數據將(jiang)不會像(xiang)專門的(de)(de)(de)(de) key-value 緩(huan)存(cun)(cun)(cun)系統再將(jiang)之前緩(huan)存(cun)(cun)(cun)的(de)(de)(de)(de)數據重新(xin)加載到(dao)緩(huan)存(cun)(cun)(cun)中。
TDengine 分配固定大小的(de)(de)內(nei)存(cun)(cun)空(kong)間作為緩(huan)(huan)存(cun)(cun)空(kong)間,緩(huan)(huan)存(cun)(cun)空(kong)間可根據應用的(de)(de)需求和硬件資源配置(zhi)。通過(guo)適當的(de)(de)設置(zhi)緩(huan)(huan)存(cun)(cun)空(kong)間,TDengine 可以提供極高(gao)性能的(de)(de)寫(xie)入和查(cha)詢(xun)的(de)(de)支持。TDengine 中每個(ge)(ge)虛(xu)擬(ni)(ni)節(jie)點(dian)(virtual node)創建(jian)時分配獨立的(de)(de)緩(huan)(huan)存(cun)(cun)池(chi)。每個(ge)(ge)虛(xu)擬(ni)(ni)節(jie)點(dian)管理(li)自己的(de)(de)緩(huan)(huan)存(cun)(cun)池(chi),不同虛(xu)擬(ni)(ni)節(jie)點(dian)間不共(gong)享緩(huan)(huan)存(cun)(cun)池(chi)。每個(ge)(ge)虛(xu)擬(ni)(ni)節(jie)點(dian)內(nei)部(bu)所屬的(de)(de)全部(bu)表共(gong)享該虛(xu)擬(ni)(ni)節(jie)點(dian)的(de)(de)緩(huan)(huan)存(cun)(cun)池(chi)。
TDengine 將內存池按塊劃分進行管理,數據在內存塊里是以行(row)的形式存儲。一個 vnode 的內存池是在 vnode 創建時按塊分配好,而且每個內存塊按照先進先出的原則進行管理。在創建內存池時,塊的大小由系統配置參數 cache 決定;每個 vnode 中內存塊的數目則由配置參數blocks決定。因此對于一個 vnode,總的內存大小為:cache * blocks。一(yi)個 cache block 需(xu)要保證(zheng)每(mei)張表能存儲至少(shao)幾十條以上記錄,才會有效(xiao)率。
你(ni)可以通過函(han)數 last_row() 快速獲取一張表(biao)或一張超級(ji)表(biao)的最后一條記(ji)錄(lu),這(zhe)樣很便于(yu)在(zai)大(da)屏顯示各設備的實時狀態或采集值(zhi)。例如:
select last_row(voltage) from meters where location='Beijing.Chaoyang';
該 SQL 語句將(jiang)獲取所有位于北京朝陽區的電(dian)表最后記錄的電(dian)壓(ya)值。

