分佈式時序數據庫QTSDB的設計與實現

奇技指南

現有的開源時序數據庫influxdb只支持單機運行,在面臨大量數據寫入時,會出現查詢慢,機器負載高,單機容量的限制。sql

爲了解決這一問題,360基礎架構團隊在單機influxdb的基礎上,開發了集羣版——QTSDB數據庫

QTSDB 簡述

QTSDB是一個分佈式時間序列數據庫,用於處理海量數據寫入與查詢。實現上,是基於開源單機時序數據庫influxdb 1.7開發的分佈式版本,除了具備influxdb自己的特性以外,還有容量擴展、副本容錯等集羣功能。微信

主要特色以下:網絡

  • 爲時間序列數據專門編寫的高性能數據存儲, 兼顧寫入性能和磁盤空間佔用;
  • 類sql查詢語句, 支持多種統計聚合函數;
  • 自動清理過時數據;
  • 內置連續查詢,自動完成用戶預設的聚合操做;
  • Golang編寫,沒有其它的依賴, 部署運維簡單;
  • 節點動態水平擴展,支持海量數據存儲;
  • 副本冗餘設計,自動故障轉移,支持高可用;
  • 優化數據寫入,支持高吞吐量;

系統架構

邏輯存儲層次結構

圖片描述

influxdb架構層次最高是database,database下邊根據數據保留時長不一樣分紅了不一樣的retension policy,造成了database下面的多個存儲容器,由於時序數據庫與時間維度關聯,因此將相同保留時長的內容存放到一塊兒,便於到期刪除。除此以外,在retension policy之下,將retension policy的保留時長繼續細分,每一個時間段的數據存儲在一個shard group中,這樣當某個分段的shard group到期以後,會將其整個刪掉,避免從存儲引擎內部摳出部分數據。例如,在database之下的數據,多是30天保留時長,多是7天保留時長,他們將存放在不一樣的retension policy之下。假設將7天的數據繼續按1天進行劃分,就將他們分別存放到7個shard group中,當第8天的數據生成時,會新建一個shard group寫入,並將第 1天的shard group整個刪除。架構

到此爲止,同一個retension policy下,發來的當下時序數據只會落在當下的時間段,也就是隻有最新的shard group有數據寫入,爲了提升併發量,一個shard group又分紅了多個shard,這些shard全局惟一,分佈於全部物理節點上,每一個shard對應一個tsm存儲引擎,負責存儲數據。併發

在請求訪問數據時,經過請求的信息能夠鎖定某個database和retension policy,而後根據請求中的時間段信息,鎖定某個(些)shard group。對於寫入的狀況,每條寫入的數據都對應一個serieskey(這個概念後面會介紹),經過對serieskey進行哈希取模就能鎖定一個shard,進行寫入。而shard是有副本的,在寫入的時候會採用無主多寫的策略同時寫入到每一個副本中。查詢時,因爲查詢請求中沒有serieskey的信息,因此只能將shard group內的shard都查詢一遍,針對一個shard,會在其副本中選擇一個可用的物理節點進行訪問。運維

那麼一個shard group要有多少shard呢,爲了達到最大併發量,又不過度干擾數據總體的有序性,在物理節點數和副本數肯定後,一個shard group內的shard數量是機器數除以副本數,保障了當下的數據能夠均勻寫入到全部的物理節點之上,也不至於由於shard過多影響查詢效率。例如,圖上data集羣有6個物理節點,用戶指定雙副本,那麼就有3個shard。分佈式

集羣結構

圖片描述

整個系統分紅三個部分:proxy、meta集羣、data集羣。proxy負責接收請求,無狀態,其前可接lvs支持水平擴展。meta集羣保存上面提到的邏輯存儲層次及其與物理節點的對應關係,經過raft協議保障元數據的強一致,這裏meta信息保存在內存中,日誌和快照會持久化到磁盤。data集羣是真正的數據存儲節點,數據以shard爲單位存儲於其上,每一個shard都對應一個tsm存儲引擎。函數

請求到來的時候,通過lvs鎖定一臺proxy,proxy先根據database、retension policy和時間段到meta集羣查找meta信息,最終獲得一個shard到物理節點的映射,而後將這個映射關係轉換爲物理節點到shard的映射返回給proxy,最後根據這個映射關係,到data集羣指定的物理節點中訪問具體的shard,至於shard之下的數據訪問後邊會介紹。高併發

數據訪問

語法格式

圖片描述

influxdb的查詢提供相似於關係數據庫的查詢方式,展現出來相似一個關係表:measurement,時序數據庫的時間做爲一個永恆的列,除此以外的列分紅兩類:

一、field

一類是field,他們是時序數據最關鍵的數據部分,其值會隨着時間的流動源源不斷的追加,例如兩臺機器之間在每一個時間點上的延遲。

二、tag

另外一類是tag,他們是一個field值的一些標記,因此都是字符串類型,而且取值範圍頗有限。例如某個時間點的延遲field值是2ms,對應有兩個標記屬性,從哪臺機器到哪臺機器的延遲,所以能夠設計兩個tag:from、to。

measurement展現出來第一行是key,剩下的能夠當作value,這樣tag有tagkey,tagvalue,field有fieldkey和fieldvalue。

數據讀寫

圖片描述

當收到一行寫入數據時,會轉化爲以下的格式:

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

若是一行中存在多個field就會劃分紅多條這樣的數據存儲。influxdb的存儲引擎能夠理解爲一個map,從measurement到fieldkey做爲存儲key,後邊的fieldvalue和time是存儲value,這些值會源源不斷追加的,在存儲引擎中,這些值會做爲一列存儲到一塊兒,由於是隨時間漸變的數據,將他們保存到一塊兒能夠提高壓縮的效果。另外將存儲key去掉fieldkey以後剩餘部分就是上邊提到的serieskey。

上邊提到,訪問請求在集羣中如何鎖定shard,這裏介紹在一個shard內的訪問。

圖片描述

influxdb的查詢相似於sql語法,可是跟sql語句的零散信息沒法直接查詢存儲引擎,因此須要一些策略將sql語句轉換成存儲key。influxdb經過構建倒排索引來將where後的tag信息轉換爲全部相關的serieskey的集合,而後將每一個serieskey拼接上select後邊的fieldkey就組成了存儲key,這樣就能夠按列取出對應的數據了。

經過對tsm存儲引擎中存儲key內serieskey的分析,可以構建出倒排索引,新版本influxdb將倒排索引持久化到每一個shard中,與存儲數據的tsm存儲引擎對應,叫作tsi存儲引擎。倒排索引至關於一個三層的map,map的key是measurment,值是一個二層的map,這個二層的map的key是tagkey,對應的值是一個一層的map,這個一層map的key是tagval,對應的值是一個serieskey的集合,這個集合中的每一個serieskey字串都包含了map索引路徑上的measurement、tagkey和tagval。

這樣能夠分析查詢sql,用from後的measurement查詢倒排索引三級map得到一個二級map,而後再分析where以後多個過濾邏輯單元,以tagkey1=tagval1爲例,將這兩個信息做爲二層map的key,查到最終的值:serieskey的集合,這個集合的每一個serieskey字串都包含了measurment、tagkey1和tagval1,他們是知足當下過濾邏輯單元的serieskey。根據這些邏輯單元的與或邏輯,將其對應的serieskey的集合進行交併運算,最終根據sql的語義過濾出全部的符合其邏輯的serieskey的集合,而後將這些serieskey與select後邊的fieldkey拼接起來,獲得最終的存儲·key,就能夠讀取數據了。

圖片描述

不帶聚合函數的查詢:如圖,對於一個serieskey,須要拼接衆多的fieldkey,進而取出多個列的數據,他們出來後面臨的問題是怎麼組合爲一行的數據,influxdb行列約束比較鬆散,不能單純按照列內偏移肯定行。Influxdb把serieskey和time做爲判斷列數據爲一行的依據,每個serieskey對應的多列就聚集爲一個以多行爲粒度的數據流,多個serieskey對應的數據流按照必定順序聚集爲一個數據流,做爲最終的結果集返回到客戶端。

圖片描述

帶聚合函數的查詢:這種方式與上邊的查詢正好相反,這裏是針對聚合函數參數field,拼接上衆多的serieskey,固然最終目的都是同樣,獲得存儲key,多個存儲key能夠讀取多個數據流,這些數據流面臨兩種處理,先將他們按照必定的順序聚集爲一個數據流,而後按照必定的策略圈定這個數據流內相鄰的一些數據進行聚合計算,進而獲得最終聚合後的值。這裏的順序和策略來自於sql語句中group by後的聚合方式。

多數據流的合併聚合方式,也一樣適用於shard之上的查詢結果。

對於寫入就比較簡單了,直接更新數據存儲引擎和倒排索引就能夠了。

整個流程

對於訪問的整個流程上邊都已經提到了,這裏總體梳理一下:分紅兩個階段,在shard之上的查詢,在shard之下的查詢。

首先訪問請求經過lvs鎖定到某個proxy,proxy到meta集羣中查找meta信息,根據請求信息,鎖定database,retension policy和shard group,進而獲得衆多的shard。

對於寫入操做,根據寫入時的serieskey,鎖定一個shard進行寫入,因爲shard存在多副本,須要同時將數據寫入到多個副本。對於查詢,沒法經過請求信息獲得serieskey,所以須要查詢全部的shard,針對每一個shard選擇一個可用的副本,進行訪問。

通過上邊的處理就得到shard到物理節點的映射,而後將其反轉爲物理節點到shard的映射,返回給proxy,proxy就能夠在data集羣的某個節點訪問對應的shard了。

在shard之下的寫入訪問,須要拆解insert語句,組合爲存儲鍵值對存入tsm存儲引擎,而後根據組合的serieskey更新倒排索引。

在shard之下的查詢訪問,分析sql語句,查詢倒排索引,獲取其相關的serieskey集合,將其拼接field,造成最終的存儲key,進行數據訪問。而後將衆多數據在data節點上進行shard之上的合併聚合,在proxy上進行data之上的合併聚合。

最終proxy將訪問結果返回給客戶端。

故障處理

策略

上邊提到influxdb針對shard提供副本容錯,當寫入數據發送到proxy,proxy將數據以無主多寫的形式發送到全部的shard副本。meta集羣以心跳的形式監控data節點是否在線,在讀取的時候,針對同一shard會在在線的data節點中隨機選擇一個讀取節點進行讀取。

在寫入時若是一個data節點不可用,則會寫入到proxy的一個臨時文件中,等網絡恢復正常會將這些暫存的數據發送到指定節點。

處理

data集羣擴容

當有全新節點加入data集羣,目前還不支持自動將現有數據進行遷移,不過也作了些努力,爲了使當下寫入數據儘快應用到新的節點,在新加入節點的時候,會將當下時間做爲當下shard group的結尾時間,而後按照全新的data節點數量新建一個shard group,這樣當下數據量立刻就能均分到各個data節點,而每一個shard group相關的meta信息都存儲在meta集羣裏,所以不會對以前數據的讀取形成干擾。

data節點短暫不可用

若是data節點處於短時間不可用狀態,包括短暫的網絡故障後自恢復,或者硬件故障後運維人員干預,最終data節點還存有掉線前的數據,那麼就能夠以原來的身份加入到data集羣。對於寫入來講,不可用期間proxy會臨時存放此data節點的數據,在data加入集羣時會將這部分數據再次發送到data節點,保障數據最終一致。

data節點長期不可用

若是data節點因爲一些緣由,不能或者不須要以原來的身份加入到集羣,須要運維人員手動將原來不可用的data節點下線,那麼這臺機器可用時,能夠以全新的data身份加入到集羣中,這等同於集羣的擴容。

總 結

QTSDB集羣實現爲:寫入時根據serieskey將數據寫到指定shard,而讀取時沒法預知serieskey,所以須要查詢每一個shard。將整個讀取過程切分爲兩個階段:在data節點上進行存儲引擎的讀取以及節點內部多shard的合併聚合,在proxy節點將多個data節點的數據彙總,進行後期的合併聚合,造成最終的結果集返回到客戶端。

QTSDB現有的集羣功能還有不完善的地方,會在以後的使用中不斷完善。

本文爲360技術原創文章,轉載請務必註明出處及文末二維碼,謝謝~

圖片描述

關於360技術

360技術是360技術團隊打造的技術分享公衆號,天天推送技術乾貨內容

更多技術信息歡迎關注「360技術」微信公衆號

相關文章
相關標籤/搜索