InfluxDB 是如何工作的(1)使用者故事及思路

語言: CN / TW / HK

前言:在雲廠的工作中,總是會面對大量的時序資料。InfluxDB 是工作中最常用的時序資料庫。時序資料與更為常用的關係資料的區別是什麼,為什麼 InfluxDB 可以高效能低儲存空間消耗的儲存海量時序資料。


一、使用者故事

時序資料有什麼特點?分析一個具體的需求:如何儲存一個數據中心內所有伺服器 CPU 分核心使用率?

就是需要表達: 裝置 在某個 時間狀態

開啟工作管理員,切換到效能 Tab,就可以看見自己裝置的 CPU 使用率圖。

只看其中 CPU 佔用率的圖,如果把它還原成表的形式:

IP地址 CPU核 時間 使用率
192.168.1.1 #0 5 1
192.168.1.1 #1 5 17
192.168.1.1 #0 6 3
192.168.1.1 #1 6 15

可以歸類為:

  • IP地址、CPU核:共同表達了是哪一個裝置,把這些統稱為 標籤
  • 時間
  • 使用率:裝置的狀態,即

這就是時序資料的基本資料模型。

對比常見的使用者表:

使用者名稱 密碼 上次登陸時間
tsrs $2a$10$Tf9Qw0W8UOW2P…… 5
rsts $2a$10$EvLK4wtA4xSHYU…… 10

發現時序資料會有這些特點:

  • 一定會存在 時間
  • 標籤 會經常重複,因為一個裝置會持續的產生資料,同一個裝置的標籤是一致的。

資料操作的需求:

  • 增:批量增,所有的裝置會間隔 1s 上報自己的 CPU 使用率
  • 刪:區間刪,刪除 7 天之前的所有數
  • 改:不會改,不會出現「把 4 月份的CPU使用率改為 0」的情況
  • 查:時間區間+標籤查,找出過去十分鐘 192.168.1.1 CPU#0 核的使用率

對比使用者系統:

  • 增:單條增,註冊
  • 刪:單條刪,銷戶
  • 改:單條改,改密
  • 查:單條查,登入

然後是計算機本身的特點:

無論是HDD、SSD還是記憶體連續讀寫效能都遠高於隨機讀寫,這種差距甚至可以達到三個數量級,其中,記憶體的隨機讀寫效能是最高的。如下圖(注意對數座標)

二、思路

假設現在重新設計一個時序資料庫:

因為硬碟和記憶體的大小總是有限的,如果能儘量少的儲存資料,就可以極大的降低成本。而且同一個資料來源上報的資料的標籤值總是一致的。在儲存資料中心內所有伺服器 CPU 分核心使用率這個場景中,資料中發生變化的只有時間和值。那麼標籤只需要存一次。

可以把擁有同樣標籤的資料稱之為一個 系列

根據這個思路改造一下上面的表,拆分成兩張:

方案一:

系列表:

編號 標籤值
1 IP=192.168.1.1;CPU=#0
2 IP=192.168.1.1;CPU=#1

資料表:

系列編號 時間 使用率
1 5 1
2 5 17
1 6 3
2 6 15

因為查詢請求總是類似 「找出過去十分鐘 192.168.1.1 #0 CPU核的使用率」這樣的針對一個系列的查詢。而連讀寫總是比隨機讀寫要快得多得多。那麼把一個系列的資料組織到一起將會有很大的益處。再改造一下設計,把資料按系列拆分開:

方案二:

系列表:

表號 標籤值
1 IP=192.168.1.1;CPU=#0
2 IP=192.168.1.1;CPU=#1

表①:

時間 使用率
5 1
6 3

表②:

時間 使用率
5 17
6 15

但是,這導致了寫變成隨機操作。

因為記憶體是所有儲存裝置中隨機效能最好的,可以利用記憶體先存起來,再改一改方案二。

方案二A:

把資料先按方案二寫到記憶體上,攢一段時間的資料再統一寫到硬碟上。

似乎很完美了。

但是,假如在記憶體上的資料寫入硬碟之前發生了意外斷電,這些資料就會丟失。繼續改方案二。

方案二B:

寫入記憶體的同時,也按照 方案一中資料表 的形式寫一份到硬碟中,把它稱之為 WAL(Write-ahead logging),對硬碟來說是連續寫,可以有很好的效能 。記憶體和硬碟上的兩份資料都寫入成功才是成功。

如果發生斷電,程式重啟後先讀取硬碟上的 WAL,恢復記憶體中尚未寫入硬碟的資料。雖然讀取 WAL 恢復記憶體結構的成本比較高,但斷電並不總是發生。

三、InfluxDB 是怎麼幹的

InfluxDB 的做法 是類似的:

func (e *Engine) WritePoints(ctx context.Context, points []models.Point) error {
  // 略去預處理程式碼
​
  // first try to write to the cache
  if err := e.Cache.WriteMulti(values); err != nil {
    return err
  }
​
  if e.WALEnabled {
    if _, err := e.WAL.WriteMulti(ctx, values); err != nil {
      return err
    }
  }
  return seriesErr
}

先寫入了記憶體上的 Cache,然後寫入硬碟上的 WAL兩者都寫入成功後才返回。

最後:我們根據時序資料和計算機本身的特點重新梳理了思路,反推出了與 InfluxDB 類似的方案 。雖然一直在用關係資料庫中的表進行說明,但是這 並不表示 InfluxDB 使用關係資料庫做為其儲存引擎 。在下一篇文章中將瞭解如何把 InfluxDB 在自己的機器上編譯執行起來,進行斷點除錯,並詳細的探索 InfluxDB 中 Cache 的結構。

擴充套件閱讀: