逝者如斯夫,不舍晝夜。
—— 孔子
時間如流水,一去不複返。自古不乏對時間流逝的感慨,而現代已經有很多技術記錄流逝的過去。我們可以拍照,可以錄像,當然還可以用時序資料庫!
時序資料庫是專門存放随着時間推移而不斷變化的資料。近些年,随着IoT等概念的流行,時序資料庫成為資料庫一個相對獨立的領域逐漸受到重視,廣泛應用于物聯網、監控系統、金融、醫療和零售等多種場景。
過去12個月時序資料庫(Time Series DBMS)熱度不斷增長
那麼雲上的使用者如何建構一個存儲海量資料的時序資料庫呢?筆者這裡推薦使用 雲HBase + OpenTSDB 方案。雲HBase是使用阿裡多年優化過的HBase核心版本,本文不作過多介紹,詳情請看
産品首頁。
OpenTSDB簡介
OpenTSDB是一款基于HBase建構的時序資料庫,它的資料存儲完全交給HBase,本身沒有任何資料存儲。所有節點是對等的,是以部署起來其實是非常友善的。因為基于HBase,是以本身就具備了橫向擴充,存儲海量資料的能力。常見的部署模式有2種,一種分離部署,一種混合部署。
獨立部署,即與多個業務共享一個HBase。适合時序業務較小,或者用不滿HBase資源。
混合部署,即TSDB程序和RS在一個VM内。适合時序業務較重,需要獨享HBase。
上述2種模式,雲HBase産品都能提供支援。
OpenTSDB資料定義
一條時間線由 Metirc + 多個tag 唯一确定,時間線上會有源源不斷的資料點(Data Point)寫入,資料點由時間戳和值組成。OpenTSDB支援秒級(10位整數),毫秒級别(13位整數)兩種時間精度。
舉個例子,比如我們監控一個手環收集的心跳資訊,那麼我們可以這樣定義:
Metric: "band.heartbeat"
Tags: "id" # 隻定義一個tag,就是手環的ID
那麼我們通過
band.heartbeat
+
id=1
就能查詢到編為1的手環收集到的心跳資訊。
OpenTSDB資料存儲格式
資料表整體設計
這個設計有幾個特點:
- 1.metric和tag映射成UID,不存儲實際字元串,以節約空間。
- 2.每條時間線每小時的資料點歸在一行,每列是一個資料點,這樣每列隻需要記錄與這行起始時間偏移,以節省空間。
- 3.每列就是一個KeyValue,如果是毫秒精度,一行最多可以有3600000個KV,這裡其實會有些問題,後面會講到。
RowKey格式
salt:打散同一metric不同時間線的熱點
metric, tagK, tagV:實際存儲的是字元串對應的UID(在tsdb-uid表中)
timestamp:每小時資料存在一行,記錄的是每小時整點秒級時間戳
metric和tag
它們長度預設是3個位元組,即最多隻能配置設定
2^24=16777216
個UID。可以通過這些參數調整:
tsd.storage.uid.width.metric # metric UID長度,預設3
tsd.storage.uid.width.tagk # tagK UID長度,預設3
tsd.storage.uid.width.tagv # tagV UID長度 預設3
# 這3者的UID配置設定分别是獨立的空間
注意:
叢集已經寫過資料後就無法修改,是以最好是一開始就确定好,建議4個位元組。因為使用壓縮技術後,RowKey多占的幾個位元組可以忽略,下文會提到。
salt
salt這個東西最好根據自己HBase叢集規模去配置,它有2個配置:
tsd.storage.salt.width # 預設1,1基本夠了,不用調整
tsd.storage.salt.buckets # 打散到幾個bucket去,預設20
查詢的時候會并發
tsd.storage.salt.buckets
個Scanner到HBase上,是以如果這個配置太大,對查詢影響比較大,容易打爆HBase。這裡其實是一個權衡,寫入熱點和查詢壓力。預設20其實我個人覺得有點多,配置3~8就差不多了,當然實際效果還和metric設計有關,如果在一個metric裡設計了很多時間線,那就得配置很多bucket。在一個metric中設計過多時間線,會影響OpenTSDB的查詢效率,是以不建議這麼做。
這個參數也是設定了就不能改的,是以也是要一開始規劃好。
Column格式
這是列名(HBase中稱為qualifier)的格式,可以看到毫米級需要多出2個位元組。是以如果你的采集間隔不需要精确到毫秒級别,那請一定使用秒級(10位整數)。Value隻能存儲整數和浮點,是以有一個bit存儲Float flag。
這裡大家一定會有疑問,直接通過qualifier長度是4還是2不就能判斷是秒級精度的資料點,還是毫秒了麼?為何還需要MS flag這樣一個标記資訊?閱讀下面的“壓縮”部分,就能知道為什麼。
OpenTSDB壓縮問題
OpenTSDB有個很常見并且很麻煩的問題,就是整點時候對HBase對流量沖擊。下面2張圖是我們一個測試叢集隻做寫入對效果:
OpenTSDB HBase
可以看到會有一個數倍流量的爆發,要持續很久才能消化。這意味着我們需要更高規格去抗這個峰值。首先我們要明白OpenTSDB為啥要做壓縮?在壓縮些什麼東西?
前面提到過OpenTSDB一行一小時的特點,那麼一行裡會有很多KV。表面上看起來好像沒什麼問題,但是實際上對比邏輯視圖和實體視圖你會發現一些問題。
很明顯,每個KV都記錄了rowX,那rowX就是一個空間浪費。這個空間不僅影響成本,還影響查詢效率(畢竟資料多了)。壓縮做的事情就是把多個小KV合成1個大KV,減少這部分浪費。是以壓縮的時候會涉及到對HBase的“讀-寫-删”,這就是整點HBase IO流量的來源。
那麼我們有沒有辦法,既做壓縮,同時又消除這部分HBase IO呢?
當然有!我們可以把壓縮的邏輯放到HBase内部去。因為HBase本身就需要對HFile做合并工作,這時候HBase本身就會讀寫資料檔案,這部分對HDFS的IO不會少,而我們通過hook在HBase讀出資料後,替換掉要寫入的資料(即壓縮好的資料)。
實作上面這個功能,當然需要一定核心開發量。好消息是通過雲HBase購買頁面購買的時序引擎,已經自帶了上述功能。不管是分離部署模式,還是混合部署模式。
這個功能的好處顯而易見,消除峰值節省成本,提升叢集穩定性。這樣我們對一個現有的HBase叢集空閑資源需求就不是那麼高了,完全可以複用了。下面是使用此功能後,同樣隻做寫入的測試叢集的流量情況: