時序數據庫連載系列: 時序數據庫一哥InfluxDB之存儲機制解析

InfluxDB 的存儲機制解析

本文介紹了InfluxDB對於時序數據的存儲/索引的設計。因爲InfluxDB的集羣版已在0.12版就再也不開源,所以如無特殊說明,本文的介紹對象都是指 InfluxDB 單機版git

1. InfluxDB 的存儲引擎演進

儘管InfluxDB自發布以來歷時三年多,其存儲引擎的技術架構已經作過幾回重大的改動, 如下將簡要介紹一下InfluxDB的存儲引擎演進的過程。github

1.1 演進簡史

  • 版本0.9.0以前golang

    **基於 LevelDB的LSMTree方案**
    複製代碼
  • 版本0.9.0~0.9.4算法

    **基於BoltDB的mmap COW B+tree方案**
    複製代碼
  • 版本0.9.5~1.2數據庫

    **基於自研的 WAL + TSMFile 方案**(TSMFile方案是0.9.6版本正式啓用,0.9.5只是提供了原型)
    複製代碼
  • 版本1.3~至今數組

    **基於自研的 WAL + TSMFile + TSIFile 方案**
    複製代碼

1.2 演進的考量

InfluxDB的存儲引擎前後嘗試過包括LevelDB, BoltDB在內的多種方案。可是對於InfluxDB的下述訴求終不能完美地支持:緩存

  • 時序數據在降採樣後會存在大批量的數據刪除bash

    => *LevelDB的LSMTree刪除代價太高*複製代碼
  • 單機環境存放大量數據時不能佔用過多文件句柄數據結構

    => *LevelDB會隨着時間增加產生大量小文件*複製代碼
  • 數據存儲須要熱備份架構

    => *LevelDB只能冷備*複製代碼
  • 大數據場景下寫吞吐量要跟得上

    => *BoltDB的B+tree寫操做吞吐量成瓶頸*複製代碼
  • 存儲需具有良好的壓縮性能

    => *BoltDB不支持壓縮*
    複製代碼

此外,出於技術棧的一致性以及部署的簡易性考慮(面向容器部署),InfluxDB團隊但願存儲引擎 與 其上層的TSDB引擎同樣都是用GO編寫,所以潛在的RocksDB選項被排除

基於上述痛點,InfluxDB團隊決定本身作一個存儲引擎的實現。

2 InfluxDB的數據模型

在解析InfluxDB的存儲引擎以前,先回顧一下InfluxDB中的數據模型。

在InfluxDB中,時序數據支持多值模型,它的一條典型的時間點數據以下所示:

圖 1
030

  • measurement:

    指標對象,也即一個數據源對象。每一個measurement能夠擁有一個或多個指標值,也即下文所述的**field**。在實際運用中,能夠把一個現實中被檢測的對象(如:「cpu」)定義爲一個measurement複製代碼
  • tags:

    概念等同於大多數時序數據庫中的tags, 一般經過tags能夠惟一標示數據源。每一個tag的key和value必須都是字符串。複製代碼
  • field:

    數據源記錄的具體指標值。每一種指標被稱做一個「field」,指標值就是 「field」對應的「value」複製代碼
  • timestamp:

    數據的時間戳。在InfluxDB中,理論上時間戳能夠精確到 **納秒**(ns)級別
    複製代碼

此外,在InfluxDB中,measurement的概念之上還有一個對標傳統DBMS的 Database 的概念,邏輯上每一個Database下面能夠有多個measurement。在單機版的InfluxDB實現中,每一個Database實際對應了一個文件系統的 目錄

2.1 Serieskey的概念

InfluxDB中的SeriesKey的概念就是一般在時序數據庫領域被稱爲 時間線 的概念, 一個SeriesKey在內存中的表示即爲下述字符串(逗號和空格被轉義)的 字節數組(github.com/influxdata/influxdb/model#MakeKey())

{measurement名}{tagK1}={tagV1},{tagK2}={tagV2},...

其中,SeriesKey的長度不能超過 65535 字節

2.2 支持的Field類型

InfluxDB的Field值支持如下數據類型:

Datatype Size in Mem Value Range
Float 8 bytes 1.797693134862315708145274237317043567981e+308 ~ 4.940656458412465441765687928682213723651e-324
Integer 8 bytes -9223372036854775808 ~ 9223372036854775807
String 0~64KB String with length less than 64KB
Boolean 1 byte true 或 false

在InfluxDB中,Field的數據類型在如下範圍內必須保持不變,不然寫數據時會報錯 類型衝突

同一Serieskey + 同一field + 同一shard

2.3 Shard的概念

在InfluxDB中, 能且只能 對一個Database指定一個 Retention Policy (簡稱:RP)。經過RP能夠對指定的Database中保存的時序數據的留存時間(duration)進行設置。而 Shard 的概念就是由duration衍生而來。一旦一個Database的duration肯定後, 那麼在該Database的時序數據將會在這個duration範圍內進一步按時間進行分片從而時數據分紅以一個一個的shard爲單位進行保存。

shard分片的時間 與 duration之間的關係以下

Duration of RP Shard Duration
< 2 Hours 1 Hour
>= 2 Hours 且 <= 6 Months 1 Day
> 6 Months 7 Days

新建的Database在未顯式指定RC的狀況下,默認的RC爲 數據的Duration爲永久,Shard分片時間爲7天

注: 在閉源的集羣版Influxdb中,用戶能夠經過RC規則指定數據在基於時間分片的基礎上再按SeriesKey爲單位進行進一步分片

3. InfluxDB的存儲引擎分析

時序數據庫的存儲引擎主要需知足如下三個主要場景的性能需求

  1. 大批量的時序數據寫入的高性能
  2. 直接根據時間線(即Influxdb中的 Serieskey )在指定時間戳範圍內掃描數據的高性能
  3. 間接經過measurement和部分tag查詢指定時間戳範圍內全部知足條件的時序數據的高性能

InfluxDB在結合了1.2所述考量的基礎上推出了他們的解決方案,即下面要介紹的 WAL + TSMFile + TSIFile的方案

3.1 WAL解析

InfluxDB寫入時序數據時爲了確保數據完整性和可用性,與大部分數據庫產品同樣,都是會先寫WAL,再寫入緩存,最後刷盤。對於InfluxDB而言,寫入時序數據的主要流程如同下圖所示:

圖 2
031_jpeg

InfluxDB對於時間線數據和時序數據自己分開,分別寫入不一樣的WAL中,其結構以下所示:

索引數據的WAL

因爲InfluxDB支持對Measurement,TagKey,TagValue的刪除操做,固然隨着時序數據的不斷寫入,天然也包括 增長新的時間線,所以索引數據的WAL會區分當前所作的操做具體是什麼,它的WAL的結構以下圖所示

圖 3
032

時序數據的WAL

因爲InfluxDB對於時序數據的寫操做永遠只有單純寫入,所以它的Entry不須要區分操做種類,直接記錄寫入的數據便可

圖 4
033

3.2 TSMFile解析

TSMFile是InfluxDB對於時序數據的存儲方案。在文件系統層面,每個TSMFile對應了一個 Shard

TSMFile的存儲結構以下圖所示:

圖 5
035

其特色是在一個TSMFile中將 時序數據(i.e Timestamp + Field value)保存在數據區;將Serieskey 和 Field Name的信息保存在索引區,經過一個基於 Serieskey + Fieldkey構建的形似B+tree的文件內索引快速定位時序數據所在的 數據塊

注: 在當前版本中,單個TSMFile的最大長度爲2GB,超過期即便是同一個Shard,也會繼續新開一個TSMFile保存數據。本文的介紹出於簡單化考慮,如下內容不考慮同一個Shard的TSMFile分裂的場景

  • 索引塊的構成

    上文的索引塊的構成,以下所示:
    
    *圖 6*複製代碼

    036

其中 **索引條目** 在InfluxDB的源碼中被稱爲`directIndex`。在TSMFile中,索引塊是按照 Serieskey + Fieldkey **排序** 後組織在一塊兒的。

明白了TSMFile的索引區的構成,就能夠很天然地理解InfluxDB如何高性能地在TSMFile掃描時序數據了:

1. 根據用戶指定的時間線(Serieskey)以及Field名 在 **索引區** 利用二分查找找到指定的Serieskey+FieldKey所處的 **索引數據塊**
2. 根據用戶指定的時間戳範圍在 **索引數據塊** 中查找數據落在哪一個(*或哪幾個*)**索引條目**
3. 將找到的 **索引條目** 對應的 **時序數據塊** 加載到內存中進行進一步的Scan

*注:上述的1,2,3只是簡單化地介紹了查詢機制,實際的實現中還有相似掃描的時間範圍跨索引塊等一系列複雜場景*

<br>
複製代碼
  • 時序數據的存儲

    在圖 2中介紹了時序數據塊的結構:即同一個 Serieskey + Fieldkey 的 全部時間戳 - Field值對被拆分開,分紅兩個區:Timestamps區和Value區分別進行存儲。它的目的是:實際存儲時能夠分別對時間戳和Field值按不一樣的壓縮算法進行存儲以減小時序數據塊的大小

    採用的壓縮算法以下所示:

    • Timestamp: Delta-of-delta encoding
    • Field Value:因爲單個數據塊的Field Value必然數據類型相同,所以能夠集中按數據類型採用不一樣的壓縮算法

    作查詢時,當利用TSMFile的索引找到文件中的時序數據塊時,將數據塊載入內存並對Timestamp以及Field Value進行解壓縮後以便繼續後續的查詢操做。

3.3 TSIFile解析

有了TSMFile,第3章開頭所說的三個主要場景中的場景1和場景2均可以獲得很好的解決。可是若是查詢時用戶並無按預期按照Serieskey來指定查詢條件,而是指定了更加複雜的條件,該如何確保它的查詢性能?一般狀況下,這個問題的解決方案是依賴倒排索引(Inverted Index)。

InfluxDB的倒排索引依賴於下述兩個數據結構

  • map<SeriesID, SeriesKey>
  • map<tagkey, map<tagvalue, List<SeriesID>>>

它們在內存中展示以下:

圖 7
037

圖 8
038

可是在實際生產環境中,因爲用戶的時間線規模會變得很大,所以會形成倒排索引使用的內存過多,因此後來InfluxDB又引入了 TSIFile

TSIFile的總體存儲機制與TSMFile類似,也是以 Shard 爲單位生成一個TSIFile。具體的存儲格式就在此不贅述了。

4. 總結

以上就是對InfluxDB的存儲機制的粗淺解析,因爲目前所見的只有單機版的InfluxDB,因此尚不知道集羣版的InfluxDB在存儲方面有哪些不一樣。可是,即使是這單機版的存儲機制,也對咱們設計時序數據庫有着重要的參考意義。

原文連接:yq.aliyun.com/articles/69…

#阿里雲開年Hi購季#幸運抽好禮!點此抽獎:https://www.aliyun.com/acts/product-section-2019/yq-lottery?utm_content=g_1000042901

相關文章
相關標籤/搜索