接下時序數據存儲的挑戰書,阿裏HiTSDB誕生了
近日,2017中國數據庫技術大會在京召開,來自阿裏巴巴中間件團隊高級技術專家鍾宇(花名悠你)在數據存儲和加速技術專場分享了題為《時間序列數據的存儲挑戰》的演講,主要介紹了時序數據的由來,時序數據處理和存儲的挑戰,以及目前業界的通用做法。在案例展示部分,他結合阿裏內部業務場景和時序數據的特點,講述阿裏時序數據處理和存儲所麵臨的問題以及解決問題的過程,以及不斷應對挑戰慢慢形成HiTSDB的過程。
演講全文:
鍾宇:大家好,我叫鍾宇,花名悠你(Uni),來自阿裏巴巴中間件(Aliware)團隊。首先我大概給大家介紹一下今天的分享內容,共有五個方麵,首先跟大家介紹一下什麼叫做時間序列,時序數據這個領域是一個比較專的領域,但同時它的範圍又很廣,因為我們平時遇到的數據裏頭有相當大的一部分都是時序數據,但是它又相對來說比較專,因為它不像我們一般的數據庫什麼都可以做,所以時序數據這個領域還是蠻特別的。
接下來我會說一說時序數據的存儲和分析的一些方案,因為處理時序數據有很多的方案可以用各種各樣的東西來做。給大家舉一個例子,我高中的時候剛開始學計算機,我當時特別不能理解,為什麼世界上會有數據庫這樣的東西,因為有個東西叫excel,它和數據庫一樣是一個表格,我隻要把數據庫放上去,在裏麵搜索一個數據比數據庫還快,所以我想為什麼要有數據庫。
等到用的東西多了以後會發現,數據的存儲和分析,excel是一種方法,數據庫也是一種方法,針對的數據量和應用場景是不一樣的。時序數據其實也是一樣的,就是說我們可以有很多種不同的方式來存儲和分析時序數據,就好比是分析一般的表格,用excel可以,用MYSQL也可以,完全是針對不同的場景做出的不同選擇。
接下來我會介紹一下時序數據庫,因為時序數據庫是時序數據存儲和分析的一個非常重要的工具。我們考察過很多時序數據庫,包括influxDB和OpenTSDB),我們對此做出了改進來適應阿裏的特殊應用場景,接下來我會著重介紹一下阿裏對時序數據庫的一些改進。
目前來看時序數據是比較年輕的領域,沒有什麼標準,整個行業發展也比較稚嫩,有很多方麵並不完善,所以最後看一下,它還有哪些方麵的發展是需要我們來繼續的。
先跟大家聊一下什麼叫時序數據。可能大家平時了解的不太多,這個東西很簡單,就是時間上分布的一係列數值,關鍵字是數值,我們一般認為的時序數據是什麼時間發生了什麼事情,但是在時序數據這個領域裏定義的時序數據全都是跟數值有關的。也就是說如果隻是一個帶有時間戳的一條數據並不能叫做時序數據。舉個例子,比如像我早上8點半上樓吃了個飯這條記錄,相當於一個日誌,這個本身不構成一個時序數據,但是如果某個餐廳早上點半同時有50個人在那裏吃飯,這個50加上餐廳的信息再加這個時間點就構成了一個時序數據。
更明顯的例子比如說股票價格,每一個時刻每一支股票都有一個交易的價格,這種其實是時序數據。還有很經典的應用是廣告數據,廣告數據中很多像PV、UV一類的東西,這些都是在某一個時間點上一個數值型的東西。然後就是一些工業和科研上的東西,氣溫變化、工業上的傳感器的數據都是這樣的。
最近還有一個比較火的概念,大家會想到物聯網,物聯網的數據大部分都是時序數據,所以從這個角度來說,時序數據可能占我們平時需要處理的數據量中相當大的一部分。
舉個廣告的例子,我們會發現一個時序數據除了有一個時間點以外,它還有一些別的部分,從頭開始說,首先是要有一個東西來標注數據源,舉個例子做廣告,廣告有很多的來源,在這個圖表裏大家會看到是AD source1、2、3,這3個是不同的廣告源,為了區分不同的廣告源在上麵打了一些標簽,1這個網站實際上是麵向google發布的,麵向的群體是男性,這是一個廣告源,這些標簽就標識了唯一的數據源,每一個數據源它都有很多的指標,我們把它叫一個指標或者一個測量。
我們在這個廣告源上測量了很多次,比如說會測量它被查了多少次、會測量它被點擊多少次、會測量它產生了多少收入,我們每個廣告源都做了三次的測量,這樣每個廣告源就產生了三個時間序列,我們叫time series,每個廣告源有三個時間序列,在這個時間序列上在每一秒鍾都會對它進行一次測量,所以每次測量會產生一個時間序列的值,我們把它叫做一個點,基本上時序數據的樣子就是這樣的。
然後再來看建模,實際上通用的建模方式有兩種,其中的一種是單值。實際上我們是針對不同的東西來建模的,多值的模型是針對數據源建模,我們每一行數據針對的是一個數據源,它的三個被測量的指標在列上,所以每一個數據源,數據的來源在每一個時間點上都有一行,這就是多值的模型。
還有一種模型是單值的模型,單值的模型我們是把它測量的精確到時間序列上,也就在時間序列的每個時間點上隻有一個值,所以是個單值,也就是說對於多值模型來說它每一行數據對應的是一個數據源,對於單值模型來說它對應的是一個時間序列,實際上多值模型對應的是一個數據源在一個時間點上就會產生一行數據,而在單值模型裏一個數據源上麵的每一個指標會產生一行數據。
下麵牽扯到時間序列處理的一些比較特別的東西,這在我們的傳統數據庫裏有可能是沒有的,叫插值和降精度,剛才我們已經看到,時間序列會分布在一些時間線上,數據源和測量指標確定了的話,時間序列是隨著時間軸往後分布的,實際上它的采樣在一個典型的場景裏是固定時間間隔的,它中間一些點做處理會牽扯到插值和降精處理。比如說中間丟失了一個點,比較簡單的方法是中間插一個值,常用的方法是線性插值,就是在時間軸上畫一個直線中間的點就插出來了。
另一個叫降精度,例如我們有個按秒采樣的時間序列,顯示時間範圍是一年的數據,為了便於查看,需要把時間精度降到一天。比如我們隻選這一天中的最大值或者最小值或者平均值,作為這一天的氣溫,也就是最高氣溫,最低氣溫和平均氣溫的概念。用算法或者把時序數據轉換成精度比較低的時間序列以便於觀察和理解它,這是在傳統數據庫裏沒有的一種方式。
這個東西就跟傳統的數據庫有點像但是聚合方式不一樣,比如這裏有很多時間線,實際上時序數據的聚合是在時間線的維度上的,而不是按點的,我們是在處理平時處理的空間聚合的話,一般是把很多數據點按照一個個聚合起來,而實際數據處理的時候一般會把它抽象的點連成線就是剛才看的時間序列,每個數據源在一個測量值上會產生一行時間線,加上時間序列,如果是根據某一個維度上的測量的話,在同一維度就能調成線就把時間序列處理出來了。
基本上插值,降精度,聚合就是時序數據處理的最常用的方式。
我們再看看特點,如果我們隻是做剛才那些東西的話其實會很簡單,但是再結合了時間序列數據的特點以後,有一些簡單的事情就會變的很複雜。首先第一是時間序列會持續的產生大量的數據,持續的產生什麼意思呢?因為我們往往對時間序列來說是定時采樣功能,比如我們說氣溫的波動,每秒測量一次,一天是86400秒,如果是我們做係統監控,或者像氣溫這樣的科學儀器持續的調數據的話,24小時都要用,平均每一個儀器儀表在一個時間點上產生一個數據點,一個儀表就產生86400個數據,如果把全國各個縣都布一個采樣點,那一天數據就上億了,實際上大家作為氣象采樣來說每一個縣對應一個溫度傳感器顯然有點不夠的,可能我們是每一個街道甚至每個小區都有這樣的傳感器,那麼這個數據加起來實際上是一個非常驚人的數字。
還有另外一個東西是數據產生率是平穩的,沒有明顯的波峰波穀。全國布滿了都是傳感器,這些傳感器隻要是不壞的是持續傳數據的,所以我們無法期望它會像我們的交易係統一樣每年雙十一有一個明顯的峰值,平時就沒什麼事了,很少有這樣的情況,這個特性對我們做優化來說有優勢也有劣勢,優勢就是我不需要考慮太多的削峰填穀,不需要為突發的大流量準備太多的資源。但不利的地方是,我們有些算法是很難在裏頭騰出時間來做一些整理的工作的。舉個例子,如果把數據存在一個像LSM Tree或者SSTable一樣的東西的話,當compaction發生,希望把兩個塊合並起來的時候,這時候在持續數據這個場景下上麵我們寫數據可以是很高很高的數據來寫,如果底下的IO太厲害的話就會產生雪崩的效應導致寫入延遲或者失敗,需要做一些限流的處理。
還有一個特點是近期數據的關注度更高。時間越久遠的數據就越少被訪問,甚至有時候就不再需要。
看到這裏我們會發現實際上時序數據我們可以用一個單表把它建立起來,我們通過在每一行數據上加很多標簽來把這行給挑出來。所以我們展示的時候往往就是對標簽做聚合計算。
首先舉個例子,阿裏巴巴內部有一個係統叫鷹眼,這個係統實際上是做一個機器的,簡單來說阿裏內部有很多服務器組成的一係列很大很大的集群,因為機器太多了,所以永遠會有失敗的時候,要麼是程序失敗要麼是鷹眼失敗,所以會有一個係統把這些所有的機器應用還有鏈路這些接入起來,時序數據也是其中的一部分,因為它需要用這些時序數據來監控整個係統的狀況,其中包括裏頭的服務,所有服務器的指標,比如說每個服務器的CPU利用率,內存使用率,還包括服務器上所有應用的指標,比如說對應用QPS精確到每一個KPI,每一秒被調用了多少次等等。這些東西會產生很多的時序數據,寫入的峰值是570萬點/秒,平均寫出來300萬,雖然說時序數據一般來說會比較平的,但是還是會有些波動。
因為在阿裏內部係統請求太多,會產生服務降級的情況,服務降級也就是說當一個集群發現它的服務能力不足以支撐的時候,它會選擇另外一種消耗更少的方式,比如原來是用一秒鍾采樣的,如果發生服務降級了是5秒鍾采樣或者1分鍾采樣,會集中在分鍾的邊界發送,導致在那一小段時間內有一個峰值。因為管理的機器很多,所以會產生很多的指標。
比如說對於服務器監控會有處理器、會有IO,對於應用的監控有QPS,不同的指標加起來有上萬個。更大的指標是因為有很多應用,每個服務器上麵可能有幾十個應用,所以加起來幾千萬個時間序列,每個時間序列平均有5個維度,一個時間序列把幾萬台機器挑出來,舉個例子說,我們部署的機房,在這個機房裏頭內部的IP係統上,機房、哪個機架、IP是多少、在上麵哪個應用以及這個應用裏哪個具體的指標,是QPS還是UV之類的那些東西,大概平均5個維度就能把一個時間序列挑出來,所以其實總的維度數量並不是很多,但是每個維度裏頭它的離散度很大,比如光是IP這個離散度就有幾十萬。因為這個鷹眼係統是內部的係統,使用者是內部的管理人和程序員,每秒大概聚合幾百次,這個場景是我們內部比較完整的時間序列的場景。
我們內部項目在做時序數據的存儲時,最早思考的是把它保存在關係數據庫裏,這是一個很通常的做法,但是後來發現這個事情可能不太可行,因為大家都知道InnoDB的寫入性能是很有限的,我們在一個內部測試大概在24台機器上,因為我們優化很好,而且是存儲設備是SSD硬盤,寫一秒鍾持續寫成達到兩萬左右,和剛才說需要的570萬還差的很多,如果我們達到這個存儲量級大概需要300台,其實出現這種情況有原因的,首先一個很大的問題是說,B樹的索引,索引是一個B樹,這個B樹有很大的開銷,雖然我們可以通過一些辦法優化,但是因為我們為了優化時序數據是一個多維數據,我們為了優化所有排列組合的產品,所以我們是很多多列的索引,這些索引每次在寫的時候每個都需要更新,所以就會導致很多的IO。
還有一個更糟糕的問題,我們發現存儲空間增長的非常快,就算每秒300萬的數據,每個數據加起來要240字節以上,300多萬×200個字節,也就是說我們一秒鍾一個G,這樣的話即使很多機器也會被這些數據塞滿了,而這還沒算上索引。類似於上述提到的時序數據的存儲,如果用這個方案的話隻能在一些很小的場景上使用,比如接入幾十台機器,上麵有很少的應用可能一秒鍾隻會寫個幾百條數據這樣子,這樣的數據量可以用innoDB來做。
另外我們還發現,剛才提到時序數據很重要的是降精度,降精度其實特別難優化,因為降精度是在時間序列維度上做的,首先要把時間序列維度拿出來然後在中間插值,而實際上SQL是不知道這件事情的,SQL是按點來操作的。所以如果要做降精度的話需要用一個值查詢把那條時間序列單挑出來,插好值之後才能做時間序列之間的聚合,這意味著我們的服務和SQL服務器之間的吞吐量非常非常之大的,相當於SQL隻是一個數據通道需要把所有值都拉出來運算一遍。很難把這個東西放到SQL服務器去。所以我們發現把時序數據放在關係數據庫上的方案不可行,就考慮了另一個方案。
一開始最大的問題是寫的慢,那我們就找個寫的快的東西來處理好了,寫的快的東西是什麼呢?就類似於SStable那種,就不用那種隨機操作的方式,就用追加的方式來寫,實際上這個東西在寫上麵是能工作的,因為我們測試了Google 的LevelDB和MyRocks,我們發現Rocks的性能比較強,所以我們在Rocks上測試了一下,大概能達到20萬點/秒,而且這是因為MyRocks寫入性能的優化不夠,它在CPU的核數多於8核的時候可以共用CPU,線程所用比較保守,阿裏內部有一個改造,把它改造成寫入性更高的東西。即使是這樣性能也不是太高,但是勉強夠了,因為能達到20萬點/秒,不需要用300台機器了,用30台機器就可以搞定。
但是我們很快發現其實沒有那麼樂觀,因為又回到剛才那個問題的,我們需要建很多個索引來保證多維這個事情,如果我們需要建很多個索引的話意味著我們每次寫實際上要去更新一個索引,比如為了保證多維查詢的性能需要建4-5個索引,等於寫入數據調到原來的1/4。所以這個東西也不太可行,而且它有一個問題是tag重複存儲其實沒有解決,我們壓縮完以後平均5個點還需要50個字節,實際上一個點真正的數據隻有8個字節,加上時間就是16個字節,差別還是蠻大的。
下麵介紹的是在業界經常被人用的方案--Elastic search,這個東西是比較有好處的,數據量不大的時候,會針對每個緯度來做索引,能很快的把時間點摘取出來。它的倒排索引很大,但是這個方案特別流行因為對於很多公司規模小、客觀業務規模小的,這個東西會非常有戲,因為它很快,而且有整個開源社區的支持。
我們後來還試了用列式存儲的方式保存時序數據,這是特別有誘惑力的一個方案,因為列式存儲第一壓縮率會比行式高很多,因為它把相似的數據都放在一起了,而且它有一點特別適合時序數據的是因為寫入磁盤的數據是不可變的,時序數據恰好不太需要修改。但是後來我們發現使用了以後踩了個坑,Druid或者inforbright那種方案處理某些時序場景合適,但是處理我們那個時序場景不太合適,因為列式存儲是把導入的數據累積到一定程度,才會打一個包把它固定到磁盤上的,但是時序數據如果長時間的查詢的話這意味著要查該時間段內每一個包。
因為所有維度的數據在每個包裏都會存在,比如要按機架的維度來看,我們積累了一萬個數據文件,每一個數據文件裏頭都有非常大的,可能會出現同一個機架的兩三行數據。這就很糟糕,實際上列式存儲的篩選數據文件機製沒有生效,沒辦法迅速的把一部分數據文件剔除掉。
接下來我們還考慮了這樣一個工具,流引擎,其實流引擎是一個非常好的東西,它同時解決了多維計算,反正你能想象的東西它基本上都能解決,除了存儲問題,流引擎不是數據存儲引擎隻是計算引擎,如果事先你的應用沒有對你需要查的數據做很好的規劃的話,隻能事後再補充一個新的拓撲再計算,但是新拓撲無法立即獲得數據,比如上一個新的拓撲,是按24小時精度的,一次要看24小時,那麼24小時後才能拿到這個數據,在某些場景下麵還是很希望把數據保存下來察看的,但是流引擎做不到這一點。流引擎能做到的是能以很高的效率來處理數據,但是提前要把需求預先定義好,沒有辦法實時計算。
所以我們後來還是轉到了使用一個專門的時間序列數據庫的方案上,業界往往把MongoDB之類的東西也算成時序數據,其實那些東西是比較通用的。專業的時序數據庫可能是InfluxDB,openTSDB這樣的,這兩個東西最大的區別是它能把時間線提取出來,它的思路相當於是事先做一係列的標簽、先找到時間序列,在這個時間序列上再找到對應的點。時序數據是按照一個時間長度分片來存在一起,所以這樣的好處就是它存儲的壓縮率會比較高,而且是搜索的時候不需要搜索每一行了,搜索的比較少。
最後我們選了openTSDB,可以保證它把一個小時的數據放到一個行裏,這樣壓縮率比較高,能做到每個數據20字節左右。
但是openTSDB用久了發現它有很多劣勢:首先,它其實是一個無狀態的節點,所以它的Meta DATA實際上在所有節點上都是全量的,所以它占用的內存會很大;其次,時間線數量很多的時候,Rowscan方式做維度查詢,這跟列存數據庫有點像,但是當查詢條件不滿足rowkey的前綴時,它的磁盤IO還是有點太多的;第三,在固定的Column中保存一小時的時間點,這個問題是,大家知道它的qualifier存在額外的開銷;第四,還有個更大的問題是openTSDB是單點聚合,也說不管你有多少節點,實際的計算,每次計算都放在一個節點上。
所以我們後來做了很多改進,其中第一個是先引入了倒排索引,我們發現用倒排索引的方式能更快的把時間線挑出來,搜索引擎的問題在於它挑的不是時間線,挑的是所有的數據,所以索引就會很大的倒排,如果我們把這個索引指定在時間線上,時間線是幾千萬的量級,對於倒排索引來說是很輕鬆的事情,所以我們引入了倒排索引。
但是引入了倒排索引以後還有很多沒有解決的問題,而且有很多新問題,比如說一旦引入倒排索引以後,我們為了保證讓它能按一致的方式工作,我們其實做了分片,我們用了很多辦法,比如binlog寫入到HDFS,保證集群的可用性及數據的可靠性。每個分片一個binlog文件還有分片策略的問題,現在簡單的說是按照metric加特定的tag,等於是用了一個結合的方式。
之後我們再繼續往下做優化,雖然引入了倒排索引,但性能有所提升有限,因為HBase mget的一些性能限製。中間還發現一個問題,是openTSDB的降精度,它保存的永遠是原始數據,所以我們又做了預先降精度的功能,把預先降精度的數據用一個東西算好存進去,這樣比如我看一小時的時候,預存有半小時了,這樣放大隻有2而不是原來的3600,這樣改動雖然比較小,但是性能提升很大。
回到剛才那個問題,FaceBook有個東西叫Gorilla,它是個高壓縮比的算法,它最厲害的是可以把一個時間點壓縮到1.37字節,在我們的測試中基本上可以做到2字節以內,這麼高壓縮比有什麼好處?意味著我們可以把最近的數據,也就是說我們用倒排索引找到的一係列時間線ID以後,大部分隻要去一個內存的表裏頭把對應的壓縮好的數據塊找出來就行了,因為一個時間點壓縮到不到兩個字節的話,意味著哪怕是每秒300多萬的點也就5、6兆一秒就解決了,通過256G內存的機器,這個東西其實提高非常大(不需要去HBase做mget操作)。
這個和倒排索引結合起來,其實就滿足了我們的讀寫了,但是這個東西帶來了一個更大的問題,是寫穿還是寫回的問題,如果你是一個寫穿的方式的話,寫性能是並沒有提高的,也就是說我們並不能這樣寫,如果我們要做的更激進一點寫回係統,一次性寫到後麵的存儲,這樣分片和高可用方案就做的很複雜。最後我們還是搞了點小技巧,把共享文件係統上的binlog寫入LDFS,即使這樣還是很複雜。
大概再提一下,因為我們做了高可用的工作,但是不管怎麼樣在時序數據庫這個領域裏頭它並不能保證ACID的,傳統數據庫是有ACID的保證,實際上時序數據庫裏隻能保證一條數據寫過來至少被處理一次,尤其是batch寫的方式,比如客戶一次性提交300條數據過來,第150條失敗了,實際上前麵149條已經寫入數據庫了,這個事情在我們這兒是被允許的,前麵的149等於是處理了兩遍,這樣的話至少每條數據會被處理一次。
然後我們還引入了一個分布式聚合引擎,解決了openTSDB的那個單點的問題,然後把零件組裝起來就形成了最後的一個產品,我們叫做HiTSDB,它協議上跟openTSDB是兼容的,但是內部已經被我們改的麵目全非了。它的主要核心的功能就是倒排索引,緩存還有分布式聚合,最核心的是倒排和緩存,有了倒排和緩存我們就能以很高的速度來處理一個典型的,在最近時間內典型的一個時間序列的查詢。
因為時序數據剛才跟流引擎對比了一下,如果用流處理的話在寫入之前就把所有的東西寫進去了,它的缺點是不具備靈活性,優點是很多場景下速度非常快,而時序數據的想法是做後計算,把所有的原始數據都寫進去,想怎麼算怎麼算,想怎麼查怎麼查,但是對於一些特別大的查詢、又特別固定的查詢,反複的計算是沒有必要的,所以下一步會把一些可以配置的預聚合功能放到數據庫裏,實際上當你往外查的時候某些東西是已經算好的,你隻要把它查出來就好了。
我們還有一些想法是把曆史數據的文件存在雲存儲上,這樣我們可以做長線離線分析,這都是我們考慮的事情。
實際上我們還是有很多場景不能很好的應付的,這個我們在內部也發現了一些:
1、發散時間序列,跟我們的搜索團隊有過合作的項目,做離線分析的會把他們的指標數據放在壓縮上,離線分布會產生幾個小時的數據,時序數據會膨脹為幾十幾百億,這個東西目前我們是不能很好的應付的。
2、還有一個是事件驅動 vs 定時采樣,我剛才說的都是定時采樣的,對於高壓縮采樣是固定20分鍾切一片然後把它壓縮起來放進去,但是如果是事件驅動的20分鍾可能沒有幾個點,有時候20分鍾也可能非常多的點,這樣對於壓縮很不均衡;
3、還有一個目前解決不了的是高頻采樣,我們現在的采樣精度最多支持到秒,時間精度是支持到毫秒,但采樣精度隻支持到秒;
4、還有一個問題是,雖然時序數據是單表,但是很多用戶希望和現在的SQL數據表互操作,目前這些問題還是都沒有支持的,所以未來我們會考慮做成一個存儲引擎。
5、還有一個是group by+topN的優化;
6、結合事件驅動和定時采樣,考慮引進一些列存的思路解決數據驅動的模型,考慮雙引擎(一個處理事件驅動一個處理定時采樣)。
再說一個比較炫酷一點的事情是硬件加速,最近阿裏在搞關於硬件加速的東西,其中有些類似於FPGA,因為FPGA會用一個流式的方式工作,FPGA下麵會帶一個萬兆或者40GE的網卡,特別適合時間序列場景的類似流架構的方式,所以我們考慮采用FPGA的方式構建下一步硬件加速體係,如果這樣的話我們有可能做到在一個板卡上做限速,也就是說數據就是以40G的速度流進來,一個固定的數據速率流出。最後稍微聊一下雲部署的事情,這些東西最後會上雲,提供公共服務。
最後謝謝一下我們團隊--阿裏中間件時間序列團隊,我們的口號是For Your Time。不浪費大家的時間了。謝謝!
最後更新:2017-06-16 16:02:12