時間序列數據的存儲和計算 - 開源時序數據庫解析

開源時序數據庫



       如圖是17年6月在db-engines上時序數據庫的排名,我會挑選開源的、分佈式的時序數據庫作詳細的解析。前十的排名中,RRD是一個老牌的單機存儲引擎,Graphite底層是Whisper,能夠認爲是一個優化的更強大的RRD數據庫。kdb+、eXtremeDB和Axibase都未開源,不作解析。InfluxDB開源版和Prometheus的底層都是基於levelDB自研的單機的存儲引擎,InfluxDB的商業版支持分佈式,Prometheus的roadmap上也規劃了分佈式存儲引擎的支持計劃。算法

       綜合看下來,我會選擇OpenTSDB、KairosDB和InfluxDB作一個詳細的解析。我對OpenTSDB比較熟悉,研究過它的源碼,因此對OpenTSDB會描述的格外詳細,而對其餘時序數據庫瞭解的沒那麼深刻,若是有描述錯的地方,歡迎指正。數據庫

1、OpenTSDB

       OpenTSDB是一個分佈式、可伸縮的時序數據庫,支持高達每秒百萬級的寫入能力,支持毫秒級精度的數據存儲,不須要降精度也能夠永久保存數據。其優越的寫性能和存儲能力,得益於其底層依賴的HBase,HBase採用LSM樹結構存儲引擎加上分佈式的架構,提供了優越的寫入能力,底層依賴的徹底水平擴展的HDFS提供了優越的存儲能力。OpenTSDB對HBase深度依賴,而且根據HBase底層存儲結構的特性,作了不少巧妙的優化。在最新的版本中,還擴展了對BigTable和Cassandra的支持。緩存

架構


       如圖是OpenTSDB的架構,核心組成部分就是TSD和HBase。TSD是一組無狀態的節點,能夠任意的擴展,除了依賴HBase外沒有其餘的依賴。TSD對外暴露HTTP和Telnet的接口,支持數據的寫入和查詢。TSD自己的部署和運維是很簡單的,得益於它無狀態的設計,不過HBase的運維就沒那麼簡單了,這也是擴展支持BigTable和Cassandra的緣由之一吧。數據結構

數據模型

OpenTSDB採用按指標建模的方式,一個數據點會包含如下組成部分:架構

1. metric:時序數據指標的名稱,例如sys.cpu.user,stock.quote等。運維

2. timestamp:秒級或毫秒級的Unix時間戳,表明該時間點的具體時間。異步

3. tags:一個或多個標籤,也就是描述主體的不一樣的維度。Tag由TagKey和TagValue組成,TagKey就是維度,TagValue就是該維度的值。分佈式

4. value:該指標的值,目前只支持數值類型的值。ide

存儲模型

OpenTSDB底層存儲的優化思想,簡單總結就是如下這幾個關鍵的優化思路:oop

1. 對數據的優化:爲Metric、TagKey和TagValue分配UniqueID,創建原始值與UniqueID的索引,數據表存儲Metric、TagKey和TagValue對應的UniqueID而不是原始值。

2. 對KeyValue數的優化:若是對HBase底層存儲模型十分了解的話,就知道行中的每一列在存儲時對應一個KeyValue,減小行數和列數,能極大的節省存儲空間以及提高查詢效率。

3. 對查詢的優化:利用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的數據。

從圖中的例子能夠解讀出:

1. TagKey爲'host',對應的UniqueID爲'001'

2. TagValue爲'static',對應的UniqueId爲'001'

3. 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、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名稱和時間範圍肯定的話,咱們至少能肯定row key的一個掃描範圍。可是這個掃描範圍,會把包含相同metric名稱和時間範圍內的全部的tag key的組合所有查詢出來,若是你的tag key的組合有不少,那你的掃描範圍是不可控的,可能會很大,這樣查詢的效率基本是不能接受的。

咱們具體看一下OpenTSDB對查詢的優化措施:

1. Server side filter

       HBase提供了豐富和可擴展的filter,filter的工做原理是在server端掃描獲得數據後,先通過filter的過濾後再將結果返回給客戶端。Server side filter的優化策略沒法減小掃描的數據量,可是能夠大大減小傳輸的數據量。OpenTSDB會將某些條件的tag key filter轉換爲底層HBase的server side filter,不過該優化帶來的效果有限,由於影響查詢最關鍵的因素仍是底層範圍掃描的效率而不是傳輸的效率。

2. 減小範圍查詢內掃描的數據量

       要想真正提升查詢效率,仍是得從根本上減小範圍掃描的數據量。注意這裏不是減少查詢的範圍,而是減小該範圍內掃描的數據量。這裏用到了HBase一個很關鍵的filter,即FuzzyRowFilter,FuzzyRowFilter可以根據指定的條件,在執行範圍掃描時,動態的跳過必定數據量。但不是全部OpenTSDB提供的查詢條件都可以應用該優化,須要符合必定的條件,具體要符合哪些條件就不在這裏說明了,有興趣的能夠去了解下FuzzyRowFilter的原理。

3. 範圍查詢優化成單行查詢

       這個優化相比上一條,更加的極端。優化思路很是好理解,若是我可以知道要查詢的全部數據對應的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成爲了一個標杆。

2、KairosDB

       KairosDB最初是從OpenTSDB 1.x版本fork出來的一個分支,目的是在OpenTSDB的代碼基礎上進行二次開發來知足新的功能需求。其改造之一就是支持可插拔式的存儲引擎,例如支持H2能夠方便本地開發和測試,而不是像OpenTSDB同樣與HBase強耦合。在其最初的幾個版本中,HBase也是做爲其主要的存儲引擎。可是在以後的存儲優化中,慢慢使用Cassandra替換了HBase,它也是第一個基於Cassandra開發的時序數據庫。在最新的幾個版本中,已再也不支持HBase,由於其存儲優化使用了Cassandra所特有而HBase沒有的一些特性。

       在總體架構上,和OpenTSDB比較相似,都是採用了一個比較成熟的數據庫來做爲底層存儲引擎。本身的主要邏輯僅僅是在存儲引擎層之上很薄的一個邏輯層,這層邏輯層的部署架構是一個無狀態的組件,能夠很容易的水平擴展。

       在功能差別性上,它在OpenTSDB 1.x上作二次開發,也是爲了對OpenTSDB的一些功能作優化,或作出一些OpenTSDB所沒有的功能。我大概羅列下我看到的主要的功能差別:

       可插拔式的存儲引擎:OpenTSDB在早期與HBase強耦合,爲了追求極致的性能,甚至自研了一個異步的HBase Client(如今做爲獨立的一個開源項目輸出:AsyncHBase)。這樣也致使其整個代碼都是採用異步驅動的模式編寫,不光增長了代碼的複雜度和下降可閱讀性,也加大了支持多種存儲引擎的難度。KairosDB嚴格定義了存儲層的API Interface,總體邏輯與存儲層耦合度較低,能比較容易的擴展多種存儲引擎。固然如今最新版的OpenTSDB也可以額外支持Cassandra和BigTable,可是從總體的架構上,還不能說是一個支持可插拔式存儲引擎的架構。

       支持多種數據類型及自定義類型的值:OpenTSDB只支持numeric的值,而KairosDB支持numeric、string類型的值,也支持自定義數值類型。在某些場景下,metric value不是一個簡單的數值,例如你要統計這個時間點的TopN,對應的metric value多是一組string值。可擴展的類型,讓將來的需求擴展會變得容易。從第一第二點差別能夠看出,KairosDB基於OpenTSDB的第一大改造就是將OpenTSDB的功能模型和代碼架構變得更加靈活。

       支持Auto-rollup:目前大部分TSDB都在朝着支持pre-aggregation和auto-rollup的方向發展,OpenTSDB是少數的不支持該feature的TSDB,在最新發布的OpenTSDB版本中,甚至都不支持多精度數據的存儲。不過如今KairosDB支持的auto-rollup功能,採起的仍是一個比較原始的實現方式,在下面的章節會詳細講解。

       不一樣的存儲模型:存儲是TSDB核心中的核心,OpenTSDB在存儲模型上使用了UID的壓縮優化,來優化查詢和存儲。KairosDB採起了一個不一樣的思路,利用了Cassandra寬表的特性,這也是它從HBase轉向Cassandra的一個最重要的緣由,在下面的章節會詳細講解。

