近日,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小時後才能拿到這個資料,在某些場景下面還是很希望把資料儲存下來察看的,但是流引擎做不到這一點。流引擎能做到的是能以很高的效率來處理資料,但是提前要把需求預先定義好,沒有辦法實時計算。
hitsdb的由來
是以我們後來還是轉到了使用一個專門的時間序列資料庫的方案上,業界往往把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。不浪費大家的時間了。謝謝!
<a href="https://mp.weixin.qq.com/s/sldicwue1rsjwbg4_45zhq">原文連結</a>