0
  • 聊天消息
  • 系統(tǒng)消息
  • 評論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會員中心
創(chuàng)作中心

完善資料讓更多小伙伴認識你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Prometheus存儲引擎簡析

jf_ro2CN3Fa ? 來源:Keep Coding ? 2023-03-28 17:57 ? 次閱讀

背景知識

時序特點

時序數(shù)據(jù)的特點可以用一話概括:垂直寫(最新數(shù)據(jù)),水平查。

6228fc52-c9f4-11ed-bfe3-dac502259ad0.jpg

對于云原生場景來說,另一個特點是數(shù)據(jù)生命周期短,一次容器的擴縮容會導(dǎo)致時間線膨脹一倍。了解這兩個特點后,來看看 Prometheus 是如何存儲數(shù)據(jù)來迎合上述模式:

├──01BKGV7JC0RY8A6MACW02A2PJD//block的ULID
│├──chunks
││└──000001
│├──tombstones
│├──index
│└──meta.json
├──chunks_head
│└──000001
└──wal
├──000000002
└──checkpoint.00000001
└──00000000

可以看到,數(shù)據(jù)目錄主要有以下幾部分:

block,一個時間段內(nèi)(默認 2 小時)的所有數(shù)據(jù),只讀,用 ULID 命名。每一個 block 內(nèi)主要包括:

chunks 固定大?。ㄗ畲?128M)的 chunks 文件

index 索引文件,主要包含倒排索引的信息

meta.json 元信息,主要包括 block 的 minTime/maxTime,方便查詢時過濾

chunks_head,當(dāng)前在寫入的 block 對應(yīng)的 chunks 文件,只讀,最多 120 個數(shù)據(jù)點,時間跨度最大 2 小時。

wal,Prometheus 采用攢批的方式來異步刷盤,因此需要 WAL 來保證數(shù)據(jù)可靠性

623e4ac6-c9f4-11ed-bfe3-dac502259ad0.jpg

通過上面的目錄結(jié)構(gòu),不難看出 Prometheus 的設(shè)計思路:

通過數(shù)據(jù)按時間分片的方式來解決數(shù)據(jù)生命周期短的問題

通過內(nèi)存攢批的方式來對應(yīng)只寫最新數(shù)據(jù)的場景

數(shù)據(jù)模式

Prometheus 支持的模式比較簡單,只支持單值模式,如下:

cpu_usage{core="1",ip="130.25.175.171"}14.041618137750
metriclabelsvaluetimesample

倒排索引

索引是支持多維搜索的主要手段,時序中的索引結(jié)構(gòu)和搜索引擎的類似,是個倒排索引,可參考下圖

624de6ac-c9f4-11ed-bfe3-dac502259ad0.jpg

在一次查詢中,會對涉及到的 label 分別求對應(yīng)的 postings lists(即時間線集合),然后根據(jù) filter 類型進行集合運算,最后根據(jù)運算結(jié)果得出的時間線,去查相應(yīng)數(shù)據(jù)即可。

磁盤存儲格式

數(shù)據(jù)格式

┌──────────────────────────────┐
│magic(0x0130BC91)<4?byte>│
├──────────────────────────────┤
│version(1)<1?byte>│
├──────────────────────────────┤
│padding(0)<3?byte>│
├──────────────────────────────┤
│┌──────────────────────────┐│
││Chunk1││
│├──────────────────────────┤│
││...││
│├──────────────────────────┤│
││ChunkN││
│└──────────────────────────┘│
└──────────────────────────────┘


>基于SpringCloudAlibaba+Gateway+Nacos+RocketMQ+Vue&Element實現(xiàn)的后臺管理系統(tǒng)+用戶小程序,支持RBAC動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
>
>*項目地址:
>*視頻教程

#單個chunk內(nèi)的結(jié)構(gòu)
┌─────────────────────┬───────────────────────┬───────────────────────┬───────────────────┬───────────────┬──────────────┬────────────────┐
|seriesref<8?byte>|mint<8?byte,?uint64>|maxt<8?byte,?uint64>|encoding<1?byte>|len|data│CRC32<4?byte>│
└─────────────────────┴───────────────────────┴───────────────────────┴────────

chunk 為數(shù)據(jù)在磁盤中的最小組織單元,需要明確以下兩點:

單個 chunk 的時間跨度默認是 2 小時,Prometheus 后臺會有合并操作,把時間相鄰的 block 合到一起

series ref 為時間線的唯一標示,由 8 個字節(jié)組成,前 4 個表示文件 id,后 4 個表示在文件內(nèi)的 offset,需配合后文的索引結(jié)構(gòu)來實現(xiàn)數(shù)據(jù)的定位

索引格式

┌────────────────────────────┬─────────────────────┐
│magic(0xBAAAD700)<4b>│version(1)<1?byte>│
├────────────────────────────┴─────────────────────┤
│┌──────────────────────────────────────────────┐│
││SymbolTable││
│├──────────────────────────────────────────────┤│
││Series││
│├──────────────────────────────────────────────┤│
││LabelIndex1││
│├──────────────────────────────────────────────┤│
││...││
│├──────────────────────────────────────────────┤│
││LabelIndexN││
│├──────────────────────────────────────────────┤│
││Postings1││
│├──────────────────────────────────────────────┤│
││...││
│├──────────────────────────────────────────────┤│
││PostingsN││
│├──────────────────────────────────────────────┤│
││LabelOffsetTable││
│├──────────────────────────────────────────────┤│
││PostingsOffsetTable││
│├──────────────────────────────────────────────┤│
││TOC││
│└──────────────────────────────────────────────┘│
└──────────────────────────────────────────────────┘

在一個索引文件中,最主要的是以下幾部分(從下往上):

TOC 存儲的是其他部分的 offset

Postings Offset Table,用來存儲倒排索引,Key 為 label name/value 序?qū)?,Value 為 Postings 在文件中的 offset。

Postings N,存儲的是具體的時間線序列

Series,存儲的是當(dāng)前時間線,對應(yīng)的 chunk 文件信息

Label Offset Table 與 Label Index 目前在查詢時沒有使用到,這里不再講述

每個部分的具體編碼格式,可參考官方文檔 Index Disk Format,這里重點講述一次查詢是如何找到符合條件的數(shù)據(jù)的:

首先在 Posting Offset Table 中,找到對應(yīng) label 的 Postings 位置

625efc62-c9f4-11ed-bfe3-dac502259ad0.jpg

然后再根據(jù) Postings 中的 series 信息,找到對應(yīng)的 chunk 位置,即上文中的 series ref。

6276edb8-c9f4-11ed-bfe3-dac502259ad0.png

使用方式

Prometheus 在啟動時,會去加載數(shù)據(jù)元信息到內(nèi)存中。主要有下面兩部分:

block 的元信息,最主要的是 mint/maxt,用來確定一次查詢是否需要查看當(dāng)前 block 文件,之后把 chunks 文件以 mmap 方式打開

//openallblocks
bDirs,err:=blockDirs(dir)
for_,bDir:=rangebDirs{
meta,_,err:=readMetaFile(bDir)
//Seeifwealreadyhavetheblockinmemoryoropenitotherwise.
block,open:=getBlock(loaded,meta.ULID)
if!open{
block,err=OpenBlock(l,bDir,chunkPool)
iferr!=nil{
corrupted[meta.ULID]=err
continue
}
}
blocks=append(blocks,block)
}
//openchunkfiles
for_,fn:=rangefiles{
f,err:=fileutil.OpenMmapFile(fn)
iferr!=nil{
returnnil,tsdb_errors.NewMulti(
errors.Wrap(err,"mmapfiles"),
tsdb_errors.CloseAll(cs),
).Err()
}
cs=append(cs,f)
bs=append(bs,realByteSlice(f.Bytes()))
}

block 對應(yīng)的索引信息,主要是倒排索引。由于單個 label 對應(yīng)的 Postings 可能會非常大,Prometheus 不是全量加載,而是每隔 32 個加載,來減輕內(nèi)存壓力。并且保證第一個與最后一個一定被加載,查詢時采用類似跳表的方式進行 posting 定位。

下面代碼為 DB 啟動時,讀入 postings 的邏輯:

//Forthepostingsoffsettablewekeepeverylabelnamebutonlyeverynth
//labelvalue(plusthefirstandlastone),tosavememory.
ReadOffsetTable(r.b,r.toc.PostingsTable,func(key[]string,_uint64,offint)error{
if_,ok:=r.postings[key[0]];!ok{
//Nextlabelname.
r.postings[key[0]]=[]postingOffset{}
iflastKey!=nil{
//Alwaysincludelastvalueforeachlabelname.
r.postings[lastKey[0]]=append(r.postings[lastKey[0]],postingOffset{value:lastKey[1],off:lastOff})
}
lastKey=nil
valueCount=0
}
ifvalueCount%32==0{
r.postings[key[0]]=append(r.postings[key[0]],postingOffset{value:key[1],off:off})
lastKey=nil
}else{
lastKey=key
lastOff=off
}
valueCount++
}
iflastKey!=nil{
r.postings[lastKey[0]]=append(r.postings[lastKey[0]],postingOffset{value:lastKey[1],off:lastOff})
}

下面代碼為根據(jù) label 查詢 postings 的邏輯,完整可見 index 的 Postings 方法:

e,ok:=r.postings[name]//name為labelkey
if!ok||len(values)==0{//values為當(dāng)前需要查詢的labelvalues
returnEmptyPostings(),nil
}
res:=make([]Postings,0,len(values))
skip:=0
valueIndex:=0
forvalueIndex=value})
ifi==len(e){
//We'repasttheend.
break
}
ifi>0&&e[i].value!=value{//postings中沒有該value,需要用前面一個來在文件中搜索
//Needtolookfrompreviousentry.
i--
}
//Don'tCrc32theentirepostingsoffsettable,thisisveryslow
//sohopeanyissueswerecaughtatstartup.
d:=encoding.NewDecbufAt(r.b,int(r.toc.PostingsTable),nil)
d.Skip(e[i].off)
//Iterateontheoffsettable.
varpostingsOffuint64//Theoffsetintothepostingstable.
ford.Err()==nil{
//...skip邏輯省略
v:=d.UvarintBytes()//Labelvalue.
postingsOff=d.Uvarint64()//Offset.
forstring(v)>=value{
ifstring(v)==value{
//Readfromthepostingstable.
d2:=encoding.NewDecbufAt(r.b,int(postingsOff),castagnoliTable)
_,p,err:=r.dec.Postings(d2.Get())
res=append(res,p)
}
valueIndex++
ifvalueIndex==len(values){
break
}
value=values[valueIndex]
}
ifi+1==len(e)||value>=e[i+1].value||valueIndex==len(values){
//Needtogotoalaterpostingsoffsetentry,ifthereisone.
break
}
}
}

內(nèi)存結(jié)構(gòu)

Block 在 Prometheus 實現(xiàn)中,主要分為兩類:

當(dāng)前正在寫入的,稱為 head。當(dāng)超過 2 小時或超過 120 個點時,head 會將 chunk 寫入到本地磁盤中,并使用 mmap 映射到內(nèi)存中,保存在下文的 mmappedChunk 中。

歷史只讀的,存放在一數(shù)組中

typeDBstruct{
blocks[]*Block
head*Head
//...忽略其他字段
}
//Block內(nèi)的主要字段是IndexReader,其內(nèi)部主要是postings,即倒排索引
//MapofLabelNametoalistofsomeLabelValues'spositionintheoffsettable.
//Thefirstandlastvaluesforeachnamearealwayspresent.
postingsmap[string][]postingOffset
typepostingOffsetstruct{
valuestring//labelvalue
offint//posting在對于文件中的offset
}

在上文磁盤結(jié)構(gòu)中介紹過,postingOffset 不是全量加載,而是每隔 32 個。

Head

typeHeadstruct{
postings*index.MemPostings//Postingslistsforterms.
//AllseriesaddressablebytheirIDorhash.
series*stripeSeries
//...忽略其他字段
}
typeMemPostingsstruct{
mtxsync.RWMutex
mmap[string]map[string][]uint64//labelkey->labelvalue->postinglists
orderedbool
}

MemPostings 是 Head 中的索引結(jié)構(gòu),與 Block 的 postingOffset 不同,posting 是全量加載的,畢竟 Head 保存的數(shù)據(jù)較小,對內(nèi)存壓力也小。

typestripeSeriesstruct{
sizeint
series[]map[uint64]*memSeries
hashes[]seriesHashmap
locks[]stripeLock
seriesLifecycleCallbackSeriesLifecycleCallback
}
typememSeriesstruct{
sync.RWMutex
mmappedChunks[]*mmappedChunk//只讀
headChunk*memChunk//讀寫
......//省略其他字段
}
typemmappedChunkstruct{
//數(shù)據(jù)文件在磁盤上的位置,即上文中的seriesref
refuint64
numSamplesuint16
minTime,maxTimeint64
}

stripeSeries 是比較的核心結(jié)構(gòu),series 字段的 key 為時間線,采用自增方式生成;value 為 memSeries,內(nèi)部有存儲具體數(shù)據(jù)的 chunk,采用分段鎖思路來減少鎖競爭。

使用方式

對于一個查詢,大概涉及的步驟:

根據(jù) label 查出所涉及到的時間線,然后根據(jù) filter 類型,進行集合運算,找出符合要求的時間線

根據(jù)時間線信息與時間范圍信息,去 block 內(nèi)查詢符合條件的數(shù)據(jù)

在第一步主要在 PostingsForMatchers 函數(shù)中完成,主要有下面幾個優(yōu)化點:

對于取反的 filter( != !~ ),轉(zhuǎn)化為等于的形式,這樣因為等于形式對應(yīng)的時間線往往會少于取反的效果,最后在合并時,減去這些取反的時間線即可。可參考:Be smarter in how we look at matchers. #572

不同 label 的時間線合并時,利用了時間線有序的特點,采用類似 mergesort 的方式來惰性合并,大致過程如下:

typeintersectPostingsstruct{
arr[]Postings//需要合并的時間線數(shù)組
curuint64//當(dāng)前的時間線
}

func(it*intersectPostings)doNext()bool{
Loop:
for{
for_,p:=rangeit.arr{
if!p.Seek(it.cur){
returnfalse
}
ifp.At()>it.cur{
it.cur=p.At()
continueLoop
}
}
returntrue
}
}

func(it*intersectPostings)Next()bool{
for_,p:=rangeit.arr{
if!p.Next(){
returnfalse
}
ifp.At()>it.cur{
it.cur=p.At()
}
}
returnit.doNext()
}

在第一步查出符合條件的 chunk 所在文件以及 offset 信息之后,第二步的取數(shù)據(jù)則相對簡單,直接使用 mmap 讀數(shù)據(jù)即可,這間接利用操作系統(tǒng)的 page cache 來做緩存,自身不需要再去實現(xiàn) Buffer Pool 之類的數(shù)據(jù)結(jié)構(gòu)。

總結(jié)

通過上文的分析,大體上把 Prometheus 的存儲結(jié)構(gòu)以及查詢流程分析了一遍,還有些細節(jié)沒再展開去介紹,比如為了節(jié)約內(nèi)存使用,label 使用了字典壓縮,但這并不妨礙讀者理解其原理。

此外,Prometheus 默認 2 小時一個 Block 對大時間范圍查詢不友好,因此其后臺會對定期 chunk 文件進行 compaction,合并后的文件大小為 min(31d, retention_time * 0.1) ,相關(guān)細節(jié)后面有機會再單獨介紹吧。






審核編輯:劉清

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請聯(lián)系本站處理。 舉報投訴
  • 操作系統(tǒng)
    +關(guān)注

    關(guān)注

    37

    文章

    6545

    瀏覽量

    122743
  • Cache
    +關(guān)注

    關(guān)注

    0

    文章

    128

    瀏覽量

    28188
  • toc
    toc
    +關(guān)注

    關(guān)注

    0

    文章

    33

    瀏覽量

    8087

原文標題:Prometheus 存儲引擎分析

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。