存儲模型

       OpenTSDB的存儲模型,其主要設計特色是採用了UID編碼,大大節省了存儲空間,而且利用UID編碼的固定字節數的特性,利用HBase的Filter作了不少查詢的優化。可是採用UID編碼後也帶來了不少的缺陷,一是須要維護metric/tagKey/tagValue到UID的映射表,全部data point的寫入和讀取都須要通過映射表的轉換,映射表一般會緩存在TSD或者client,增長了額外的內存消耗;二是因爲採用了UID編碼,致使metric/tagKey/tagValue的基數是有上限的,取決於UID使用的字節數,而且在UID的分配上會有衝突,會影響寫入。

       本質上,OpenTSDB存儲模型採用的UID編碼優化,主要解決的就兩個問題:

       存儲空間優化:UID編碼解決重複的row key存儲形成的冗餘的存儲空間問題。

       查詢優化:利用UID編碼後TagKey和TagValue固定字節長度的特性,利用HBase的FuzzyRowFilter作特定場景的查詢優化。

       KairosDB在解決這兩個問題上,採起了另一種不一樣的方式,使其不須要使用UID編碼,也不存在使用UID編碼後遺留的問題。先看下KairosDB的存儲模型是怎樣的,它主要由如下三張表構成:

       DataPoints: 存儲全部原始數據點,每一個數據點也是由metric、tags、timestamp和value構成。該表中一行數據的時間跨度是三週,也就是說三週內的全部數據點都存儲在同一行,而OpenTSDB內的行的時間跨度只有一個小時。RowKey的組成與OpenTSDB相似,結構爲tagv2>...,不一樣的是metric, tag key和tag value都存儲原始值,而不是UID。

       RowKeyIndex: 該表存儲全部metric對應DataPoints表內全部row key的映射,也就是說同一個metric上寫入的全部的row key,都會存儲在同一行內,而且按時間排序。該表主要被用於查詢,在根據tag key或者tag value作過濾時,會先從這張表過濾出要查詢的時間段內全部符合條件的row key,後在DataPoints表內查詢數據。

       StringIndex: 該表就三行數據,每一行分別存儲全部的metric、tag key和tag value。

      KairosDB採起的存儲模型,是利用了Cassandra寬表的特性。HBase的底層文件存儲格式中,每一列會對應一個KeyValue,Key爲該行的RowKey,因此HBase中一行中的每一列,都會重複的存儲相同的RowKey,這也是爲什麼採用了UID編碼後能大大節省存儲空間的主要緣由,也是爲什麼有了UID編碼後還能採用compaction策略(將一行中全部列合併爲一列)來進一步壓縮存儲空間的緣由。而Cassandra的底層文件存儲格式與HBase不一樣,它一行數據不會爲每一列都重複的存儲RowKey,因此它不須要使用UID編碼。Cassandra內下降存儲空間的一個優化方案就是縮減行數,這也是爲什麼它一行存儲三週數據而不是一個小時數據的緣由。要進一步瞭解兩種設計方案的緣由,能夠看下HBase文件格式以及Cassandra文件格式。

       利用Cassandra的寬表特性,即便不採用UID編碼,存儲空間上相比採用UID編碼的OpenTSDB,也不會差太多。能夠看下官方的解釋:


      在查詢優化上,採起的也是和OpenTSDB不同的優化方式。先看下KairosDB內查詢的整個流程:

1. 根據查詢條件,找出全部DataPoints表裏的row key

      若是有自定義的plugin,則從plugin中獲取要查詢的全部row key。(經過Plugin能夠擴展使用外部索引系統來對row key進行索引,例如使用ElasticSearch)

      若是沒有自定義的plugin,則在RowKeyIndex表裏根據metric和時間範圍,找出全部的row key。(根據列名的範圍來縮小查詢範圍,列名的範圍是(metric+startTime, metric+endTime))

2. 根據row key,從DataPoints表裏找出全部的數據

       相比OpenTSDB直接在數據表上進行掃描來過濾row key的方式,KairosDB利用索引表無疑會大大減小掃描的數據量。在metric下tagKey和tagValue組合有限的狀況下,會大大的提升查詢效率。而且KairosDB還提供了QueryPlugin的方式,可以擴展利用外部組件來對row key進行索引,例如能夠利用ElasticSearch,或者其餘的索引系統,畢竟經過索引的方式,纔是最優的查詢方案,這也是Heroic相比KairosDB最大的一個改進的地方。

Auto-rollup

       KairosDB的官方文檔中有關於auto-rollup如何配置的章節,可是在討論組內,其關於auto-rollup的說明以下:


       總結來講,目前KairosDB提供的auto-rollup方案,仍是比較簡單的實現。就是一個可配置的單機組件,可以定時啓動,把已經寫入的數據讀出後進行aggregation後再次寫入,確實很是的原始,可用性和性能都比較低。

       可是有總比沒有好,支持auto-rollup必定是全部TSDB的趨勢,也是能拉開功能差別和提升核心競爭力的關鍵功能。

