基於內存的分佈式數據庫SnappyData(OLAP+OLTP)

轉自:https://blog.csdn.net/lmalds/article/details/79107024

    實時的OLTP+OLAP的HTAP場景的數據處理,優先保證低延遲的OLAP查詢。說到這裏,很容易讓人想到Google的F1、Spanner,開源領域的代表TiDB。TiDB是個分佈式的MySQL,對OLTP的支持很好,其有一個子項目叫做TiSpark,依賴Spark與TiKV做些OLAP的請求,但是這些複雜SQL執行的優先級(DistSQL API)是低於OLTP請求的,且當數據量大時(上億條+多表join),這些SQL執行的時間不是很理想。

    由於我們的需求是同時對流數據以及歷史數據做OLAP查詢,要求是快速的返回結果。Apache Flink等純流式處理框架處理的是實時的數據,如果融入歷史數據,那麼實現起來也不是很方便。最主要的是如果OLAP查詢的維度非常多,且不固定時,例如可以選擇商圈、城市、省份、用戶、時間等維度做聚合,那麼flink去處理的話, 會發現key的選擇很多,實現起來既麻煩也費時。如果選擇druid或者kylin建立cube,那麼由於我們的數據還會有些OLTP的操作,同時實時性也較差,因此也不太適合。

    因此我們注意到一個完全基於內存的分佈式數據庫(同步或異步寫到磁盤):SnappyData,其是一個行、列混和的內存分佈式數據庫,內部由GemFire(12306的商業版)+Spark SQL(支持列存可壓縮)實現,既支持OLTP,也支持複雜的OLAP請求,且效率很高。

    上邊說了來龍去脈,下面開始針對SnappyData發表的論文,對其進行簡單的介紹。

二、SnappyData介紹

    在互聯網時代,許多場景同時要求事務型操作、分析型操作以及流處理。企業爲了應對這些需求,通常搭建各自用途的平臺來分別處理OLTP類的關係型數據庫,以及OLAP的數據倉庫和Streaming流處理框架。在實現OLAP的過程中,已經有很多SQL On Hadoop的技術方案來實現OLAP的查詢了,例如Hive、Impala、Kudu、Spark SQL等。這些系統的一大特點就是數據不可變,例如Spark RDD以及Hive中的數據,雖然各自在批處理的優化上做了很多的努力,但是還是缺乏對事務的ACID的支持。

    流處理框架倒是可以支持流數據的處理,但是如果要關聯大量的歷史數據進行處理,顯然效率也是較低的,且支持複雜的查詢也比較困難。

    那麼爲了支持這種混合負載的業務,通常公司都會進行大量的工作,既費時也費力,且效率較低。這些異構的系統雖然可以實現不同的需求,但是卻有以下一些缺點:

1、複雜度和總成本增加:需要引入Hive、Spark、Flink、KV,Kylin、Druid等,且要求開發人員具備這些能力並進行運維。
2、性能低下:一份數據要在不同的系統中存儲和轉換,例如RDBMS中一套、Hive中一套、Druid中一套,數據還得經過Spark、Flink轉換處理。
3、浪費資源:依賴的系統越多,使用的資源也就越多,這個是顯而易見的。
4、一致性的挑戰:分佈式系統事務的一致性實現困難,且當流處理失敗時,雖然檢查點可恢復但是有可能重複寫入外部存儲中(sink的exatcly-once沒法保證)。
    因此,我們目標就是在單一的集羣中同時提供流式注入、事務型處理以及分析型處理。在其他類似的解決方案相比,它具有更好的性能、更低的複雜度和更少的資源。當然也是很有挑戰的,例如列存適合分析,行存則適合事務更新,而流式則適合增量處理;同時對於不同場景,其HA的期望值也不同。

    我們的方法是將ApacheSpark作爲計算引擎與Apache GemFire無縫集成,作爲內存事務存儲。 通過利用這兩個開源框架的互補功能,SnappyData同時實現了這3種需求。同時,SnapppyData的商業版還通過概率的預算提供對超大歷史數據查詢時近似精確的結果。

    下面我們看看SnappyData的具體實現。

三、方法與挑戰

    爲了支持混合型的工作,SnappyData設計成了Spark+GemFire的組合。

    Spark的RDD很高效,但是其畢竟只是個計算引擎,而並不是存儲引擎;同時其要求數據是不可變的。

    GemFire(也叫Geode),是一個面向行的分佈式存儲,可以實現分佈式事務的一致性,同時支持點的更新以及批量更新,數據保存在內存中,也可以根據append-log持續的寫入到磁盤中。

    SnappyData則對兩者進行了完美的結合,用Spark作爲編程模型,數據的可變性和HA的需求則是使用GemFire的複製技術和細粒度的更新技術實現。

    當然在兩者融合上也有些挑戰,最典型的需求就是擴展Spark,要其利用GemFire的鎖服務以及可變性的操作,對數據進行點查詢,點更新以及複雜結構上的insert操作。最後就是SnappyData要完全兼容Spark。

四、SnappyData架構

    說到這,相信大家對SnappyData能幹什麼有個大體的瞭解,採用與Spark RDD一樣的列存,同時根據GemFire支持行存,以及兩者互補,在行、列存儲上進行點查、點更新以及批量寫入和更新。那麼其架構到底是什麼樣呢?


    從圖中可以看到,SnappyData的存儲主要在內存中,同時支持行存與列存,以及預測型的存儲。列存是來源於Spark的RDD,行存則擴展了GemFire的表且支持行存上建索引。而且SnappyData還有個AQP的概念,即以近似準確的結果來快速的響應對超大歷史數據的查詢,不過這個功能只能用於商業版上。

    SnappyData支持2種編程模型:SQL和Spark API。因此SnappyData是一個SQL數據庫,其可以使用Spark SQL進行開發。SnappyData的副本一致性和點更新則依賴GemFire的P2P對等網絡,事務支持則是依賴GemFire中通過Paxos實現的2階段提交實現。

五、混合存儲模型

    SnappyData中的表是可以分片的,也可以在每個節點的內存中複製。

    行表會佔用較大的內存空間,但是適合較小的表,且可以創建索引。列表也可以被更新,且做了壓縮,減少了對內存的壓力。那麼SnappyData是如何對列存進行更新的呢?其使用了一個delta row buffer的區域,當記錄被寫入列表時,首先會進入delta row buffer中,它是一個在內存中與更新的列表有相同分區策略的行存,這個行存由混合隊列支持,可以週期性的寫入到列存中並清空自己,當連續的對同一記錄進行更新操作時,只會把最終的狀態寫入列存中,即合併的含義,而不是一次一次的寫入。同時,delta row buffer使用了copy-on-write的語義來保證併發更新時的數據一致性。

    SnappyData中將GemFire的表進行分區或複製到不同的節點上,根據分區數據被放到不同的buckets中,因此訪問數據是並行進行的。這個與Spark讀取外部parquet或Json數據很像,但是SnappyData內部做了優化,例如每個分區本身就是列式存儲,避免了數據的複製和序列化開銷,同時允許表與表之間的存儲本地化,即colocate相關聯的數據,例如一個事實表可以根據分區鍵colocate其父表配置或者維度信息表,這樣在做join時可以避免shuffle和跨節點的數據傳輸,效率非常高。


    這裏的colocate的作用,是爲了在分佈式join時,避免分佈式鎖、shuffle的網絡開銷。通過colocate操作,將分佈式的join變成了本地join,並剪枝了不必要的分區。個人覺得這個技術或者思路,也許會應用到未來很多的分佈式join的場景中。除了colocate外,還有一種方法是將一個表的數據進行replicated操作,即複製到每個server的內存中一份,也可以避免分佈式join,但是其要求是個很小的表,否則內存壓力太大了。

六、混合集羣管理

    SnappyData作爲一個存儲和計算引擎,必須要靠高併發、高可用以及數據的一致性。

    爲了快速的檢測失敗,SnappyData依賴於UDP的neighbor ping和TCP的ack超時機制,而一致性則依賴GemFire內部的機制。這裏的具體實現忽略。

七、總結

    SnappyData的性能有很多benchmark,例如早期的測試如下:


    總結起來,SnappyData = stream寫入+ OLTP + OLAP。由於其結合了GemFire與Spark,使得其可以在列存上進行更新;同時由於其數據既存在於內存又可刷到磁盤,因此全內存的計算使得其速度很快;最後其colocate的設置使得多表join的性能很高。綜上所屬,如果你的業務對OLAP類型的延時要求很低,同時要能夠查詢實時的數據,那麼SnappyData是個非常不錯的選擇。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章