收藏 人收藏

    評論

    相關(guān)推薦

    新能源電池產(chǎn)業(yè)鏈及投資機會-磷酸亞鐵鋰

    新能源電池產(chǎn)業(yè)鏈及投資機會-磷酸亞鐵鋰  一、前言
    發(fā)表于 12-25 09:34 ?964次閱讀

    Prometheus的基本原理與開發(fā)指南

    PromQL高級實戰(zhàn) 告警引擎深度解析 本地存儲與遠程存儲 梯度運維管理平臺監(jiān)控模塊架構(gòu) 01監(jiān)控系統(tǒng)概述 導(dǎo)讀:本章從監(jiān)控的作用、架構(gòu)分類、指標監(jiān)控發(fā)展史、指標監(jiān)控典型架構(gòu)等4個方面介紹監(jiān)控系統(tǒng)的相關(guān)概念。 1.1.監(jiān)控的作
    的頭像 發(fā)表于 11-09 10:45 ?931次閱讀
    <b class='flag-5'>Prometheus</b>的基本原理與開發(fā)指南

    prometheus做監(jiān)控服務(wù)的整個流程介紹

    ;然后介紹如何收集監(jiān)控數(shù)據(jù),如何展示監(jiān)控數(shù)據(jù),如何觸發(fā)告警;最后展示一個業(yè)務(wù)系統(tǒng)監(jiān)控的demo。監(jiān)控架構(gòu)Prometheus的整個架構(gòu)流程可以參考如下圖片:整個流程大致分為收集數(shù)據(jù),存儲數(shù)據(jù),展示監(jiān)控
    發(fā)表于 12-23 17:34

    MySQL存儲引擎

    MySQL存儲引擎InnoDB??InnoDB 的存儲文件有兩個,后綴名分別是.frm和.idb,其中.frm是表的定義文件,而.idb是數(shù)據(jù)文件。InnoDB 中存在表鎖和行鎖,不過行鎖是在命中
    發(fā)表于 09-06 06:07

    基于ATM理念的UTRAN傳輸架構(gòu)

    基于ATM理念的UTRAN傳輸架構(gòu):UTRAN(UMTS無線接入網(wǎng))系統(tǒng)傳輸網(wǎng)承載其內(nèi)部業(yè)務(wù)傳送及至CN(核心網(wǎng))側(cè)的業(yè)務(wù)匯聚功能,考慮3G網(wǎng)絡(luò)內(nèi),話音、媒體流及Internet等數(shù)據(jù)業(yè)務(wù)的多樣
    發(fā)表于 10-22 10:49 ?15次下載

    電動汽車用鋰離子電池技術(shù)的國內(nèi)外進展

    電動汽車用鋰離子電池技術(shù)的國內(nèi)外進展
    發(fā)表于 11-10 13:53 ?765次閱讀

    PCB線路板電鍍銅工藝

    PCB線路板電鍍銅工藝   一.電鍍工藝的分類:   酸性光亮銅電鍍電鍍鎳/金電鍍錫   二.工藝流程:
    發(fā)表于 11-17 14:01 ?3947次閱讀

    EPON技術(shù)

    EPON技術(shù) EPON是一個新技術(shù),用于保證提供一個高品質(zhì)與高帶寬利用率的應(yīng)用。   EPON在日本、韓國、中國大陸、中國臺灣及其它以以太網(wǎng)絡(luò)為基礎(chǔ)的地區(qū)都
    發(fā)表于 01-22 10:43 ?824次閱讀

    BGA封裝技術(shù)與質(zhì)量控制

    BGA封裝技術(shù)與質(zhì)量控制  ?。樱停裕⊿urface Mount Technology)表面安裝技術(shù)順應(yīng)了電子產(chǎn)品小型化、輕型化的潮流趨勢,為實現(xiàn)電子
    發(fā)表于 03-30 16:49 ?1431次閱讀

    鼠標HID例程(中)

    鼠標 HID 例程 緊接《鼠標 HID 例程(上)》一文,繼續(xù)向大家介紹鼠 標 HID 例程的未完的內(nèi)容。
    發(fā)表于 07-26 15:18 ?0次下載

    籠型三相異步電動機噪聲故障

    籠型三相異步電動機噪聲故障_陳金剛
    發(fā)表于 01-01 15:44 ?1次下載

    prometheus-book Prometheus操作指南

    ./oschina_soft/prometheus-book.zip
    發(fā)表于 05-16 09:11 ?5次下載
    <b class='flag-5'>prometheus</b>-book <b class='flag-5'>Prometheus</b>操作指南

    5G AAU 功放控制和監(jiān)測模塊

    5G AAU 功放控制和監(jiān)測模塊
    發(fā)表于 10-28 12:00 ?2次下載
    5G AAU 功放控制和監(jiān)測模塊<b class='flag-5'>簡</b><b class='flag-5'>析</b>

    prometheus下載安裝教程

    Server 并不直接服務(wù)監(jiān)控特定的目標,其主要任務(wù)負責(zé)數(shù)據(jù)的收集,存儲并且對外提供數(shù)據(jù)查詢支持。因此為了能夠能夠監(jiān)控到某些東西,如主機的CPU使用率,我們需要使用到Exporter。Prometheus
    的頭像 發(fā)表于 01-13 16:07 ?7688次閱讀
    <b class='flag-5'>prometheus</b>下載安裝教程

    AFE8092幀同步特性

    AFE8092幀同步特性
    的頭像 發(fā)表于 08-24 13:37 ?558次閱讀
    AFE8092幀同步特性<b class='flag-5'>簡</b><b class='flag-5'>析</b>