溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

分布式時(shí)序數(shù)據(jù)庫(kù)QTSDB的設(shè)計(jì)與實(shí)現(xiàn)

發(fā)布時(shí)間:2020-08-08 08:25:32 來(lái)源:ITPUB博客 閱讀:209 作者:HULK一線技術(shù)雜談 欄目:數(shù)據(jù)庫(kù)

現(xiàn)有的開(kāi)源時(shí)序數(shù)據(jù)庫(kù)influxdb只支持單機(jī)運(yùn)行,在面臨大量數(shù)據(jù)寫入時(shí),會(huì)出現(xiàn)查詢慢,機(jī)器負(fù)載高,單機(jī)容量的限制。

為了解決這一問(wèn)題,360基礎(chǔ)架構(gòu)團(tuán)隊(duì)在單機(jī)influxdb的基礎(chǔ)上,開(kāi)發(fā)了集群版——QTSDB。

QTSDB 簡(jiǎn)述

QTSDB是一個(gè)分布式時(shí)間序列數(shù)據(jù)庫(kù),用于處理海量數(shù)據(jù)寫入與查詢。實(shí)現(xiàn)上,是基于開(kāi)源單機(jī)時(shí)序數(shù)據(jù)庫(kù)influxdb 1.7開(kāi)發(fā)的分布式版本,除了具有influxdb本身的特性之外,還有容量擴(kuò)展、副本容錯(cuò)等集群功能。

主要特點(diǎn)如下:

  • 為時(shí)間序列數(shù)據(jù)專門編寫的高性能數(shù)據(jù)存儲(chǔ), 兼顧寫入性能和磁盤空間占用;

  • 類sql查詢語(yǔ)句,支持多種統(tǒng)計(jì)聚合函數(shù);

  • 自動(dòng)清理過(guò)期數(shù)據(jù);

  • 內(nèi)置連續(xù)查詢,自動(dòng)完成用戶預(yù)設(shè)的聚合操作;

  • Golang編寫,沒(méi)有其它的依賴, 部署運(yùn)維簡(jiǎn)單;

  • 節(jié)點(diǎn)動(dòng)態(tài)水平擴(kuò)展,支持海量數(shù)據(jù)存儲(chǔ);

  • 副本冗余設(shè)計(jì),自動(dòng)故障轉(zhuǎn)移,支持高可用;

  • 優(yōu)化數(shù)據(jù)寫入,支持高吞吐量;

系統(tǒng)架構(gòu)

邏輯存儲(chǔ)層次結(jié)構(gòu)

分布式時(shí)序數(shù)據(jù)庫(kù)QTSDB的設(shè)計(jì)與實(shí)現(xiàn)

influxdb架構(gòu)層次最高是database,database下邊根據(jù)數(shù)據(jù)保留時(shí)長(zhǎng)不同分成了不同的retension policy,形成了database下面的多個(gè)存儲(chǔ)容器,因?yàn)闀r(shí)序數(shù)據(jù)庫(kù)與時(shí)間維度關(guān)聯(lián),所以將相同保留時(shí)長(zhǎng)的內(nèi)容存放到一起,便于到期刪除。除此之外,在retension policy之下,將retension policy的保留時(shí)長(zhǎng)繼續(xù)細(xì)分,每個(gè)時(shí)間段的數(shù)據(jù)存儲(chǔ)在一個(gè)shard group中,這樣當(dāng)某個(gè)分段的shard group到期之后,會(huì)將其整個(gè)刪掉,避免從存儲(chǔ)引擎內(nèi)部摳出部分?jǐn)?shù)據(jù)。例如,在database之下的數(shù)據(jù),可能是30天保留時(shí)長(zhǎng),可能是7天保留時(shí)長(zhǎng),他們將存放在不同的retension policy之下。假設(shè)將7天的數(shù)據(jù)繼續(xù)按1天進(jìn)行劃分,就將他們分別存放到7個(gè)shard group中,當(dāng)?shù)?天的數(shù)據(jù)生成時(shí),會(huì)新建一個(gè)shard group寫入,并將第 1天的shard group整個(gè)刪除。