BlueFlood

       上面主要分析了KairosDB,第一個基於Cassandra構建的TSDB,那乾脆繼續分析下其餘基於Cassandra構建的TSDB。

       BlueFlood也是一個基於Cassandra構建的TSDB,能夠看到總體架構上核心組成部分主要有三個:

1. Ingest module: 處理數據寫入。

2. Rollup module: 作自動的預聚合和降精度。

3. Query module: 處理數據查詢。

相比KairosDB,其在數據模型上與其餘的TSDB有略微差別,主要在:

1. 引入了租戶的維度:這是一個創新,若是你是作一個服務化的TSDB,那租戶這個維度是必需的。

2. 不支持Tag:這一點上,是比較讓我差別的地方。在大多數TSDB都基本上把Tag做爲模型的不可缺乏部分的狀況下,BlueFlood在模型上竟然不支持Tag。不過這有多是其沒有想好如何優化Tag維度查詢的一種取捨,既然沒想好怎麼優化,那乾脆就先不支持,反正將來再去擴展Tag是能夠徹底兼容的。BlueFlood當前已經利用ElasticSearch去構建metric的索引,我相信它將來的方案,應該也是基於ElasticSearch去構建Tag的索引,在這個方案徹底支持好後,應該纔會去引入Tag。

模型上的不足,BlueFlood不須要去考慮Tag查詢如何優化,把精力都投入到了其餘功能的優化上,例如auto-rollup。它在auto-rollup的功能支持上,甩了KairosDB和OpenTSDB幾條街。來看看它的Auto-rollup功能的特色:

1. 僅支持固定的Interval:5min,20min,60min,4hour,1day。

2. 提供分佈式的Rollup Service:rollup任務能夠分佈式的調度,rollup的數據是經過離線的批量掃描獲取。

從它14年的介紹PPT上,還能夠看到它在將來規劃的幾個功能點:

1. ElasticSearch Indexer and discovery: 目前這個已經實現,可是僅支持metric的索引,將來引入Tag後,可能也會用於Tag的索引。

2. Cloud files exporter for rollups: 這種方式對離線計算更加優化,rollup的大批量歷史數據讀取就不會影響在線的業務。

3. Apache Kafka exporter for rollups: 這種方式相比離線計算更進一步,rollup能夠用流計算來作,實時性更加高。

       總結來講,若是你不須要Tag的支持,而且對Rollup有強需求,那BlueFlood相比KairosDB會是一個更好的選擇,反之仍是選擇KairosDB。

Heroic

      第三個要分析的基於Cassandra的TSDB是Heroic,它在DB-Engines上的排名是第19,雖然比BlueFlood和KairosDB都落後,可是我認爲它的設計實現倒是最好的一個。       Spotify在決定研發Heroic以前,在OpenTSDB、InfluxDB、KairosDB等TSDB中選用KairosDB來替換他們老的監控系統的底層。可是很快就遇到了KairosDB在查詢方面的問題,最主要仍是KairosDB對metric和tag沒有索引,在metric和tag基數達到必定數量級後,查詢會變的很慢。因此Spotify研發Heroic的最大動機就是解決KairosDB的查詢問題,採用的解決方案是使用ElasticSearch來做爲索引優化查詢引擎,而數據的寫入和數據表的Schema則徹底與KairosDB一致。

簡單總結下它的特色:

1. 完整的數據模型,徹底遵循metric2.0的規範。

2. 數據存儲模型與KairosDB一致,使用ElasticSearch優化查詢引擎。(這是除了InfluxDB外,其餘TSDB如KairosDB、OpenTSDB、BlueFlood等現存最大的問題,是其核心競爭力之一)

3. 不支持auto-rollup,這是它的缺陷之一。

若是你須要TSDB支持完整的數據模型,且但願獲得高效的索引查詢,那Heroic會是你的選擇。

3、InfluxDB

       InfluxDB在DB-Engines的時序數據庫類別裏排名第一,實至名歸,從它的功能豐富性、易用性以及底層實現來看,都有不少的亮點,值得大篇幅來分析。

首先簡單概括下它的幾個比較重要的特性:

1. 極簡架構:單機版的InfluxDB只須要安裝一個binary,便可運行使用,徹底沒有任何的外部依賴。相比來看幾個反面例子,OpenTSDB底層是HBase,拖家帶口就得帶上ZooKeeper、HDFS等,若是你不熟悉Hadoop技術棧,通常運維起來是有必定的難度,這也是其被人抱怨最多的一個點。KairosDB稍微好點,它依賴Cassandra和ZooKeeper,單機測試可使用H2。總的來講,依賴一個外部的分佈式數據庫的TSDB,在架構上會比徹底自包含的TSDB複雜一點,畢竟一個成熟的分佈式數據庫自己就很複雜,固然這一點在雲計算這個時代已經徹底消除。

2. TSM Engine:底層採用自研的TSM存儲引擎,TSM也是基於LSM的思想,提供極強的寫能力以及高壓縮率,在後面的章節會對其作一個比較詳細的分析。

3. InfluxQL:提供SQL-Like的查詢語言,極大的方便了使用,數據庫在易用性上演進的終極目標都是提供Query Language。

4. Continuous Queries: 經過CQ可以支持auto-rollup和pre-aggregation,對常見的查詢操做能夠經過CQ來預計算加速查詢。

5. TimeSeries Index: 對Tags會進行索引,提供高效的檢索。這一項功能,對比OpenTSDB和KairosDB等,在Tags檢索的效率上提高了很多。OpenTSDB在Tags檢索上作了很多的查詢優化,可是受限於HBase的功能和數據模型,因此然並卵。不過目前穩定版中的實現採用的是memory-based index的實現方式,這種方案在實現上比較簡單,查詢上效率最高,可是帶來了很多的問題,在下面的章節會詳細描述。

6. Plugin Support: 支持自定義插件,可以擴展到兼容多種協議,如Graphite、collectd和OpenTSDB。

       在下面的章節,會主要對其基本概念、TSM存儲引擎、Continuous Queries以及TimeSeries Index作詳細的解析。

基本概念

先來了解下InfluxDB中的幾個基本概念,看下具體的例子:


上面是一條向InfluxDB中寫入一條數據的命令行,來看下這條數據由哪幾個部分組成:

1. Measurement:Measurement的概念與OpenTSDB的Metric相似,表明數據所屬監控指標的名稱。例如上述例子是對機器指標的監控,因此其measurement命名爲machine_metric。

2. Tags:與OpenTSDB的Tags概念相似,用於描述主體的不一樣的維度,容許存在一個或多個Tag,每一個Tag也是由TagKey和TagValue構成。

3. Field:在OpenTSDB的邏輯數據模型中,一行metric數據對應一個value。而在InfluxDB中,一行measurement數據能夠對應多個value,每一個value根據Field來區分。

4. Timestamp: 時序數據的必備屬性,表明該條數據所屬的時間點,能夠看到InfluxDB的時間精度可以精確到納秒。

5. TimeSeries:Measurement+Tags的組合,在InfluxDB中被稱爲TimeSeries。TimeSeries就是時間線,根據時間可以定位到某個時間點,因此TimeSeries+Field+Timestamp可以定位到某個Value。這個概念比較重要,在後續的章節中都會提到。

最終在邏輯上每一個Measurement內的數據會組織成一張大的數據表,以下圖所示:


       在查詢時,InfluxDB支持在Measurement內任意維度的條件查詢,你能夠指定任意某個Tag或者Filed的條件作查詢。接着上面的數據案例,你能夠構造如下查詢條件:


      從數據模型以及查詢的條件上看,Tag和Field沒有任何區別。從語義上來看,Tag用於描述Measurement,而Field用於描述Value。從內部實現來上看,Tag會被全索引,而Filed不會,因此根據Tag來進行條件查詢會比根據Filed來查詢效率高不少。

TSM

       InfluxDB底層的存儲引擎經歷了從LevelDB到BlotDB,再到選擇自研TSM的過程,整個選擇轉變的思考能夠在其官網文檔裏看到。整個思考過程很值得借鑑,對技術選型和轉變的思考老是比平白的描述某個產品特性讓人印象深入的多。

       我簡單總結下它的整個存儲引擎選型轉變的過程,第一階段是LevelDB,選型LevelDB的主要緣由是其底層數據結構採用LSM,對寫入很友好,可以提供很高的寫入吞吐量,比較符合時序數據的特性。在LevelDB內,數據是採用KeyValue的方式存儲且按Key排序,InfluxDB使用的Key設計是SeriesKey+Timestamp的組合,因此相同SeriesKey的數據是按timestamp來排序存儲的,可以提供很高效的按時間範圍的掃描。

       不過使用LevelDB的一個最大的問題是,InfluxDB支持歷史數據自動刪除(Retention Policy),在時序數據場景下數據自動刪除一般是大塊的連續時間段的歷史數據刪除。LevelDB不支持Range delete也不支持TTL,因此要刪除只能是一個一個key的刪除,會形成大量的刪除流量壓力,且在LSM這種數據結構下,真正的物理刪除不是即時的,在compaction時纔會生效。各種TSDB實現數據刪除的作法大體分爲兩類:

1. 數據分區:按不一樣的時間範圍劃分爲不一樣的分區(Shard),由於時序數據寫入都是按時間線性產生的,因此分區的產生也是按時間線性增加的,寫入一般是在最新的分區,而不會散列到多個分區。分區的優勢是數據回收的物理刪除很是簡單,直接把整個分區刪除便可。缺點是數據回收的精細度比較大,爲整個分區,而回收的時間精度取決於分區的時間跨度。分區的實現能夠是在應用層提供,也能夠是存儲引擎層提供,例如能夠利用RocksDB的column family來做爲數據分區。InfluxDB採用這種模式,默認的Retention Policy下數據會以7天時間跨度組成爲一個分區。

2. TTL:底層數據引擎直接提供數據自動過時的功能,能夠爲每條數據設定存儲時間(time to live),當數據存活時間到達後存儲引擎會自動對數據進行物理刪除。這種方式的優勢是數據回收的精細度很高,精細到秒級及行級的數據回收。缺點是LSM的實現上,物理刪除發生在compaction的時候,比較不及時。RocksDB、HBase、Cassandra和阿里雲表格存儲都提供數據TTL的功能。

       InfluxDB採用的是第一種策略,會按7天一個週期,將數據分爲多個不一樣的Shard,每一個Shard都是一個獨立的數據庫實例。隨着運行時間的增加,shard的個數會愈來愈多。而因爲每一個shard都是一個獨立的數據庫實例,底層都是一套獨立的LevelDB存儲引擎,這時帶來的問題是,每一個存儲引擎都會打開比較多的文件,隨着shard的增多,最終進程打開的文件句柄會很快觸及到上限。LevelDB底層採用level compaction策略,是文件數多的緣由之一。實際上level compaction策略不適合時序數據這種寫入模式,這點緣由InfluxDB沒有說起。

       因爲遇到大量的客戶反饋文件句柄過多的問題,InfluxDB在新版本的存儲引擎選型中選擇了BoltDB替換LevelDB。BoltDB底層數據結構是mmap B+樹,其給出的選型理由是:1.與LevelDB相同語義的API;2.純Go實現,便於集成和跨平臺;3.單個數據庫只使用一個文件,解決了文件句柄消耗過多的問題,這條是他們選型BoltDB的最主要理由。可是BoltDB的B+樹結構與LSM相比,在寫入能力上是一個弱勢,B+樹會產生大量的隨機寫。因此InfluxDB在使用BoltDB以後,很快遇到了IOPS的問題,當數據庫大小達到幾個GB後,會常常遇到IOPS的瓶頸,極大影響寫入能力。雖然InfluxDB後續也採用了一些寫入優化措施,例如在BoltDB以前加了一層WAL,數據寫入先寫WAL,WAL能保證數據是順序寫盤,可是最終寫入BoltDB仍是會帶來比較大的IOPS資源消耗。

       InfluxDB在經歷了幾個小版本的BoltDB後,最終決定自研TSM,TSM的設計目標一是解決LevelDB的文件句柄過多問題,二是解決BoltDB的寫入性能問題。TSM全稱是Time-Structured Merge Tree,思想相似LSM,不過是基於時序數據的特性作了一些特殊的優化。來看下TSM的一些重要組件:

1. Write Ahead Log(WAL): 數據會先寫入WAL,後進入memory-index和cache,寫入WAL會同步刷盤,保證數據持久化。Cache內數據會異步刷入TSM File,在Cache內數據未持久化到TSM File以前若遇到進程crash,則會經過WAL內的數據來恢復cache內的數據,這個行爲與LSM是徹底相似的。

2. Cache: TSM的Cache與LSM的MemoryTable相似,其內部的數據爲WAL中未持久化到TSM File的數據。若進程發生failover,則cache中的數據會根據WAL中的數據進行重建。Cache內數據保存在一個SortedMap中,Map的Key爲TimeSeries+Timestamp的組成。因此能夠看到,在內存中數據是按TimeSeries組織的,TimeSeries中的數據按時間順序存放。

3. TSM Files: TSM File與LSM的SSTable相似,TSM File由四個部分組成,分別爲:header, blocks, index和footer。其中最重要的部分是blocks和index:

(1) Block:每一個block內存儲的是某個TimeSeries的一段時間範圍內的值,即某個時間段下某個measurement的某組tag set對應的某個field的全部值,Block內部會根據field的不一樣的值的類型採起不一樣的壓縮策略,以達到最優的壓縮效率。

(2) Index:文件內的索引信息保存了每一個TimeSeries下全部的數據Block的位置信息,索引數據按TimeSeries的Key的字典序排序。在內存中不會把完整的index數據加載進去,這樣會很大,而是隻對部分Key作索引,稱之爲indirectIndex。indirectIndex中會有一些輔助定位的信息,例如該文件中的最小最大時間以及最小最大Key等,最重要的是保存了部分Key以及其Index數據的文件offset信息。若想要定位某個TimeSeries的Index數據,會先根據內存中的部分Key信息找到與其最相近的Index Offset,以後從該起點開始順序掃描文件內容再精肯定位到該Key的Index數據位置。

4. Compaction: compaction是一個將write-optimized的數據存儲格式優化爲read-optimized的數據存儲格式的一個過程,是LSM結構存儲引擎作存儲和查詢優化很重要的一個功能,compaction的策略和算法的優劣決定了存儲引擎的質量。在時序數據的場景下,基本不多發生update或者delete,數據都是按時間順序生成的,因此基本不會有overlap,Compaction起到的做用主要在於壓縮和索引優化。

(1)LevelCompaction: InfluxDB將TSM文件分爲4個層級(Level 1-4),compaction只會發生在同層級文件內,同層級的文件compaction後會晉升到下一層級。從這個規則看,根據時序數據的產生特性,level越高數據生成時間越舊,訪問熱度越低。由Cache數據初次生成的TSM文件稱爲Snapshot,多個Snapshot文件compaction後產生Level1的TSM文件,Level1的文件compaction後生成level2的文件,依次類推。低Level和高Level的compaction會採用不一樣的算法,低level文件的compaction採用低CPU消耗的作法,例如不會作解壓縮和block合併,而高level文件的compaction則會作block解壓縮以及block合併,以進一步提升壓縮率。我理解這種設計是一種權衡,compaction一般在後臺工做,爲了避免影響實時的數據寫入,對compaction消耗的資源是有嚴格的控制,資源受限的狀況下必然會影響compaction的速度。而level越低的數據越新,熱度也越高,須要有一種更快的加速查詢的compaction,因此InfluxDB在低level採用低資源消耗的compaction策略,這徹底是貼合時序數據的寫入和查詢特性來設計的。

(2)IndexOptimizationCompaction: 當Level4的文件積攢到必定個數後,index會變得很大,查詢效率會變的比較低。影響查詢效率低的因素主要在於同一個TimeSeries數據會被多個TSM文件所包含,因此查詢不可避免的須要跨多個文件進行數據整合。因此IndexOptimizationCompaction的主要做用就是將同一TimeSeries下的數據合併到同一個TSM文件中,儘可能減小不一樣TSM文件間的TimeSeries重合度。

(3)FullCompaction: InfluxDB在判斷某個Shard長時間內不會再有數據寫入以後,會對數據作一次FullCompaction。FullCompaction是LevelCompaction和IndexOptimization的整合,在作完一次FullCompaction以後,這個Shard不會再作任何的compaction,除非有新的數據寫入或者刪除發生。這個策略是對冷數據的一個規整,主要目的在於提升壓縮率。

Continuous Queries

       對InfluxDB內的數據作預聚合和降精度有兩種推薦的策略,一種是使用InfluxData內的數據計算引擎Kapacitor,另外一種是使用InfluxDB自帶的Continuous Queries。


       如上是一個簡單的配置Continuous Queries的CQL,所起的做用是可以讓InfluxDB啓動一個定時任務,每隔5分鐘將『machine_metric』這個measurement下的全部數據按cluster+hostname這個維度進行聚合,計算cpu這個Field的平均值,最終結果寫入average_machine_cpu_5m這個新的measurement內。

       InfluxDB的Continuous Queries與KairosDB的auto-rollup功能相似,都是單節點調度,數據的聚合是滯後而非實時的流計算,在計算時對存儲會產生較大的讀壓力。

