寫在前面
近些年,大數(shù)據(jù)背后的價(jià)值也開始得到關(guān)注和重視,越來越多的企業(yè)開始保存和分析數(shù)據(jù),希望從中挖掘大數(shù)據(jù)的價(jià)值。大數(shù)據(jù)產(chǎn)生的根本還是增量數(shù)據(jù),單純的用戶數(shù)據(jù)不足以構(gòu)成大數(shù)據(jù),然而用戶的行為或行為相關(guān)的日志的數(shù)據(jù)量,加之隨著物聯(lián)網(wǎng)的發(fā)力,產(chǎn)生的增量數(shù)據(jù)將不可預(yù)估,存儲和查詢增量數(shù)據(jù)尤為關(guān)鍵。所以,在筆者的工作經(jīng)歷中,本著以下的目標(biāo),尋找更優(yōu)的大數(shù)據(jù)存儲和查詢方案:
????數(shù)據(jù)無損:數(shù)據(jù)分析挖掘都依賴于我們保存的數(shù)據(jù),只有做到數(shù)據(jù)的無損,才有可能任意的定義指標(biāo),滿足各種業(yè)務(wù)需求。
????保證數(shù)據(jù)實(shí)時(shí)性:數(shù)據(jù)的實(shí)時(shí)性越來越重要,實(shí)時(shí)的數(shù)據(jù)能夠更好的運(yùn)維產(chǎn)品和調(diào)整策略,價(jià)值更高。單進(jìn)程每秒接入3.5萬數(shù)據(jù)以上,數(shù)據(jù)從產(chǎn)生到能夠查詢到結(jié)果這個(gè)間隔不會超過5秒。
????業(yè)務(wù)需求快速響應(yīng):隨著越來越快的業(yè)務(wù)發(fā)展和數(shù)據(jù)應(yīng)用要求的提高,數(shù)據(jù)的查詢需要更靈活,快速響應(yīng)不同且多變的需求。最好是任意定義指標(biāo)后能夠?qū)崟r(shí)查詢出結(jié)果。
????數(shù)據(jù)靈活探索性:探索性數(shù)據(jù)分析在對數(shù)據(jù)進(jìn)行概括性描述,發(fā)現(xiàn)變量之間的相關(guān)性以及引導(dǎo)出新的假設(shè)。到了大數(shù)據(jù)時(shí)代,海量的無結(jié)構(gòu)、半結(jié)構(gòu)數(shù)據(jù)從多種渠道源源不斷地積累,不受分析模型和研究假設(shè)的限制,如何從中找出規(guī)律并產(chǎn)生分析模型和研究假設(shè)成為新挑戰(zhàn)。因此,探索性數(shù)據(jù)分析成為大數(shù)據(jù)分析中不可缺少的一步并且走向前臺。
????超大數(shù)據(jù)集,統(tǒng)計(jì)分析秒級響應(yīng):萬億數(shù)據(jù)量級,千級維度(非稀疏)的統(tǒng)計(jì)分析秒級響應(yīng)。
目前大數(shù)據(jù)存儲查詢方案大概可以分為:Hbase系、Dremel系、預(yù)聚合系、Lucene系,筆者就自身的使用經(jīng)驗(yàn)說說這幾個(gè)系的優(yōu)缺點(diǎn),如有紕漏,歡迎一起探討。
數(shù)據(jù)查詢包括大體可以分為兩步,首先根據(jù)某一個(gè)或幾個(gè)字段篩選出符合條件的數(shù)據(jù),然后根據(jù)關(guān)聯(lián)填充其他所需字段信息或者聚合其他字段信息,本文中提到的大數(shù)據(jù)技術(shù),都將圍繞這兩方面。
一、Hbase系
筆者認(rèn)為Hbase系的解決方案(例如Opentsdb和Kylin)適合相對固定的業(yè)務(wù)報(bào)表類需求,只需要統(tǒng)計(jì)少量維度即可滿足業(yè)務(wù)報(bào)表需求,對于單值查詢有優(yōu)勢,但很難滿足靈活聚合數(shù)據(jù)的場景。
Hbase的表包含的的概念有rowkey、列簇、列限定符、版本(timestamp)和值,對應(yīng)實(shí)際Hdfs的存儲結(jié)構(gòu)可以用下圖做一個(gè)簡單總結(jié):

Hbase表中的每一個(gè)列簇會對應(yīng)一個(gè)實(shí)際的文件,某種層面來說,Hbase并非真正意義的列式存儲方案,只是列簇存儲。每個(gè)文件有若干個(gè)DataBlock(數(shù)據(jù)塊默認(rèn)64k),DataBlock是HBase中數(shù)據(jù)存儲的最小單元,DataBlock中以KeyValue的方式存儲用戶數(shù)據(jù)(KeyValue后面有timestamp,圖中未標(biāo)注),其他信息主要包含索引、元數(shù)據(jù)等信息,在此不做深入探討。每個(gè)KeyValue都由4個(gè)部分構(gòu)成,分別為key length,value length,key和value。其中key的結(jié)構(gòu)相對復(fù)雜,包括rowkey、列、KeyType等信息,而value值對應(yīng)具體列值的二進(jìn)制數(shù)據(jù)。為了便于查詢,對key做了一個(gè)簡單的倒排索引,直接使用了java的ConcurrentSkipListMap。
Hbase管理的核心思想是分級分塊,存儲時(shí)根據(jù)Rowkey的范圍不同,分散到不同的Region,Region又按照列簇分為不同的Store,每個(gè)Store實(shí)際上又包括StoreFile(對應(yīng)Hfile)和MemStore,然后由RegionServer管理不同的Region,RegionServer即對應(yīng)具體的進(jìn)程,分散不同的機(jī)器,提供分布式的存儲和查詢。查詢時(shí),首先獲取meta表(一種特殊的Region)所在的RegionServer,通過meta表查找表rowkey相對應(yīng)的Region和RegionServer信息,最后連接數(shù)據(jù)所在的RegionServer,查找到相應(yīng)的數(shù)據(jù)。
Hbase的這種結(jié)構(gòu),特別適合根據(jù)rowkey做單值查詢,不適合scan的場景,因?yàn)榇蟛糠諷can的情況基本上需要掃描所有數(shù)據(jù),性能會非常差。雖然也有擴(kuò)展的Hbase二級索引方案,但基本上都是通過協(xié)處理器,需要另外建立一份rowkey的對應(yīng)關(guān)系,Scan的時(shí)候先通過二級索引查找rowkey,然后在根據(jù)rowkey查找相應(yīng)的數(shù)據(jù)。
這種方式一定程度上能加快數(shù)據(jù)掃描,但那對于一些識別度不高的列,如性別這樣的字段,對應(yīng)的rowkey相當(dāng)之多,這樣的字段在查找二級索引時(shí)的作用很小,另外二級索引所帶來的IO性能的開銷都會隨之增加。而在需要聚合的場景,對于Hbase而言恰恰需要大量scan數(shù)據(jù),會非常影響性能。Hbase只有一個(gè)簡單rowkey的倒排索引,缺少列索引,所有的查詢和聚合只能依賴于rowkey,很難解決聚合的性能問題。
隨著Hbase的發(fā)展,基于Hbase做數(shù)據(jù)存儲包括Opentsdb和Kylin也隨之產(chǎn)生,例如Kylin也是一種預(yù)聚合方案,因其底層存儲使用Hbase,故筆者將其歸為Hbase系。在筆者看來,Opentsdb和Kylin的數(shù)據(jù)結(jié)構(gòu)極其相似,都是將各種維度值組合,結(jié)合時(shí)間戳拼成rowkey,利用字典的原理將維度值標(biāo)簽化,達(dá)到壓縮的目的。如此,可以滿足快速查詢數(shù)據(jù)的需要,但同時(shí)也會受限于Hbase索引,聚合需要大量scan,并不能提升數(shù)據(jù)聚合的速度。
為了避免查詢數(shù)據(jù)時(shí)的聚合,Kylin可以通過cube的方式定制數(shù)據(jù)結(jié)構(gòu),在數(shù)據(jù)接入時(shí)通過指定metric來提前聚合數(shù)據(jù)。這樣雖然在一定程度上解決了數(shù)據(jù)聚合慢的情況,但這是一種典型的空間換時(shí)間的方案,組合在維度多、或者有高基數(shù)維度的情況,數(shù)據(jù)膨脹會非常嚴(yán)重,筆者曾遇到存儲后的數(shù)據(jù)比原始數(shù)據(jù)大90倍的情況。另外,業(yè)務(wù)的變化會導(dǎo)致重建cube,難以靈活的滿足業(yè)務(wù)需要。
二、Dremel系
Parquet作為Dremel系的代表,相對Hbase的方案,Scan的性能更好,也避免了存儲索引和生成索引的開銷。但對于數(shù)據(jù)還原和聚合,相對直接使用正向索引來說成本會很高,而且以離線處理為主,很難提高數(shù)據(jù)寫的實(shí)時(shí)性。
Google的Dremel,其最早用于網(wǎng)頁文檔數(shù)據(jù)分析,所以設(shè)計(jì)為嵌套的數(shù)據(jù)結(jié)構(gòu),當(dāng)然它也可以用于扁平的二維表數(shù)據(jù)存儲。開源技術(shù)中,Parquet算是Dremel系的代表,各種查詢引擎(Hive/Impala/Drill)、計(jì)算框架甚至一些序列化結(jié)構(gòu)數(shù)據(jù)(如ProtoBuf)都對其進(jìn)行了支持,甚至Spark還專門針對Parquet的數(shù)據(jù)格式進(jìn)行了優(yōu)化,前途一片光明,本文主要結(jié)合Parquet來展開論述。
可以用下圖簡單表示Parquet的文件格式:

Parquet的數(shù)據(jù)水平切分為多個(gè)Row Group,Row Group為數(shù)據(jù)讀寫的緩存單元,每個(gè)Row Group包含各個(gè)的數(shù)據(jù)列(Column Chunk),數(shù)據(jù)列有若干Page,Page是壓縮和編碼的單元,其相應(yīng)存儲的信息包括元數(shù)據(jù)信息(PageHeader)、重復(fù)深度(Repetition Levels)、定義深度(Definition Levels)和列值(Values)信息。
Page實(shí)際有三種類型:數(shù)據(jù)Page、字典Page和索引Page。數(shù)據(jù)Page用于存儲當(dāng)前行組中該列的值,字典Page存儲該列值的編碼字典,每一個(gè)列塊中最多包含一個(gè)字典Page,索引Page目前還不支持,未來可能會引入Bloom Filter,能夠判斷列值是否存在,更有利于判斷搜索條件,提升查詢速度。
從Parquet的存儲結(jié)構(gòu)來看,Parquet沒有嚴(yán)格意義上的索引,在查詢的過程中需要直接對Row Group的列數(shù)據(jù)進(jìn)行掃描,有兩方面來保證查詢優(yōu)化,一個(gè)是映射下推(Project PushDown),另外一個(gè)是謂詞下推(Predicate PushDown)。
映射下推主要是利用列式存儲的優(yōu)勢,查詢數(shù)據(jù)時(shí)只需要掃描查詢中需要的列,由于每一列的所有值都是連續(xù)存儲的,所以分區(qū)取出每一列的所有值就可以實(shí)現(xiàn)TableScan算子,而避免掃描整個(gè)文件內(nèi)容。
謂詞下推在數(shù)據(jù)庫之類的查詢系統(tǒng)中最常用的優(yōu)化手段之一,通過將一些過濾條件盡可能的在最底層執(zhí)行,減少上層交互的數(shù)據(jù)量,從而提升性能。另外,針對謂詞下推Parquet做了更進(jìn)一步的優(yōu)化,優(yōu)化的方法是對每一個(gè)Row Group的每一個(gè)Column Chunk在存儲的時(shí)候都計(jì)算對應(yīng)的統(tǒng)計(jì)信息,包括該Column Chunk的最大值、最小值和空值個(gè)數(shù)。通過這些統(tǒng)計(jì)值和該列的過濾條件可以判斷該Row Group是否需要掃描。未來還會增加諸如Bloom Filter和Index等優(yōu)化數(shù)據(jù),更加有效的完成謂詞下推。
通過這兩方面的優(yōu)化,Parquet的查詢時(shí)掃描數(shù)據(jù)的性能能夠得到大幅度提升。那Parquet如果填充數(shù)據(jù)(不同的列拼成一行記錄)和聚合數(shù)據(jù)呢?
主要是使用了Striping/Assembly算法實(shí)現(xiàn)的,該算法的思想是將數(shù)據(jù)的值分為三部分:重復(fù)深度(Repetition Levels)、定義深度(Definition Levels)和列值(Values)。通過重復(fù)深度可以在讀取的時(shí)候結(jié)合Schema的定義可以知道需要在哪一層創(chuàng)建一個(gè)新的repeated節(jié)點(diǎn)(如第一層的的為0,表示是新記錄,否則則表示repeat的數(shù)據(jù)),然后通過定義深度知道該值的路徑上第幾層開始是未定義,從而還原出數(shù)據(jù)的嵌套結(jié)構(gòu),如此便能清楚的把一行數(shù)據(jù)還原出來。由于缺少行號對應(yīng)的列正向索引,沒有辦法直接尋址,單純的依賴于Striping/Assembly算法還原數(shù)據(jù)或者聚合處理,相對來說成本會高很多。
另外,Parquet的實(shí)時(shí)寫方面是硬傷,基于Parquet的方案基本上都是批量寫。一般情況,都是定期生成Parquet文件,所以數(shù)據(jù)延遲比較嚴(yán)重。為了提高數(shù)據(jù)的實(shí)時(shí)性,還需要其他解決方案來解決數(shù)據(jù)實(shí)時(shí)的查詢,Parquet只能作為歷史數(shù)據(jù)查詢的補(bǔ)充。
Parquet存儲是相對索引的存儲來說,是一種折中處理,沒有倒排索引,而是通過Row Group水平分割數(shù)據(jù),然后再根據(jù)Column垂直分割,保證數(shù)據(jù)IO不高,直接Scan數(shù)據(jù)進(jìn)行查詢,相對Hbase的方案,Scan的性能更好。這種方式,避免了存儲索引和生成索引的開銷,隨著索引Page的完善,相信查詢性能值得信賴。而對于數(shù)據(jù)還原和聚合也沒有利用正向索引,而是通過Striping/Assembly算法來解決,這種方式更好能夠很取巧的解決數(shù)據(jù)嵌套填充的問題, 但是相對直接使用正向索引來說成本會很高。
另外,由于是基于Row Group為讀寫的基本單元,屬于粗粒度的數(shù)據(jù)寫入,數(shù)據(jù)生成應(yīng)該還是以離線處理為主,很難提高數(shù)據(jù)寫的實(shí)時(shí)性,而引入其他的解決方案又會帶來存儲架構(gòu)的復(fù)雜性,維護(hù)成本都會相應(yīng)增加。
三、預(yù)聚合系
最近幾年,隨著OLAP場景的需要,預(yù)聚合的解決方案越來越多。其中比較典型的有Kylin、Druid和Pinot。預(yù)聚合的方案,筆者不想做過多介紹,其本身只是單純的為了滿足OLAP查詢的場景,需要指定預(yù)聚合的指標(biāo),在數(shù)據(jù)接入的時(shí)候根據(jù)指定的指標(biāo)進(jìn)行聚合運(yùn)算,數(shù)據(jù)在聚合的過程中會丟失metric對應(yīng)的列值信息。
筆者認(rèn)為,這種方式需要以有損數(shù)據(jù)為代價(jià),雖然能夠滿足短期的OLAP需求,但是對于數(shù)據(jù)存儲是非常不利的,會丟掉數(shù)據(jù)本身存在的潛在價(jià)值。另外,查詢的指標(biāo)也相對固定,沒有辦法靈活的自由定義所需的指標(biāo),只能查詢提前聚合好的指標(biāo)。
四、Lucene系
Lucene算是java中最先進(jìn)的開源全文檢索工具,基于它有兩個(gè)很不錯(cuò)的全文檢索產(chǎn)品ElasticSearch和Solr。Lucene經(jīng)過多年的發(fā)展,整個(gè)索引體系已經(jīng)非常完善,能夠滿足的的查詢場景遠(yuǎn)多于傳統(tǒng)的數(shù)據(jù)庫存儲,這都?xì)w功于其強(qiáng)大的索引。但對于日志、行為類時(shí)序數(shù)據(jù),所有的搜索請求都也必須搜索所有的分片,另外,對于聚合分析場景的支持也是軟肋。
Lucene中把一條數(shù)據(jù)對應(yīng)為一個(gè)Document,數(shù)據(jù)中的字段對應(yīng)Lucene的Field,F(xiàn)ield的信息可以拆分為多個(gè)Term,同時(shí)Term中會包含其所屬的Field信息,在Lucene中每一個(gè)Document都會分配一個(gè)行號。然后在數(shù)據(jù)接入時(shí)建立Term和行號的對應(yīng)關(guān)系,就能夠根據(jù)字段的信息快速的搜索出相應(yīng)的行號,而Term與行號的對應(yīng)關(guān)系我們稱之為字典。大部分時(shí)候查詢是多個(gè)條件的組合,于是Lucene引入了跳表的思想,來加快行號的求交和求并。字典和跳表就共同組成了Lucene的倒排索引。Lucene從4開始使用了FST的數(shù)據(jù)結(jié)構(gòu),即得到了很高的字典壓縮率,又加快了字典的檢索。
為了快速的還原數(shù)據(jù)信息和聚合數(shù)據(jù),Lucene還引入了列正向索引和行正向索引。列正向索引主要是行號和Term的對應(yīng)關(guān)系,行正向主要是行號和Document的對應(yīng)關(guān)系。這兩種索引都是可以根據(jù)需要配置使用,例如只有單純的查詢,只是用行正向索引就可以,為了實(shí)現(xiàn)數(shù)據(jù)的聚合則必須列正向索引。
有了這些索引后,就可以通過Term來查詢出行號,利用正向索引根據(jù)行號還原數(shù)據(jù)信息,或者對數(shù)據(jù)進(jìn)行聚合。
另外,為了滿足全文檢索的需求,Lucene還引入了分詞、詞向量、高亮以及打分的機(jī)制等等。
總的來看,Lucene的整個(gè)索引體系比較臃腫,其設(shè)計(jì)的根本還是搜索引擎的思想,滿足全文檢索的需求。
Lucene本身是單機(jī)版的,沒有辦法分布式,也就以為著其能處理的還是小數(shù)據(jù)量。ElasticSearch提供了Lucene的分布式處理的解決方案,其核心思想是將Lucene的索引分片。
在寫入場景中,對于同一個(gè)index的數(shù)據(jù),會按照設(shè)定的分片數(shù)分別建立分片索引,這些分片索引可能位于同一臺服務(wù)器,也可能不同。同時(shí),各分片索引還需要為自己對應(yīng)的副本進(jìn)行同步,直到副本寫入成功,一次寫入才算完整的完成。當(dāng)然,單個(gè)文檔的寫入請求只會涉及到一個(gè)分片的寫入。
搜索場景則大致是逆過程,接受請求的節(jié)點(diǎn)將請求分發(fā)至所有承擔(dān)該分片查詢請求的節(jié)點(diǎn),然后匯總查詢請求。這里值得注意的是,任意一個(gè)搜索請求均需要在該index的所有分片上執(zhí)行。
由于ElasticSearch是一個(gè)搜索框架,對于所有的搜索請求,都必須搜索所有的分片。對于一個(gè)針對內(nèi)容的搜索應(yīng)用來說,這顯然沒有什么問題,因?yàn)閷?yīng)的內(nèi)容會被存儲到哪一個(gè)分片往往是不可知的。然而對于日志、行為類數(shù)據(jù)則不然,因?yàn)楹芏鄷r(shí)候我們關(guān)注的是某一個(gè)特定時(shí)間段的數(shù)據(jù),這時(shí)如果我們可以針對性的搜索這一部分?jǐn)?shù)據(jù),那么搜索性能顯然會得到明顯的提升。
同時(shí),這類數(shù)據(jù)往往具有另一個(gè)非常重要的特征,即時(shí)效性。很多時(shí)候我們的需求往往是這樣的:對于最近一段時(shí)間的熱數(shù)據(jù),其查詢頻率往往要比失去時(shí)效的冷數(shù)據(jù)高得多,而ElasticSearch這樣不加區(qū)分的分片方式顯然不足以支持這樣的需求。
而另外一方面,ElasticSearch對于聚合分析場景的支持也是軟肋,典型的問題是,使用Hyperloglog這類求基數(shù)的聚合函數(shù)時(shí),非常容易發(fā)生oom。這固然跟這類聚合算法的內(nèi)存消耗相對高有關(guān)(事實(shí)上,hll在基數(shù)估計(jì)領(lǐng)域是以內(nèi)存消耗低著稱的,高是相對count,sum這類簡單聚合而言)。
五、Tindex
數(shù)果智能根據(jù)開源的方案自研了一套數(shù)據(jù)存儲的解決方案,該方案的索引層通過改造Lucene實(shí)現(xiàn),數(shù)據(jù)查詢和索引寫入框架通過擴(kuò)展Druid實(shí)現(xiàn)。既保證了數(shù)據(jù)的實(shí)時(shí)性和指標(biāo)自由定義的問題,又能滿足大數(shù)據(jù)量秒級查詢的需求,系統(tǒng)架構(gòu)如下圖,基本實(shí)現(xiàn)了文章開頭提出的幾個(gè)目標(biāo)。

Tindex主要涉及的幾個(gè)組件
Tindex-Segment,負(fù)責(zé)文件存儲格式,包括數(shù)據(jù)的索引和存儲,查詢優(yōu)化,以及段內(nèi)數(shù)據(jù)搜索與實(shí)時(shí)聚合等。Tindex是基于Lucene的思想重構(gòu)實(shí)現(xiàn)的,由于Lucene索引內(nèi)容過于復(fù)雜,但是其索引的性能在開源方案中比較完善,在數(shù)據(jù)的壓縮和性能之間做了很好的平衡。我們通過改造,主要保留了其必要的索引信息,比原有的Lucene節(jié)省了更多的存儲空間,同時(shí)也加快了查詢速度。主要改進(jìn)有以下幾點(diǎn):
1、高效壓縮存儲格式
對于海量行為數(shù)據(jù)的存儲來說,存儲容量無疑是一個(gè)不容忽視的問題。對于使用索引的方案來說,索引后的數(shù)據(jù)容量通常相對原有數(shù)據(jù)會有一定程度的膨脹。針對這類情況,Tindex針對索引的不同部分,分別使用了不同形式的壓縮技術(shù),保障了能夠支持高效查詢的同時(shí)僅僅需要較少的容量。對于數(shù)據(jù)內(nèi)容部分,使用字典的方式編碼存儲,每條記錄僅僅存儲文檔編號。對于字典本身的存儲,使用了前綴壓縮的方式,從而降低高基數(shù)維度的空間消耗。實(shí)際情況下,使用 Tindex 壓縮后的數(shù)據(jù)占用的存儲容量僅僅為原始數(shù)據(jù)的1/5左右。
2、列式倒排和正向索引的存儲
由于實(shí)際使用中,往往需要同時(shí)支持搜索和聚合兩種場景,而這兩種方式對于索引結(jié)構(gòu)的需求是完全相反的。針對這兩種情況,Tindex結(jié)合了倒排索引和列正向索引這兩種不同類型的索引。對于倒排索引部分,使用字典和跳表等技術(shù),實(shí)現(xiàn)了數(shù)據(jù)的快速檢索,而對于正向部分,則通過高效的壓縮技術(shù),實(shí)現(xiàn)了對于海量行下指定列的快速讀取。同時(shí),根據(jù)不同的情況,可以選擇性的只建立其中一種索引(默認(rèn)情況對于每一列均會同時(shí)建兩種索引),從而節(jié)省大約一般的存儲空間和索引時(shí)間。
Tindex-Druid,負(fù)責(zé)分布式查詢引擎、指標(biāo)定義引擎、數(shù)據(jù)的實(shí)時(shí)導(dǎo)入、實(shí)時(shí)數(shù)據(jù)和元數(shù)據(jù)管理以及數(shù)據(jù)緩存。之所以選擇Druid是因?yàn)槲覀儼l(fā)現(xiàn)其框架擴(kuò)展性、查詢引擎設(shè)計(jì)的非常好,很多性能細(xì)節(jié)都考慮在內(nèi)。例如:
堆外內(nèi)存的復(fù)用,避免GC問題;
根據(jù)查詢數(shù)據(jù)的粒度,以Sequence的方式構(gòu)建小批量的數(shù)據(jù),內(nèi)存利用率更高;
查詢有bySegment級別的緩存,可以做到大范圍固定模式的查詢;
多種query,最大化提升查詢性能,例如topN、timeSeries等查詢等等。
框架可靈活的擴(kuò)展,也是我們考慮的一個(gè)很重要的元素,在我們重寫了索引后,Druid社區(qū)針對高基數(shù)維度的查詢上線了groupByV2,我們很快就完成了groupByV2也可見其框架非常靈活。
在我們看來,Druid的查詢引擎很強(qiáng)大,但是索引層還是針對OLAP查詢的場景,這就是我們選擇Druid框架進(jìn)行索引擴(kuò)展的根本原因。 另外其充分考慮分布式的穩(wěn)定性,HA策略,針對不同的機(jī)器設(shè)備情況和應(yīng)用場景,靈活的配置最大化利用硬件性能來滿足場景需要也是我們所看重的。
在開源的Druid版本上自研,繼承了Druid所有優(yōu)點(diǎn)的同時(shí),對查詢部分代碼全部重新實(shí)現(xiàn),從而在以下幾個(gè)方面做了較大改進(jìn):
1、去掉指標(biāo)預(yù)聚合,指標(biāo)可以在查詢時(shí)自由定義:
對于數(shù)據(jù)接入來說,不必區(qū)分維度和指標(biāo),只需要定義數(shù)據(jù)類型即可,數(shù)據(jù)使用原始數(shù)據(jù)的方式進(jìn)行存儲。當(dāng)需要聚合時(shí),在查詢時(shí)定義指標(biāo)即可。假設(shè)我們要接入一條包含數(shù)字的數(shù)據(jù),我們現(xiàn)在只需要定義一個(gè)float類型的普通維度。
2、支持多種類型:
不同于原生的Druid只支持string類型維度的情況,我們改進(jìn)后的版本可以支持string, int, long, float、時(shí)間等多種維度類型。在原生的Druid中,如果我們需要一個(gè)數(shù)值型的維度,那么我們只能通過string來實(shí)現(xiàn),這樣會帶來一個(gè)很大的問題,即基于范圍的過濾不能利用有序的倒排表,只能通過逐個(gè)比較來實(shí)現(xiàn)(因?yàn)槲覀儾荒馨炎址笮‘?dāng)成數(shù)值大小,這樣會導(dǎo)致這樣的結(jié)果‘12’ < ’2’),從而性能會非常差,因?yàn)閿?shù)值類型維度很容易出現(xiàn)高基維。對于改進(jìn)后的版本,這樣的問題就簡單多了,將維度定義為對應(yīng)的類型即可。
3、實(shí)現(xiàn)數(shù)據(jù)動態(tài)加載:
原有的Druid線上的數(shù)據(jù),需要在啟動時(shí),全部加載才可以提供查詢服務(wù)。我們通過改造,實(shí)現(xiàn)了LRU策略,啟動的時(shí)候只需要加載段的元數(shù)據(jù)信息和少量的段信息即可。一方面提升了服務(wù)的啟動時(shí)間,另外一方面,由于索引文件的讀取基本都是MMap,當(dāng)有大量數(shù)據(jù)段需要加載,在內(nèi)存不足的情況,會直接使用磁盤swap Cache換頁,嚴(yán)重影響查詢性能。數(shù)據(jù)動態(tài)加載的很好的避免了使用磁盤swap Cache換頁,查詢都盡量使用內(nèi)存,可以通過配置,最大限度的通過硬件環(huán)境提供最好的查詢環(huán)境。
HDFS,大數(shù)據(jù)發(fā)展這么多年,HDFS已經(jīng)成為PB級、ZB級甚至更多數(shù)據(jù)的分布式存儲標(biāo)準(zhǔn),很成熟了,所以數(shù)果也選用HDFS,不必重新造輪子。Tindex與HDFS可以完美結(jié)合,可以作為一個(gè)高壓縮、自帶索引的文件格式,兼容Hive,Spark的所有操作。
Kafka/MetaQ,消息隊(duì)列,目前Tindex支持kafka、MetaQ等消息隊(duì)列,由于Tindex對外擴(kuò)展接口都是基于SPI機(jī)制實(shí)現(xiàn),所以如有需要也可以擴(kuò)展支持更多的消息隊(duì)列。
Ecosystem Tools,負(fù)責(zé)Tindex的生態(tài)工具支持,目前主要支持Spark、Hive,計(jì)劃擴(kuò)展支持Impala、Drill等大數(shù)據(jù)查詢引擎。
支持冷數(shù)據(jù)下線,通過離線方式(spark/Hive)查詢,對于時(shí)序數(shù)據(jù)庫普遍存在的一個(gè)問題是,對于失去時(shí)效性的數(shù)據(jù),我們往往不希望它們繼續(xù)占據(jù)寶貴的查詢資源。然后我們往往需要在某些時(shí)候?qū)λ麄儾樵?。對于Tindex而言,可以通過將超過一定時(shí)間的數(shù)據(jù)定義為冷數(shù)據(jù),這樣對應(yīng)的索引數(shù)據(jù)會從查詢節(jié)點(diǎn)下線。當(dāng)我們需要再次查詢時(shí),只需要調(diào)用對應(yīng)的離線接口進(jìn)行查詢即可。
SQL Engine,負(fù)責(zé)SQL語義轉(zhuǎn)換及表達(dá)式定義。
Zookeeper,負(fù)責(zé)集群狀態(tài)管理。
未來還會持續(xù)優(yōu)化改造后的Lucene索引,來得到更高的查詢性能。優(yōu)化指標(biāo)聚合方式,包括:小批量的處理數(shù)據(jù),充分利用CPU向量化并行計(jì)算的能力;利用code compile避免聚合虛函數(shù)頻繁調(diào)用;與大數(shù)據(jù)生態(tài)對接的持續(xù)完善等等。
作者簡介
王勁,數(shù)果智能,創(chuàng)始人&CEO。
曾任酷狗音樂大數(shù)據(jù)技術(shù)負(fù)責(zé)人、大數(shù)據(jù)架構(gòu)師,負(fù)責(zé)酷狗大數(shù)據(jù)技術(shù)規(guī)劃、建設(shè)、應(yīng)用。
13年IT從業(yè)經(jīng)驗(yàn),2年分布式應(yīng)用開發(fā),1年移動互聯(lián)網(wǎng)廣告系統(tǒng)架構(gòu)設(shè)計(jì),5年大數(shù)據(jù)技術(shù)實(shí)踐經(jīng)驗(yàn),多年的團(tuán)隊(duì)管理經(jīng)驗(yàn),主要研究方向流式計(jì)算、大數(shù)據(jù)存儲計(jì)算、分布式存儲系統(tǒng)、NoSQL、搜索引擎等。