清華自研時間序列資料庫Apache IoTDB原理解析

語言: CN / TW / HK

雲智慧 AIOps 社群是由雲智慧發起,針對運維業務場景,提供演算法、算力、資料集整體的服務體系及智慧運維業務場景的解決方案交流社群。該社群致力於傳播 AIOps 技術,旨在與各行業客戶、使用者、研究者和開發者們共同解決智慧運維行業技術難題,推動 AIOps 技術在企業中落地,建設健康共贏的AIOps 開發者生態。

智慧運維領域的資料特點

指標資料作為運維場景中的重要觀測項,是服務可用性監控、系統健康度度量等場景的主要資料來源。從下面架構示意圖中們可以看出,採集器採集伺服器上各種指標資料,發往訊息佇列,通過實時流處理和離線計算最終存入到資料庫。

在這個上述場景中,我們往往會遇到以下幾種資料挑戰:

  1. 我們日常需要監控的指標數量超百萬,峰值時甚至會達到千萬級,每天沉澱下來的指標資料量達到GB級別,甚至TB級別。
  2. 針對指標資料的日常分析行為通常涉及到近1小時、近1天、近7天、近30天、近1年等多種時間跨度。對範圍查詢的效能有一定要求。

3)在資料傳輸過程中,由於受到網路、裝置資源等原因造成短時間內出現亂序到達、缺丟點、峰谷潮、重複資料等問題

4)由於伺服器或裝置本身原因,採集的指標資料時間往往不夠精準,導致資料粒度不齊整的問題。例如對於秒級別的指標,上一個採集的資料點的時間戳是2021-01-01 10:00:00:000下一個資料點的有可能是2021-01-01 10:00:01:015。而不同的指標相同時刻採集的資料點時間戳分別是2021-01-01 10:00:00:000和2021-01-01 10:00:00:015。

至此整體的需求基本已經明確,在做資料庫選型時需要滿足以下需求:

1)支援資料長時間儲存;

2)支援大時間跨度的快速檢索;

3)高速的資料吞吐能力;

4)高效的資料壓縮比;

5)能夠有效的解決資料的亂序、缺失值、粒度不齊整以及重複資料等資料質量問題。

智慧運維領域的時序資料該如何儲存

對於上述的需求我們該如何選型?是傳統的關係型資料庫,還是通用的NoSQL資料庫,亦或是專用的時序資料庫?他們能否滿足上述資料庫選型的需求?

資料如何儲存還要結合資料本身的特點。這裡以一個真實場景中的案例,某運營商有約3000萬的監控指標,並且採集的過程中存在空值資料、資料缺失、資料重複等情況,甚至會出現新的指標。如果一分鐘採集一次指標在允許一定資料延遲的情況下,寫入速率要超過50w/s,一天需要儲存432億的資料,這對關係資料庫來說無論是從寫入速率是在查詢時效都很難滿足需求。

再看看通用的NoSQL資料庫,首先先簡單梳理一下這些指標資料的特點,我們發現這些指標資料除了有時間戳和指標值外還會有一些tag 來標識資料來自那臺機器,通過採集器實際的採集資料的樣例如下圖所示:

在通用的NoSql 資料庫雖然可以滿足吞吐量效能以及查詢效能,但是為了滿足指標的動態變更我們只能按照一個裝置一張表或者多個裝置共享一張表的建模方式如下圖所示。

無論是一個裝置一張表還是多個裝置共享一張表的儲存方式,為了能夠區分資料來哪個指標,我們只能把tags作為一列進行儲存,不難發現這種這樣建表方式會出現大量的tag資料冗餘儲存的問題。並且通用的NoSql資料庫往往在解決資料重複的問題上並不友好,更多的是依靠一些排重策略來實現。排重策略通常有兩種:一種是依靠外部的排重方式達到儲存時資料已經排重,另一種是儲存不排除查詢時依靠sql來做排重。如果使用第一種資料排重無疑會增加系統的複雜度,如果使用第二種這會導致在處理過程的出現數據冗餘儲存的情況。而且通用的NoSql資料庫還存在一個問題就是:沒有原生操作支援粒度卡齊或者線性填充來解決資料質量差的問題。

