數(shù)據(jù)模型和整體架構(gòu)
數(shù)據(jù)模型
物聯(lián)網(wǎng)典型場(chǎng)景
在典型的物聯(lián)網(wǎng)、車聯(lián)網(wǎng)、運(yùn)維監(jiān)測(cè)場(chǎng)景中,往往有多種不同類型的數(shù)據(jù)采集設(shè)備,采集一個(gè)到多個(gè)不同的物理量。而同一種采集設(shè)備類型,往往又有多個(gè)具體的采集設(shè)備分布在不同的地點(diǎn)。大數(shù)據(jù)處理系統(tǒng)就是要將各種采集的數(shù)據(jù)匯總,然后進(jìn)行計(jì)算和分析。對(duì)于同一類設(shè)備,其采集的數(shù)據(jù)都是很規(guī)則的。以智能電表為例,假設(shè)每個(gè)智能電表采集電流、電壓、相位三個(gè)量,其采集的數(shù)據(jù)類似如下的表格:
| 設(shè)備ID | 時(shí)間戳 | 采集量 | 標(biāo)簽 | |||
|---|---|---|---|---|---|---|
| Device ID | Time Stamp | current | voltage | phase | location | groupId |
| d1001 | 1538548685000 | 10.3 | 219 | 0.31 | Beijing.Chaoyang | 2 |
| d1002 | 1538548684000 | 10.2 | 220 | 0.23 | Beijing.Chaoyang | 3 |
| d1003 | 1538548686500 | 11.5 | 221 | 0.35 | Beijing.Haidian | 3 |
| d1004 | 1538548685500 | 13.4 | 223 | 0.29 | Beijing.Haidian | 2 |
| d1001 | 1538548695000 | 12.6 | 218 | 0.33 | Beijing.Chaoyang | 2 |
| d1004 | 1538548696600 | 11.8 | 221 | 0.28 | Beijing.Haidian | 2 |
| d1002 | 1538548696650 | 10.3 | 218 | 0.25 | Beijing.Chaoyang | 3 |
| d1001 | 1538548696800 | 12.3 | 221 | 0.31 | Beijing.Chaoyang | 2 |
每一條記錄都有設(shè)備 ID,時(shí)間戳,采集的物理量(如上圖中的電流、電壓、相位),還有與每個(gè)設(shè)備相關(guān)的靜態(tài)標(biāo)簽(如上述表1中的位置 Location 和分組 groupId)。每個(gè)設(shè)備是受外界的觸發(fā),或按照設(shè)定的周期采集數(shù)據(jù)。采集的數(shù)據(jù)點(diǎn)是時(shí)序的,是一個(gè)數(shù)據(jù)流。
數(shù)據(jù)特征
除時(shí)序特征外,仔細(xì)研究發(fā)現(xiàn),物聯(lián)網(wǎng)、車聯(lián)網(wǎng)、運(yùn)維監(jiān)測(cè)類數(shù)據(jù)還具有很多其他明顯的特征:
- 數(shù)據(jù)高度結(jié)構(gòu)化;
- 數(shù)據(jù)極少有更新或刪除操作;
- 無需傳統(tǒng)數(shù)據(jù)庫的事務(wù)處理;
- 相對(duì)互聯(lián)網(wǎng)應(yīng)用,寫多讀少;
- 流量平穩(wěn),根據(jù)設(shè)備數(shù)量和采集頻次,可以預(yù)測(cè)出來;
- 用戶關(guān)注的是一段時(shí)間的趨勢(shì),而不是某一特定時(shí)間點(diǎn)的值;
- 數(shù)據(jù)有保留期限;
- 數(shù)據(jù)的查詢分析一定是基于時(shí)間段和空間區(qū)域;
- 除存儲(chǔ)、查詢操作外,還需要各種統(tǒng)計(jì)和實(shí)時(shí)計(jì)算操作;
- 數(shù)據(jù)量巨大,一天可能采集的數(shù)據(jù)就可以超過 100 億條。
充分利用上述特征,TDengine 采取了經(jīng)特殊優(yōu)化的存儲(chǔ)和計(jì)算設(shè)計(jì)來處理時(shí)序數(shù)據(jù),它將系統(tǒng)處理能力顯著提高,同時(shí)大幅降低了系統(tǒng)運(yùn)維的復(fù)雜度。
關(guān)系型數(shù)據(jù)庫模型
因?yàn)椴杉臄?shù)據(jù)一般是結(jié)構(gòu)化數(shù)據(jù),同時(shí)為降低學(xué)習(xí)門檻,TDengine 采用傳統(tǒng)的關(guān)系型數(shù)據(jù)庫模型管理數(shù)據(jù)。因此用戶需要先創(chuàng)建庫,然后創(chuàng)建表,之后才能插入或查詢數(shù)據(jù)。TDengine 采用的是結(jié)構(gòu)化存儲(chǔ),而不是 NoSQL 的 key-value 存儲(chǔ)。
一個(gè)數(shù)據(jù)采集點(diǎn)一張表
為充分利用其數(shù)據(jù)的時(shí)序性和其他數(shù)據(jù)特點(diǎn),TDengine 要求對(duì)每個(gè)數(shù)據(jù)采集點(diǎn)單獨(dú)建表(比如有一千萬個(gè)智能電表,就需創(chuàng)建一千萬張表,上述表格中的 d1001, d1002, d1003, d1004 都需單獨(dú)建表),用來存儲(chǔ)這個(gè)采集點(diǎn)所采集的時(shí)序數(shù)據(jù)。這種設(shè)計(jì)有幾大優(yōu)點(diǎn):
- 能保證一個(gè)采集點(diǎn)的數(shù)據(jù)在存儲(chǔ)介質(zhì)上是以塊為單位連續(xù)存儲(chǔ)的。如果讀取一個(gè)時(shí)間段的數(shù)據(jù),它能大幅減少隨機(jī)讀取操作,成數(shù)量級(jí)的提升讀取和查詢速度。
- 由于不同采集設(shè)備產(chǎn)生數(shù)據(jù)的過程完全獨(dú)立,每個(gè)設(shè)備的數(shù)據(jù)源是唯一的,一張表也就只有一個(gè)寫入者,這樣就可采用無鎖方式來寫,寫入速度就能大幅提升。
- 對(duì)于一個(gè)數(shù)據(jù)采集點(diǎn)而言,其產(chǎn)生的數(shù)據(jù)是時(shí)序的,因此寫的操作可用追加的方式實(shí)現(xiàn),進(jìn)一步大幅提高數(shù)據(jù)寫入速度。
如果采用傳統(tǒng)的方式,將多個(gè)設(shè)備的數(shù)據(jù)寫入一張表,由于網(wǎng)絡(luò)延時(shí)不可控,不同設(shè)備的數(shù)據(jù)到達(dá)服務(wù)器的時(shí)序是無法保證的,寫入操作是要有鎖保護(hù)的,而且一個(gè)設(shè)備的數(shù)據(jù)是難以保證連續(xù)存儲(chǔ)在一起的。采用一個(gè)數(shù)據(jù)采集點(diǎn)一張表的方式,能最大程度的保證單個(gè)數(shù)據(jù)采集點(diǎn)的插入和查詢的性能是最優(yōu)的。
TDengine 建議用數(shù)據(jù)采集點(diǎn)的名字(如上表中的 D1001)來做表名。每個(gè)數(shù)據(jù)采集點(diǎn)可能同時(shí)采集多個(gè)物理量(如上表中的 current, voltage, phase),每個(gè)物理量對(duì)應(yīng)一張表中的一列,數(shù)據(jù)類型可以是整型、浮點(diǎn)型、字符串等。除此之外,表的第一列必須是時(shí)間戳,即數(shù)據(jù)類型為 timestamp。對(duì)采集的數(shù)據(jù),TDengine 將自動(dòng)按照時(shí)間戳建立索引,但對(duì)采集的物理量不建任何索引。數(shù)據(jù)用列式存儲(chǔ)方式保存。
超級(jí)表:同一類型數(shù)據(jù)采集點(diǎn)的集合
由于一個(gè)數(shù)據(jù)采集點(diǎn)一張表,導(dǎo)致表的數(shù)量巨增,難以管理,而且應(yīng)用經(jīng)常需要做采集點(diǎn)之間的聚合操作,聚合的操作也變得復(fù)雜起來。為解決這個(gè)問題,TDengine 引入超級(jí)表(Super Table,簡(jiǎn)稱為 STable)的概念。
超級(jí)表是指某一特定類型的數(shù)據(jù)采集點(diǎn)的集合。同一類型的數(shù)據(jù)采集點(diǎn),其表的結(jié)構(gòu)是完全一樣的,但每個(gè)表(數(shù)據(jù)采集點(diǎn))的靜態(tài)屬性(標(biāo)簽)是不一樣的。描述一個(gè)超級(jí)表(某一特定類型的數(shù)據(jù)采集點(diǎn)的集合),除需要定義采集量的表結(jié)構(gòu)之外,還需要定義其標(biāo)簽的 schema,標(biāo)簽的數(shù)據(jù)類型可以是整數(shù)、浮點(diǎn)數(shù)、字符串,標(biāo)簽可以有多個(gè),可以事后增加、刪除或修改。如果整個(gè)系統(tǒng)有 N 個(gè)不同類型的數(shù)據(jù)采集點(diǎn),就需要建立 N 個(gè)超級(jí)表。
在 TDengine 的設(shè)計(jì)里,表用來代表一個(gè)具體的數(shù)據(jù)采集點(diǎn),超級(jí)表用來代表一組相同類型的數(shù)據(jù)采集點(diǎn)集合。當(dāng)為某個(gè)具體數(shù)據(jù)采集點(diǎn)創(chuàng)建表時(shí),用戶使用超級(jí)表的定義做模板,同時(shí)指定該具體采集點(diǎn)(表)的標(biāo)簽值。與傳統(tǒng)的關(guān)系型數(shù)據(jù)庫相比,表(一個(gè)數(shù)據(jù)采集點(diǎn))是帶有靜態(tài)標(biāo)簽的,而且這些標(biāo)簽可以事后增加、刪除、修改。一張超級(jí)表包含有多張表,這些表具有相同的時(shí)序數(shù)據(jù) schema,但帶有不同的標(biāo)簽值。
當(dāng)對(duì)多個(gè)具有相同數(shù)據(jù)類型的數(shù)據(jù)采集點(diǎn)進(jìn)行聚合操作時(shí),TDengine 會(huì)先把滿足標(biāo)簽過濾條件的表從超級(jí)表中找出來,然后再掃描這些表的時(shí)序數(shù)據(jù),進(jìn)行聚合操作,這樣需要掃描的數(shù)據(jù)集會(huì)大幅減少,從而顯著提高聚合計(jì)算的性能。
集群與基本邏輯單元
TDengine 的設(shè)計(jì)是基于單個(gè)硬件、軟件系統(tǒng)不可靠,基于任何單臺(tái)計(jì)算機(jī)都無法提供足夠計(jì)算能力和存儲(chǔ)能力處理海量數(shù)據(jù)的假設(shè)進(jìn)行設(shè)計(jì)的。因此 TDengine 從研發(fā)的第一天起,就按照分布式高可靠架構(gòu)進(jìn)行設(shè)計(jì),是支持水平擴(kuò)展的,這樣任何單臺(tái)或多臺(tái)服務(wù)器發(fā)生硬件故障或軟件錯(cuò)誤都不影響系統(tǒng)的可用性和可靠性。同時(shí),通過節(jié)點(diǎn)虛擬化并輔以自動(dòng)化負(fù)載均衡技術(shù),TDengine 能最高效率地利用異構(gòu)集群中的計(jì)算和存儲(chǔ)資源降低硬件投資。
主要邏輯單元
TDengine 分布式架構(gòu)的邏輯結(jié)構(gòu)圖如下:

一個(gè)完整的 TDengine 系統(tǒng)是運(yùn)行在一到多個(gè)物理節(jié)點(diǎn)上的,邏輯上,它包含數(shù)據(jù)節(jié)點(diǎn)(dnode)、TDengine 應(yīng)用驅(qū)動(dòng)(taosc)以及應(yīng)用(app)。系統(tǒng)中存在一到多個(gè)數(shù)據(jù)節(jié)點(diǎn),這些數(shù)據(jù)節(jié)點(diǎn)組成一個(gè)集群(cluster)。應(yīng)用通過 taosc 的 API 與 TDengine 集群進(jìn)行互動(dòng)。下面對(duì)每個(gè)邏輯單元進(jìn)行簡(jiǎn)要介紹。
物理節(jié)點(diǎn)(pnode): pnode 是一獨(dú)立運(yùn)行、擁有自己的計(jì)算、存儲(chǔ)和網(wǎng)絡(luò)能力的計(jì)算機(jī),可以是安裝有OS的物理機(jī)、虛擬機(jī)或 Docker 容器。物理節(jié)點(diǎn)由其配置的 FQDN (Fully Qualified Domain Name)來標(biāo)識(shí)。TDengine 完全依賴 FQDN 來進(jìn)行網(wǎng)絡(luò)通訊,如果不了解 FQDN,請(qǐng)看博文《一篇文章說清楚 TDengine 的 FQDN》。
數(shù)據(jù)節(jié)點(diǎn)(dnode): dnode 是 TDengine 服務(wù)器側(cè)執(zhí)行代碼 taosd 在物理節(jié)點(diǎn)上的一個(gè)運(yùn)行實(shí)例,一個(gè)工作的系統(tǒng)必須有至少一個(gè)數(shù)據(jù)節(jié)點(diǎn)。dnode 包含零到多個(gè)邏輯的虛擬節(jié)點(diǎn)(vnode),零或者至多一個(gè)邏輯的管理節(jié)點(diǎn)(mnode)。dnode 在系統(tǒng)中的唯一標(biāo)識(shí)由實(shí)例的 End Point (EP)決定。EP 是 dnode 所在物理節(jié)點(diǎn)的 FQDN (Fully Qualified Domain Name)和系統(tǒng)所配置的網(wǎng)絡(luò)端口號(hào)(Port)的組合。通過配置不同的端口,一個(gè)物理節(jié)點(diǎn)(一臺(tái)物理機(jī)、虛擬機(jī)或容器)可以運(yùn)行多個(gè)實(shí)例,或有多個(gè)數(shù)據(jù)節(jié)點(diǎn)。
虛擬節(jié)點(diǎn)(vnode): 為更好的支持?jǐn)?shù)據(jù)分片、負(fù)載均衡,防止數(shù)據(jù)過熱或傾斜,數(shù)據(jù)節(jié)點(diǎn)被虛擬化成多個(gè)虛擬節(jié)點(diǎn)(vnode,圖中 V2, V3, V4等)。每個(gè) vnode 都是一個(gè)相對(duì)獨(dú)立的工作單元,是時(shí)序數(shù)據(jù)存儲(chǔ)的基本單元,具有獨(dú)立的運(yùn)行線程、內(nèi)存空間與持久化存儲(chǔ)的路徑。一個(gè) vnode 包含一定數(shù)量的表(數(shù)據(jù)采集點(diǎn))。當(dāng)創(chuàng)建一張新表時(shí),系統(tǒng)會(huì)檢查是否需要?jiǎng)?chuàng)建新的 vnode。一個(gè)數(shù)據(jù)節(jié)點(diǎn)上能創(chuàng)建的 vnode 的數(shù)量取決于該數(shù)據(jù)節(jié)點(diǎn)所在物理節(jié)點(diǎn)的硬件資源。一個(gè) vnode 只屬于一個(gè) DB,但一個(gè) DB 可以有多個(gè) vnode。一個(gè) vnode 除存儲(chǔ)的時(shí)序數(shù)據(jù)外,也保存有所包含的表的 schema、標(biāo)簽值等。一個(gè)虛擬節(jié)點(diǎn)由所屬的數(shù)據(jù)節(jié)點(diǎn)的EP,以及所屬的 VGroup ID 在系統(tǒng)內(nèi)唯一標(biāo)識(shí),由管理節(jié)點(diǎn)創(chuàng)建并管理。
管理節(jié)點(diǎn)(mnode): 一個(gè)虛擬的邏輯單元,負(fù)責(zé)所有數(shù)據(jù)節(jié)點(diǎn)運(yùn)行狀態(tài)的監(jiān)控和維護(hù),以及節(jié)點(diǎn)之間的負(fù)載均衡(圖中 M)。同時(shí),管理節(jié)點(diǎn)也負(fù)責(zé)元數(shù)據(jù)(包括用戶、數(shù)據(jù)庫、表、靜態(tài)標(biāo)簽等)的存儲(chǔ)和管理,因此也稱為 Meta Node。TDengine 集群中可配置多個(gè)(最多不超過 3 個(gè)) mnode,它們自動(dòng)構(gòu)建成為一個(gè)虛擬管理節(jié)點(diǎn)組(圖中 M0, M1, M2)。mnode 間采用 master/slave 的機(jī)制進(jìn)行管理,而且采取強(qiáng)一致方式進(jìn)行數(shù)據(jù)同步, 任何數(shù)據(jù)更新操作只能在 Master 上進(jìn)行。mnode 集群的創(chuàng)建由系統(tǒng)自動(dòng)完成,無需人工干預(yù)。每個(gè) dnode 上至多有一個(gè) mnode,由所屬的數(shù)據(jù)節(jié)點(diǎn)的EP來唯一標(biāo)識(shí)。每個(gè) dnode 通過內(nèi)部消息交互自動(dòng)獲取整個(gè)集群中所有 mnode 所在的 dnode 的EP。
虛擬節(jié)點(diǎn)組(VGroup): 不同數(shù)據(jù)節(jié)點(diǎn)上的 vnode 可以組成一個(gè)虛擬節(jié)點(diǎn)組(vnode group)來保證系統(tǒng)的高可靠。虛擬節(jié)點(diǎn)組內(nèi)采取 master/slave 的方式進(jìn)行管理。寫操作只能在 master vnode 上進(jìn)行,系統(tǒng)采用異步復(fù)制的方式將數(shù)據(jù)同步到 slave vnode,這樣確保了一份數(shù)據(jù)在多個(gè)物理節(jié)點(diǎn)上有拷貝。一個(gè) vgroup 里虛擬節(jié)點(diǎn)個(gè)數(shù)就是數(shù)據(jù)的副本數(shù)。如果一個(gè) DB 的副本數(shù)為 N,系統(tǒng)必須有至少 N 數(shù)據(jù)節(jié)點(diǎn)。副本數(shù)在創(chuàng)建DB時(shí)通過參數(shù) replica 可以指定,缺省為 1。使用 TDengine 的多副本特性,可以不再需要昂貴的磁盤陣列等存儲(chǔ)設(shè)備,就可以獲得同樣的數(shù)據(jù)高可靠性。虛擬節(jié)點(diǎn)組由管理節(jié)點(diǎn)創(chuàng)建、管理,并且由管理節(jié)點(diǎn)分配一個(gè)系統(tǒng)唯一的 ID,VGroup ID。如果兩個(gè)虛擬節(jié)點(diǎn)的 vnode group ID 相同,說明他們屬于同一個(gè)組,數(shù)據(jù)互為備份。虛擬節(jié)點(diǎn)組里虛擬節(jié)點(diǎn)的個(gè)數(shù)是可以動(dòng)態(tài)改變的,容許只有一個(gè),也就是沒有數(shù)據(jù)復(fù)制。VGroup ID 是永遠(yuǎn)不變的,即使一個(gè)虛擬節(jié)點(diǎn)組被刪除,它的ID也不會(huì)被收回重復(fù)利用。
TAOSC: taosc 是 TDengine 給應(yīng)用提供的驅(qū)動(dòng)程序(driver),負(fù)責(zé)處理應(yīng)用與集群的接口交互,提供 C/C++ 語言原生接口,內(nèi)嵌于 JDBC、C#、Python、Go、Node.js 語言連接庫里。應(yīng)用都是通過 taosc 而不是直接連接集群中的數(shù)據(jù)節(jié)點(diǎn)與整個(gè)集群進(jìn)行交互的。這個(gè)模塊負(fù)責(zé)獲取并緩存元數(shù)據(jù);將插入、查詢等請(qǐng)求轉(zhuǎn)發(fā)到正確的數(shù)據(jù)節(jié)點(diǎn);在把結(jié)果返回給應(yīng)用時(shí),還需要負(fù)責(zé)最后一級(jí)的聚合、排序、過濾等操作。對(duì)于 JDBC、C/C++、C#、Python、Go、Node.js 接口而言,這個(gè)模塊是在應(yīng)用所處的物理節(jié)點(diǎn)上運(yùn)行。同時(shí),為支持全分布式的 RESTful 接口,taosc 在 TDengine 集群的每個(gè) dnode 上都有一運(yùn)行實(shí)例。
節(jié)點(diǎn)之間的通訊
通訊方式:TDengine 系統(tǒng)的各個(gè)數(shù)據(jù)節(jié)點(diǎn)之間,以及應(yīng)用驅(qū)動(dòng)與各數(shù)據(jù)節(jié)點(diǎn)之間的通訊是通過 TCP/UDP 進(jìn)行的。因?yàn)榭紤]到物聯(lián)網(wǎng)場(chǎng)景,數(shù)據(jù)寫入的包一般不大,因此 TDengine 除采用 TCP 做傳輸之外,還采用 UDP 方式,因?yàn)?UDP 更加高效,而且不受連接數(shù)的限制。TDengine 實(shí)現(xiàn)了自己的超時(shí)、重傳、確認(rèn)等機(jī)制,以確保 UDP 的可靠傳輸。對(duì)于數(shù)據(jù)量不到15K的數(shù)據(jù)包,采取 UDP 的方式進(jìn)行傳輸,超過 15K 的,或者是查詢類的操作,自動(dòng)采取 TCP 的方式進(jìn)行傳輸。同時(shí),TDengine 根據(jù)配置和數(shù)據(jù)包,會(huì)自動(dòng)對(duì)數(shù)據(jù)進(jìn)行壓縮/解壓縮,數(shù)字簽名/認(rèn)證等處理。對(duì)于數(shù)據(jù)節(jié)點(diǎn)之間的數(shù)據(jù)復(fù)制,只采用 TCP 方式進(jìn)行數(shù)據(jù)傳輸。
FQDN配置:一個(gè)數(shù)據(jù)節(jié)點(diǎn)有一個(gè)或多個(gè) FQDN,可以在系統(tǒng)配置文件 taos.cfg 通過參數(shù)"fqdn"進(jìn)行指定,如果沒有指定,系統(tǒng)將自動(dòng)獲取計(jì)算機(jī)的 hostname 作為其 FQDN。如果節(jié)點(diǎn)沒有配置 FQDN,可以直接將該節(jié)點(diǎn)的配置參數(shù) fqdn 設(shè)置為它的IP地址。但不建議使用 IP,因?yàn)?IP 地址可變,一旦變化,將讓集群無法正常工作。一個(gè)數(shù)據(jù)節(jié)點(diǎn)的 EP(End Point) 由 FQDN + Port 組成。采用 FQDN,需要保證 DNS 服務(wù)正常工作,或者在節(jié)點(diǎn)以及應(yīng)用所在的節(jié)點(diǎn)配置好 hosts 文件。另外,這個(gè)參數(shù)值的長(zhǎng)度需要控制在 96 個(gè)字符以內(nèi)。
端口配置:一個(gè)數(shù)據(jù)節(jié)點(diǎn)對(duì)外的端口由 TDengine 的系統(tǒng)配置參數(shù) serverPort 決定,對(duì)集群內(nèi)部通訊的端口是 serverPort+5。為支持多線程高效的處理 UDP 數(shù)據(jù),每個(gè)對(duì)內(nèi)和對(duì)外的 UDP 連接,都需要占用5個(gè)連續(xù)的端口。
- 集群內(nèi)數(shù)據(jù)節(jié)點(diǎn)之間的數(shù)據(jù)復(fù)制操作占用一個(gè) TCP 端口,是 serverPort+10。
- 集群數(shù)據(jù)節(jié)點(diǎn)對(duì)外提供 RESTful 服務(wù)占用一個(gè) TCP 端口,是 serverPort+11。
- 集群內(nèi)數(shù)據(jù)節(jié)點(diǎn)與 Arbitrator 節(jié)點(diǎn)之間通訊占用一個(gè) TCP 端口,是 serverPort+12。
因此一個(gè)數(shù)據(jù)節(jié)點(diǎn)總的端口范圍為 serverPort 到 serverPort+12,總共 13 個(gè) TCP/UDP 端口。使用時(shí),需要確保防火墻將這些端口打開。每個(gè)數(shù)據(jù)節(jié)點(diǎn)可以配置不同的 serverPort。(詳細(xì)的端口情況請(qǐng)參見 TDengine 2.0 端口說明)
集群對(duì)外連接:TDengine 集群可以容納單個(gè)、多個(gè)甚至幾千個(gè)數(shù)據(jù)節(jié)點(diǎn)。應(yīng)用只需要向集群中任何一個(gè)數(shù)據(jù)節(jié)點(diǎn)發(fā)起連接即可,連接需要提供的網(wǎng)絡(luò)參數(shù)是一數(shù)據(jù)節(jié)點(diǎn)的 End Point(FQDN加配置的端口號(hào))。通過命令行CLI啟動(dòng)應(yīng)用 taos 時(shí),可以通過選項(xiàng)-h來指定數(shù)據(jù)節(jié)點(diǎn)的 FQDN, -P 來指定其配置的端口號(hào),如果端口不配置,將采用 TDengine 的系統(tǒng)配置參數(shù) serverPort。
集群內(nèi)部通訊:各個(gè)數(shù)據(jù)節(jié)點(diǎn)之間通過 TCP/UDP 進(jìn)行連接。一個(gè)數(shù)據(jù)節(jié)點(diǎn)啟動(dòng)時(shí),將獲取 mnode 所在的 dnode 的 EP 信息,然后與系統(tǒng)中的 mnode 建立起連接,交換信息。獲取 mnode 的 EP 信息有三步:
- 檢查 mnodeEpSet.json 文件是否存在,如果不存在或不能正常打開獲得 mnode EP 信息,進(jìn)入第二步;
- 檢查系統(tǒng)配置文件 taos.cfg,獲取節(jié)點(diǎn)配置參數(shù) firstEp、secondEp(這兩個(gè)參數(shù)指定的節(jié)點(diǎn)可以是不帶 mnode 的普通節(jié)點(diǎn),這樣的話,節(jié)點(diǎn)被連接時(shí)會(huì)嘗試重定向到 mnode 節(jié)點(diǎn)),如果不存在或者 taos.cfg 里沒有這兩個(gè)配置參數(shù),或無效,進(jìn)入第三步;
- 將自己的EP設(shè)為 mnode EP,并獨(dú)立運(yùn)行起來。
獲取 mnode EP 列表后,數(shù)據(jù)節(jié)點(diǎn)發(fā)起連接,如果連接成功,則成功加入進(jìn)工作的集群,如果不成功,則嘗試 mnode EP 列表中的下一個(gè)。如果都嘗試了,但連接都仍然失敗,則休眠幾秒后,再進(jìn)行嘗試。
MNODE的選擇:TDengine 邏輯上有管理節(jié)點(diǎn),但沒有單獨(dú)的執(zhí)行代碼,服務(wù)器側(cè)只有一套執(zhí)行代碼 taosd。那么哪個(gè)數(shù)據(jù)節(jié)點(diǎn)會(huì)是管理節(jié)點(diǎn)呢?這是系統(tǒng)自動(dòng)決定的,無需任何人工干預(yù)。原則如下:一個(gè)數(shù)據(jù)節(jié)點(diǎn)啟動(dòng)時(shí),會(huì)檢查自己的 End Point, 并與獲取的 mnode EP List 進(jìn)行比對(duì),如果在其中,該數(shù)據(jù)節(jié)點(diǎn)認(rèn)為自己應(yīng)該啟動(dòng) mnode 模塊,成為 mnode。如果自己的 EP 不在 mnode EP List 里,則不啟動(dòng) mnode 模塊。在系統(tǒng)的運(yùn)行過程中,由于負(fù)載均衡、宕機(jī)等原因,mnode 有可能遷移至新的 dnode,但一切都是透明的,無需人工干預(yù),配置參數(shù)的修改,是 mnode 自己根據(jù)資源做出的決定。
新數(shù)據(jù)節(jié)點(diǎn)的加入:系統(tǒng)有了一個(gè)數(shù)據(jù)節(jié)點(diǎn)后,就已經(jīng)成為一個(gè)工作的系統(tǒng)。添加新的節(jié)點(diǎn)進(jìn)集群時(shí),有兩個(gè)步驟,第一步:使用 TDengine CLI 連接到現(xiàn)有工作的數(shù)據(jù)節(jié)點(diǎn),然后用命令"create dnode"將新的數(shù)據(jù)節(jié)點(diǎn)的 End Point 添加進(jìn)去; 第二步:在新的數(shù)據(jù)節(jié)點(diǎn)的系統(tǒng)配置參數(shù)文件 taos.cfg 里,將 firstEp, secondEp 參數(shù)設(shè)置為現(xiàn)有集群中任意兩個(gè)數(shù)據(jù)節(jié)點(diǎn)的 EP 即可。具體添加的詳細(xì)步驟請(qǐng)見詳細(xì)的用戶手冊(cè)。這樣就把集群一步一步的建立起來。
重定向:無論是 dnode 還是 taosc,最先都是要發(fā)起與 mnode 的連接,但 mnode 是系統(tǒng)自動(dòng)創(chuàng)建并維護(hù)的,因此對(duì)于用戶來說,并不知道哪個(gè) dnode 在運(yùn)行 mnode。TDengine 只要求向系統(tǒng)中任何一個(gè)工作的 dnode 發(fā)起連接即可。因?yàn)槿魏我粋€(gè)正在運(yùn)行的 dnode,都維護(hù)有目前運(yùn)行的 mnode EP List。當(dāng)收到一個(gè)來自新啟動(dòng)的 dnode 或 taosc 的連接請(qǐng)求,如果自己不是 mnode,則將 mnode EP List 回復(fù)給對(duì)方,taosc 或新啟動(dòng)的 dnode 收到這個(gè) list, 就重新嘗試建立連接。當(dāng) mnode EP List 發(fā)生改變,通過節(jié)點(diǎn)之間的消息交互,各個(gè)數(shù)據(jù)節(jié)點(diǎn)就很快獲取最新列表,并通知 taosc。
一個(gè)典型的消息流程
為解釋 vnode、mnode、taosc 和應(yīng)用之間的關(guān)系以及各自扮演的角色,下面對(duì)寫入數(shù)據(jù)這個(gè)典型操作的流程進(jìn)行剖析。

