prometheus的儲存結構-tsdb是參考了facebook的gorilla之後,自行實現的。所以閱讀
這篇文章《gorilla: a fast, scalable, in-memory time series database》
,可以對prometheus為何採用這樣的儲存結構有著清晰的理解。
下面是乙個非常典型的監控曲線。
可以觀察到,監控資料都是由乙個乙個資料點組成,所以可以用下面的結構來儲存最基本的儲存單元
type sample struct
同時我們還需要注意到的資訊是,我們需要知道這些點屬於什麼機器的哪種監控。這種資訊在promtheus中就用label(標籤來表示)。乙個監控項一般會有多個label(例如圖中),所以一般用labels label。
由於在我們的習慣中,並不關心單獨的點,而是要關心這段時間內的曲線情況。所以自然而然的,我們儲存結構肯定邏輯上是這個樣子:
這樣,我們就可以很容易的通過乙個labels(標籤們)找到對應的資料了。
prometheus將最近的資料儲存在記憶體中,這樣查詢最近的資料會變得非常快,然後通過乙個compactor定時將資料打包到磁碟。資料在記憶體中最少保留2個小時(storage.tsdb.min-block-duration。至於為什麼設定2小時這個值,應該是gorilla那篇**中觀察得出的結論
即壓縮率在2小時時候達到最高,如果保留的時間更短,就無法最大化的壓縮。
接下來,我們看下具體的資料結構
type memseries stuct
其中memchunk是真正儲存資料的記憶體塊,將在後面講到。我們先來觀察下memseries在記憶體中的組織。
由此我們可以看到,針對乙個最終端的監控項(包含抓取的所有標籤,以及新新增的標籤,例如ip),我們都在記憶體有乙個memseries結構。
如果通過一堆標籤快速找到對應的memseries。自然的,prometheus就採用了hash。主要結構體為:
type stripeseries struct
type serieshashmap map[uint64]*memseries
由於在prometheus中會頻繁的對map[hash/refid]memseries進行操作,例如檢查這個labelset對應的memseries是否存在,不存在則建立等。由於golang的map非執行緒安全,所以其採用了分段鎖去拆分鎖。
而hash值是依據labelsets的值而算出來。
為了讓prometheus在記憶體和磁碟中儲存更大的資料量,勢必需要進行壓縮。而memchunk在記憶體中儲存的正是採用xor演算法壓縮過的資料。在這裡,筆者只給出gorilla**中的xor描述
更具體的演算法在**中有詳細描述。總之,使用了xor演算法後,平均每個資料點能從16bytes壓縮到1.37bytes,也就是說所用空間直接降為原來的1/12!
上面討論的是標籤全部給出的查詢情況。那麼我們怎麼快速找到某個或某幾個標籤(非全部標籤)的資料呢。這就需要引入以label為key的倒排索引。我們先給出一組標籤集合
可以看到,由於標籤取值不同,我們會有四種不同的memseries。如果一次性給定4個標籤,應該是很容易從map中直接獲取出對應的memseries(儘管prometheus並沒有這麼做)。但大部分我們的promql只是給定了部分標籤,如何快速的查詢符合標籤的資料呢?
這就引入倒排索引。
先看一下,上面例子中的memseries在記憶體中會有4種,同時記憶體中還夾雜著其它監控項的series
如果我們想知道job:api-server,group為production在一段時間內所有的http請求數量,那麼必須獲取標籤攜帶
()的所有監控資料。
如果沒有倒排索引,那麼我們必須遍歷記憶體中所有的memseries(數萬乃至數十萬),一一按照labels去比對,這顯然在效能上是不可接受的。而有了倒排索引,我們就可以通過求交集的手段迅速的獲取需要哪些memseries。
注意,這邊倒排索引儲存的refid必須是有序的。這樣,我們就可以在o(n)複雜度下順利的算出交集,另外,針對其它請求形式,還有並集/差集的操作,對應實現結構體為:
type intersectpostings struct // 交集
type mergedpostings struct // 並集
type removedpostings struct // 差集
倒排索引的插入組織即為prometheus下面的**
add(labels,t,v)
|->getorcreatewithid
|->mempostings.add
// add a label set to the postings index.
func (p *mempostings) add(id uint64, lset labels.labels)
p.addfor(id, allpostingskey) // allpostingkey "","" every one都加進去
p.mtx.unlock()
}
事實上,給定特定的label:value還是無法滿足我們的需求。我們還需要對標籤正則化,例如取出所有ip為1.1.*字首的http_requests監控資料。為了這種正則,prometheus還維護了乙個標籤所有可能的取值。對應**為:
add(labels,t,v)
|->getorcreatewithid
|->mempostings.add
func (h *head) getorcreatewithid(id, hash uint64, lset labels.labels)
h.values[l.name] = valset
} // 將可能取值塞入stringset中
valset.set(l.value)
// 符號表的維護
h.symbols[l.name] = struct{}{}
h.symbols[l.value] = struct{}{}
} ...
}
那麼,在記憶體中,我們就有了如下的表
圖中所示,有了label和對應所有value集合的表,乙個正則請求就可以很容的分解為若干個非正則請求並最後求交/並/查集,即可得到最終的結果。
時序資料庫
看到這類資料處理基本是要對乙個時間範圍的資料,根據時間段,維度進行歸類,做一些聚合運算。時序資料庫要解決的問題就是如何能在海量資料中,快速響應使用者的此類查詢。序資料庫的一些基本概念 不同的時序資料庫稱呼略有不同 metric 度量,相當於關係型資料庫中的table。data point 資料點,相...
時序資料庫介紹
什麼是時序資料庫 先來介紹什麼是時序資料。時序資料是基於時間的一系列的資料。在有時間的座標中將這些資料點連成線,往過去看可以做成多緯度報表,揭示其趨勢性 規律性 異常性 往未來看可以做大資料分析,機器學習,實現 和預警。時序資料庫就是存放時序資料的資料庫,並且需要支援時序資料的快速寫入 持久化 多維...
Influxdb 時序資料庫 windows 安裝
influxdb 是一款比較火爆的時序資料庫,本文介紹如何在 windows 平台下安裝。1.場景 windows 平台的 influxdb 似乎只支援單機非windows 服務的安裝方式 適用於測試環境或者想體驗什麼是時序資料庫。2.準備安裝包 截止到 2018 07 05,最新版本是 1.5.4...