TDengine是一款輕量級、高效且單機開源的面向物聯(lián)網(wǎng)的數(shù)據(jù)處理引擎,其核心是一個時序數(shù)據(jù)庫(Time-Series Database)。作為一款專門為物聯(lián)網(wǎng)設計并實現(xiàn)的數(shù)據(jù)引擎,TDengine在數(shù)據(jù)的寫入、查詢以及存儲方面擁有其他數(shù)據(jù)庫無法比擬的優(yōu)勢。本文主要探討了TDengine在架構(gòu)設計和存儲方面的創(chuàng)新,以方便用戶理解TDengine強大性能背后的邏輯。
TDengine架構(gòu)設計
如圖1所示,TDengine服務主要包含兩大模塊:管理節(jié)點模塊(MGMT) 和 數(shù)據(jù)節(jié)點模塊(DNODE)。整個TDengine還包含客戶端模塊。

管理節(jié)點模塊
管理節(jié)點模塊主要負責元數(shù)據(jù)的存儲和查詢等工作,其中包括用戶信息的管理、數(shù)據(jù)庫和表信息的創(chuàng)建、刪除以及查詢等。應用連接TDengine時會首先連接到管理節(jié)點。在創(chuàng)建/刪除數(shù)據(jù)庫和表時,請求也會首先發(fā)送請求到管理節(jié)點模塊。由管理節(jié)點模塊首先創(chuàng)建/刪除元數(shù)據(jù)信息,然后發(fā)送請求到數(shù)據(jù)節(jié)點模塊進行分配/刪除所需要的資源。在數(shù)據(jù)寫入和查詢時,應用同樣會首先訪問管理節(jié)點模塊,獲取元數(shù)據(jù)信息。然后根據(jù)元數(shù)據(jù)管理信息訪問數(shù)據(jù)節(jié)點模塊。
數(shù)據(jù)節(jié)點模塊
寫入數(shù)據(jù)的存儲和查詢工作是由數(shù)據(jù)節(jié)點模塊負責。 為了更高效地利用資源,以及方便將來進行水平擴展,TDengine內(nèi)部對數(shù)據(jù)節(jié)點進行了虛擬化,引入了虛擬節(jié)點(vnode)的概念,作為存儲、資源分配以及數(shù)據(jù)備份(商業(yè)版本中)的單元。如圖2所示,在一個dnode上,通過虛擬化,可以將該dnode視為多個虛擬節(jié)點的集合。每個虛擬節(jié)點存儲一定數(shù)量的表中的數(shù)據(jù)。不同的vnode之間資源互不共享。每個虛擬節(jié)點都有自己的緩存,在硬盤上也有自己的存儲目錄。而同一vnode內(nèi)部無論是緩存還是硬盤的存儲都是共享的。通過虛擬化,TDengine可以將dnode上有限的物理資源合理地分配給不同的vnode,大大提高資源的利用率和并發(fā)度。一臺物理機器上的虛擬節(jié)點個數(shù)可以根據(jù)其硬件資源進行配置。

客戶端模塊
TDengine客戶端模塊主要負責將應用傳來的請求(SQL語句)進行解析,轉(zhuǎn)化為內(nèi)部結(jié)構(gòu)體再發(fā)送到服務端。TDengine的各種接口都是基于TDengine的客戶端模塊進行開發(fā)的。
TDengine寫入流程
TDengine的完整寫入流程如圖3所示。為了保證寫入數(shù)據(jù)的安全性和完整性,TDengine在寫入數(shù)據(jù)時采用[預寫日志算法]。客戶端發(fā)來的數(shù)據(jù)在經(jīng)過驗證以后,首先會寫入預寫日志中,以保證TDengine能夠在斷電等因素導致的服務重啟時從預寫日志中恢復數(shù)據(jù),避免數(shù)據(jù)的丟失。寫入預寫日志后,數(shù)據(jù)會被寫到對應的vnode的緩存中。隨后,服務端會發(fā)送確認信息給客戶端表示寫入成功。TDengine中存在兩種機制可以促使緩存中的數(shù)據(jù)寫入到硬盤上進行持久化存儲:

- 時間驅(qū)動的落盤:TDengine服務會定時將vnode緩存中的數(shù)據(jù)寫入到硬盤上,默認為一個小時落一次盤。落盤間隔可在配置文件中配置。
- 數(shù)據(jù)驅(qū)動的落盤:當vnode中緩存的數(shù)據(jù)達到一定規(guī)模時,為了不阻塞后續(xù)數(shù)據(jù)的寫入,TDengine也會拉起落盤線程將緩存中的數(shù)據(jù)清空。數(shù)據(jù)驅(qū)動的落盤會刷新定時落盤的時間。
TDengine在數(shù)據(jù)落盤時會打開新的預寫日志文件,在落盤后則會刪除老的預寫日志文件,避免日志文件無限制的增長。
元數(shù)據(jù)的存儲
TDengine中的元數(shù)據(jù)信息包括TDengine中的數(shù)據(jù)庫,表等信息。元數(shù)據(jù)信息默認存放在 /var/lib/taos/mgmt/ 文件夾下。
/var/lib/taos/
+--mgmt/
+--db.db
+--meters.db
+--user.db
+--vgroups.db
元數(shù)據(jù)文件只進行追加操作,即便是元數(shù)據(jù)的刪除,也只是在數(shù)據(jù)文件中追加一條刪除的記錄。
寫入數(shù)據(jù)的存儲
TDengine中寫入的數(shù)據(jù)在硬盤上是按時間維度進行分片的。同一個vnode中的表在同一時間范圍內(nèi)的數(shù)據(jù)都存放在同一文件組中,如下圖中的v0f1804*文件。這一數(shù)據(jù)分片方式可以大大簡化數(shù)據(jù)在時間維度的查詢,提高查詢速度。在默認配置下,硬盤上的每個文件存放10天數(shù)據(jù)。用戶可根據(jù)需要進行配置。
數(shù)據(jù)在文件中是按塊存儲的。每個數(shù)據(jù)塊只包含一張表的數(shù)據(jù),且數(shù)據(jù)是按照時間主鍵遞增排列的。數(shù)據(jù)在數(shù)據(jù)塊中按列存儲,這樣使得同類型的數(shù)據(jù)存放在一起,可以大大提高壓縮的比例,節(jié)省存儲空間。
TDengine的數(shù)據(jù)文件默認存放在 /var/lib/taos/data/ 下。而 /var/lib/taos/tsdb/ 文件夾下存放了vnode的信息、vnode中表的信息以及數(shù)據(jù)文件的鏈接。完整目錄結(jié)構(gòu)如下所示:
/var/lib/taos/
+--tsdb/
| +--vnode0
| +--meterObj.v0
| +--db/
| +--v0f1804.head->/var/lib/taos/data/vnode0/v0f1804.head1
| +--v0f1804.data->/var/lib/taos/data/vnode0/v0f1804.data
| +--v0f1804.last->/var/lib/taos/data/vnode0/v0f1804.last1
| +--v0f1805.head->/var/lib/taos/data/vnode0/v0f1805.head1
| +--v0f1805.data->/var/lib/taos/data/vnode0/v0f1805.data
| +--v0f1805.last->/var/lib/taos/data/vnode0/v0f1805.last1
| :
+--data/
+--vnode0/
+--v0f1804.head1
+--v0f1804.data
+--v0f1804.last1
+--v0f1805.head1
+--v0f1805.data
+--v0f1805.last1
:
meterObj文件
每個vnode中只存在一個meterObj文件。該文件中存儲了vnode的基本信息(創(chuàng)建時間,配置信息,vnode的統(tǒng)計信息等)以及該vnode中表的信息。其結(jié)構(gòu)如下所示:
<文件開始> [文件頭] [表記錄1偏移量和長度] [表記錄2偏移量和長度] … [表記錄N偏移量和長度] [表記錄1] [表記錄2] … [表記錄N] [表記錄] <文件結(jié)尾>
其中,文件頭大小為512字節(jié),主要存放vnode的基本信息。每條表記錄代表屬于該vnode中的一張表在硬盤上的表示。
head文件
head文件中存放了其對應的data文件中數(shù)據(jù)塊的索引信息。該文件組織形式如下:
<文件開始> [文件頭] [表1偏移量] [表2偏移量] … [表N偏移量] [表1數(shù)據(jù)索引] [表2數(shù)據(jù)索引] … [表N數(shù)據(jù)索引] <文件結(jié)尾>
文件開頭的偏移量列表表示對應表的數(shù)據(jù)索引塊的開始位置在文件中的偏移量。每張表的數(shù)據(jù)索引信息在head文件中都是連續(xù)存放的。這也使得TDengine在讀取單表數(shù)據(jù)時,可以將該表所有的數(shù)據(jù)塊索引一次性讀入內(nèi)存,大大提高讀取速度。表的數(shù)據(jù)索引塊組織如下:
[索引塊信息] [數(shù)據(jù)塊1索引] [數(shù)據(jù)塊2索引] … [數(shù)據(jù)塊N索引]
其中,索引塊信息中記錄了數(shù)據(jù)塊的個數(shù)等描述信息。每個數(shù)據(jù)塊索引對應一個在data文件或last文件中的一個單獨的數(shù)據(jù)塊。索引信息中記錄了數(shù)據(jù)塊存放的文件、數(shù)據(jù)塊起始位置的偏移量、數(shù)據(jù)塊中數(shù)據(jù)時間主鍵的范圍等。索引塊中的數(shù)據(jù)塊索引是按照時間范圍順序排放的,這也就是說,索引塊M對應的數(shù)據(jù)塊中的數(shù)據(jù)時間范圍都大于索引塊M-1的。這種預先排序的存儲方式使得在TDengine在進行按照時間戳進行查詢時可以使用折半查找算法,大大提高查詢速度。
data文件
data文件中存放了真實的數(shù)據(jù)塊。該文件只進行追加操作。其文件組織形式如下:
<文件開始> [文件頭] [數(shù)據(jù)塊1] [數(shù)據(jù)塊2] … [數(shù)據(jù)塊N] <文件結(jié)尾>
每個數(shù)據(jù)塊只屬于vnode中的一張表,且數(shù)據(jù)塊中的數(shù)據(jù)按照時間主鍵排列。數(shù)據(jù)塊中的數(shù)據(jù)按列組織排放,使得同一類型的數(shù)據(jù)排放在一起,方便壓縮和讀取。每個數(shù)據(jù)塊的組織形式如下所示:
[列1信息] [列2信息] … [列N信息] [列1數(shù)據(jù)] [列2數(shù)據(jù)] … [列N數(shù)據(jù)]
列信息中包含該列的類型,列的壓縮算法,列數(shù)據(jù)在文件中的偏移量以及長度等。除此之外,列信息中也包含該內(nèi)存塊中該列數(shù)據(jù)的預計算結(jié)果,從而在過濾查詢時根據(jù)預計算結(jié)果判定是否讀取數(shù)據(jù)塊,大大提高讀取速度。
last文件
為了防止數(shù)據(jù)塊的碎片化,提高查詢速度和壓縮率,TDengine引入了last文件。當要落盤的數(shù)據(jù)塊中的數(shù)據(jù)條數(shù)低于某個閾值時,TDengine會先將該數(shù)據(jù)塊寫入到last文件中進行暫時存儲。當有新的數(shù)據(jù)需要落盤時,last文件中的數(shù)據(jù)會被讀取出來與新數(shù)據(jù)組成新的數(shù)據(jù)塊寫入到data文件中。last文件的組織形式與data文件類似。
小結(jié)
TDengine通過其創(chuàng)新的架構(gòu)和存儲結(jié)構(gòu)設計,有效提高了計算機資源的使用率。一方面,TDengine的虛擬化使得TDengine的水平擴展及備份非常容易。另一方面,TDengine將表中數(shù)據(jù)按時間主鍵排序存儲且其列式存儲的組織形式都使TDengine在寫入、查詢以及壓縮方面擁有非常大的優(yōu)勢。



互聯(lián)網(wǎng).png)



-1.png)











伙伴.png)
伙伴.png)