然而我們上面的遇到的一些資料挑戰實際是屬於時序資料庫要解決的典型問題,市面上也有許多優秀的時序資料庫,例如InfluxDB、Apache IoTDB等,它們在高吞吐、低延時查詢、資料去重、資料填充、資料降取樣、高壓縮比等功能方面皆能滿足第一章中的資料儲存需求。接下來我們來重點看下完全開源的Apache IoTDB它是如何設計的。

IoTDB的設計

IoTDB的架構

IoTDB是基於LSM-Tree(Log-Structured Merge Tree)的架構進行設計的列式儲存資料庫,LSMtree的核心思想就是放棄部分讀的能力來換取最大的寫入能力。從下圖中IoTDB的整體架構圖中我們可以看出IoTDB主要有三部分構成:分別是資料庫引擎、儲存引擎和分析引擎。

資料庫引擎主要是負責sql語句的解析、資料寫入、資料查詢、資料刪除等功能。

儲存引擎主要是由TsFile來組成也是IoTDB的最具特色的設計,它不僅可以為IoTDB儲存引擎使用,而且還可以直接通過連結器供分析引擎使用,同時還對外開放了TsFile的API,使用者也可以自己直接通過API來獲取裡面的內容。

分析引擎主要是用於與開源的資料處理平臺對接等。

IoTDB的資料讀寫流程

上面提到了IoTDB是基於LSM-Tree的思想來實現的,從以下資料寫入流程圖中我們可以看出:資料經過time detector時會根據記憶體中維護的最大時間戳來判斷是否資料有序,在記憶體緩衝區memtable中分為有序序列和亂序序列,同時為了保障在斷電後資料不丟失,IoTDB也會把資料寫入到WAL(Write-Ahead Logging)中,到此客戶端的資料寫入就已經完成。隨著資料的不斷寫入,memtable中的資料達到一定的程度後,IoTDB通過submit flush task把memtable變成Immutable最終刷到磁碟變成Sstable即TsFile檔案,同時當持久化的TsFile檔案達到一定程度會觸發合併。

上面介紹了IoTDB資料的寫入流程後,我們再來看下IoTDB核心的查詢流程。如下圖所示,當客戶端傳送查詢請求時,首先通過Antlr4進行sql 解析,然後去記憶體中的MemTable、ImmuTable和硬碟中TsFile中進行查詢。當然,IoTDB這裡會通過BloomFilter 和索引來提高資料的查詢效率。我們知道BloomFilter的原理是雜湊結果不存在那麼一定沒有此資料,如果雜湊結果存在,IoTDB那麼還需要繼續借助索引進行進一步的查詢。

TsFile結構

上一節IoTDB的讀寫流程都離不開TsFile,那我們看看IoTDB最核心TsFile是怎樣一個結構。從下面的TsFile 結構示意圖中可以看出TsFile整體分為兩部分:一部分是資料區,另一部分是索引區。

資料區主要包括Page 資料頁、Chunk資料塊和ChunkGroup資料組。其中Page 由一個 PageHeader 和一段資料(time-value 編碼的鍵值對)組成,Chunk資料塊由多個Page和一個Chunk Header組成,ChunkGroup 儲存了一個實體(Entity) 一段時間的資料,它由若干個 Chunk, 一個位元組的分隔符 0x00 和一個ChunkFooter組成。

索引區主要包括TimeseriesIndex、IndexOfTimeseriesIndex和BloomFilter,其中TimeseriesIndex包含1個頭資訊和資料塊索引(ChunkIndex)列表,頭資訊記錄檔案內某條時間序列的資料型別、統計資訊(最大最小時間戳等);資料塊索引列表記錄該序列各Chunk在檔案中的 offset,並記錄相關統計資訊(最大最小時間戳等);IndexOfTimeseriesIndex用於索引各TimeseriesIndex在檔案中的 offset;BloomFilter針對實體(Entity)的布隆過濾器。下圖中TsFile包括兩個實體 d1、d2,每個實體分別包含三個物理量 s1、s2、s3,共 6 個時間序列,每個時間序列包含兩個 Chunk。

