數據分析 - 時序數據庫


1 海量數據分析

海量數據分析類系統的設計主要面臨2個大問題:

  • 1 海量數據如何存儲?

    • a 借助於於Hadoop生態體系中的存儲系統或者其他存儲系統來存儲海量數據,自身提供對上述數據的分布式查詢分析功能,如Impala、Hive、SparkSQL、Presto、Drill、Kylin、OpenTSDB等

    優勢和劣勢:

    加入了Hadoop體系的生態圈,更加容易被接受,同時省去了研發分布式存儲系統的麻煩,更多的是在分布式查詢上做優化。但無法在存儲上做更加深度的優化,比如沒有倒排索引支持,過濾查找速度可能相對弱些,后面會重點分析下OpenTSDB的困局。

    • b 自身提供分布式存儲,如Elasticsearch、Druid、ClickHouse、Palo

    優勢和劣勢:

    可以在存儲上進行深度的優化,為自己特定數據模型進行定制。代價就是自己要實現分布式存儲

  • 2 如何對海量數據進行快速分析?

    • a 對Hadoop上的原始數據集進行大規模並行的分析處理,如SQL on Hadoop之類的Hive、SparkSQL、Impala等等。通過並行的內存計算分析來提高查詢速度。

    • b 預聚合之類的系統,如Kylin、Druid等。有效減少了查詢的數據量,提高了查詢速度,但是丟失了原始數據。Druid中的預聚合只是在時間維度進行預聚合,其他維度上的聚合在查詢時計算得到,而Kylin會根據用戶配置計算出所有要聚合的維度,這個聚合量就大了很多。最終占用的磁盤空間也相對比較大,查詢速度相對來說快。

    • c 含有列式存儲、倒排索引等特性之類的系統,如Elasticsearch、Druid、InfluxDB。如倒排索引可以高效的進行數據過濾,進而可以提高查詢的速度。還有很多特性如CBO和Vectorization、查詢的流式處理等查詢方面的優化由於篇幅暫不在本文的討論范圍內,本文注重於存儲層面對查詢的影響。

2 時序數據庫

時序數據庫也屬於海量數據分析的范疇內,典型的系統如InfluxDB、Druid、OpenTSDB、Prometheus,也有人拿Elasticsearch來做時序數據庫(比如騰訊)。具體有以下幾個顯著的場景特點:

  • 1 目前的幾個時序數據庫的數據模型基本統一

    Metric:指標名稱

    Timestamp:時間戳

    Tags:維度組合

    Fields:指標值

    相對關系型數據庫的數據模型來說,區分就是

    • a 必定含有時間字段
    • b 區分列的類型,需要用戶將列划分為維度列和指標列。

    划分后的好處就是:可以做一些存儲上的優化,比如自動對維度列建立倒排索引,不需要用戶使用關系型數據庫那樣針對某一列或者多列手動建立索引。總的來說:雖然給用戶帶來了分區維度列和指標列的麻煩,但是帶來的收益確實非常大的。

  • 2 需要支持海量數據的實時寫入與查詢

    時序數據庫使用最多的場景就是監控領域,訂單量的實時監控、機器的CPU、內存、網絡等實時監控

    要做到這點目前來說基本上需要實現一個LSM樹存儲模型,上述幾個時序數據庫基本都有。

  • 3 在查詢數據時都有時間范圍

    在存儲的設計上都會按照時間進行分片存儲,按照時間范圍查詢時可以快速過濾掉無關的數據。

下面就簡述下目前的幾個時序數據庫如何去解決前面提到海量數據分析的2大問題。

2.1 OpenTSDB

  • 1 海量數據如何存儲?

    OpenTSDB自身並不實現分布式存儲,而是借助於HBase或者Cassandra來存儲海量的數據。

    對於實時寫入也不是問題。

  • 2 如何對海量數據進行快速分析?

    • a OpenTSDB在數據過濾能力上還是比較弱的,主要是因為它所依賴的底層存儲HBase自身目前暫時不支持二級索引(實際上有辦法支持但是這其實就不在OpenTSDB考慮的范圍內了)或者倒排索引。

    • b 在預聚合方面,OpenTSDB也是比較弱的。OpenTSDB提的2個概念,Rollup和Pre-Aggregates。Rollup是將同一個維度組合Series不同時間點上的數據聚合起來,解決查詢時間范圍大的問題,Pre-Aggregates是將不同的Series在同一時間點上的數據聚合起來,解決維度過多的問題。

      OpenTSDB要實現這2大功能,如果底層存儲支持聚合操作,那么OpenTSDB中的TSD就可以將部分聚合的數據發往底層存儲,由底層存儲完成最終的聚合,這里顯然底層存儲HBase並不支持聚合並且可能不會為了OpenTSDB來實現聚合功能。所以OpenTSDB中的TSD就只能先聚合好最終的數據然后再發往底層存儲。TSD原本是無狀態設計,每個TSD只能見到部分的數據,這是無法完成上述任務的。如果將相同Metric的數據路由到同一台TSD,又會帶來很多問題,同時會有時間窗口的引入,當數據在該時間窗口內未達到就會被丟棄。OpenTSDB又只能寄希望外部的流式處理來完成聚合任務。上述的諸多問題最根本的原因就是底層存儲不支持聚合。這一問題詳見OpenTSDB的Rollup和Pre-Aggregates

    • 在時間范圍過濾上也還是能定位到startKey和endKey,也能做到快速過濾。

OpenTSDB依賴了其他存儲系統,在數據規模小的時候還能忍受,數據規模大了,查詢速度就慢很多,想改變卻又因底層存儲不支持自己特定需求的原因而困難重重。

不過OpenTSDB將metric、tagk、tagv轉換成id的方式確實可以省去很多的存儲容量,這部分值的借鑒。

2.2 InfluxDB

  • 1 海量數據如何存儲?

    InfluxDB是自研存儲,通過hash分片的方式來存儲海量數據。

    對於實時寫入,也是通過LSM方式來實現數據的快速寫入。

  • 2 如何對海量數據進行快速分析?

    • a InfluxDB實現了倒排索引,因此可以實現快速的數據過濾功能。倒排索引的代價就是降低了寫入速度,再加上倒排索引的占用量可能會很大,並不能完全放內存,為了解決上述問題,倒排索引的寫入也引入了LSM模型,即InfluxDB的TSI。其實InfluxDB的倒排索引的設計並不突出,倒排索引針對數據過濾場景非常有優勢,但是對於沒有數據過濾的場景如果仍然沿用倒排索引的查詢方式通過一系列series id去隨機查找數據會很慢很慢,在這方面InfluxDB做的也不好。綜上2方面的因素,InfluxDB在對比Druid、我們自研的LinDB會都會弱一些。后面會詳細分析下3者的倒排索引實現。

    • b 在預聚合方面,InfluxDB引入了Continuous Query和Retention Policy。2者配合使用可以提高數據的查詢速度。相比於OpenTSDB中的Rollup和Pre-Aggregates,InfluxDB全部融合到了Continuous Query,既可以對時間維度進行Rollup,又可以將多個Series進行Pre-Aggregates。其實現原理很簡單:就是查詢一遍聚合后的數據寫入到新的指標名下或者新的Retention Policy下。這種通過查詢數據來實現預聚合的方式都有一個缺點:每次查詢都是查詢最近一段時間范圍的數據,對於之前已經查詢過的時間范圍若來了新的數據,並不會再次查詢一次更新下結果。

      這種方式的預聚合的確能提高速度,但是對用戶來說干預很大,用戶需要理解Continuous Query,並且根據自己的查詢需求來編寫對應的Continuous Query,在查詢時又要手動去選擇合適的Retention Policy去進行查詢,即還不能夠做到自動化,InfluxDB自己又不能對所有metric都自動執行Continuous Query, 因為它不知道該如何聚合,不同用戶寫入的metric指標可能有不同的聚合需求。

    • c 列式存儲,對於不需要查詢的列可以顯著降低IO,也是借鑒了Facebook的gorilla論文中的壓縮算法進行壓縮,壓縮比高

    • d 在時間范圍過濾上,由於InfluxDB存儲本身就是按照時間范圍划分的,所以也可以高效過濾

2.3 Druid

  • 1 海量數據如何存儲?

    Druid也是自研存儲,但是這個分布式存儲的架構設計相當復雜。

    對於實時寫入,也是通過類似LSM方式來實現數據的快速寫入。

  • 2 如何對海量數據進行快速分析?

    • a 實現了倒排索引,在數據過濾方面也是非常高效的,詳見后面的InfluxDB、Druid、我們自研的LinDB實現對比。

    • b 在時間范圍過濾上,Druid的存儲也是按照時間范圍划分的,也能達到快速過濾。

    • c 在預聚合上,Druid支持在時間維度上的聚合,解決了部分問題,並沒有解決維度基數很大時的預聚合問題,並且Druid不支持同一份數據聚合出不同粒度的數據(比如segmentGranularity為10s,對於查詢幾個月的數據量來說10s粒度還是很慢的)。這就需要用戶自己同一份數據多次輸入不同的datasource(每個datasource不同的segmentGranularity)來解決,查詢時又要根據查詢時間范圍手動來選擇對應粒度的datasource。

2.4 Elasticsearch

這里只是重點來說Elasticsearch在海量數據的聚合分析領域的應用,這里並不涉及到全文搜索(這是Elasticsearch立足的主戰場,鮮有對手,但是在海量數據的聚合分析領域Elasticsearch對手就很多)

  • 1 海量數據如何存儲?

    Elasticsearch也是借助於Lucene擁有自己的存儲,同時也實現了分布式存儲相關功能。

    對於實時寫入,也是通過LSM方式來實現數據的快速寫入的。

  • 2 如何對海量數據進行快速分析?

    • a 借助於Lucene實現了倒排索引,在數據過濾方面也是非常高效的。

    • b 在時間范圍過濾上,Elasticsearch由於其通用性並沒有針對時間進行特殊優化,導致在這方面相對InfluxDB、Druid遜色一些。雖然可以通過每天建立一個Index來緩解這個問題,但是仍然有避不開的麻煩,即在設計聚合分析時需要能夠支持多個Index,這無疑增加了復雜度,不是一個理想方案。

    • c 在預聚合方面,Elasticsearch在Elastic{ON} 2018實現了這個功能。目前來看實現上和InfluxDB應該是類似的,都是通過定時任務查詢原始數據來實現Rollup,並且支持多時間粒度聚合,來適應不同的查詢時間范圍。只是目前該功能還不能自動化,需要用戶參與。

2.5 Kylin

在一定程度上Kylin也會被用做時序方面的數據分析。

  • 1 海量數據如何存儲?

    Kylin依托於HBase(也可以換成別的存儲)來實現海量數據的存儲。

    對於實時寫入,由於Kylin的重點是在數據攝入時做了大量的預聚合,那么就會導致實時性相比其他的幾個系統還是慢很多的。

  • 2 如何對海量數據進行快速分析?

    • a 在預聚合方面,Kylin做了很多工作,基本上把所有維度組合都進行預聚合了一遍,大大減少了在查詢時的聚合量,速度相比前面幾個還是非常快的。拿占用更多的空間和降低了實時性的代價換取更短的查詢時間。Kylin通過各種方式來減少預聚合的量來降低上述代價,但是這都需要用戶理解並參與優化,增加了用戶的使用負擔。

    • b 在時間范圍過濾上,時間也屬於預聚合中的一個維度,所以時間范圍過濾也是很高效的。

    • c kylin雖然說它所依賴的存儲並不支持倒排索引,但是由於大量的預聚合,在一定程度上已經減少了查詢時要聚合的數據量,所以即使HBase的過濾能力弱最終的速度也還是ok的。

3 LinDB時序數據庫

在調研了上述諸多系統之后,來看看LinDB的設計

  • 1 海量數據如何存儲?

    LinDB通過借鑒Kafka的集群功能來實現海量數據的存儲,目前只依賴ZooKeeper,並且在部署方面完全可以任意台部署,並不要求至少3台。

    對於實時寫入,LinDB內部也是采用LSM方式來實現快速寫入。

  • 2 如何對海量數據進行快速分析?

    • a 實現了倒排索引,在數據過濾方面也是非常高效的,先總結下其他系統的倒排索引:

      • 整體實現方式

        InfluxDB:基本實現方式是tagKey-tagValue-[seriesKey offset list],作為全局索引,優點:在時序場景下,相對文件級別索引,大部分時間每個文件索引基本上都差不多的,所以在查詢上只需要1次索引查詢即可,不像文件級別索引每個文件都要進行索引查詢。

        Druid:基本實現方式是tagKey-tagValaue-[row id list],每個文件包含自己的索引。缺點:每個文件都要進行索引查詢。優點:在數據遷移方面非常有利,只需要將整個文件復制即可,而InfluxDB就相對麻煩很多。

        LinDB:基本實現方式是tagKey-tagValue-[series Id list],也是作為全局索引,優缺點和InfluxDB一樣的。LinDB這樣設計主要還是基於時序場景下大部分情況下文件索引都是重復的這一情況考慮的。

      • 倒排索引大小

        這里指的是上述一個list的大小

        InfluxDB、LinDB:倒排索引大小就是組合數

        Druid:倒排索引大小就是行數,在時序數據場景下,一個文件中的數據基本是所有組合數多個時間點的數據,數據行數相比組合數大了很多,因此相對InfluxDB和LinDB大了很多

      • 數據存儲大小

        InfluxDB:數據文件中的key是seriesKey+FieldKey,這一部分也是相當耗費存儲的,並沒有像OpenTSDB那樣將他們轉換成id來存儲。對於時間的壓縮采用差值的方式進行壓縮,對於數據的壓縮采用facebook的gorilla論文中的方式。

        Druid:倒排索引list中存儲的是行號,每個文件中都將tags轉換成對應的id來存儲,相比InfluxDB也沒有重復存儲大量的tags。對於時間的壓縮和數據都采用LZ4方式進行壓縮

        LinDB:倒排索引list中存儲的是series id,數據文件中是按照id來存儲的,相比InfluxDB沒有重復存儲大量的tags,相比Druid沒有重復存儲tags到id的映射,以及每個tags的bitmap。因此從整體設計上來看LinDB是最節省存儲的。對於時間的壓縮采用一個bit的方式來代表該數據槽位是否有數據(這種設計幾乎在大部分情況下是非常有利的,除非是只有開始和結束槽位有數據,中間槽位都沒來數據,這種特別的場景極少),因此相比InfluxDB和Druid做了極致,對於數據的壓縮采用facebook的gorilla論文中的方式。

      • 查找數據方面

        InfluxDB:先根據過濾條件和倒排索引找到符合條件的所有seriesKey offset,然后再根據offset找到對應seriesKey,再將seriesKey和field拼成數據文件中的key,到符合查找時間范圍的數據文件中查找對應的數據,所有的key分片隨機查找,每個key查找是二分查找。

        Druid:在每一個符合查找時間范圍的文件中,先根據過濾條件和倒排索引找到符合條件的所有row id,再按照所有的row id順序性查找field的數據。比InfluxDB的優勢在於:

        1 InfluxDB通過倒排索引找到的是seriesKey offset,還不能根據這個offset直接到數據文件中查找,還必須多一步offset到seriesKey的查找,假如符合條件的series有300萬,那么這一步就已經相當耗時了,慢也就是必然的了

        2 InfluxDB對所有的seriesKey+field拼接成的key的查找是隨機查找,每個key的查找是二分查找的方式,假如符合條件的series有300萬,那么300萬次的二分查找也是相當耗時(並且key的長度可加劇了耗時)。InfluxDB可以對所有的key進行分片多線程查找,相對來說快了一點,但是並不改變查詢效率差的本質。

        LinDB:先根據過濾條件找到符合條件的所有series id,我們就可以直接拿着series id到數據文件中查找,相比InfluxDB省了offset到seriesKey的查找過程。Druid是文件內索引,過濾條件出來的結果是row id,天然順序性,並且可以直接定位到數據位置。LinDB和InfluxDB是全局索引,過濾出來的結果需要經過一個查找過程才能找到對應文件中的offset。InfluxDB是最原始的二分查找,效率並不高。LinDB通過計算要查找的series id是文件中的第幾個series id,就可以根據這個序號找到對應的offset。我們通過RoaringBitMap的分桶策略,順序性分片,只需要算出在當前分片的第幾個位置再加上初始位置就可以得到總的位置,計算當前分片的第幾個位置是二分查找。

        總的來說:

        InfluxDB: 多線程查找、二分查找、查找是長字符串之間的比較(還多一步series offset到seriesKey的查找)。這就是InfluxDB慢的一部分原因。

        Druid:單線程查找、O(1)查找、查找是數字之間的比較

        LinDB: 多線程查找、局部二分查找、查找是數字之間的比較

    • b 在時間范圍過濾上,LinDB底層存儲就是按照時間范圍進行物理划分的,所以可以快速過濾

    • c 在預聚合上,先總結下其他系統的預聚合:

      • OpenTSDB的預聚合是它的痛點

      • InfluxDB的預聚合通過查詢來實現有缺陷,以及用戶需要理解Continuous Query和Retention Policy

      • Druid底層存儲支持時間維度的預聚合,並且只能有1種聚合粒度

      • Elasticsearch最近發布支持預聚合跟InfluxDB實現有點類似

      • Kylin預聚合比較全面,但是需要用戶深度參與優化

      從目前公司內部的實際使用情況來看,很多用戶就前面所說的時序數據模型都沒有理解,更別指望他們去理解Continuous Query、Kylin Cube等概念了,對於他們來說這些概念他們也不想了解,只管打點即可,剩下的性能問題都是系統維護者的事。所以我們LinDB面向用戶的首要目標是簡單易用,用戶只需理解時序數據模型即可。

      要實現這個目標並不簡單,查詢的數據量大小主要有2方面的因素:組合數的大小*每個組合數的點數,比如100個host,每個host 32個核,每個host的每個核 1s一個點,那么查詢所有host 7天內的所涉及的數據量大小為 (100*32) * (7*24*3600)。為了減少這個數據量的大小,就需要從下面2個方向入手:

      • 對時間維度進行預聚合

        提前將每個組合1s一個點的數據聚合成10s一個點、10分鍾一個點、1天一個點。這樣就可以將數據量降低至1/10、1/(10*60)、1/(24*3600)。查詢最近幾個小時,可以用10s粒度的數據區查,查詢幾天的數據可以用10分鍾粒度的數據區查,查詢幾個月甚至幾年的數據可以用1天粒度的數據去查,這已經大大降低了要查詢的數據量,意味着查詢幾年的數據的響應時間都可以是毫秒級別的。

        用戶唯一需要做的:用戶需要給出每個數據即Field的聚合方式即可

        我們如何實現:10s、10分鍾、1天粒度,這3種粒度可以自定義,完全可以滿足從最近幾小時到幾年范圍的數據查詢,用戶在查詢指標時會根據用戶的查詢時間范圍自動選擇合適粒度的數據,占用的存儲基本是原始數據的1/10、1/(10*60)、1/(24*3600)。這一切都是用戶無感知的。在實現上,我們不是建了多個庫,1份數據寫到每個庫中(每個庫都包含復制、LSM模型),我們不是像InfluxDB那樣通過查詢來實現,我們是每個庫支持多粒度存儲,在寫入時寫入到最小粒度中,flush出多個文件后,將多個文件一起讀取聚合到下一個粒度中。

      • 對組合數進行預聚合

        提前將每個host的所有核聚合起來,但是這是跟用戶的查詢需求密不可分的,還是需要用戶參與。由於時間維度預聚合已經基本滿足了我們的需求,所以這個目前我們還暫時沒做,之后我們可能會在時間維度預聚合的結果上再來實現這一功能,將徹底解決大時間范圍大維度查詢的問題。

      我們可以看到InfluxDB、Kylin等Rollup的實現是將時間維度預聚合和組合數預聚合合並在一個功能中,雖然簡化了開發,但是卻麻煩了用戶。我們則必須要把他們分開,我們將時間維度預聚合完全自動化,使得幾乎所有的用戶不用陷入如何優化的煩惱中,針對極個別的用戶我們之后再通過組合數預聚合讓用戶參與優化。總之,LinDB在功能設計上都以簡化用戶使用為目標。

特別有意思的是很多系統在自己特定領域站穩腳跟后都會進行擴張到其他相關領域,比如OLTP的數據庫向OLAP擴張,再比如Elasticsearch在全文搜索領域站穩腳跟后擴張到時序數據庫領域,如果在設計之初就能考慮到其領域的關鍵點的話,那么擴張可能會順利很多,比如時序數據庫中非常重要的倒排索引和預聚合

未來對數據的實時寫入和實時查詢要求會越來越高,因此時序數據庫相比依賴於HDFS的Hive、SparkSQL、Impala等優勢很大,但是目前時序數據庫的查詢豐富性方面相比它們還差很多,通常都是對單指標的filter和group by查詢,比如對多指標的join暫時暫時都不支持。在監控場景下,join的需求不是很強烈,但是時序數據庫要想走向其他場景下的數據分析領域,瓜分他們的地盤,join還是必不可少的功能,這時對時序數據庫的分布式SQL查詢要求也就變高了,如果能做到的話才更容易走出時序數據分析領域,向其他數據分析領域進軍

參考文檔:

OpenTSDB文檔

InfluxDB文檔

InfluxDB系列解析

Druid文檔

Druid Storage 原理

Elasticsearch Rollups

Elasticsearch技術研討知乎專欄

Roaring Bitmaps


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM