1. 程式人生 > >opentsdb在HBase中表結構設計

opentsdb在HBase中表結構設計

摘要 

OpenTSDB是一個分散式的、可伸縮的時間序列資料庫,在DB-engines的時間序列資料庫排行榜上排名第五。它的特點是能夠提供最高毫秒級精度的時間序列資料儲存,能夠長久儲存原始資料並且不失精度。它擁有很強的資料寫入能力,支援大併發的資料寫入,並且擁有可無限水平擴充套件的儲存容量。

它的強大的資料寫入能力與儲存能力得益於它底層依賴的HBase資料庫,也得益於它在表結構設計上做的大量的儲存優化。

本篇文章會詳細講解其表結構設計,在理解它的表結構設計的同時,分析其採取該設計的深層次原因以及優缺點。它的表結構設計完全貼合HBase的儲存模型,而表格儲存(TableStore、原OTS)與HBase有類似的儲存模型,理解透OpenTSDB的表結構設計後,我們也能夠對這類資料庫的儲存模型有一個更深的理解。

儲存模型

在解析OpenTSDB的表結構設計前,我們需要先對其底層的HBase的儲存模型有一個理解。

表分割槽

HBase會按Rowkey的範圍,將一張大表切成多個region,每個region會由一個region server載入並提供服務。Rowkey的切分與表格儲存的分割槽類似,一個良好設計的表,需要保證讀寫壓力能夠均勻的分散到表的各個region,這樣才能充分發揮分散式叢集的能力。

儲存結構

如圖所示為表結構以及對應的儲存結構示例,在HBase或表格儲存這類底層採用LSM-tree的資料庫中,表資料會按列儲存。每行中的每一列在儲存檔案中都會以Key-value的形式存在於檔案中。其中Key的結構為:行主鍵 + 列名,Value為列的值。該種儲存結構的特點是:         a. 每行主鍵會重複儲存,取決於列的個數         b. 列名會重複儲存,每一列的儲存都會攜帶列名         c. 儲存資料按row-key排序,相鄰的row-key會儲存在相鄰的塊中

OpenTSDB的基本概念

OpenTSDB定義每個時間序列資料需要包含以下屬性:

1. 指標名稱(metric name)

2. 時間戳(UNIX timestamp,毫秒或者秒精度)

3. 值(64位整數或者單精度浮點數)

4. 一組標籤(tags,用於描述資料屬性,至少包含一個或多個標籤,每個標籤由tagKey和tagValue組成,tagKey和tagValue均為字串)

舉個例子,在監控場景中,我們可以這樣定義一個監控指標:

指標名稱:
    sys.cpu.user
標籤:
    host = 10.101.168.111
    cpu = 0
指標值:
    0.5
指標名稱代表這個監控指標是對使用者態CPU的使用監控,引入了兩個標籤,分別標識該監控位於哪臺機器的哪個核。


OpenTSDB支援的查詢場景為:指定指標名稱和時間範圍,給定一個或多個標籤名稱和標籤的值作為條件,查詢出所有的資料。

以上面那個例子舉例,我們可以查詢:

    a. sys.cpu.user (host=*,cpu=*)(1465920000 <= timestamp < 1465923600):查詢凌晨0點到1點之間,所有機器的所有CPU核上的使用者態CPU消耗。

    b. sys.cpu.user (host=10.101.168.111,cpu=*)(1465920000 <= timestamp < 1465923600):查詢凌晨0點到1點之間,某臺機器的所有CPU核上的使用者態CPU消耗。

    c. sys.cpu.user (host=10.101.168.111,cpu=0)(1465920000 <= timestamp < 1465923600):查詢凌晨0點到1點之間,某臺機器的第0個CPU核上的使用者態CPU消耗。

OpenTSDB的儲存優化

瞭解了OpenTSDB的基本概念後,我們來嘗試設計一下表結構。

如上圖是一個簡單的表結構設計,rowkey採用metric name + timestamp + tags的組合,因為這幾個元素才能唯一確定一個指標值。

這張表已經能滿足我們的寫入和查詢的業務需求,但是OpenTSDB採用的表結構設計遠沒有這麼簡單,我們接下來一項一項看它對錶結構做的一些優化。

優化一:縮短row key

觀察這張表記憶體儲的資料,在rowkey的組成部分內,其實有很大一部分的重複資料,重複的指標名稱,重複的標籤。以上圖為例,如果每秒採集一次監控指標,cpu為2核,host規模為100臺,則一天時間內sys.cpu.user這個監控指標就會產生17280000行資料,而這些行中,監控指標名稱均是重複的。如果能將這部分重複資料的長度儘可能的縮短,則能帶來非常大的儲存空間的節省。

OpenTSDB採用的策略是,為每個metric、tag key和tag value都分配一個UID,UID為固定長度三個位元組。

上圖為優化後的儲存結構,可以看出,rowkey的長度大大的縮短了。rowkey的縮短,帶來了很多好處:

    a. 節省儲存空間

    b. 提高查詢效率:減少key匹配查詢的時間

    c. 提高傳輸效率:不光節省了從檔案系統讀取的頻寬,也節省了資料返回佔用的頻寬,提高了資料寫入和讀取的速度。

    d. 緩解Java程式記憶體壓力:Java程式,GC是老大難的問題,能節省記憶體的地方儘量節省。原先用String儲存的metric name、tag key或tag value,現在均可以用3個位元組的byte array替換,大大節省了記憶體佔用。

優化二:減少Key-Value數

優化一是OpenTSDB做的最核心的一個優化,很直觀的可以看到儲存的資料量被大大的節省了。原理也很簡單,將長的變短。但是是否還可以進一步優化呢?

在上面的儲存模型章節中,我們瞭解到。HBase在底層儲存結構中,每一列都會以Key-Value的形式儲存,每一列都會包含一個rowkey。如果要進一步縮短儲存量,那就得想辦法減少Key-Value的個數。

OpenTSDB分了幾個步驟來減少Key-Value的個數:

1. 將多行合併為一行,多行單列變為單行多列。

2. 將多列合併為一列,單行多列變為單行單列。

多行單列合併為單行單列

OpenTSDB將同屬於一個時間週期內的具有相同TSUID(相同的metric name,以及相同的tags)的資料合併為一行儲存。OpenTSDB內預設的時間週期是一個小時,也就是說同屬於這一個小時的所有資料點,會合併到一行記憶體儲,如圖上所示。合併為一行後,該行的rowkey中的timestamp會指定為該小時的起始時間(所屬時間週期的base時間),而每一列的列名,則記錄真實資料點的時間戳與該時間週期起始時間(base)的差值。

這裡列名採用差值而不是真實值也是一個有特殊考慮的設計,如儲存模型章節所述,列名也是會存在於每個Key-Value中,佔用一定的儲存空間。如果是秒精度的時間戳,需要4個位元組,如果是毫秒精度的時間戳,則需要8個位元組。但是如果列名只存差值且時間週期為一個小時的話,則如果是秒精度,則差值取值範圍是0-3600,只需要2個位元組;如果是毫秒精度,則差值取值範圍是0-360000,只需要4個位元組;所以相比存真實時間戳,這個設計是能節省不少空間的。

單行多列合併為單行單列

多行合併為單行後,並不能真實的減少Key-Value個數,因為總的列數並沒有減少。所以要達到真實的節省儲存的目的,還需要將一行的列變少,才能真正的將Key-Value數變少。

OpenTSDB採取的做法是,會在後臺定期的將一行的多列合併為一列,稱之為『compaction』,合併完之後效果如下。

同一行中的所有列被合併為一列,如果是秒精度的資料,則一行中的3600列會合併為1列,Key-Value數從3600個降低到只有1個。

優化三:併發寫優化

上面兩個優化主要是OpenTSDB對儲存的優化,儲存量下降以及Key-Value個數下降後,除了直觀的儲存量上的縮減,對讀和寫的效率都是有一定提升的。

時間序列資料的寫入,有一個不可規避的問題是寫熱點問題,當某一個metric下資料點很多時,則該metric很容易造成寫入熱點。OpenTSDB採取了和這篇文章中介紹的一樣的方法,允許將metric預分桶,可通過『tsd.storage.salt.buckets』配置項來配置。

如上圖所示,預分桶後的變化就是在rowkey前會拼上一個桶編號(bucket index)。預分桶後,可將某個熱點metric的寫壓力分散到多個桶中,避免了寫熱點的產生。

總結

OpenTSDB作為一個應用廣泛的時間序列資料庫,在儲存上做了大量的優化,優化的選擇也是完全契合其底層依賴的HBase資料庫的儲存模型。表格儲存擁有和HBase一樣的儲存模型,這部分優化經驗可以直接借鑑使用到表格儲存的應用場景中,值得我們好好學習。有問題歡迎大家一起探討。