萬字詳解 TDengine 2-0 整體架構設計思路

**導讀:**濤思數據 8 月 3 日將 TDengine 的集羣功能開源,TDengine 具有超強的性能和功能,爲什麼能做到?它到底有哪些技術創新?今將 TDengine 的整體設計文檔分享出來。

1: 數據模型

物聯網典型場景

在典型的物聯網、車聯網、運維監測場景中,往往有多種不同類型的數據採集設備,採集一個到多個不同的物理量。而同一種採集設備類型,往往又有多個具體的採集設備分佈在不同的地點。大數據處理系統就是要將各種採集的數據彙總,然後進行計算和分析。對於同一類設備,其採集的數據都是很規則的。以智能電錶爲例,假設每個智能電錶採集電流、電壓、相位三個量,其採集的數據類似如下的表格:

表 1 智能電錶數據示例

每一條記錄都有設備 ID,時間戳,採集的物理量 (如上圖中的電流、電壓、相位),還有與每個設備相關的靜態標籤(如上述表 1 中的位置 Location 和分組 groupId)。每個設備是受外界的觸發,或按照設定的週期採集數據。採集的數據點是時序的,是一個數據流。

數據特徵

除時序特徵外,仔細研究發現,物聯網、車聯網、運維監測類數據還具有很多其他明顯的特徵:

  1. 數據高度結構化;

  2. 數據極少有更新或刪除操作;

  3. 無需傳統數據庫的事務處理;

  4. 相對互聯網應用,寫多讀少;

  5. 流量平穩,根據設備數量和採集頻次,可以預測出來;

  6. 用戶關注的是一段時間的趨勢,而不是某一特定時間點的值;

  7. 數據有保留期限;

  8. 數據的查詢分析一定是基於時間段和空間區域;

  9. 除存儲、查詢操作外,還需要各種統計和實時計算操作;

  10. 數據量巨大,一天可能採集的數據就可以超過 100 億條。

充分利用上述特徵,TDengine 採取了經特殊優化的存儲和計算設計來處理時序數據,它將系統處理能力顯著提高,同時大幅降低了系統運維的複雜度。

關係型數據庫模型

因爲採集的數據一般是結構化數據,同時爲降低學習門檻,TDengine 採用傳統的關係型數據庫模型管理數據。因此用戶需要先創建庫,然後創建表,之後才能插入或查詢數據。TDengine 採用的是結構化存儲,而不是 NoSQL 的 key-value 存儲。

一個數據採集點一張表

爲充分利用其數據的時序性和其他數據特點,TDengine 要求對每個數據採集點單獨建表(比如有一千萬個智能電錶,就需創建一千萬張表,上述表格中的 d1001, d1002, d1003, d1004 都需單獨建表),用來存儲這個採集點所採集的時序數據。這種設計有幾大優點:

  1. 能保證一個採集點的數據在存儲介質上是以塊爲單位連續存儲的。如果讀取一個時間段的數據,它能大幅減少隨機讀取操作,成數量級的提升讀取和查詢速度。

  2. 由於不同採集設備產生數據的過程完全獨立,每個設備的數據源是唯一的,一張表也就只有一個寫入者,這樣就可採用無鎖方式來寫,寫入速度就能大幅提升。

  3. 對於一個數據採集點而言,其產生的數據是時序的,因此寫的操作可用追加的方式實現,進一步大幅提高數據寫入速度。

如果採用傳統的方式,將多個設備的數據寫入一張表,由於網絡延時不可控,不同設備的數據到達服務器的時序是無法保證的,寫入操作是要有鎖保護的,而且一個設備的數據是難以保證連續存儲在一起的。採用一個數據採集點一張表的方式,能最大程度的保證單個數據採集點的插入和查詢的性能是最優的

TDengine 建議用數據採集點的名字 (如上表中的 D1001) 來做表名。每個數據採集點可能同時採集多個物理量(如上表中的 curent, voltage, phase),每個物理量對應一張表中的一列,數據類型可以是整型、浮點型、字符串等。除此之外,表的第一列必須是時間戳,即數據類型爲 timestamp。對採集的數據,TDengine 將自動按照時間戳建立索引,但對採集的物理量不建任何索引。數據用列式存儲方式保存。

超級表:同一類型數據採集點的集合

由於一個數據採集點一張表,導致表的數量巨增,難以管理,而且應用經常需要做採集點之間的聚合操作,聚合的操作也變得複雜起來。爲解決這個問題,TDengine 引入超級表 (Super Table,簡稱爲 STable) 的概念。

超級表是指某一特定類型的數據採集點的集合。同一類型的數據採集點,其表的結構是完全一樣的,但每個表(數據採集點)的靜態屬性(標籤)是不一樣的。描述一個超級表(某一特定類型的數據採集點的結合),除需要定義採集量的表結構之外,還需要定義其標籤的 schema,標籤的數據類型可以是整數、浮點數、字符串,標籤可以有多個,可以事後增加、刪除或修改。如果整個系統有 N 個不同類型的數據採集點,就需要建立 N 個超級表。

在 TDengine 的設計裏,表用來代表一個具體的數據採集點,超級表用來代表一組相同類型的數據採集點集合。當爲某個具體數據採集點創建表時,用戶使用超級表的定義做模板,同時指定該具體採集點(表)的標籤值。與傳統的關係型數據庫相比,表(一個數據採集點)是帶有靜態標籤的,而且這些標籤可以事後增加、刪除、修改。一張超級表包含有多張表,這些表具有相同的時序數據 schema,但帶有不同的標籤值

當對多個具有相同數據類型的數據採集點進行聚合操作時,TDengine 將先把滿足標籤過濾條件的表從超級表的中查找出來,然後再掃描這些表的時序數據,進行聚合操作,這樣能將需要掃描的數據集大幅減少,從而大幅提高聚合計算的性能。

2: 集羣與基本邏輯單元

TDengine 的設計是基於單個硬件、軟件系統不可靠,基於任何單臺計算機都無法提供足夠計算能力和存儲能力處理海量數據的假設進行設計的。因此 TDengine 從研發的第一天起,就按照分佈式高可靠架構進行設計,是支持水平擴展的,這樣任何單臺或多臺服務器發生硬件故障或軟件錯誤都不影響系統的可用性和可靠性。同時,通過節點虛擬化並輔以自動化負載均衡技術,TDengine 能最高效率地利用異構集羣中的計算和存儲資源降低硬件投資。

主要邏輯單元

TDengine 分佈式架構的邏輯結構圖如下:

圖 1 TDengine 架構示意圖

一個完整的 TDengine 系統是運行在一到多個物理節點上的,邏輯上,它包含數據節點 (dnode)、TDengine 客戶端(taosc) 以及應用(app)。系統中存在一到多個數據節點,這些數據節點組成一個集羣(cluster)。應用通過 taosc 的 API 與 TDengine 集羣進行互動。下面對每個邏輯單元進行簡要介紹。

物理節點 (pnode): pnode 是一獨立運行、擁有自己的計算、存儲和網絡能力的計算機,可以是安裝有 OS 的物理機、虛擬機或容器。物理節點由其配置的 FQDN(Fully Qualified Domain Name) 來標識。

數據節點 (dnode): dnode 是 TDengine 服務器側執行代碼 taosd 在物理節點上的一個運行實例,一個工作的系統必須有至少一個數據節點。dnode 包含零到多個邏輯的虛擬節點 (VNODE),零或者至多一個邏輯的管理節點(mnode)。dnode 在系統中的唯一標識由實例的 End Point (EP ) 決定。EP 是 dnode 所在物理節點的 FQDN (Fully Qualified Domain Name)和系統所配置的網絡端口號 (Port) 的組合。通過配置不同的端口,一個物理節點(一臺物理機、虛擬機或容器)可以運行多個實例,或有多個數據節點。

虛擬節點 (vnode): 爲更好的支持數據分片、負載均衡,防止數據過熱或傾斜,數據節點被虛擬化成多個虛擬節點 (vnode,圖中 V2, V3, V4 等)。每個 vnode 都是一個相對獨立的工作單元,是時序數據存儲的基本單元,具有獨立的運行線程、內存空間與持久化存儲的路徑。一個 vnode 包含一定數量的表(數據採集點)。當創建一張新表時,系統會檢查是否需要創建新的 vnode。一個數據節點上能創建的 vnode 的數量取決於該數據節點所在物理節點的硬件資源。一個 vnode 只屬於一個 DB,但一個 DB 可以有多個 vnode。一個 vnode 除存儲的時序數據外,也保存有所包含的表的 SCHEMA、標籤值等。一個虛擬節點由所屬的數據節點的 EP,以及所屬的 VGroup ID 在系統內唯一標識,由管理節點創建並管理。

管理節點 (mnode): 一個虛擬的邏輯單元,負責所有數據節點運行狀態的監控和維護,以及節點之間的負載均衡 (圖中 M)。同時,管理節點也負責元數據(包括用戶、數據庫、表、靜態標籤等) 的存儲和管理,因此也稱爲 Meta Node。TDengine 集羣中可配置多個(最多不超過 5 個) mnode,它們自動構建成爲一個虛擬管理節點組(圖中 M0, M1, M2)。mnode 間採用 master/slave 的機制進行管理,而且採取強一致方式進行數據同步, 任何數據更新操作只能在 Master 上進行。mnode 集羣的創建由系統自動完成,無需人工干預。每個 dnode 上至多有一個 mnode,由所屬的數據節點的 EP 來唯一標識。每個 dnode 通過內部消息交互自動獲取整個集羣中所有 mnode 所在的 dnode 的 EP。

虛擬節點組 (VGroup): 不同數據節點上的 vnode 可以組成一個虛擬節點組 (vnode group) 來保證系統的高可靠。虛擬節點組內採取 master/slave 的方式進行管理。寫操作只能在 master vnode 上進行,系統採用異步複製的方式將數據同步到 slave vnode,這樣確保了一份數據在多個物理節點上有拷貝。一個 vgroup 裏虛擬節點個數就是數據的副本數。如果一個 DB 的副本數爲 N,系統必須有至少 N 個數據節點。副本數在創建 DB 時通過參數 replica 可以指定,缺省爲 1。使用 TDengine 的多副本特性,可以不再需要昂貴的磁盤陣列等存儲設備,就可以獲得同樣的數據高可靠性。虛擬節點組由管理節點創建、管理,並且由管理節點分配一個系統唯一的 ID,VGroup ID。如果兩個虛擬節點的 vnode group ID 相同,說明他們屬於同一個組,數據互爲備份。虛擬節點組裏虛擬節點的個數是可以動態改變的,容許只有一個,也就是沒有數據複製。VGroup ID 是永遠不變的,即使一個虛擬節點組被刪除,它的 ID 也不會被收回重複利用。

TAOSC: taosc 是 TDengine 給應用提供的驅動程序 (driver),負責處理應用與集羣的接口交互,內嵌於 JDBC、ODBC driver 中,或者 C、Python、Go 語言連接庫裏。應用都是通過 taosc 而不是直接連接集羣中的數據節點與整個集羣進行交互的。這個模塊負責獲取並緩存元數據;將插入、查詢等請求轉發到正確的數據節點;在把結果返回給應用時,還需要負責最後一級的聚合、排序、過濾等操作。對於 JDBC, ODBC, C/C++ 接口而言,這個模塊是在應用所處的物理節點上運行,但消耗的資源很小。同時,爲支持全分佈式的 RESTful 接口,taosc 在 TDengine 集羣的每個 dnode 上都有一運行實例。

節點之間的通訊

通訊方式:TDengine 系統的各個節點之間的通訊是通過 TCP/UDP 進行的。因爲考慮到物聯網場景,數據寫入的包一般不大,因此 TDengine 除採用 TCP 做傳輸之外,還採用 UDP 方式,因爲 UDP 更加高效,而且不受連接數的限制。TDengine 實現了自己的超時、重傳、確認等機制,以確保 UDP 的可靠傳輸。對於數據量不到 15K 的數據包,採取 UDP 的方式進行傳輸,超過 15K 的,或者是查詢類的操作,自動採取 TCP 的方式進行傳輸。同時,TDengine 根據配置和數據包,會自動對數據進行壓縮 / 解壓縮,數字簽名 / 認證等處理。對於數據節點之間的數據複製,只採用 TCP 方式進行數據傳輸。

FQDN 配置:一個數據節點有一個或多個 FQDN,可以在系統配置文件 taos.cfg 通過選項 “fqdn" 進行指定,如果沒有指定,系統將自動獲取 FQDN。如果節點沒有配置 FQDN,可以直接使用 IP 地址作爲 FQDN,但不建議使用,因爲 IP 地址可變,一旦變化,將讓集羣無法正常工作。一個數據節點的 EP(End Point) 由 FQDN + Port 組成。

端口配置:一個數據節點對外的端口由 TDengine 的系統配置參數 serverPort 決定,對集羣內部通訊的端口是 serverPort+5。集羣內數據節點之間的數據複製操作還佔有一個 TCP 端口,是 serverPort+10. 爲支持多線程高效的處理 UDP 數據,每個對內和對外的 UDP 鏈接,都需要佔用 5 個連續的端口。因此一個數據節點總的端口範圍爲 serverPort 到 serverPort + 10,總共 11 個 TCP/UDP 端口。使用時,需要確保防火牆將這些端口打開。每個數據節點可以配置不同的 serverPort。

集羣對外鏈接: TDengine 集羣可以容納單個、多個甚至幾千個數據節點。應用只需要向集羣中任何一個數據節點發起連接即可,鏈接需要提供的網絡參數是一數據節點的 End Point(FQDN 加配置的端口號)。通過命令行 CLI 啓動應用 taos 時,可以通過選項 - h 來指定數據節點的 FQDN, -P 來指定其配置的端口號,如果端口不配置,將採用 TDengine 的系統配置參數 serverPort。

集羣內部通訊: 各個數據節點之間通過 TCP/UDP 進行鏈接。一個數據節點啓動時,將獲取 mnode 所在的 dnode 的 EP 信息,然後與系統中的 mnode 建立起鏈接,交換信息。獲取 mnode 的 EP 信息有三步,1:檢查 mnodeEpList 文件是否存在,如果不存在或不能正常打開獲得 mnode EP 信息,進入第二步;2:檢查系統配置文件 taos.cfg, 獲取 mnode EP 配置參數 first, second,如果不存在或者 taos.cfg 裏沒有這兩個配置參數,或無效,進入第三步;3:將自己的 EP 設爲 mnode EP, 並獨立運行起來。獲取 mnode EP 列表後,數據節點發起鏈接,如果鏈接成功,則成功加入進工作的集羣,如果不成功,則嘗試 mnode EP 列表中的下一個。如果都嘗試了,但鏈接都仍然失敗,則休眠幾秒後,再進行嘗試。

MNODE 的選擇: TDengine 邏輯上有管理節點,但沒有單獨的執行代碼,服務器側只有一套執行代碼 taosd。那麼哪個數據節點會是管理節點呢?這是系統自動決定的,無需任何人工干預。原則如下:一個數據節點啓動時,會檢查自己的 End Point, 並與獲取的 mnode EP List 進行比對,如果在其中,該數據節點認爲自己應該啓動 mnode 模塊,成爲 mnode。如果自己的 EP 不在 mnode EP List 裏,則不啓動 mnode 模塊。在系統的運行過程中,由於負載均衡、宕機等原因,mnode 有可能遷移至新的 dnode,但一切都是透明的,無需人工干預,配置參數的修改,是 mnode 自己根據資源做出的決定。

新數據節點的加入:系統有了一個數據節點後,就已經成爲一個工作的系統。添加新的節點進集羣時,有兩個步驟,第一步:使用 TDengine CLI 鏈接到現有工作的數據節點,然後用命令”create dnode" 將新的數據節點的 End Point 添加進去; 第二步:在新的數據節點的系統配置參數文件 taos.cfg 裏,將 first, second 參數設置爲現有集羣中任意兩個數據節點的 EP 即可。具體添加的詳細步驟請見詳細的用戶手冊。這樣就把集羣一步一步的建立起來。

重定向:無論是 dnode 還是 taosc,最先都是要發起與 mnode 的鏈接,但 mnode 是系統自動創建並維護的,因此對於用戶來說,並不知道哪個 dnode 在運行 mnode。TDengine 只要求向系統中任何一個工作的 dnode 發起鏈接即可。因爲任何一個正在運行的 dnode,都維護有目前運行的 mnode EP List。當收到一個來自新啓動的 dnode 或 taosc 的鏈接請求,如果自己不是 mnode,則將 mnode EP List 回覆給對方,taosc 或新啓動的 dnode 收到這個 list, 就重新嘗試建立鏈接。當 mnode EP List 發生改變,通過節點之間的消息交互,各個數據節點就很快獲取最新列表,並通知 taosc。

一個典型的消息流程

爲解釋 vnode, mnode, taosc 和應用之間的關係以及各自扮演的角色,下面對寫入數據這個典型操作的流程進行剖析。

圖 2 TDengine 典型的操作流程

  1. 應用通過 JDBC、ODBC 或其他 API 接口發起插入數據的請求。

  2. taosc 會檢查緩存,看是否保存有該表的 meta data。如果有,直接到第 4 步。如果沒有,taosc 將向 mnode 發出 get meta-data 請求。

  3. mnode 將該表的 meta-data 返回給 taosc。Meta-data 包含有該表的 schema, 而且還有該表所屬的 vgroup 信息(vnode ID 以及所在的 dnode 的 End Point,如果副本數爲 N,就有 N 組 End Point)。如果 taosc 遲遲得不到 mnode 迴應,而且存在多個 mnode, taosc 將向下一個 mnode 發出請求。

  4. taosc 向 master vnode 發起插入請求。

  5. vnode 插入數據後,給 taosc 一個應答,表示插入成功。如果 taosc 遲遲得不到 vnode 的迴應,taosc 會認爲該節點已經離線。這種情況下,如果被插入的數據庫有多個副本,taosc 將向 vgroup 裏下一個 vnode 發出插入請求。

  6. taosc 通知 APP,寫入成功。

對於第二和第三步,taosc 啓動時,並不知道 mnode 的 End Point,因此會直接向配置的集羣對外服務的 End Point 發起請求。如果接收到該請求的 dnode 並沒有配置 mnode,該 dnode 會在回覆的消息中告知 mnode EP 列表,這樣 taosc 會重新向新的 mnode 的 EP 發出獲取 meta-data 的請求。

對於第四和第五步,沒有緩存的情況下,taosc 無法知道虛擬節點組裏誰是 master,就假設第一個 vnodeID 就是 master, 向它發出請求。如果接收到請求的 vnode 並不是 master, 它會在回覆中告知誰是 master,這樣 taosc 就向建議的 master vnode 發出請求。一旦得到插入成功的回覆,taosc 會緩存 master 節點的信息。

上述是插入數據的流程,查詢、計算的流程也完全一致。taosc 把這些複雜的流程全部封裝屏蔽了,對於應用來說無感知也無需任何特別處理。

通過 taosc 緩存機制,只有在第一次對一張表操作時,才需要訪問 mnode, 因此 mnode 不會成爲系統瓶頸。但因爲 schema 有可能變化,而且 vgroup 有可能發生改變(比如負載均衡發生),因此 taosc 會定時和 mnode 交互,自動更新緩存。

3: 存儲模型與數據分區、分片

存儲模型

TDengine 存儲的數據包括採集的時序數據以及庫、表相關的元數據、標籤數據等,這些數據具體分爲三部分:

與典型的 NoSQL 存儲模型相比,TDengine 將標籤數據與時序數據完全分離存儲,它具有兩大優勢:

數據分片

對於海量的數據管理,爲實現水平擴展,一般都需要採取分片 (Sharding) 分區 (Partitioning) 策略。TDengine 是通過 vnode 來實現數據分片的,通過一個時間段一個數據文件來實現時序數據分區的。

vnode(虛擬數據節點) 負責爲採集的時序數據提供寫入、查詢和計算功能。爲便於負載均衡、數據恢復、支持異構環境,TDengine 將一個數據節點根據其計算和存儲資源切分爲多個 vnode。這些 vnode 的管理是 TDengine 自動完成的,對應用完全透明。

對於單獨一個數據採集點,無論其數據量多大,一個 vnode(或 vnode group, 如果副本數大於 1)有足夠的計算資源和存儲資源來處理(如果每秒生成一條 16 字節的記錄,一年產生的原始數據不到 0.5G),因此 TDengine 將一張表(一個數據採集點)的所有數據都存放在一個 vnode 裏,而不會讓同一個採集點的數據分佈到兩個或多個 dnode 上。而且一個 vnode 可存儲多個數據採集點 (表)的數據,一個 vnode 可容納的表的數目的上限爲一百萬。設計上,一個 vnode 裏所有的表都屬於同一個 DB。一個數據節點上,除非特殊配置,一個 DB 擁有的 vnode 數目不會超過系統核的數目。

創建 DB 時,系統並不會馬上分配資源。但當創建一張表時,系統將看是否有已經分配的 vnode, 且該 vnode 是否有空餘的表空間,如果有,立即在該有空位的 vnode 創建表。如果沒有,系統將從集羣中,根據當前的負載情況,在一個 dnode 上創建一新的 vnode, 然後創建表。如果 DB 有多個副本,系統不是隻創建一個 vnode,而是一個 vgroup(虛擬數據節點組)。系統對 vnode 的數目沒有任何限制,僅僅受限於物理節點本身的計算和存儲資源。

每張表的 meda data(包含 schema, 標籤等)也存放於 vnode 裏,而不是集中存放於 mnode,實際上這是對 Meta 數據的分片,這樣便於高效並行的進行標籤過濾操作。

數據分區

TDengine 除 vnode 分片之外,還對時序數據按照時間段進行分區。每個數據文件只包含一個時間段的時序數據,時間段的長度由 DB 的配置參數 days 決定。這種按時間段分區的方法還便於高效實現數據的保留策略,只要數據文件超過規定的天數(系統配置參數 keep),將被自動刪除。而且不同的時間段可以存放於不同的路徑和存儲介質,以便於大數據的冷熱管理,實現多級存儲。

總的來說,TDengine 是通過 vnode 以及時間兩個維度,對大數據進行切分,便於並行高效的管理,實現水平擴展。

負載均衡

每個 dnode 都定時向 mnode(虛擬管理節點) 報告其狀態(包括硬盤空間、內存大小、CPU、網絡、虛擬節點個數等),因此 mnode 瞭解整個集羣的狀態。基於整體狀態,當 mnode 發現某個 dnode 負載過重,它會將 dnode 上的一個或多個 vnode 挪到其他 dnode。在挪動過程中,對外服務繼續進行,數據插入、查詢和計算操作都不受影響。

如果 mnode 一段時間沒有收到 dnode 的狀態報告,mnode 會認爲這個 dnode 已經離線。如果離線時間超過一定時長(時長由配置參數 offlineThreshold 決定),該 dnode 將被 mnode 強制剔除出集羣。該 dnode 上的 vnodes 如果副本數大於一,系統將自動在其他 dnode 上創建新的副本,以保證數據的副本數。如果該 dnode 上還有 mnode, 而且 mnode 的副本數大於一,系統也將自動在其他 dnode 上創建新的 mnode, 以保證 mnode 的副本數。

當新的數據節點被添加進集羣,因爲新的計算和存儲被添加進來,系統也將自動啓動負載均衡流程。

負載均衡過程無需任何人工干預,應用也無需重啓,將自動連接新的節點,完全透明。

4: 數據寫入與複製流程

如果一個數據庫有 N 個副本,那一個虛擬節點組就有 N 個虛擬節點,但是隻有一個是 Master,其他都是 slave。當應用將新的記錄寫入系統時,只有 Master vnode 能接受寫的請求。如果 slave vnode 收到寫的請求,系統將通知 taosc 需要重新定向。

Master vnode 寫入流程

Master Vnode 遵循下面的寫入流程:

圖 3 TDengine Master 寫入流程

  1. Master vnode 收到應用的數據插入請求,驗證 OK,進入下一步;

  2. 如果系統配置參數 walLevel 打開(設置爲 2),vnode 將把該請求的原始數據包寫入數據庫日誌文件 WAL,以保證 TDengine 能夠在斷電等因素導致的服務重啓時從數據庫日誌文件中恢復數據,避免數據的丟失;

  3. 如果有多個副本,vnode 將把數據包轉發給同一虛擬節點組內 slave vnodes, 該轉發包帶有數據的版本號 (version);

  4. 寫入內存,並加記錄加入到 skip list;

  5. Master vnode 返回確認信息給應用,表示寫入成功。

  6. 如果第 2,3,4 步中任何一步失敗,將直接返回錯誤給應用。

Slave vnode 寫入流程

對於 slave vnode, 寫入流程是:

圖 4 TDengine Slave 寫入流程

  1. Slave vnode 收到 Master vnode 轉發了的數據插入請求。

  2. 如果系統配置參數 walLevl 設置爲 2,vnode 將把該請求的原始數據包寫入日誌 (WAL);

  3. 寫入內存,更新內存中的 skip list。

與 Master vnode 相比,slave vnode 不存在轉發環節,也不存在回覆確認環節,少了兩步。但寫內存與 WAL 是完全一樣的。

異地容災、IDC 遷移

從上述 Master 和 Slave 流程可以看出,TDengine 採用的是異步複製的方式進行數據同步。這種方式能夠大幅提高寫入性能,網絡延時對寫入速度不會有大的影響。通過配置每個物理節點的 IDC 和機架號,可以保證對於一個虛擬節點組,虛擬節點由來自不同 IDC、不同機架的物理節點組成,從而實現異地容災。因此 TDengine 原生支持異地容災,無需再使用其他工具。

另外一方面,TDengine 支持動態修改副本數,一旦副本數增加,新加入的虛擬節點將立即進入數據同步流程,同步結束後,新加入的虛擬節點即可提供服務。而在同步過程中,master 以及其他已經同步的虛擬節點都可以對外提供服務。利用這一特性,TDengine 可以實現無服務中斷的 IDC 機房遷移。只需要將新 IDC 的物理節點加入現有集羣,等數據同步完成後,再將老的 IDC 的物理節點從集羣中剔除即可。

但是,這種異步複製的方式,存在極小的時間窗口,丟失寫入的數據。具體場景如下:

  1. Master vnode 完成了它的 5 步操作,已經給 APP 確認寫入成功,然後宕機;

  2. Slave vnode 收到寫入請求後,在第 2 步寫入日誌之前,處理失敗

  3. Slave vnode 將成爲新的 master, 從而丟失了一條記錄

理論上,只要是異步複製,就無法保證 100% 不丟失。但是這個窗口極小,mater 與 slave 要同時發生故障,而且發生在剛給應用確認寫入成功之後。

注:異地容災、IDC 無中斷遷移,僅僅企業版支持

主從選擇

Vnode 會保持一個數據版本號 (Version),對內存數據進行持久化存儲時,對該版本號也進行持久化存儲。每個數據更新操作,無論是採集的時序數據還是元數據,這個版本號將增一。

一個 vnode 啓動時,角色 (master、slave) 是不定的,數據是處於未同步狀態,它需要與虛擬節點組內其他節點建立 TCP 鏈接,並互相交換 status,其中包括 version 和自己的角色。通過 status 的交換,系統進入選主流程,規則如下:

  1. 如果只有一個副本,該副本永遠就是 master

  2. 所有副本都在線時,版本最高的被選爲 master

  3. 在線的虛擬節點數過半,而且有虛擬節點是 slave 的話,該虛擬節點自動成爲 master

  4. 對於 2 和 3,如果多個虛擬節點滿足成爲 master 的要求,那麼虛擬節點組的節點列表裏,最前面的選爲 master

更多的關於數據複製的流程,請見 TDengine 2.0 數據複製模塊設計。

同步複製

對於數據一致性要求更高的場景,異步數據複製無法滿足要求,因爲有極小的概率丟失數據,因此 TDengine 提供同步複製的機制供用戶選擇。在創建數據庫時,除指定副本數 replica 之外,用戶還需要指定新的參數 quorum。如果 quorum 大於一,它表示每次 Master 轉發給副本時,需要等待 quorum-1 個回覆確認,才能通知應用,數據在 slave 已經寫入成功。如果在一定的時間內,得不到 quorum-1 個回覆確認,master vnode 將返回錯誤給應用。

採用同步複製,系統的性能會有所下降,而且 latency 會增加。因爲元數據要強一致,mnode 之間的數據同步缺省就是採用的同步複製。

注:vnode 之間的同步複製僅僅企業版支持

5: 緩存與持久化

緩存

TDengine 採用時間驅動緩存管理策略(First-In-First-Out,FIFO),又稱爲寫驅動的緩存管理機制。這種策略有別於讀驅動的數據緩存模式(Least-Recent-Used,LRU),直接將最近寫入的數據保存在系統的緩存中。當緩存達到臨界值的時候,將最早的數據批量寫入磁盤。一般意義上來說,對於物聯網數據的使用,用戶最爲關心的是剛產生的數據,即當前狀態。TDengine 充分利用這一特性,將最近到達的(當前狀態)數據保存在緩存中。

TDengine 通過查詢函數向用戶提供毫秒級的數據獲取能力。直接將最近到達的數據保存在緩存中,可以更加快速地響應用戶針對最近一條或一批數據的查詢分析,整體上提供更快的數據庫查詢響應能力。從這個意義上來說,可通過設置合適的配置參數將 TDengine 作爲數據緩存來使用,而不需要再部署 Redis 或其他額外的緩存系統,可有效地簡化系統架構,降低運維的成本。需要注意的是,TDengine 重啓以後系統的緩存將被清空,之前緩存的數據均會被批量寫入磁盤,緩存的數據將不會像專門的 Key-value 緩存系統再將之前緩存的數據重新加載到緩存中。

每個 vnode 有自己獨立的內存,而且由多個固定大小的內存塊組成,不同 vnode 之間完全隔離。數據寫入時,類似於日誌的寫法,數據被順序追加寫入內存,但每個 vnode 維護有自己的 skip list,便於迅速查找。當一半以上的內存塊寫滿時,啓動落盤操作,而且後續寫的操作在新的內存塊進行。這樣,一個 vnode 裏有一半內存塊是保留有最近的數據的,以達到緩存、快速查找的目的。一個 vnode 的內存塊的個數由配置參數 blocks 決定,內存塊的大小由配置參數 cache 決定。

持久化存儲

TDengine 採用數據驅動的方式讓緩存中的數據寫入硬盤進行持久化存儲。當 vnode 中緩存的數據達到一定規模時,爲了不阻塞後續數據的寫入,TDengine 也會拉起落盤線程將緩存的數據寫入持久化存儲。TDengine 在數據落盤時會打開新的數據庫日誌文件,在落盤成功後則會刪除老的數據庫日誌文件,避免日誌文件無限制的增長。

爲充分利用時序數據特點,TDengine 將一個 vnode 保存在持久化存儲的數據切分成多個文件,每個文件只保存固定天數的數據,這個天數由系統配置參數 days 決定。切分成多個文件後,給定查詢的起止日期,無需任何索引,就可以立即定位需要打開哪些數據文件,大大加快讀取速度。

對於採集的數據,一般有保留時長,這個時長由系統配置參數 keep 決定。超過這個設置天數的數據文件,將被系統將自動刪除,釋放存儲空間。

給定 days 與 keep 兩個參數,一個 vnode 總的數據文件數爲:keep/days。總的數據文件個數不宜過大,也不宜過小。10 到 100 以內合適。基於這個原則,可以設置合理的 days。目前的版本,參數 keep 可以修改,但對於參數 days,一但設置後,不可修改。

在每個數據文件裏,一張表的數據是一塊一塊存儲的。一張表可以有一到多個數據文件塊。在一個文件塊裏,數據是列式存儲的,佔用的是一片連續的存儲空間,這樣大大提高讀取速度。文件塊的大小由系統參數 maxRows(每塊最大記錄條數)決定,缺省值爲 4096。這個值不宜過大,也不宜過小。過大,定位具體時間段的數據的搜索時間會變長,影響讀取速度;過小,數據塊的索引太大,壓縮效率偏低,也影響讀取速度。

每個數據文件 (.data 結尾) 都有一個對應的索引文件(.head 結尾),該索引文件對每張表都有一數據塊的摘要信息,記錄了每個數據塊在數據文件中的偏移量,數據的起止時間等信息,以幫助系統迅速定位需要查找的數據。每個數據文件還有一對應的 last 文件(.last 結尾),該文件是爲防止落盤時數據塊碎片化而設計的。如果一張表落盤的記錄條數沒有達到系統配置參數 minRows(每塊最小記錄條數),將被先存儲到 last 文件,等下次落盤時,新落盤的記錄將與 last 文件的記錄進行合併,再寫入數據文件。

數據寫入磁盤時,根據系統配置參數 comp 決定是否壓縮數據。TDengine 提供了三種壓縮選項:無壓縮、一階段壓縮和兩階段壓縮,分別對應 comp 值爲 0、1 和 2 的情況。一階段壓縮根據數據的類型進行了相應的壓縮,壓縮算法包括 delta-delta 編碼、simple 8B 方法、zig-zag 編碼、LZ4 等算法。二階段壓縮在一階段壓縮的基礎上又用通用壓縮算法進行了壓縮,壓縮率更高。

多級存儲

在默認配置下,TDengine 會將所有數據保存在 / var/lib/taos 目錄下,而且每個 vnode 的數據文件保存在該目錄下的不同目錄。爲擴大存儲空間,儘量減少文件讀取的瓶頸,提高數據吞吐率 TDengine 可通過配置系統參數 dataDir 讓多個掛載的硬盤被系統同時使用。除此之外,TDengine 也提供了數據分級存儲的功能,即根據數據文件的新老程度存儲在不同的存儲介質上。比如最新的數據存儲在 SSD 上,超過一週的數據存儲在本地硬盤上,超過 4 周的數據存儲在網絡存儲設備上,這樣來降低存儲成本,而又保證高效的訪問數據。數據在不同存儲介質上的移動是由系統自動完成的,對應用是完全透明的。數據的分級存儲也是通過系統參數 dataDir 來配置。

dataDir 的配置格式如下:

dataDir data_path [tier_level]

其中 data_path 爲掛載點的文件夾路徑,tier_level 爲介質存儲等級。介質存儲等級越高,盛放數據文件越老。同一存儲等級可掛載多個硬盤,同一存儲等級上的數據文件分佈在該存儲等級的所有硬盤上。TDengine 最多支持 3 級存儲,所以 tier_level 的取值爲 0、1 和 2。在配置 dataDir 時,必須存在且只有一個掛載路徑不指定 tier_level,稱之爲特殊掛載盤(路徑)。該掛載路徑默認爲 0 級存儲介質,且包含特殊文件鏈接,不可被移除,否則會對寫入的數據產生毀滅性影響。

假設一物理節點有六個可掛載的硬盤 / mnt/disk1、/mnt/disk2、…、/mnt/disk6,其中 disk1 和 disk2 需要被指定爲 0 級存儲介質,disk3 和 disk4 爲 1 級存儲介質, disk5 和 disk6 爲 2 級存儲介質。disk1 爲特殊掛載盤,則可在 / etc/taos/taos.cfg 中做如下配置:

dataDir /mnt/disk1/taosdataDir /mnt/disk2/taos 0dataDir /mnt/disk3/taos 1dataDir /mnt/disk4/taos 1dataDir /mnt/disk5/taos 2dataDir /mnt/disk6/taos 2

掛載的盤也可以是非本地的網絡盤,只要系統能訪問即可。

注:多級存儲功能僅企業版支持

6: 數據查詢

TDengine 提供了多種多樣針對表和超級表的查詢處理功能,除了常規的聚合查詢之外,還提供針對時序數據的窗口查詢、統計聚合等功能。TDengine 的查詢處理需要客戶端、vnode, mnode 節點協同完成。

單表查詢

SQL 語句的解析和校驗工作在客戶端完成。解析 SQL 語句並生成抽象語法樹 (Abstract Syntax Tree, AST),然後對其進行校驗和檢查。以及向管理節點(mnode) 請求查詢中指定表的元數據信息(table metadata)。

根據元數據信息中的 End Point 信息,將查詢請求序列化後發送到該表所在的數據節點(dnode)。dnode 接收到查詢請求後,識別出該查詢請求指向的虛擬節點(vnode),將消息轉發到 vnode 的查詢執行隊列。vnode 的查詢執行線程建立基礎的查詢執行環境,並立即返回該查詢請求,同時開始執行該查詢。

客戶端在獲取查詢結果的時候,dnode 的查詢執行隊列中的工作線程會等待 vnode 執行線程執行完成,才能將查詢結果返回到請求的客戶端。

按時間軸聚合、降採樣、插值

時序數據有別於普通數據的顯著特徵是每條記錄均具有時間戳,因此針對具有時間戳數據在時間軸上進行聚合是不同於普通數據庫的重要功能。從這點上來看,與流計算引擎的窗口查詢有相似的地方。

在 TDengine 中引入關鍵詞 interval 來進行時間軸上固定長度時間窗口的切分,並按照時間窗口對數據進行聚合,對窗口範圍內的數據按需進行聚合。例如:

select count(*) from d1001 interval(1h)

針對 d1001 設備採集的數據,按照 1 小時的時間窗口返回每小時存儲的記錄數量。

在需要連續獲得查詢結果的應用場景下,如果給定的時間區間存在數據缺失,會導致該區間數據結果也丟失。TDengine 提供策略針對時間軸聚合計算的結果進行插值,通過使用關鍵詞 Fill 就能夠對時間軸聚合結果進行插值。例如:

select count(*) from d1001 interval(1h) fill(prev)

針對 d1001 設備採集數據統計每小時記錄數,如果某一個小時不存在數據,則返回之前一個小時的統計數據。TDengine 提供前向插值 (prev)、線性插值 (linear)、NULL 值填充 (NULL)、特定值填充 (value)。

多表聚合查詢

多表聚合查詢與單表查詢的整體流程相同,但是存在如下的差異:

預計算

爲有效提升查詢處理的性能,針對物聯網數據的不可更改的特點,在數據塊頭部記錄該數據塊中存儲數據的統計信息:包括最大值、最小值、和。我們稱之爲預計算單元。如果查詢處理涉及整個數據塊的全部數據,直接使用預計算結果,完全不需要讀取數據塊的內容。由於預計算數據量遠小於磁盤上存儲的數據塊數據的大小,對於磁盤 IO 爲瓶頸的查詢處理,使用預計算結果可以極大地減小讀取 IO 壓力,加速查詢處理的流程。預計算機制與 Postgre SQL 的索引 BRIN(block range index)有異曲同工之妙。

**作者簡介:**陶建輝,濤思數據創始人。1994 年到美國留學,1997 年起,先後在芝加哥 Motorola、3Com 等公司從事無線互聯網的研發工作。2008 年初回到北京創辦和信,後被聯發科收購。2013 年初創辦快樂媽咪,後被太平洋網絡收購。2017 年 5 月創辦濤思數據,不僅主導了 TDengine 的整體架構設計,還貢獻了 4 萬多行代碼,每天仍然戰鬥在研發第一線。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/6wKz11z5r5YsJeKBekorzQ