- 應(yīng)用通過 JDBC 或其他API接口發(fā)起插入數(shù)據(jù)的請(qǐng)求。
- taosc 會(huì)檢查緩存,看是否保存有該表的 meta data。如果有,直接到第 4 步。如果沒有,taosc 將向 mnode 發(fā)出 get meta-data 請(qǐng)求。
- mnode 將該表的 meta-data 返回給 taosc。Meta-data 包含有該表的 schema, 而且還有該表所屬的 vgroup信息(vnode ID 以及所在的 dnode 的 End Point,如果副本數(shù)為 N,就有 N 組 End Point)。如果 taosc 遲遲得不到 mnode 回應(yīng),而且存在多個(gè) mnode, taosc 將向下一個(gè) mnode 發(fā)出請(qǐng)求。
- taosc 向 master vnode 發(fā)起插入請(qǐng)求。
- vnode 插入數(shù)據(jù)后,給 taosc 一個(gè)應(yīng)答,表示插入成功。如果 taosc 遲遲得不到 vnode 的回應(yīng),taosc 會(huì)認(rèn)為該節(jié)點(diǎn)已經(jīng)離線。這種情況下,如果被插入的數(shù)據(jù)庫有多個(gè)副本,taosc 將向 vgroup 里下一個(gè) vnode 發(fā)出插入請(qǐng)求。
- taosc 通知 APP,寫入成功。
對(duì)于第二和第三步,taosc 啟動(dòng)時(shí),并不知道 mnode 的 End Point,因此會(huì)直接向配置的集群對(duì)外服務(wù)的 End Point 發(fā)起請(qǐng)求。如果接收到該請(qǐng)求的 dnode 并沒有配置 mnode,該 dnode 會(huì)在回復(fù)的消息中告知mnode EP 列表,這樣 taosc 會(huì)重新向新的 mnode 的 EP 發(fā)出獲取 meta-data 的請(qǐng)求。
對(duì)于第四和第五步,沒有緩存的情況下,taosc 無法知道虛擬節(jié)點(diǎn)組里誰是 master,就假設(shè)第一個(gè) vnodeID 就是 master,向它發(fā)出請(qǐng)求。如果接收到請(qǐng)求的 vnode 并不是 master,它會(huì)在回復(fù)中告知誰是 master,這樣 taosc 就向建議的 master vnode 發(fā)出請(qǐng)求。一旦得到插入成功的回復(fù),taosc 會(huì)緩存 master 節(jié)點(diǎn)的信息。
上述是插入數(shù)據(jù)的流程,查詢、計(jì)算的流程也完全一致。taosc 把這些復(fù)雜的流程全部封裝屏蔽了,對(duì)于應(yīng)用來說無感知也無需任何特別處理。
通過 taosc 緩存機(jī)制,只有在第一次對(duì)一張表操作時(shí),才需要訪問 mnode,因此 mnode 不會(huì)成為系統(tǒng)瓶頸。但因?yàn)?schema 有可能變化,而且 vgroup 有可能發(fā)生改變(比如負(fù)載均衡發(fā)生),因此 taosc 會(huì)定時(shí)和mnode 交互,自動(dòng)更新緩存。
存儲(chǔ)模型與數(shù)據(jù)分區(qū)、分片
存儲(chǔ)模型
TDengine 存儲(chǔ)的數(shù)據(jù)包括采集的時(shí)序數(shù)據(jù)以及庫、表相關(guān)的元數(shù)據(jù)、標(biāo)簽數(shù)據(jù)等,這些數(shù)據(jù)具體分為三部分:
- 時(shí)序數(shù)據(jù):存放于 vnode 里,由 data、head 和 last 三個(gè)文件組成,數(shù)據(jù)量大,查詢量取決于應(yīng)用場(chǎng)景。容許亂序?qū)懭?,但暫時(shí)不支持刪除操作,并且僅在 update 參數(shù)設(shè)置為 1 時(shí)允許更新操作。通過采用一個(gè)采集點(diǎn)一張表的模型,一個(gè)時(shí)間段的數(shù)據(jù)是連續(xù)存儲(chǔ),對(duì)單張表的寫入是簡(jiǎn)單的追加操作,一次讀,可以讀到多條記錄,這樣保證對(duì)單個(gè)采集點(diǎn)的插入和查詢操作,性能達(dá)到最優(yōu)。
- 標(biāo)簽數(shù)據(jù):存放于 vnode 里的 meta 文件,支持增刪改查四個(gè)標(biāo)準(zhǔn)操作。數(shù)據(jù)量不大,有 N 張表,就有 N 條記錄,因此可以全內(nèi)存存儲(chǔ)。如果標(biāo)簽過濾操作很多,查詢將十分頻繁,因此 TDengine 支持多核多線程并發(fā)查詢。只要計(jì)算資源足夠,即使有數(shù)千萬張表,過濾結(jié)果能毫秒級(jí)返回。
- 元數(shù)據(jù):存放于 mnode 里,包含系統(tǒng)節(jié)點(diǎn)、用戶、DB、Table Schema 等信息,支持增刪改查四個(gè)標(biāo)準(zhǔn)操作。這部分?jǐn)?shù)據(jù)的量不大,可以全內(nèi)存保存,而且由于客戶端有緩存,查詢量也不大。因此目前的設(shè)計(jì)雖是集中式存儲(chǔ)管理,但不會(huì)構(gòu)成性能瓶頸。
與典型的 NoSQL 存儲(chǔ)模型相比,TDengine 將標(biāo)簽數(shù)據(jù)與時(shí)序數(shù)據(jù)完全分離存儲(chǔ),它具有兩大優(yōu)勢(shì):
- 能夠極大地降低標(biāo)簽數(shù)據(jù)存儲(chǔ)的冗余度:一般的 NoSQL 數(shù)據(jù)庫或時(shí)序數(shù)據(jù)庫,采用的 K-V 存儲(chǔ),其中的 Key 包含時(shí)間戳、設(shè)備 ID、各種標(biāo)簽。每條記錄都帶有這些重復(fù)的內(nèi)容,浪費(fèi)存儲(chǔ)空間。而且如果應(yīng)用要在歷史數(shù)據(jù)上增加、修改或刪除標(biāo)簽,需要遍歷數(shù)據(jù),重寫一遍,操作成本極其昂貴。
- 能夠?qū)崿F(xiàn)極為高效的多表之間的聚合查詢:做多表之間聚合查詢時(shí),先把符合標(biāo)簽過濾條件的表查找出來,然后再查找這些表相應(yīng)的數(shù)據(jù)塊,這樣大幅減少要掃描的數(shù)據(jù)集,從而大幅提高查詢效率。而且標(biāo)簽數(shù)據(jù)采用全內(nèi)存的結(jié)構(gòu)進(jìn)行管理和維護(hù),千萬級(jí)別規(guī)模的標(biāo)簽數(shù)據(jù)查詢可以在毫秒級(jí)別返回。
數(shù)據(jù)分片
對(duì)于海量的數(shù)據(jù)管理,為實(shí)現(xiàn)水平擴(kuò)展,一般都需要采取分片(Sharding)分區(qū)(Partitioning)策略。TDengine 是通過 vnode 來實(shí)現(xiàn)數(shù)據(jù)分片的,通過一個(gè)時(shí)間段一個(gè)數(shù)據(jù)文件來實(shí)現(xiàn)時(shí)序數(shù)據(jù)分區(qū)的。
vnode(虛擬數(shù)據(jù)節(jié)點(diǎn))負(fù)責(zé)為采集的時(shí)序數(shù)據(jù)提供寫入、查詢和計(jì)算功能。為便于負(fù)載均衡、數(shù)據(jù)恢復(fù)、支持異構(gòu)環(huán)境,TDengine 將一個(gè)數(shù)據(jù)節(jié)點(diǎn)根據(jù)其計(jì)算和存儲(chǔ)資源切分為多個(gè) vnode。這些 vnode 的管理是TDengine 自動(dòng)完成的,對(duì)應(yīng)用完全透明。
對(duì)于單獨(dú)一個(gè)數(shù)據(jù)采集點(diǎn),無論其數(shù)據(jù)量多大,一個(gè) vnode(或 vnode group, 如果副本數(shù)大于 1)有足夠的計(jì)算資源和存儲(chǔ)資源來處理(如果每秒生成一條 16 字節(jié)的記錄,一年產(chǎn)生的原始數(shù)據(jù)不到 0.5G),因此 TDengine 將一張表(一個(gè)數(shù)據(jù)采集點(diǎn))的所有數(shù)據(jù)都存放在一個(gè) vnode 里,而不會(huì)讓同一個(gè)采集點(diǎn)的數(shù)據(jù)分布到兩個(gè)或多個(gè) dnode 上。而且一個(gè) vnode 可存儲(chǔ)多個(gè)數(shù)據(jù)采集點(diǎn)(表)的數(shù)據(jù),一個(gè) vnode 可容納的表的數(shù)目的上限為一百萬。設(shè)計(jì)上,一個(gè) vnode 里所有的表都屬于同一個(gè) DB。一個(gè)數(shù)據(jù)節(jié)點(diǎn)上,除非特殊配置,一個(gè) DB 擁有的 vnode 數(shù)目不會(huì)超過系統(tǒng)核的數(shù)目。
創(chuàng)建 DB 時(shí),系統(tǒng)并不會(huì)馬上分配資源。但當(dāng)創(chuàng)建一張表時(shí),系統(tǒng)將看是否有已經(jīng)分配的 vnode, 且該 vnode 是否有空余的表空間,如果有,立即在該有空位的 vnode 創(chuàng)建表。如果沒有,系統(tǒng)將從集群中,根據(jù)當(dāng)前的負(fù)載情況,在一個(gè) dnode 上創(chuàng)建一新的 vnode, 然后創(chuàng)建表。如果DB有多個(gè)副本,系統(tǒng)不是只創(chuàng)建一個(gè) vnode,而是一個(gè) vgroup (虛擬數(shù)據(jù)節(jié)點(diǎn)組)。系統(tǒng)對(duì) vnode 的數(shù)目沒有任何限制,僅僅受限于物理節(jié)點(diǎn)本身的計(jì)算和存儲(chǔ)資源。
每張表的 meta data(包含 schema, 標(biāo)簽等)也存放于 vnode 里,而不是集中存放于 mnode,實(shí)際上這是對(duì) Meta 數(shù)據(jù)的分片,這樣便于高效并行的進(jìn)行標(biāo)簽過濾操作。
數(shù)據(jù)分區(qū)
TDengine 除 vnode 分片之外,還對(duì)時(shí)序數(shù)據(jù)按照時(shí)間段進(jìn)行分區(qū)。每個(gè)數(shù)據(jù)文件只包含一個(gè)時(shí)間段的時(shí)序數(shù)據(jù),時(shí)間段的長(zhǎng)度由 DB 的配置參數(shù) days 決定。這種按時(shí)間段分區(qū)的方法還便于高效實(shí)現(xiàn)數(shù)據(jù)的保留策略,只要數(shù)據(jù)文件超過規(guī)定的天數(shù)(系統(tǒng)配置參數(shù) keep),將被自動(dòng)刪除。而且不同的時(shí)間段可以存放于不同的路徑和存儲(chǔ)介質(zhì),以便于大數(shù)據(jù)的冷熱管理,實(shí)現(xiàn)多級(jí)存儲(chǔ)。
總的來說,TDengine 是通過 vnode 以及時(shí)間兩個(gè)維度,對(duì)大數(shù)據(jù)進(jìn)行切分,便于并行高效的管理,實(shí)現(xiàn)水平擴(kuò)展。
負(fù)載均衡
每個(gè) dnode 都定時(shí)向 mnode(虛擬管理節(jié)點(diǎn))報(bào)告其狀態(tài)(包括硬盤空間、內(nèi)存大小、CPU、網(wǎng)絡(luò)、虛擬節(jié)點(diǎn)個(gè)數(shù)等),因此 mnode 了解整個(gè)集群的狀態(tài)?;谡w狀態(tài),當(dāng) mnode 發(fā)現(xiàn)某個(gè)dnode負(fù)載過重,它會(huì)將dnode 上的一個(gè)或多個(gè) vnode 挪到其他 dnode。在挪動(dòng)過程中,對(duì)外服務(wù)繼續(xù)進(jìn)行,數(shù)據(jù)插入、查詢和計(jì)算操作都不受影響。
如果 mnode 一段時(shí)間沒有收到 dnode 的狀態(tài)報(bào)告,mnode 會(huì)認(rèn)為這個(gè) dnode 已經(jīng)離線。如果離線時(shí)間超過一定時(shí)長(zhǎng)(時(shí)長(zhǎng)由配置參數(shù) offlineThreshold 決定),該 dnode 將被 mnode 強(qiáng)制剔除出集群。該dnode 上的 vnodes 如果副本數(shù)大于 1,系統(tǒng)將自動(dòng)在其他 dnode 上創(chuàng)建新的副本,以保證數(shù)據(jù)的副本數(shù)。如果該 dnode 上還有 mnode, 而且 mnode 的副本數(shù)大于1,系統(tǒng)也將自動(dòng)在其他 dnode 上創(chuàng)建新的 mnode, 以保證 mnode 的副本數(shù)。
當(dāng)新的數(shù)據(jù)節(jié)點(diǎn)被添加進(jìn)集群,因?yàn)樾碌挠?jì)算和存儲(chǔ)被添加進(jìn)來,系統(tǒng)也將自動(dòng)啟動(dòng)負(fù)載均衡流程。
負(fù)載均衡過程無需任何人工干預(yù),應(yīng)用也無需重啟,將自動(dòng)連接新的節(jié)點(diǎn),完全透明。
提示:負(fù)載均衡由參數(shù) balance 控制,決定開啟/關(guān)閉自動(dòng)負(fù)載均衡。
數(shù)據(jù)寫入與復(fù)制流程
如果一個(gè)數(shù)據(jù)庫有 N 個(gè)副本,那一個(gè)虛擬節(jié)點(diǎn)組就有 N 個(gè)虛擬節(jié)點(diǎn),但是只有一個(gè)是 master,其他都是 slave。當(dāng)應(yīng)用將新的記錄寫入系統(tǒng)時(shí),只有 master vnode 能接受寫的請(qǐng)求。如果 slave vnode 收到寫的請(qǐng)求,系統(tǒng)將通知 taosc 需要重新定向。
Master Vnode 寫入流程
Master Vnode 遵循下面的寫入流程:

- master vnode 收到應(yīng)用的數(shù)據(jù)插入請(qǐng)求,驗(yàn)證OK,進(jìn)入下一步;
- 如果系統(tǒng)配置參數(shù) walLevel 大于 0,vnode 將把該請(qǐng)求的原始數(shù)據(jù)包寫入數(shù)據(jù)庫日志文件 WAL。如果 walLevel 設(shè)置為 2,而且 fsync 設(shè)置為 0,TDengine 還將 WAL 數(shù)據(jù)立即落盤,以保證即使宕機(jī),也能從數(shù)據(jù)庫日志文件中恢復(fù)數(shù)據(jù),避免數(shù)據(jù)的丟失;
- 如果有多個(gè)副本,vnode 將把數(shù)據(jù)包轉(zhuǎn)發(fā)給同一虛擬節(jié)點(diǎn)組內(nèi)的 slave vnodes, 該轉(zhuǎn)發(fā)包帶有數(shù)據(jù)的版本號(hào)(version);
- 寫入內(nèi)存,并將記錄加入到 skip list;
- master vnode 返回確認(rèn)信息給應(yīng)用,表示寫入成功。
- 如果第 2、3、4 步中任何一步失敗,將直接返回錯(cuò)誤給應(yīng)用。
Slave Vnode 寫入流程
對(duì)于 slave vnode,寫入流程是:

- slave vnode 收到 Master vnode 轉(zhuǎn)發(fā)了的數(shù)據(jù)插入請(qǐng)求。檢查 last version 是否與 master 一致,如果一致,進(jìn)入下一步。如果不一致,需要進(jìn)入同步狀態(tài)。
- 如果系統(tǒng)配置參數(shù) walLevel 大于 0,vnode 將把該請(qǐng)求的原始數(shù)據(jù)包寫入數(shù)據(jù)庫日志文件 WAL。如果 walLevel 設(shè)置為 2,而且 fsync 設(shè)置為 0,TDengine 還將 WAL 數(shù)據(jù)立即落盤,以保證即使宕機(jī),也能從數(shù)據(jù)庫日志文件中恢復(fù)數(shù)據(jù),避免數(shù)據(jù)的丟失。
- 寫入內(nèi)存,更新內(nèi)存中的 skip list。
與 master vnode 相比,slave vnode 不存在轉(zhuǎn)發(fā)環(huán)節(jié),也不存在回復(fù)確認(rèn)環(huán)節(jié),少了兩步。但寫內(nèi)存與 WAL 是完全一樣的。
主從選擇
Vnode 會(huì)保持一個(gè)數(shù)據(jù)版本號(hào)(version),對(duì)內(nèi)存數(shù)據(jù)進(jìn)行持久化存儲(chǔ)時(shí),對(duì)該版本號(hào)也進(jìn)行持久化存儲(chǔ)。每個(gè)數(shù)據(jù)更新操作,無論是采集的時(shí)序數(shù)據(jù)還是元數(shù)據(jù),這個(gè)版本號(hào)將增加 1。
一個(gè) vnode 啟動(dòng)時(shí),角色(master、slave) 是不定的,數(shù)據(jù)是處于未同步狀態(tài),它需要與虛擬節(jié)點(diǎn)組內(nèi)其他節(jié)點(diǎn)建立 TCP 連接,并互相交換 status,其中包括 version 和自己的角色。通過 status 的交換,系統(tǒng)進(jìn)入選主流程,規(guī)則如下:
- 如果只有一個(gè)副本,該副本永遠(yuǎn)就是 master
- 所有副本都在線時(shí),版本最高的被選為 master
- 在線的虛擬節(jié)點(diǎn)數(shù)過半,而且有虛擬節(jié)點(diǎn)是 slave 的話,該虛擬節(jié)點(diǎn)自動(dòng)成為 master
- 對(duì)于 2 和 3,如果多個(gè)虛擬節(jié)點(diǎn)滿足成為 master 的要求,那么虛擬節(jié)點(diǎn)組的節(jié)點(diǎn)列表里,最前面的選為 master
更多的關(guān)于數(shù)據(jù)復(fù)制的流程,請(qǐng)見TDengine 2.0 數(shù)據(jù)復(fù)制模塊設(shè)計(jì)。
同步復(fù)制
對(duì)于數(shù)據(jù)一致性要求更高的場(chǎng)景,異步數(shù)據(jù)復(fù)制無法滿足要求,因?yàn)橛袠O小的概率丟失數(shù)據(jù),因此 TDengine 提供同步復(fù)制的機(jī)制供用戶選擇。在創(chuàng)建數(shù)據(jù)庫時(shí),除指定副本數(shù) replica 之外,用戶還需要指定新的參數(shù) quorum。如果 quorum 大于1,它表示每次master轉(zhuǎn)發(fā)給副本時(shí),需要等待 quorum-1 個(gè)回復(fù)確認(rèn),才能通知應(yīng)用,數(shù)據(jù)在 slave 已經(jīng)寫入成功。如果在一定的時(shí)間內(nèi),得不到 quorum-1 個(gè)回復(fù)確認(rèn),master vnode 將返回錯(cuò)誤給應(yīng)用。
采用同步復(fù)制,系統(tǒng)的性能會(huì)有所下降,而且 latency 會(huì)增加。因?yàn)樵獢?shù)據(jù)要強(qiáng)一致,mnode 之間的數(shù)據(jù)同步缺省就是采用的同步復(fù)制。
緩存與持久化
緩存
TDengine 采用時(shí)間驅(qū)動(dòng)緩存管理策略(First-In-First-Out,F(xiàn)IFO),又稱為寫驅(qū)動(dòng)的緩存管理機(jī)制。這種策略有別于讀驅(qū)動(dòng)的數(shù)據(jù)緩存模式(Least-Recent-Used,LRU),直接將最近寫入的數(shù)據(jù)保存在系統(tǒng)的緩存中。當(dāng)緩存達(dá)到臨界值的時(shí)候,將最早的數(shù)據(jù)批量寫入磁盤。一般意義上來說,對(duì)于物聯(lián)網(wǎng)數(shù)據(jù)的使用,用戶最為關(guān)心的是剛產(chǎn)生的數(shù)據(jù),即當(dāng)前狀態(tài)。TDengine 充分利用這一特性,將最近到達(dá)的(當(dāng)前狀態(tài))數(shù)據(jù)保存在緩存中。
TDengine 通過查詢函數(shù)向用戶提供毫秒級(jí)的數(shù)據(jù)獲取能力。直接將最近到達(dá)的數(shù)據(jù)保存在緩存中,可以更加快速地響應(yīng)用戶針對(duì)最近一條或一批數(shù)據(jù)的查詢分析,整體上提供更快的數(shù)據(jù)庫查詢響應(yīng)能力。從這個(gè)意義上來說,可通過設(shè)置合適的配置參數(shù)將 TDengine 作為數(shù)據(jù)緩存來使用,而不需要再部署 Redis 或其他額外的緩存系統(tǒng),可有效地簡(jiǎn)化系統(tǒng)架構(gòu),降低運(yùn)維的成本。需要注意的是,TDengine 重啟以后系統(tǒng)的緩存將被清空,之前緩存的數(shù)據(jù)均會(huì)被批量寫入磁盤,緩存的數(shù)據(jù)將不會(huì)像專門的 key-value 緩存系統(tǒng)再將之前緩存的數(shù)據(jù)重新加載到緩存中。
每個(gè) vnode 有自己獨(dú)立的內(nèi)存,而且由多個(gè)固定大小的內(nèi)存塊組成,不同 vnode 之間完全隔離。數(shù)據(jù)寫入時(shí),類似于日志的寫法,數(shù)據(jù)被順序追加寫入內(nèi)存,但每個(gè) vnode 維護(hù)有自己的 skip list,便于迅速查找。當(dāng)三分之一以上的內(nèi)存塊寫滿時(shí),啟動(dòng)落盤操作,而且后續(xù)寫的操作在新的內(nèi)存塊進(jìn)行。這樣,一個(gè) vnode 里有三分之一內(nèi)存塊是保留有最近的數(shù)據(jù)的,以達(dá)到緩存、快速查找的目的。一個(gè) vnode 的內(nèi)存塊的個(gè)數(shù)由配置參數(shù) blocks 決定,內(nèi)存塊的大小由配置參數(shù) cache 決定。
持久化存儲(chǔ)
TDengine 采用數(shù)據(jù)驅(qū)動(dòng)的方式讓緩存中的數(shù)據(jù)寫入硬盤進(jìn)行持久化存儲(chǔ)。當(dāng) vnode 中緩存的數(shù)據(jù)達(dá)到一定規(guī)模時(shí),為了不阻塞后續(xù)數(shù)據(jù)的寫入,TDengine 也會(huì)拉起落盤線程將緩存的數(shù)據(jù)寫入持久化存儲(chǔ)。TDengine 在數(shù)據(jù)落盤時(shí)會(huì)打開新的數(shù)據(jù)庫日志文件,在落盤成功后則會(huì)刪除老的數(shù)據(jù)庫日志文件,避免日志文件無限制地增長(zhǎng)。
為充分利用時(shí)序數(shù)據(jù)特點(diǎn),TDengine 將一個(gè) vnode 保存在持久化存儲(chǔ)的數(shù)據(jù)切分成多個(gè)文件,每個(gè)文件只保存固定天數(shù)的數(shù)據(jù),這個(gè)天數(shù)由系統(tǒng)配置參數(shù) days 決定。切分成多個(gè)文件后,給定查詢的起止日期,無需任何索引,就可以立即定位需要打開哪些數(shù)據(jù)文件,大大加快讀取速度。
對(duì)于采集的數(shù)據(jù),一般有保留時(shí)長(zhǎng),這個(gè)時(shí)長(zhǎng)由系統(tǒng)配置參數(shù) keep 決定。超過這個(gè)設(shè)置天數(shù)的數(shù)據(jù)文件,將被系統(tǒng)自動(dòng)刪除,釋放存儲(chǔ)空間。
給定 days 與 keep 兩個(gè)參數(shù),一個(gè)典型工作狀態(tài)的 vnode 中總的數(shù)據(jù)文件數(shù)為:向上取整(keep/days)+1個(gè)??偟臄?shù)據(jù)文件個(gè)數(shù)不宜過大,也不宜過小。10到100以內(nèi)合適?;谶@個(gè)原則,可以設(shè)置合理的 days。目前的版本,參數(shù) keep 可以修改,但對(duì)于參數(shù) days,一旦設(shè)置后,不可修改。
在每個(gè)數(shù)據(jù)文件里,一張表的數(shù)據(jù)是一塊一塊存儲(chǔ)的。一張表可以有一到多個(gè)數(shù)據(jù)文件塊。在一個(gè)文件塊里,數(shù)據(jù)是列式存儲(chǔ)的,占用的是一片連續(xù)的存儲(chǔ)空間,這樣大大提高讀取速度。文件塊的大小由系統(tǒng)參數(shù) maxRows (每塊最大記錄條數(shù))決定,缺省值為 4096。這個(gè)值不宜過大,也不宜過小。過大,定位具體時(shí)間段的數(shù)據(jù)的搜索時(shí)間會(huì)變長(zhǎng),影響讀取速度;過小,數(shù)據(jù)塊的索引太大,壓縮效率偏低,也影響讀取速度。
每個(gè)數(shù)據(jù)文件(.data 結(jié)尾)都有一個(gè)對(duì)應(yīng)的索引文件(.head 結(jié)尾),該索引文件對(duì)每張表都有一數(shù)據(jù)塊的摘要信息,記錄了每個(gè)數(shù)據(jù)塊在數(shù)據(jù)文件中的偏移量,數(shù)據(jù)的起止時(shí)間等信息,以幫助系統(tǒng)迅速定位需要查找的數(shù)據(jù)。每個(gè)數(shù)據(jù)文件還有一對(duì)應(yīng)的 last 文件(.last 結(jié)尾),該文件是為防止落盤時(shí)數(shù)據(jù)塊碎片化而設(shè)計(jì)的。如果一張表落盤的記錄條數(shù)沒有達(dá)到系統(tǒng)配置參數(shù) minRows(每塊最小記錄條數(shù)),將被先存儲(chǔ)到 last 文件,等下次落盤時(shí),新落盤的記錄將與 last 文件的記錄進(jìn)行合并,再寫入數(shù)據(jù)文件。
數(shù)據(jù)寫入磁盤時(shí),根據(jù)系統(tǒng)配置參數(shù) comp 決定是否壓縮數(shù)據(jù)。TDengine 提供了三種壓縮選項(xiàng):無壓縮、一階段壓縮和兩階段壓縮,分別對(duì)應(yīng) comp 值為 0、1 和 2 的情況。一階段壓縮根據(jù)數(shù)據(jù)的類型進(jìn)行了相應(yīng)的壓縮,壓縮算法包括 delta-delta 編碼、simple 8B 方法、zig-zag 編碼、LZ4 等算法。二階段壓縮在一階段壓縮的基礎(chǔ)上又用通用壓縮算法進(jìn)行了壓縮,壓縮率更高。
多級(jí)存儲(chǔ)
說明:多級(jí)存儲(chǔ)功能僅企業(yè)版支持,從 2.0.16.0 版本開始提供。
在默認(rèn)配置下,TDengine 會(huì)將所有數(shù)據(jù)保存在 /var/lib/taos 目錄下,而且每個(gè) vnode 的數(shù)據(jù)文件保存在該目錄下的不同目錄。為擴(kuò)大存儲(chǔ)空間,盡量減少文件讀取的瓶頸,提高數(shù)據(jù)吞吐率 TDengine 可通過配置系統(tǒng)參數(shù) dataDir 讓多個(gè)掛載的硬盤被系統(tǒng)同時(shí)使用。
除此之外,TDengine 也提供了數(shù)據(jù)分級(jí)存儲(chǔ)的功能,將不同時(shí)間段的數(shù)據(jù)存儲(chǔ)在掛載的不同介質(zhì)上的目錄里,從而實(shí)現(xiàn)不同“熱度”的數(shù)據(jù)存儲(chǔ)在不同的存儲(chǔ)介質(zhì)上,充分利用存儲(chǔ),節(jié)約成本。比如,最新采集的數(shù)據(jù)需要經(jīng)常訪問,對(duì)硬盤的讀取性能要求高,那么用戶可以配置將這些數(shù)據(jù)存儲(chǔ)在 SSD 盤上。超過一定期限的數(shù)據(jù),查詢需求量沒有那么高,那么可以存儲(chǔ)在相對(duì)便宜的 HDD 盤上。
多級(jí)存儲(chǔ)支持3級(jí),每級(jí)最多可配置 16 個(gè)掛載點(diǎn)。
TDengine 多級(jí)存儲(chǔ)配置方式如下(在配置文件/etc/taos/taos.cfg中):
dataDir [path] <level> <primary>
- path: 掛載點(diǎn)的文件夾路徑
- level: 介質(zhì)存儲(chǔ)等級(jí),取值為 0,1,2。
0級(jí)存儲(chǔ)最新的數(shù)據(jù),1級(jí)存儲(chǔ)次新的數(shù)據(jù),2級(jí)存儲(chǔ)最老的數(shù)據(jù),省略默認(rèn)為 0。
各級(jí)存儲(chǔ)之間的數(shù)據(jù)流向:0 級(jí)存儲(chǔ) -> 1 級(jí)存儲(chǔ) -> 2 級(jí)存儲(chǔ)。
同一存儲(chǔ)等級(jí)可掛載多個(gè)硬盤,同一存儲(chǔ)等級(jí)上的數(shù)據(jù)文件分布在該存儲(chǔ)等級(jí)的所有硬盤上。
需要說明的是,數(shù)據(jù)在不同級(jí)別的存儲(chǔ)介質(zhì)上的移動(dòng),是由系統(tǒng)自動(dòng)完成的,用戶無需干預(yù)。 - primary: 是否為主掛載點(diǎn),0(否)或 1(是),省略默認(rèn)為 1。
在配置中,只允許一個(gè)主掛載點(diǎn)的存在(level=0, primary=1),例如采用如下的配置方式:
dataDir /mnt/data1 0 1
dataDir /mnt/data2 0 0
dataDir /mnt/data3 1 0
dataDir /mnt/data4 1 0
dataDir /mnt/data5 2 0
dataDir /mnt/data6 2 0
注意:
- 多級(jí)存儲(chǔ)不允許跨級(jí)配置,合法的配置方案有:僅 0 級(jí),僅 0 級(jí)+ 1 級(jí),以及 0 級(jí)+ 1 級(jí)+ 2 級(jí)。而不允許只配置 level=0 和 level=2,而不配置 level=1。
- 禁止手動(dòng)移除使用中的掛載盤,掛載盤目前不支持非本地的網(wǎng)絡(luò)盤。
- 多級(jí)存儲(chǔ)目前不支持刪除已經(jīng)掛載的硬盤的功能。
數(shù)據(jù)查詢
TDengine 提供了多種多樣針對(duì)表和超級(jí)表的查詢處理功能,除了常規(guī)的聚合查詢之外,還提供針對(duì)時(shí)序數(shù)據(jù)的窗口查詢、統(tǒng)計(jì)聚合等功能。TDengine 的查詢處理需要客戶端、vnode、mnode 節(jié)點(diǎn)協(xié)同完成。
單表查詢
SQL 語句的解析和校驗(yàn)工作在客戶端完成。解析 SQL 語句并生成抽象語法樹(Abstract Syntax Tree, AST),然后對(duì)其進(jìn)行校驗(yàn)和檢查。以及向管理節(jié)點(diǎn)(mnode)請(qǐng)求查詢中指定表的元數(shù)據(jù)信息(table metadata)。
根據(jù)元數(shù)據(jù)信息中的 End Point 信息,將查詢請(qǐng)求序列化后發(fā)送到該表所在的數(shù)據(jù)節(jié)點(diǎn)(dnode)。dnode 接收到查詢請(qǐng)求后,識(shí)別出該查詢請(qǐng)求指向的虛擬節(jié)點(diǎn)(vnode),將消息轉(zhuǎn)發(fā)到 vnode 的查詢執(zhí)行隊(duì)列。vnode 的查詢執(zhí)行線程建立基礎(chǔ)的查詢執(zhí)行環(huán)境,并立即返回該查詢請(qǐng)求,同時(shí)開始執(zhí)行該查詢。
客戶端在獲取查詢結(jié)果的時(shí)候,dnode 的查詢執(zhí)行隊(duì)列中的工作線程會(huì)等待 vnode 執(zhí)行線程執(zhí)行完成,才能將查詢結(jié)果返回到請(qǐng)求的客戶端。
按時(shí)間軸聚合、降采樣、插值
時(shí)序數(shù)據(jù)有別于普通數(shù)據(jù)的顯著特征是每條記錄均具有時(shí)間戳,因此針對(duì)具有時(shí)間戳的數(shù)據(jù)在時(shí)間軸上進(jìn)行聚合是不同于普通數(shù)據(jù)庫的重要功能。從這點(diǎn)上來看,與流計(jì)算引擎的窗口查詢有相似的地方。
在 TDengine 中引入關(guān)鍵詞 interval 來進(jìn)行時(shí)間軸上固定長(zhǎng)度時(shí)間窗口的切分,并按照時(shí)間窗口對(duì)數(shù)據(jù)進(jìn)行聚合,對(duì)窗口范圍內(nèi)的數(shù)據(jù)按需進(jìn)行聚合。例如:
SELECT COUNT(*) FROM d1001 INTERVAL(1h);
針對(duì) d1001 設(shè)備采集的數(shù)據(jù),按照1小時(shí)的時(shí)間窗口返回每小時(shí)存儲(chǔ)的記錄數(shù)量。
在需要連續(xù)獲得查詢結(jié)果的應(yīng)用場(chǎng)景下,如果給定的時(shí)間區(qū)間存在數(shù)據(jù)缺失,會(huì)導(dǎo)致該區(qū)間數(shù)據(jù)結(jié)果也丟失。TDengine 提供策略針對(duì)時(shí)間軸聚合計(jì)算的結(jié)果進(jìn)行插值,通過使用關(guān)鍵詞 fill 就能夠?qū)r(shí)間軸聚合結(jié)果進(jìn)行插值。例如:
SELECT COUNT(*) FROM d1001 WHERE ts >= '2017-7-14 00:00:00' AND ts < '2017-7-14 23:59:59' INTERVAL(1h) FILL(PREV);
針對(duì) d1001 設(shè)備采集數(shù)據(jù)統(tǒng)計(jì)每小時(shí)記錄數(shù),如果某一個(gè)小時(shí)不存在數(shù)據(jù),則返回之前一個(gè)小時(shí)的統(tǒng)計(jì)數(shù)據(jù)。TDengine 提供前向插值(prev)、線性插值(linear)、NULL值填充(NULL)、特定值填充(value)。
多表聚合查詢
TDengine 對(duì)每個(gè)數(shù)據(jù)采集點(diǎn)單獨(dú)建表,但在實(shí)際應(yīng)用中經(jīng)常需要對(duì)不同的采集點(diǎn)數(shù)據(jù)進(jìn)行聚合。為高效的進(jìn)行聚合操作,TDengine 引入超級(jí)表(STable)的概念。超級(jí)表用來代表一特定類型的數(shù)據(jù)采集點(diǎn),它是包含多張表的表集合,集合里每張表的模式(schema)完全一致,但每張表都帶有自己的靜態(tài)標(biāo)簽,標(biāo)簽可以有多個(gè),可以隨時(shí)增加、刪除和修改。應(yīng)用可通過指定標(biāo)簽的過濾條件,對(duì)一個(gè) STable 下的全部或部分表進(jìn)行聚合或統(tǒng)計(jì)操作,這樣大大簡(jiǎn)化應(yīng)用的開發(fā)。其具體流程如下圖所示:

- 應(yīng)用將一個(gè)查詢條件發(fā)往系統(tǒng);
- taosc 將超級(jí)表的名字發(fā)往 meta node(管理節(jié)點(diǎn));
- 管理節(jié)點(diǎn)將超級(jí)表所擁有的 vnode 列表發(fā)回 taosc;
- taosc 將計(jì)算的請(qǐng)求連同標(biāo)簽過濾條件發(fā)往這些 vnode 對(duì)應(yīng)的多個(gè)數(shù)據(jù)節(jié)點(diǎn);
- 每個(gè) vnode 先在內(nèi)存里查找出自己節(jié)點(diǎn)里符合標(biāo)簽過濾條件的表的集合,然后掃描存儲(chǔ)的時(shí)序數(shù)據(jù),完成相應(yīng)的聚合計(jì)算,將結(jié)果返回給 taosc;
- taosc 將多個(gè)數(shù)據(jù)節(jié)點(diǎn)返回的結(jié)果做最后的聚合,將其返回給應(yīng)用。
由于 TDengine 在 vnode 內(nèi)將標(biāo)簽數(shù)據(jù)與時(shí)序數(shù)據(jù)分離存儲(chǔ),通過在內(nèi)存里過濾標(biāo)簽數(shù)據(jù),先找到需要參與聚合操作的表的集合,將需要掃描的數(shù)據(jù)集大幅減少,大幅提升聚合計(jì)算速度。同時(shí),由于數(shù)據(jù)分布在多個(gè) vnode/dnode,聚合計(jì)算操作在多個(gè) vnode 里并發(fā)進(jìn)行,又進(jìn)一步提升了聚合的速度。 對(duì)普通表的聚合函數(shù)以及絕大部分操作都適用于超級(jí)表,語法完全一樣,細(xì)節(jié)請(qǐng)看 TAOS SQL。
預(yù)計(jì)算
為有效提升查詢處理的性能,針對(duì)物聯(lián)網(wǎng)數(shù)據(jù)的不可更改的特點(diǎn),在數(shù)據(jù)塊頭部記錄該數(shù)據(jù)塊中存儲(chǔ)數(shù)據(jù)的統(tǒng)計(jì)信息:包括最大值、最小值、和。我們稱之為預(yù)計(jì)算單元。如果查詢處理涉及整個(gè)數(shù)據(jù)塊的全部數(shù)據(jù),直接使用預(yù)計(jì)算結(jié)果,完全不需要讀取數(shù)據(jù)塊的內(nèi)容。由于預(yù)計(jì)算數(shù)據(jù)量遠(yuǎn)小于磁盤上存儲(chǔ)的數(shù)據(jù)塊數(shù)據(jù)的大小,對(duì)于磁盤 I/O 為瓶頸的查詢處理,使用預(yù)計(jì)算結(jié)果可以極大地減小讀取 I/O 壓力,加速查詢處理的流程。預(yù)計(jì)算機(jī)制與 Postgre SQL 的索引 BRIN(block range index)有異曲同工之妙。