到此為止,同一個(gè)retension policy下,發(fā)來(lái)的當(dāng)下時(shí)序數(shù)據(jù)只會(huì)落在當(dāng)下的時(shí)間段,也就是只有最新的shard group有數(shù)據(jù)寫入,為了提高并發(fā)量,一個(gè)shard group又分成了多個(gè)shard,這些shard全局唯一,分布于所有物理節(jié)點(diǎn)上,每個(gè)shard對(duì)應(yīng)一個(gè)tsm存儲(chǔ)引擎,負(fù)責(zé)存儲(chǔ)數(shù)據(jù)。

在請(qǐng)求訪問(wèn)數(shù)據(jù)時(shí),通過(guò)請(qǐng)求的信息可以鎖定某個(gè)database和retension policy,然后根據(jù)請(qǐng)求中的時(shí)間段信息,鎖定某個(gè)(些)shard group。對(duì)于寫入的情況,每條寫入的數(shù)據(jù)都對(duì)應(yīng)一個(gè)serieskey(這個(gè)概念后面會(huì)介紹),通過(guò)對(duì)serieskey進(jìn)行哈希取模就能鎖定一個(gè)shard,進(jìn)行寫入。而shard是有副本的,在寫入的時(shí)候會(huì)采用無(wú)主多寫的策略同時(shí)寫入到每個(gè)副本中。查詢時(shí),由于查詢請(qǐng)求中沒(méi)有serieskey的信息,所以只能將shard group內(nèi)的shard都查詢一遍,針對(duì)一個(gè)shard,會(huì)在其副本中選擇一個(gè)可用的物理節(jié)點(diǎn)進(jìn)行訪問(wèn)。

那么一個(gè)shard group要有多少shard呢,為了達(dá)到最大并發(fā)量,又不過(guò)分干擾數(shù)據(jù)整體的有序性,在物理節(jié)點(diǎn)數(shù)和副本數(shù)確定后,一個(gè)shard group內(nèi)的shard數(shù)量是機(jī)器數(shù)除以副本數(shù),保障了當(dāng)下的數(shù)據(jù)可以均勻?qū)懭氲剿械奈锢砉?jié)點(diǎn)之上,也不至于因?yàn)閟hard過(guò)多影響查詢效率。例如,圖上data集群有6個(gè)物理節(jié)點(diǎn),用戶指定雙副本,那么就有3個(gè)shard。

集群結(jié)構(gòu)

分布式時(shí)序數(shù)據(jù)庫(kù)QTSDB的設(shè)計(jì)與實(shí)現(xiàn)

整個(gè)系統(tǒng)分成三個(gè)部分:proxy、meta集群、data集群。proxy負(fù)責(zé)接收請(qǐng)求,無(wú)狀態(tài),其前可接lvs支持水平擴(kuò)展。meta集群保存上面提到的邏輯存儲(chǔ)層次及其與物理節(jié)點(diǎn)的對(duì)應(yīng)關(guān)系,通過(guò)raft協(xié)議保障元數(shù)據(jù)的強(qiáng)一致,這里meta信息保存在內(nèi)存中,日志和快照會(huì)持久化到磁盤。data集群是真正的數(shù)據(jù)存儲(chǔ)節(jié)點(diǎn),數(shù)據(jù)以shard為單位存儲(chǔ)于其上,每個(gè)shard都對(duì)應(yīng)一個(gè)tsm存儲(chǔ)引擎。

請(qǐng)求到來(lái)的時(shí)候,經(jīng)過(guò)lvs鎖定一臺(tái)proxy,proxy先根據(jù)database、retension policy和時(shí)間段到meta集群查找meta信息,最終得到一個(gè)shard到物理節(jié)點(diǎn)的映射,然后將這個(gè)映射關(guān)系轉(zhuǎn)換為物理節(jié)點(diǎn)到shard的映射返回給proxy,最后根據(jù)這個(gè)映射關(guān)系,到data集群指定的物理節(jié)點(diǎn)中訪問(wèn)具體的shard,至于shard之下的數(shù)據(jù)訪問(wèn)后邊會(huì)介紹。

數(shù)據(jù)訪問(wèn)

語(yǔ)法格式

分布式時(shí)序數(shù)據(jù)庫(kù)QTSDB的設(shè)計(jì)與實(shí)現(xiàn)

influxdb的查詢提供類似于關(guān)系數(shù)據(jù)庫(kù)的查詢方式,展示出來(lái)類似一個(gè)關(guān)系表:measurement,時(shí)序數(shù)據(jù)庫(kù)的時(shí)間作為一個(gè)永恒的列,除此之外的列分成兩類:

1、field

一類是field,他們是時(shí)序數(shù)據(jù)最關(guān)鍵的數(shù)據(jù)部分,其值會(huì)隨著時(shí)間的流動(dòng)源源不斷的追加,例如兩臺(tái)機(jī)器之間在每個(gè)時(shí)間點(diǎn)上的延遲。

2、tag

另一類是tag,他們是一個(gè)field值的一些標(biāo)記,所以都是字符串類型,并且取值范圍很有限。例如某個(gè)時(shí)間點(diǎn)的延遲field值是2ms,對(duì)應(yīng)有兩個(gè)標(biāo)記屬性,從哪臺(tái)機(jī)器到哪臺(tái)機(jī)器的延遲,因此可以設(shè)計(jì)兩個(gè)tag:from、to。

measurement展示出來(lái)第一行是key,剩下的可以看成value,這樣tag有tagkey,tagvalue,field有fieldkey和fieldvalue。

數(shù)據(jù)讀寫

分布式時(shí)序數(shù)據(jù)庫(kù)QTSDB的設(shè)計(jì)與實(shí)現(xiàn)

當(dāng)收到一行寫入數(shù)據(jù)時(shí),會(huì)轉(zhuǎn)化為如下的格式:

measurement+tagkey1+tagvalue1+tagkey2+tagvalue2+fieldkey+fieldvalue+time。

如果一行中存在多個(gè)field就會(huì)劃分成多條這樣的數(shù)據(jù)存儲(chǔ)。influxdb的存儲(chǔ)引擎可以理解為一個(gè)map,從measurement到fieldkey作為存儲(chǔ)key,后邊的fieldvalue和time是存儲(chǔ)value,這些值會(huì)源源不斷追加的,在存儲(chǔ)引擎中,這些值會(huì)作為一列存儲(chǔ)到一起,因?yàn)槭请S時(shí)間漸變的數(shù)據(jù),將他們保存到一起可以提升壓縮的效果。另外將存儲(chǔ)key去掉fieldkey之后剩余部分就是上邊提到的serieskey。

上邊提到,訪問(wèn)請(qǐng)求在集群中如何鎖定shard,這里介紹在一個(gè)shard內(nèi)的訪問(wèn)。

分布式時(shí)序數(shù)據(jù)庫(kù)QTSDB的設(shè)計(jì)與實(shí)現(xiàn)

influxdb的查詢類似于sql語(yǔ)法,但是跟sql語(yǔ)句的零散信息無(wú)法直接查詢存儲(chǔ)引擎,所以需要一些策略將sql語(yǔ)句轉(zhuǎn)換成存儲(chǔ)key。influxdb通過(guò)構(gòu)建倒排索引來(lái)將where后的tag信息轉(zhuǎn)換為所有相關(guān)的serieskey的集合,然后將每個(gè)serieskey拼接上select后邊的fieldkey就組成了存儲(chǔ)key,這樣就可以按列取出對(duì)應(yīng)的數(shù)據(jù)了。

通過(guò)對(duì)tsm存儲(chǔ)引擎中存儲(chǔ)key內(nèi)serieskey的分析,能夠構(gòu)建出倒排索引,新版本influxdb將倒排索引持久化到每個(gè)shard中,與存儲(chǔ)數(shù)據(jù)的tsm存儲(chǔ)引擎對(duì)應(yīng),叫做tsi存儲(chǔ)引擎。倒排索引相當(dāng)于一個(gè)三層的map,map的key是measurment,值是一個(gè)二層的map,這個(gè)二層的map的key是tagkey,對(duì)應(yīng)的值是一個(gè)一層的map,這個(gè)一層map的key是tagval,對(duì)應(yīng)的值是一個(gè)serieskey的集合,這個(gè)集合中的每個(gè)serieskey字串都包含了map索引路徑上的measurement、tagkey和tagval。

這樣可以分析查詢sql,用from后的measurement查詢倒排索引三級(jí)map獲得一個(gè)二級(jí)map,然后再分析where之后多個(gè)過(guò)濾邏輯單元,以tagkey1=tagval1為例,將這兩個(gè)信息作為二層map的key,查到最終的值:serieskey的集合,這個(gè)集合的每個(gè)serieskey字串都包含了measurment、tagkey1和tagval1,他們是滿足當(dāng)下過(guò)濾邏輯單元的serieskey。根據(jù)這些邏輯單元的與或邏輯,將其對(duì)應(yīng)的serieskey的集合進(jìn)行交并運(yùn)算,最終根據(jù)sql的語(yǔ)義過(guò)濾出所有的符合其邏輯的serieskey的集合,然后將這些serieskey與select后邊的fieldkey拼接起來(lái),得到最終的存儲(chǔ)·key,就可以讀取數(shù)據(jù)了。

分布式時(shí)序數(shù)據(jù)庫(kù)QTSDB的設(shè)計(jì)與實(shí)現(xiàn)

不帶聚合函數(shù)的查詢:如圖,對(duì)于一個(gè)serieskey,需要拼接眾多的fieldkey,進(jìn)而取出多個(gè)列的數(shù)據(jù),他們出來(lái)后面臨的問(wèn)題是怎么組合為一行的數(shù)據(jù),influxdb行列約束比較松散,不能單純按照列內(nèi)偏移確定行。Influxdb把serieskey和time作為判斷列數(shù)據(jù)為一行的依據(jù),每一個(gè)serieskey對(duì)應(yīng)的多列就匯集為一個(gè)以多行為粒度的數(shù)據(jù)流,多個(gè)serieskey對(duì)應(yīng)的數(shù)據(jù)流按照一定順序匯集為一個(gè)數(shù)據(jù)流,作為最終的結(jié)果集返回到客戶端。

分布式時(shí)序數(shù)據(jù)庫(kù)QTSDB的設(shè)計(jì)與實(shí)現(xiàn)

帶聚合函數(shù)的查詢:這種方式與上邊的查詢正好相反,這里是針對(duì)聚合函數(shù)參數(shù)field,拼接上眾多的serieskey,當(dāng)然最終目的都是一樣,得到存儲(chǔ)key,多個(gè)存儲(chǔ)key可以讀取多個(gè)數(shù)據(jù)流,這些數(shù)據(jù)流面臨兩種處理,先將他們按照一定的順序匯集為一個(gè)數(shù)據(jù)流,然后按照一定的策略圈定這個(gè)數(shù)據(jù)流內(nèi)相鄰的一些數(shù)據(jù)進(jìn)行聚合計(jì)算,進(jìn)而得到最終聚合后的值。這里的順序和策略來(lái)自于sql語(yǔ)句中g(shù)roup by后的聚合方式。

多數(shù)據(jù)流的合并聚合方式,也同樣適用于shard之上的查詢結(jié)果。

對(duì)于寫入就比較簡(jiǎn)單了,直接更新數(shù)據(jù)存儲(chǔ)引擎和倒排索引就可以了。

整個(gè)流程

對(duì)于訪問(wèn)的整個(gè)流程上邊都已經(jīng)提到了,這里整體梳理一下:分成兩個(gè)階段,在shard之上的查詢,在shard之下的查詢。

首先訪問(wèn)請(qǐng)求通過(guò)lvs鎖定到某個(gè)proxy,proxy到meta集群中查找meta信息,根據(jù)請(qǐng)求信息,鎖定database,retension policy和shard group,進(jìn)而得到眾多的shard。

對(duì)于寫入操作,根據(jù)寫入時(shí)的serieskey,鎖定一個(gè)shard進(jìn)行寫入,由于shard存在多副本,需要同時(shí)將數(shù)據(jù)寫入到多個(gè)副本。對(duì)于查詢,無(wú)法通過(guò)請(qǐng)求信息得到serieskey,因此需要查詢所有的shard,針對(duì)每個(gè)shard選擇一個(gè)可用的副本,進(jìn)行訪問(wèn)。

經(jīng)過(guò)上邊的處理就獲得shard到物理節(jié)點(diǎn)的映射,然后將其反轉(zhuǎn)為物理節(jié)點(diǎn)到shard的映射,返回給proxy,proxy就可以在data集群的某個(gè)節(jié)點(diǎn)訪問(wèn)對(duì)應(yīng)的shard了。

在shard之下的寫入訪問(wèn),需要拆解insert語(yǔ)句,組合為存儲(chǔ)鍵值對(duì)存入tsm存儲(chǔ)引擎,然后根據(jù)組合的serieskey更新倒排索引。

在shard之下的查詢?cè)L問(wèn),分析sql語(yǔ)句,查詢倒排索引,獲取其相關(guān)的serieskey集合,將其拼接field,形成最終的存儲(chǔ)key,進(jìn)行數(shù)據(jù)訪問(wèn)。然后將眾多數(shù)據(jù)在data節(jié)點(diǎn)上進(jìn)行shard之上的合并聚合,在proxy上進(jìn)行data之上的合并聚合。

最終proxy將訪問(wèn)結(jié)果返回給客戶端。

故障處理

策略

上邊提到influxdb針對(duì)shard提供副本容錯(cuò),當(dāng)寫入數(shù)據(jù)發(fā)送到proxy,proxy將數(shù)據(jù)以無(wú)主多寫的形式發(fā)送到所有的shard副本。meta集群以心跳的形式監(jiān)控data節(jié)點(diǎn)是否在線,在讀取的時(shí)候,針對(duì)同一shard會(huì)在在線的data節(jié)點(diǎn)中隨機(jī)選擇一個(gè)讀取節(jié)點(diǎn)進(jìn)行讀取。

在寫入時(shí)如果一個(gè)data節(jié)點(diǎn)不可用,則會(huì)寫入到proxy的一個(gè)臨時(shí)文件中,等網(wǎng)絡(luò)恢復(fù)正常會(huì)將這些暫存的數(shù)據(jù)發(fā)送到指定節(jié)點(diǎn)。

處理

data集群擴(kuò)容

當(dāng)有全新節(jié)點(diǎn)加入data集群,目前還不支持自動(dòng)將現(xiàn)有數(shù)據(jù)進(jìn)行遷移,不過(guò)也做了些努力,為了使當(dāng)下寫入數(shù)據(jù)盡快應(yīng)用到新的節(jié)點(diǎn),在新加入節(jié)點(diǎn)的時(shí)候,會(huì)將當(dāng)下時(shí)間作為當(dāng)下shard group的結(jié)尾時(shí)間,然后按照全新的data節(jié)點(diǎn)數(shù)量新建一個(gè)shard group,這樣當(dāng)下數(shù)據(jù)量馬上就能均分到各個(gè)data節(jié)點(diǎn),而每個(gè)shard group相關(guān)的meta信息都存儲(chǔ)在meta集群里,因此不會(huì)對(duì)之前數(shù)據(jù)的讀取造成干擾。

data節(jié)點(diǎn)短暫不可用

如果data節(jié)點(diǎn)處于短期不可用狀態(tài),包括短暫的網(wǎng)絡(luò)故障后自恢復(fù),或者硬件故障后運(yùn)維人員干預(yù),最終data節(jié)點(diǎn)還存有掉線前的數(shù)據(jù),那么就可以以原來(lái)的身份加入到data集群。對(duì)于寫入來(lái)說(shuō),不可用期間proxy會(huì)臨時(shí)存放此data節(jié)點(diǎn)的數(shù)據(jù),在data加入集群時(shí)會(huì)將這部分?jǐn)?shù)據(jù)再次發(fā)送到data節(jié)點(diǎn),保障數(shù)據(jù)最終一致。

data節(jié)點(diǎn)長(zhǎng)期不可用

如果data節(jié)點(diǎn)由于一些原因,不能或者不需要以原來(lái)的身份加入到集群,需要運(yùn)維人員手動(dòng)將原來(lái)不可用的data節(jié)點(diǎn)下線,那么這臺(tái)機(jī)器可用時(shí),可以以全新的data身份加入到集群中,這等同于集群的擴(kuò)容。

 總 結(jié) 

QTSDB集群實(shí)現(xiàn)為:寫入時(shí)根據(jù)serieskey將數(shù)據(jù)寫到指定shard,而讀取時(shí)無(wú)法預(yù)知serieskey,因此需要查詢每個(gè)shard。將整個(gè)讀取過(guò)程切分為兩個(gè)階段:在data節(jié)點(diǎn)上進(jìn)行存儲(chǔ)引擎的讀取以及節(jié)點(diǎn)內(nèi)部多shard的合并聚合,在proxy節(jié)點(diǎn)將多個(gè)data節(jié)點(diǎn)的數(shù)據(jù)匯總,進(jìn)行后期的合并聚合,形成最終的結(jié)果集返回到客戶端。

QTSDB現(xiàn)有的集群功能還有不完善的地方,會(huì)在之后的使用中不斷完善。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI