本篇主要是根據AnalyticDB的論文,來討論AnalyticDB出現的背景,各個模塊的設計,一些特性的解析。可能還會在一些點上還會穿插一些與當前業界開源實現的比對,希望能夠有一個更加深入的探討。OK,那我們開始吧。 AnalyticDB介紹與背景 要說AnalyticDB,那起碼得知道它是乾什麼 ...
目錄
本篇主要是根據AnalyticDB的論文,來討論AnalyticDB出現的背景,各個模塊的設計,一些特性的解析。可能還會在一些點上還會穿插一些與當前業界開源實現的比對,希望能夠有一個更加深入的探討。OK,那我們開始吧。
AnalyticDB介紹與背景
要說AnalyticDB,那起碼得知道它是乾什麼的。這裡直接貼下百度百科的介紹:
AnalyticDB是阿裡雲自主研發的一款實時分析資料庫,可以毫秒級針對千億級數據進行即時的多維分析透視。
簡單地說,就是實時OLAP型資料庫,它的對標產品是Apache Kylin,Apache Druid,Clickhouse這些。然後AnalyticDB的特點,包括高併發實時攝入數據,相容Mysql協議,無需預計算即可有的極快響應時間,多種數據源接入,大規模集群管理等。好吧,這幾個特點都很官方,不急,接下來會逐漸討論各個點。
然後介紹下AnalyticDB的背景。
首先先說說傳統的OLAP型數據倉庫,以往構建OLAP型數據倉庫通常都是採用離線模式,即在晚上設置定時任務將前一天的數據同步到數據倉庫中,第二天數據分析師或報表工具就可以根據數據產出分析結果。但這樣的問題是數據延遲太高了,商業瞬息萬變,可能今天線上出現了什麼訂單激增的情況,數據分析師卻要等明天才能進行分析,這誰受得了呀。所以近幾年的趨勢就是實時數倉,簡單說就是增加一個實時接收數據以供查詢的模塊,這也叫做lambda架構。如圖,就是用一個Batch層和一個Real-time層共同提供查詢結果。[1]
好像有點扯遠了,說回AnalyticDB,它就是在大背景下提出的,所以它的一個主要特性就是實時。然後由於它本身是雲原生的結構,也就是本身就是根植於阿裡雲上面的,面向的客戶更加廣泛,所以是有通用性的要求的。比如傳統企業都是使用Mysql,Postgresql等關係型資料庫,這些企業也沒有人力去搭建和維護Hadoop和Kylin,Druid這些集群。而Postgresql這類關係型資料庫可能會有對複雜結構對支持,比如json,vector等,所以AnalyticDB也提供了對這種複雜類型的支持。
在性能方面,AnalyticDB維持所有列的索引,用以快速檢索數據。在存儲方面,使用行-列混合存儲,使得AnalyticDB可以同時對OLAP分析和行級查詢快速響應。然後為了高併發的查詢和高吞吐的寫入,又提出了讀,寫分離。這幾個性能方面的特性,以及這些優化如何與實時查詢結合起來,在後面會詳細介紹。
總而言之,目前業界對海量數據的OLAP分析查詢方案無非兩種,通過預計算構建多維立方體,在查詢的時候直接讀取預計算好的數據做一些簡單的合併(因為分區存儲)然後返回給用戶。這種類型的代表是Kylin和Druid,它們的好處是比較簡單,OLAP分析查詢速度很快,缺點是不夠靈活,比如Kylin一點改動可能就要全部數據rebuild。
另一種是非預計算,充分利用各種資源(CPU,記憶體,列存儲,向量化執行),或是架構儘量優化(如AnalyticDB),來讓海量數據快速查詢得到結果。比較典型的代表是Clickhouse,查詢性能不賴,也相對靈活,但缺點是集群數據量沒法拓展到很大。
這兩種方案都有辦法這實時這個點上進行拓展,只是實現思路也不大一樣。第一種是添加一個流式層,OLAP查詢的時候分別查詢歷史數據和流式層數據然後合併返回。第二種則是用微批方式倒入數據倉庫中實現流式查詢。
整體上,AnalyticDB更加偏向於第二種非預計算的方式實現,不過在很多設計上還考慮了行級查詢的實現和性能,所以要比Clickhouse這種要複雜一些。下麵我們從幾個方面來討論它的實現。
AnalyticDB詳細解析
AnalyticDB是一個能夠在PB數據集上高併發,低延遲,實時分析查詢,並且能夠在2000+雲伺服器上運行的OLAP資料庫。在設計上有多個挑戰,需要兼顧多種查詢類型的性能要求。這裡的多種情境包括全表掃描分析,多表join的點查詢操作,多個列的多個篩選條件等等,而這些操作又難以優化。
第二個挑戰是要設計一個底層存儲,應對不同類型的查詢所需要的不同存儲結構。比如OLAP查詢需要列式存儲,而點查詢(行級查詢)需要行式存儲。如何將這兩種存儲結構(列式,行式)結合起來以供不同查詢類型使用,同時還需要考慮到複雜類型,json,vector,text等,這也是一個難點。
第三個是實時方面的,要如何做到每秒數百萬數據寫入吞吐的同時,呈現給用戶低延遲的查詢響應和數據延遲。以前的做法將讀寫操作交由同一進程處理,但這樣一來讀寫操作的性能是互斥的,即高吞吐的寫入會影響到查詢性能和數據延遲[2]。
為瞭解決上述挑戰,AnalyticDB引入以下特性:
- 高效的索引引擎
- 混合(列-行)存儲引擎
- 讀寫分離
- 高效檢索引擎
這些特性暫時就有個映像就好,後面會詳細對這部分闡述。
架構設計
要說這塊,我們先來看看整體的架構設計圖。
前面與說到,AnalyticDB是雲原生的,AnalyticDB主要依賴於兩個外部結構,任務管理與調度組件Fuxi,和分散式存儲系統Pangu,而這幾個組件又都是基於阿裡雲的Apsara(負責管理底層的物理主機並向上層提供服務)。
整體架構上看還是比較簡單的,主要就是對外提供JDBC/ODBC介面,內部由多個協調器(Coordinator)負責統一管理寫節點和讀節點(讀寫分離)。
- 協調器(Conrdinator):協調器負責接收JDBC/ODBC的讀寫請求,分發到不同的讀或寫節點。
- 寫節點(Write Node):負責處理寫請求並將數據寫入到Pangu中持久化。
- 寫節點(Read Node):負責處理查詢請求並返回。
在具體的處理流程中,Fuxi資源分配和非同步調度(類似yarn)。而數據計算則是使用管道的方式進行計算,如下圖:
圖中,數據按頁(Page)進行切分(Pangu的存儲特性),數據處理以管道的方式進行處理,且數據流轉的不同階段均在記憶體中執行。看這張圖其實有點像Spark的數據處理流程,當然AnalyticDB本身也是使用DAG模型進行數據處理。
不過按照論文中說的數據完全在記憶體中處理還是有點不現實,雖然這樣能極大提高處理的效率,但遇到數據量太大導致記憶體裝不下的情況,還是需要暫時落到磁碟上,就類似Spark有提供多種persist方案一樣。否則查詢的併發量勢必會受到一些影響,但這樣一來可能查詢響應又降低了,魚與熊掌不可兼得啊。
數據分區
在一開始創建表的時候,可以分配數據按照兩級分區進行存儲,這裡通過論文中的小例子闡述兩級分區的實現,如以下建表語句:
CREATE TABLE db_name.table_name (
id int,
city varchar,
dob date,
primary key (id)
)
PARTITION BY HASH KEY(id)
PARTITION NUM 50
SUBPARTITION BY LIST (dob)
SUBPARTITION OPTIONS (available_partition_num = 12);
第一級索引,可以讓數據按照指定列進行hash分區以及指定分區數,比如上述建表語句指定一級索引為id,分區數是50個。這樣可以讓數據根據id的hash值分佈到不同的50個分區中,這一列通常是使用高基數的列,諸如用戶Id等。
第二級索引(SUBPARTITION,可選)可以針對某個列指定最大分區數,用來對數據保留和回收,通常使用日期類型數據。比如如果指定按天進行分區,最大分區為12,那麼數據僅會保留12天內的數據。
讀寫分離和讀寫流程
大多數傳統的OLAP資料庫,都是使用一個線程負責處理用戶SQL的操作,不管是寫請求(Insert)還是讀請求(Select)。這在查詢和寫入的併發量都很高的情況下會出現資源爭用的情況,針對這種情況AnalyticDB提出讀寫分離的解決方案。寫節點負責寫,讀節點負責讀數據,兩種節點彼此分離,這樣就避免了高併發場景下讀寫資源互斥的情況。
寫節點主要是master和worker架構,由zookeeper進行協調管理。寫master節點負責分配一張表的分區給不同的寫worker節點。在一個SQL到達的時候,Coordinators會首先識別是讀還是寫SQL語句,若是寫,那麼會先發送到對應的寫worker節點,寫worker節點先將數據存到記憶體,定期以日誌的形式持久化到Pangu中形成Pangu日誌。當日誌一定規模的時候,才會構建真正的數據和全量索引。
而對於讀節點,同樣每個節點會被實現分配不同的分區。功能上,AnalyticDB有兩種讀模式,實時讀取(real-time read),寫入數據立即可讀,和延遲讀(boundedstaleness),即容忍一定時間的寫入數據延遲。延遲讀是預設採用的方式,雖然與一定數據延遲,但查詢響應更快,通常而言也足夠了。
而實時讀,那麼可以立即查詢到剛剛寫入的數據。之所以能這麼快,其中一個原因是讀節點會直接從寫節點中獲取更新數據,也就是說寫節點在某種程度上說充當了緩存。其他的OLAP資料庫的做法通常有兩種,一種是用一個segment專門存儲實時數據,OLAP查詢的時候,會掃描實時segment和離線數據,合併後返回用戶,比如最新的kylin streaming就是這樣實現。一種是微批導入數據到存儲引擎中,然後用以檢索,這樣的話寫入頻率(微批的間隔)會大大影響檢索性能。AnalyticDB的方式可以說是一種比較新穎的方式,藉助讀寫分離的架構和強大的索引能力(下麵介紹),可以實現實時寫入且低延遲檢索。
不過其實這會面臨一個問題,數據同步的一致性問題(讀寫節點數據不一致),AnalyticDB是怎麼做的呢?這裡也不賣關子,主要是使用一個版本號來處理。Coordinators在分發寫請求給寫節點,寫節點更新後會返回更新後的分區版本號給Coordinators。Coordinators分發讀請求給讀節點時,也會帶上這一個分區版本號,讀節點就會與自己緩存的版本號對比,發現自己小的話,就會去拉取寫節點的最新數據(寫節點有一定的緩存功能)。
可以發現,通過讀寫分離的機制,以及預先分配好讀/寫節點的數據分區(hash),能提高數據處理的並行度,並且減少數據計算產生的數據傳輸網路開銷,比如join的shuffle操作就不需要進行大規模的數據再分區。而後有能夠將兩種請求相互解耦,每種操作關心自身就可以,方便以後的拓展。
OK,到這裡系統的架構,數據分區,讀寫流程就差不多說完了,接下來再討論下它的其他特性。
其他特性介紹
混合(列-行)存儲引擎
先說下背景,OLAP查詢一般會有全表掃描操作,所以主流做法是使用列式存儲,因為列式存儲可以極大減少磁碟IO操作,提高提高全表掃描性能,但這種對點查詢(即行級別)查詢和更新等不甚友好。而如Mysql這種行級存儲,點查詢方便,但OLAP操作又會又額外更多開銷(數據壓縮比低)。許多主流系統的做法是,基本摒棄另一種功能,比如Mysql不適合做大規模OLAP查詢,Kylin,或者說hive這種不支持行級別更新(特殊情況下可以,但支持不好),Druid則更加極致,直接就不存明細了。
而AnalyticDB卻通過行-列混合存儲結構,不僅兼顧OLAP分析和點查詢,還實現了複雜類型的存儲(json,vector)。不過在介紹它的行-列混合存儲結前,先來看看流行的列式存儲結構,然後再引出AnalyticDB的行列混合。
我們以開源的列式存儲結構Parquet為例來看列式存儲是怎麼存儲數據的。
Parquet本身是hadoop底層使用的存儲引擎,其強大毋庸置疑。所謂列式存儲,可以簡單理解成就是將一整列數據壓縮打包,然後按順序存儲。
存儲中有三級結構:
- 行組(Row Group):按照行將數據物理上劃分為多個單元,每一個行組包含一定的行數。一個行組包含這個行組對應的區間內的所有列的列塊。
- 列塊(Column Chunk):在一個行組中每一列保存在一個列塊中,行組中的所有列連續的存儲在這個行組文件中。不同的列塊可能使用不同的演算法進行壓縮。一個列塊由多個頁組成。
- 頁(Page):每一個列塊劃分為多個頁,頁是壓縮和編碼的單元,對數據模型來說頁是透明的。在同一個列塊的不同頁可能使用不同的編碼方式[3]。
在最後是Footer模塊,這裡存儲的是數據的元數據信息,比如列名,列的類型。還有一些統計信息,min,max,用以提升部分檢索的效率。同時Parquet也支持複雜類型的存儲,說簡單點就是將複雜類型Map,List等轉換成schema樹,把樹的葉子節點當做列數據存儲。
簡單瞭解了列式存儲,我們再來看AnalyticDB的行-列混合存儲。
註意圖中左右兩部分分別是兩個文件,左邊的是元數據文件,存儲諸如欄位名,一些簡單的統計信息幫助過濾,這個文件比較小通常駐存在記憶體中。這部分內容和前面的Parquet的Footer存儲內容類似,這裡就不多介紹了。主要還是介紹下右邊部分,即數據存儲方式。
圖片右邊,數據以row group的形式存儲,每個row group中存儲固定數量的行。但是在row group中依舊採用列式存儲,即同一列的被存儲到一起,稱為Data bolck,所有的Data block按順序存儲(這點和列式存儲一樣)。而Data block是最小的操作單元(緩存,讀取等)。註意這裡不像Parquet那樣,每一個Data block再分多個Page。
看上去,它的存儲結構和Parquet是類似的,只是沒有再將Data block劃分成多個Page,這裡論文沒和Parquet對比,也沒論述很清楚。不過最主要的區別應該就是這裡了。為什麼Data block不需要再劃分?因為它沒那麼多數據呀,在Parquet里,一個row group的數據量是GB級別的,所以一個row group中的列需要再劃分。而AnalyticDB中,它的row group明顯是小數量級的,可能一個row group僅僅是MB級別的數據量。這一點細微的差別,使AnalyticDB在點查詢的時候就可以直接幾MB內獲取一行全部數據,而Parquet可能需要在1G內才能獲取一行數據。這也是為什麼AnalyticDB的叫做行-列混合存儲結構。
對比Parquet和AnalyticDB,它們的設計分歧可能是天生的,Hadoop適合存儲追加的數據,以及非結構化數據,它的場景更多是在大數據存儲和載入,所以不會考慮單行查詢的場景。而AnalyticDB要考慮各種檢索,所以設計上就會要差異。當然AnalyticDB這樣也不是沒有缺點,比如它在全表掃描性能會有所下降。
說完AnalyticDB的存儲結構,再來說說AnalyticDB如何存儲複雜類型數據。
複雜類型數據(json,vector)存儲有個難點,這種複雜類型的數據通常大小是不定的,而且往往會出乎意料的大。如果按照上面提到row group的方式,可能一個block entry會非常大,所以需要一種其他類型的存儲結構來存儲複雜類型數據。
具體的做法可以說借鑒了hadoop的存儲思路。既然複雜類型數據大小不一樣,可能大可能小,那就將數據統一用32KB大小的塊組織起來,稱為FBlock。一個複雜類型數據可能分散在多個FBlock中(超過32KB),多個FBlock按順序存儲。然後使用稀疏索引,方便快速查詢。這樣的設計無疑可以方便得將複雜數據進行存儲,同時通過稀疏索引又能在一定程度上保證檢索的速度。
最後再說說如何支持update和delete操作。
一般的列式存儲是不怎麼支持行級更新和刪除操作的,因為數據都是壓縮成二進位進行存儲,如果支持行級更新,那並你需要先解壓縮,整塊數據,然後刪除數據,再壓縮存儲。要是併發量一上來那簡直是災難。
那hbase的底層是hadoop,它是怎樣實現更新和刪除的呢?這是因為hbase使用LSM-tree,我之前也過一篇介紹這個東西,也興趣可以看看數據的存儲結構淺析LSM-Tree和B-tree。粗略說就是將更新和刪除操作都按key-value的形式追加到文件末尾,然後整個文件定期去重,只保留最新的key的數據,舊的key數據就被刪除了。檢索的時候如果也多個key,只會認最新的那個key的數據。當然具體細節要複雜得多。
AnalyticDB也是類似的思想,不過做了一些改變。它使用一個存儲在記憶體中的bit-set結構,記錄更新和刪除的數據id以及對應版本號。同時使用copy-on-write(寫時複製)技術提供多版本支持。更新和刪除操作都會改變版本號,然後查詢的時候會提供一個版本號去查找對應的更新和刪除信息,然後在查詢結果中和結果進行合併。這樣就實現了更新和刪除操作。
稍稍總結下AnalyticDB的存儲結構,行-列混合存儲的優勢確實是也的,它算是犧牲一部分OLAP查詢的性能,換取一些靈活性。而這樣的換取,使得它擁有快速行級檢索,更新刪除的能力,對AnalyticDB而言是值得的。
索引
索引可以說是一個資料庫系統中極為重要的優化設計。目前主流的索引,包括B+tree,倒排索引,稀疏索引等等,但它們都有各種局限,比如B+tree插入分裂代價太大,倒排索引只支持特定類型,有些索引雖然能提供快速檢索的能力但對寫入性能有負擔。那麼AnalyticDB的索引是怎樣實現的呢?
AnalyticDB重度使用倒排索引加速檢索效率,首先,AnalyticDB對每一列都建立一個倒排索引,索引的key是列的值,索引的值的列的行號。前面的存儲結構中可以看到,每個row group存儲的是固定的行數,所以可以快速檢索到對應的行。而針對不同的數據量特點,提供了bitmap和int array兩種結構存儲倒排索引,達到一定閾值的時候會做相應轉化。
而針對複雜類型數據(三種,json,full text,vector),還是通過倒排提供支持,只是針對不同類型做了不同的優化改動。
先說json,以往查詢json數據的做法,是要先讀取json然後解析再然後查詢,這樣效率很低。AnalyticDB採用空間換時間的思路,將json數據先解析,然後對每個列構建倒排索引(和單列倒排索引類似)。在查詢的時候就可以直接根據索引快速定位到對應的json。
full-text的索引方式應該是和ElasticSearch類似的,即詞到整個文檔的倒排索引,查詢時還會按TF/IDF評分將結果返回給用戶(ES也是這樣)。
第三種類型是vector類型數據,主要採用NNS(nearest neighbour search)方法來加速查詢(看名字和KNN演算法有點像),大意也是用類似計算臨近數據的方式加速檢索。
對於增量數據,由於數據是落盤到磁碟上才構建全量索引,索引增量數據和已經落盤的數據有檢索性能的區別,所以需要對增量數據額外構建索引來彌補這種差距。而AnalyticDB對增量數據構建的是排序索引。所謂排序索引,本質上是一個數組,存儲的是數據的id。具體原理比較難解釋清楚,可以理解為就是存儲排序後的數據的id。通過排序索引可以將全表檢索的複雜度從O(n)降低到O(log n),也是一種空間換時間的思路了。
說完AnalyticDB,我們來對比下其他索引結構。Apache Kylin就不必多說了,基本就是依托於Hbase的row-key索引機制,算是比較弱的索引機制。
和AnalyticDB比較像是應該算是Druid,也是倒排索引,不過是bitmap結構存儲的倒排索引,它的倒排索引是經過優化的,叫Roaring bitmap,可以規避存儲小數據時候的存儲空間問題。相比於AnalyticDB的大而全的索引,Druid可以說是小而美。只對維度數據存儲bitmap索引,並且是和數據一起存儲在文件中,而非AnalyticDB那樣數據和索引分開存。出現這樣的原因一個是場景上,Druid畢竟是面向OLAP查詢的,索引它只需要對維度索引構建就行。這樣的好處在於實現簡單,存儲也不會占用太多空間。而針對單一OLAP場景,其實這樣也已經足夠了。
小結
總而言之言而總之,AnalyticDB因為其是雲原生,底層存儲,資源調度等都是依托於阿裡雲的其他服務,所以它開源出來是不現實的(畢竟人家還靠這個賺錢),哪怕真的開源,使用者使用開源存儲和資源調度方案估計也難以做到它在阿裡雲生態上那麼好。
不過它的架構和一些特性還是很有借鑒意義的,比如讀寫分離,預先分區,還有行-列混合存儲,強大的索引機制,索引機制如何跟底層的存儲相互配合等,這些東西目前開源的一些系統可能還沒有或沒AnalyticDB那麼完善。一方面可能是因為這些東西實現起來後,配置上會比較複雜,阿裡雲的東西不怕複雜,因為後端對用戶是不可見的。要是開源系統搞得特別複雜,工程師們就不大會想用這些東西。畢竟為了一些可能用不著的性能提升,引入一個後續可能維護複雜的系統,是否值得也是需要權衡的。
總體上看,AnalyticDB還是走在了業界的前頭的,好像也通過了TPC-DS的全流程測試,算是未來可期。未來開源資料庫的方向會不會從分化走上AnalyticDB這種全面的道路呢?
以上~