TsFile索引構建

TsFile中所有的索引節點構成一棵類B+樹結構的多叉索引樹,這棵樹由兩部分組成:實體索引部分和物理量索引部分。下面舉一個例子來展示索引樹的構成:假設我們設定樹的度為10,我們有150個裝置每個裝置有150個測點共計22500條時間序列,這時我們需構建一個深度為6的索引樹即可,這時我們查詢資料所在位置需做6次磁碟的IO,具體如下圖所示。

上面這種方式看似磁碟IO次數比較多,這是由於我們設定的樹的度比較小從而導致整體樹的深度比較大。如果我們把樹的度增大到300,在實體索引部分一個高度為2的子樹即可實現90000個裝置儲存,同理一個高度為2個物理量索引部分也可存放90000個物理量,最終形成的整個索引樹可存放81億條時間序列。這時我們再讀取資料只需要做4次磁碟IO即可定位到我們需要的資料位置。

TsFile查詢流程

在瞭解了TsFile的結構以及索引的構建,那麼IoTDB是如何在TsFile內部完成一次查詢的,下面用一個具體查詢例如select s1 from root.ln.d1 where time>100 and time<200,來演示在TsFile中是如何定位到所需資料。他的具體步驟以及示意圖如下所示:

1)讀取TsFile MetadataSize資訊

2)根據TsFile MetadataSize和offset獲取TsFile MetaData的位置

3)讀取Metadata IndexNode中的資料,通過MetadataIndexEntry中的name定位到裝置root.ln.d1

4)讀取到裝置root.ln.d1中的offset偏移量,根據偏移量找到TimeSeries Metadata 中的資訊進而找到s1

5)通過ChunkMetadata記錄的統計資訊startTime和endTime與我們查詢的區間(100,200)對比獲取到root.ln.d1裝置下面測點s1的偏移量

6)根據s1中的偏移量可以直接獲取到ChunkGroup

7)依次通過ChunkGroupHeader、ChunkHeader定位到Chunk資料依次讀取Page中的PageHeader,如果時間區間在(100,200)中那麼我們就直接讀取PageData資料。

總結

本文拋磚引玉簡單的介紹了IoTDB的讀寫以及TsFile的核心檔案的設計,實際上IoTDB整體設計和實現還是比較雜,裡面涉及了很多的細節這裡就不再展開介紹了。

寫在最後

近年來,在AIOps領域快速發展的背景下,IT工具、平臺能力、解決方案、AI場景及可用資料集的迫切需求在各行業迸發。基於此,雲智慧在2021年8月釋出了AIOps社群, 旨在樹起一面開源旗幟,為各行業客戶、使用者、研究者和開發者們構建活躍的使用者及開發者社群,共同貢獻及解決行業難題、促進該領域技術發展。

社群先後 開源 了資料視覺化編排平臺-FlyFish、運維管理平臺 OMP 、雲服務管理平臺-摩爾平臺、 Hours 演算法等產品。

視覺化編排平臺-FlyFish:

專案介紹:https://www.cloudwise.ai/flyFish.html

Github地址: https://github.com/CloudWise-OpenSource/FlyFish

Gitee地址: https://gitee.com/CloudWise/fly-fish

行業案例:https://www.bilibili.com/video/BV1z44y1n77Y/

部分大屏案例:

請您通過上方連結瞭解我們,新增小助手(xiaoyuerwie)備註:飛魚。加入開發者交流群,可與業內大咖進行1V1交流!

也可通過小助手獲取雲智慧AIOps資訊,瞭解雲智慧FlyFish最新進展!