時間序列數據的存儲和計算 - 開源時序資料庫解析(一)
開源時序資料庫
如圖是17年6月在db-engines上時序資料庫的排名,我會挑選開源的、分散式的時序資料庫做詳細的解析。前十的排名中,RRD是一個老牌的單機存儲引擎,Graphite底層是Whisper,可以認為是一個優化的更強大的RRD資料庫。kdb+、eXtremeDB和Axibase都未開源,不做解析。InfluxDB開源版和Prometheus的底層都是基於levelDB自研的單機的存儲引擎,InfluxDB的商業版支持分散式,Prometheus的roadmap上也規划了分散式存儲引擎的支持計劃。
我會挑選HBase系和Cassandra系做重點解析,以及Top1的InfluxDB。我對OpenTSDB比較熟悉,研究過它的源碼,所以對OpenTSDB會描述的格外詳細,而對其他時序資料庫了解的沒那麼深入,如果有描述錯的地方,歡迎指正。OpenTSDB
OpenTSDB是一個分散式、可伸縮的時序資料庫,支持高達每秒百萬級的寫入能力,支持毫秒級精度的數據存儲,不需要降精度也可以永久保存數據。其優越的寫性能和存儲能力,得益於其底層依賴的HBase,HBase採用LSM樹結構存儲引擎加上分散式的架構,提供了優越的寫入能力,底層依賴的完全水平擴展的HDFS提供了優越的存儲能力。OpenTSDB對HBase深度依賴,並且根據HBase底層存儲結構的特性,做了很多巧妙的優化。關於存儲的優化,我在這篇文章中有詳細的解析。在最新的版本中,還擴展了對BigTable和Cassandra的支持。
架構
如圖是OpenTSDB的架構,核心組成部分就是TSD和HBase。TSD是一組無狀態的節點,可以任意的擴展,除了依賴HBase外沒有其他的依賴。TSD對外暴露HTTP和Telnet的介面,支持數據的寫入和查詢。TSD本身的部署和運維是很簡單的,得益於它無狀態的設計,不過HBase的運維就沒那麼簡單了,這也是擴展支持BigTable和Cassandra的原因之一吧。
數據模型
OpenTSDB採用按指標建模的方式,一個數據點會包含以下組成部分:
- metric:時序數據指標的名稱,例如sys.cpu.user,stock.quote等。
- timestamp:秒級或毫秒級的Unix時間戳,代表該時間點的具體時間。
- tags:一個或多個標籤,也就是描述主體的不同的維度。Tag由TagKey和TagValue組成,TagKey就是維度,TagValue就是該維度的值。
- value:該指標的值,目前只支持數值類型的值。
存儲模型
OpenTSDB底層存儲的優化思想,可以參考這篇文章,簡單總結就是以下這幾個關鍵的優化思路:
- 對數據的優化:為Metric、TagKey和TagValue分配UniqueID,建立原始值與UniqueID的索引,數據表存儲Metric、TagKey和TagValue對應的UniqueID而不是原始值。
- 對KeyValue數的優化:如果對HBase底層存儲模型十分了解的話,就知道行中的每一列在存儲時對應一個KeyValue,減少行數和列數,能極大的節省存儲空間以及提升查詢效率。
- 對查詢的優化:利用HBase的Server Side Filter來優化多維查詢,利用Pre-aggregation和Rollup來優化GroupBy和降精度查詢。
UIDTable
接下來看一下OpenTSDB在HBase上的幾個關鍵的表結構的設計,首先是tsdb-uid表,結構如下:
Metric、TagKey和TagValue都會被分配一個相同的固定長度的UniqueID,默認是三個位元組。tsdb-uid表使用兩個ColumnFamily,存儲了Metric、TagKey和TagValue與UniqueID的映射和反向映射,總共是6個Map的數據。
從圖中的例子可以解讀出:
- TagKey為host,對應的UniqueID為001
- TagValue為static,對應的UniqueId為001
- Metric為proc.loadavg.1m,對應的UniqueID為052
為每一個Metric、TagKey和TagValue都分配UniqueID的好處,一是大大降低了存儲空間和傳輸數據量,每個值都只需要3個位元組就可以表示,這個壓縮率是很客觀的;二是採用固定長度的位元組,可以很方便的從row key中解析出所需要的值,並且能夠大大減少Java堆內的內存佔用(bytes相比String能節省很多的內存佔用),降低GC的壓力。
不過採用固定位元組的UID編碼後,對於UID的個數是有上限要求的,3個位元組最多只允許有16777216個不同的值,不過在大部分場景下都是夠用的。當然這個長度是可以調整的,不過不支持動態更改。DataTable
第二張關鍵的表是數據表,結構如下:
該表中,同一個小時內的數據會存儲在同一行,行中的每一列代表一個數據點。如果是秒級精度,那一行最多會有3600個點,如果是毫秒級精度,那一行最多會有3600000個點。
這張表設計的精妙之處在於row key和qualifier(列名)的設計,以及對整行數據的compaction策略。row key格式為:
<metric><timestamp><tagk1><tagv1><tagk2>tagv2>...<tagkn><tagvn>
其中metric、tagk和tagv都是用uid來表示,由於uid固定位元組長度的特性,所以在解析row key的時候,可以很方便的通過位元組偏移來提取對應的值。Qualifier的取值為數據點的時間戳在這個小時的時間偏差,例如如果你是秒級精度數據,第30秒的數據對應的時間偏差就是30,所以列名取值就是30。列名採用時間偏差值的好處,主要在於能大大節省存儲空間,秒級精度的數據只要佔用2個位元組,毫秒精度的數據只要佔用4個位元組,而若存儲完整時間戳則要6個位元組。整行數據寫入後,OpenTSDB還會採取compaction的策略,將一行內的所有列合併成一列,這樣做的主要目的是減少KeyValue數目。
查詢優化
HBase僅提供簡單的查詢操作,包括單行查詢和範圍查詢。單行查詢必須提供完整的RowKey,範圍查詢必須提供RowKey的範圍,掃描獲得該範圍下的所有數據。通常來說,單行查詢的速度是很快的,而範圍查詢則是取決於掃描範圍的大小,掃描個幾千幾萬行問題不大,但是若掃描個十萬上百萬行,那讀取的延遲就會高很多。
OpenTSDB提供豐富的查詢功能,支持任意TagKey上的過濾,支持GroupBy以及降精度。TagKey的過濾屬於查詢的一部分,GroupBy和降精度屬於對查詢後的結果的計算部分。在查詢條件中,主要的參數會包括:metric名稱、tag key過濾條件以及時間範圍。上面一章中指出,數據表的rowkey的格式為:<metric><timestamp><tagk1><tagv1><tagk2>tagv2>...<tagkn><tagvn>
,從查詢的參數上可以看到,metric名稱和時間範圍確定的話,我們至少能確定row key的一個掃描範圍。但是這個掃描範圍,會把包含相同metric名稱和時間範圍內的所有的tag key的組合全部查詢出來,如果你的tag key的組合有很多,那你的掃描範圍是不可控的,可能會很大,這樣查詢的效率基本是不能接受的。
我們具體看一下OpenTSDB對查詢的優化措施:
- Server side filterHBase提供了豐富和可擴展的filter,filter的工作原理是在server端掃描得到數據後,先經過filter的過濾後再將結果返回給客戶端。Server side filter的優化策略無法減少掃描的數據量,但是可以大大減少傳輸的數據量。OpenTSDB會將某些條件的tag key filter轉換為底層HBase的server side filter,不過該優化帶來的效果有限,因為影響查詢最關鍵的因素還是底層範圍掃描的效率而不是傳輸的效率。
- 減少範圍查詢內掃描的數據量要想真正提高查詢效率,還是得從根本上減少範圍掃描的數據量。注意這裡不是減小查詢的範圍,而是減少該範圍內掃描的數據量。這裡用到了HBase一個很關鍵的filter,即FuzzyRowFilter,FuzzyRowFilter能夠根據指定的條件,在執行範圍掃描時,動態的跳過一定數據量。但不是所有OpenTSDB提供的查詢條件都能夠應用該優化,需要符合一定的條件,具體要符合哪些條件就不在這裡說明了,有興趣的可以去了解下FuzzyRowFilter的原理。
- 範圍查詢優化成單行查詢這個優化相比上一條,更加的極端。優化思路非常好理解,如果我能夠知道要查詢的所有數據對應的row key,那就不需要範圍掃描了,而是單行查詢就行了。這裡也不是所有OpenTSDB提供的查詢條件都能夠應用該優化,同樣需要符合一定的條件。單行查詢要求給定確定的row key,而數據表中row key的組成部分包括metric名稱、timestamp以及tags,metric名稱和timestamp是能夠確定的,如果tags也能夠確定,那我們就能拼出完整的row key。所以很簡單,如果要能夠應用此優化,你必須提供所有tag key對應的tag value才行。
以上就是OpenTSDB對HBase查詢的一些優化措施,但是除了查詢,對查詢後的數據還需要進行GroupBy和降精度。GroupBy和降精度的計算開銷也是非常可觀的,取決於查詢後的結果的數量級。對GroupBy和降精度的計算的優化,幾乎所有的時序資料庫都採用了同樣的優化措施,那就是pre-aggregation和auto-rollup。思路就是預先進行計算,而不是查詢後計算。不過OpenTSDB在已發布的最新版本中,還未支持pre-aggregation和rollup。而在開發中的2.4版本中,也只提供了半吊子的方案,它只提供了一個新的介面支持將pre-aggregation和rollup的結果進行寫入,但是對數據的pre-aggregation和rollup的計算還需要用戶自己在外層實現。
總結
OpenTSDB的優勢在於數據的寫入和存儲能力,得益於底層依賴的HBase所提供的能力。劣勢在於數據查詢和分析的能力上的不足,雖然在查詢上已經做了很多的優化,但是不是所有的查詢場景都能適用。可以說,OpenTSDB在TagValue過濾查詢優化,是這次要對比的幾個時序資料庫中,優化的最差的。在GroupBy和Downsampling的查詢上,也未提供Pre-aggregation和Auto-rollup的支持。不過在功能豐富程度上,OpenTSDB的API是支持最豐富的,這也讓OpenTSDB的API成為了一個標杆。
推薦閱讀:
※MongoDB 存儲引擎 mmapv1 原理解析
※對於 Web 2.0 實時應用、大數據量,MongoDB 和 memcached + SQL 哪個性能更好、在國內比較容易僱工程師?
※現在最成熟的開源nosql是什麼?分別有什麼優缺點?
※Redis 在 SNS 類應用中的最佳實踐有哪些?
※為什麼 Cassandra 的寫速度比 MySQL 快?