TimeSeries Index

      時序數據庫除了支撐時序數據的存儲和計算外,還須要可以提供多維度查詢。InfluxDB爲了提供更快速的多維查詢,對TimeSeries進行了索引。關於數據和索引,InfluxDB是這麼描述本身的:


      在InfluxDB 1.3以前,TimeSeries Index(下面簡稱爲TSI)只支持Memory-based的方式,即全部的TimeSeries的索引都是放在內存內,這種方式有好處可是也會帶來不少的問題。而在最新發布的InfluxDB 1.3版本上,提供了另一種方式的索引可供選擇,新的索引方式會把索引存儲在磁盤上,效率上相比內存索引差一點,可是解決了內存索引存在的很多問題。

Memory-based Index


如上是InfluxDB 1.3的源碼中對內存索引數據結構的定義,主要有兩個重要的數據結構體:

Series: 對應某個TimeSeries,其內存儲TimeSeries相關的一些基本屬性以及它所屬的Shard

1. Key:對應measurement + tags序列化後的字符串。

2.tags: 該TimeSeries下全部的TagKey和TagValue

3. ID: 用於惟一區分的整數ID。

4. measurement: 所屬的measurement。

5. shardIDs: 全部包含該Series的ShardID列表。

Measurement: 每一個measurement在內存中都會對應一個Measurement結構,其內部主要是一些索引來加速查詢。

1. seriesByID:經過SeriesID查詢Series的一個Map。

2. seriesByTagKeyValue:雙層Map,第一層是TagKey對應其全部的TagValue,第二層是TagValue對應的全部Series的ID。能夠看到,當TimeSeries的基數變得很大,這個map所佔的內存會至關多。

3. sortedSeriesIDs:一個排序的SeriesID列表。

全內存索引結構帶來的好處是可以提供很是高效的多維查詢,可是相應的也會存在一些問題:

1. 可以支持的TimeSeries基數有限,主要受限於內存的大小。若TimeSeries個數超過上限,則整個數據庫會處於不可服務的狀態。這類問題通常由用戶錯誤的設計TagKey引起,例如某個TagKey是一個隨機的ID。一旦遇到這個問題的話,也很難恢復,每每只能經過手動刪數據。

2. 若進程重啓,恢復數據的時間會比較長,由於須要從全部的TSM文件中加載全量的TimeSeries信息來在內存中構建索引。

Disk-based Index

       針對全內存索引存在的這些問題,InfluxDB在最新的1.3版本中提供了另一種索引的實現。得益於代碼設計上良好的擴展性,索引模塊和存儲引擎模塊都是插件化的,用戶能夠在配置中自由選擇使用哪一種索引。


       InfluxDB實現了一個特殊的存儲引擎來作索引數據的存儲,其結構也與LSM相似,如上圖就是一個Disk-based Index的結構圖。

       索引數據會先寫入Write-Ahead-Log,WAL中的數據按LogEntry組織,每一個LogEntry對應一個TimeSeries,包含Measurement、Tags以及checksum信息。寫入WAL成功後,數據會進入一個內存索引結構內。當WAL積攢到必定大小後,LogFile會Flush成IndexFile。IndexFile的邏輯結構與內存索引的結構一致,表示的也是Measurement到TagKey,TagKey到TagValue,TagValue到TimeSeries的Map結構。InfluxDB會使用mmap來訪問文件,同時文件中對每一個Map都會保存HashIndex來加速查詢。

       當IndexFile積攢到必定數量後,InfluxDB也提供compaction的機制,將多個IndexFile合併爲一個,節省存儲空間以及加速查詢。

總結

       InfluxDB內全部的組件所有采起自研,自研的好處是每一個組件均可以貼合時序數據的特性來作設計,將性能發揮到極致。整個社區也是很是活躍,可是動不動就會有一次大的功能升級,例如改個存儲格式換個索引實現啥的,對於用戶來講就比較折騰了。總的來講,我仍是比較看好InfluxDB的發展,不過惋惜的是集羣版沒有開源。

相關文章
相關標籤/搜索