【譯者介紹】
蔡延亮,北京大學計算機碩士畢業(yè),明略數(shù)據(jù)技術(shù)合伙人。專注于大數(shù)據(jù)解決方案的研發(fā)和實施,擁有豐富的大數(shù)據(jù)分析平臺建設(shè)實施經(jīng)驗。熟悉商務(wù)智能(BI)系統(tǒng)的設(shè)計、架構(gòu)和演進規(guī)劃,擅長其在電信運營商的應(yīng)用;在數(shù)據(jù)ETL處理、模型設(shè)計、數(shù)據(jù)備份、生命周期管理、安全管理等領(lǐng)域有豐富的實踐經(jīng)驗;熟悉數(shù)據(jù)挖掘、機器學習等分析算法和工程應(yīng)用;熟悉軟件項目管理。 導讀:One size does not fit all! 數(shù)據(jù)處理平臺已不集中于傳統(tǒng)關(guān)系型數(shù)據(jù)庫,各種其他平臺層出不窮,也各有其適用范圍。 從哪些角度去理解各種數(shù)據(jù)處理平臺的設(shè)計思想及發(fā)展演進呢?下面我們從幾個角度討論一下:
一、單機存儲引擎設(shè)計(數(shù)據(jù)的位置) 從某種意義上說,當我們處理數(shù)據(jù)的時候,實際上是在管理數(shù)據(jù)的位置,管理數(shù)據(jù)在CPU的位置,數(shù)據(jù)相對其他數(shù)據(jù)的位置。CPU特別適合處理順序性操作數(shù)據(jù)指令,這樣他可以進行數(shù)據(jù)預(yù)取。但是隨機讀取操作使得預(yù)取功能幾乎失效,好多預(yù)取到緩存、前端總線的數(shù)據(jù)都是無效的。 傳統(tǒng)意義上說,磁盤的存取性能要弱于內(nèi)存,但是要分隨機存取及順序存取不同的場景下討論。在流式順序處理場景,磁盤及SSD的讀取速度已經(jīng)超過內(nèi)存隨機讀取速度。 我們?nèi)绾伪M量實現(xiàn)數(shù)據(jù)的順序存取呢?讓我們設(shè)計一個很簡單的數(shù)據(jù)庫開始,存取一個文件。 1、數(shù)據(jù)存儲和更新 追加寫可以讓我們盡量保持順序存儲文件。但是當數(shù)據(jù)要進行更新的時候,有兩種選擇,一種是在數(shù)據(jù)原地進行更新操作,這樣我們就有了隨機IO操作。另一種是把更新都放到文件末尾,然后需要讀取更新數(shù)據(jù)的時候進行替換。 2、數(shù)據(jù)讀取 一下子讀取整個文件,也是很耗費時間的事情,例如數(shù)據(jù)庫中的全表掃描。當我們讀取文件中某一個字段時候,我們需要索引。索引的方式有多種,我們可以用一種簡單的固定數(shù)值大小的有序數(shù)組來做索引,數(shù)組里存的是當前數(shù)據(jù)在文件中的存儲偏移量。還有其他索引技術(shù),如hash索引,位圖索引等。 索引相當于在數(shù)據(jù)之上又加了一層樹狀結(jié)構(gòu),可以迅速的讀取數(shù)據(jù)。但是打破了我們前面講的數(shù)據(jù)的追加寫,這些數(shù)據(jù)都是根據(jù)索引隨機寫入的。在數(shù)據(jù)庫上建立索引的時候都會遇到這個問題,在傳統(tǒng)的機械式磁盤上,這個問題會造成1000倍的性能差異。 有三種方法可以解決上述問題: 1)把索引放到內(nèi)存中,可以隨機存儲和讀取,把數(shù)據(jù)順序存儲到硬盤上。MongoDB,Cassandra都是采取這種方式。這種方式有一個弊端是存儲的數(shù)據(jù)量受限于內(nèi)存的大小,數(shù)據(jù)量一大,索引也增大,數(shù)據(jù)就飽和了。 2)第二種方式是把大的索引結(jié)構(gòu),拆成很多小的索引來存儲。在內(nèi)存中批量進來的數(shù)據(jù),當積累到一個預(yù)定的量,就排序然后順序?qū)懙酱疟P上,本身就是一個小的索引,數(shù)據(jù)存儲完,最后加一塊小的全局索引數(shù)據(jù)即可。這樣讀取數(shù)據(jù)的時候,要遍歷一些小的索引,會有隨機讀取。本質(zhì)是用部分小的隨機讀換取了整體的數(shù)據(jù)順序存儲。我們通過在內(nèi)存中保存一個元索引或者Bloom filter來實現(xiàn)處理那些小索引的低延遲。 日志結(jié)構(gòu)的歸并樹(log structed merge tree, 簡稱LSM tree)是一種典型的實現(xiàn),有三個特征: a)一組小的、不變的索引集。 b)只能追加寫 ,合并重復(fù)的文件。 c)少量的內(nèi)存索引消耗換來讀取的性能提升。這是一種寫優(yōu)化索引結(jié)構(gòu)。 HBase、Cassandra、Bigtable都是通過這種比較小的內(nèi)存開銷來實現(xiàn)讀取和存儲的平衡 3)列式存儲或者面向列的存儲(暴力方式)。 純列式存儲和谷歌bigtable那種列式存儲還是有所不同的,大家最好分開來看,雖然占用了同一個名字。列式存儲很好理解,就是把數(shù)據(jù)按照列順序存儲到文件中,讀取的時候只讀需要的列。列式存儲需要保持每一列數(shù)據(jù)都有相同的順序,即行N在每一列都有相同的偏移。這很重要,因為同一查詢中可能要返回多個列的數(shù)據(jù),同時可能我們要對多列直接進行連接。每一列保持同樣的順序我們可以用非常簡單的循環(huán)實現(xiàn)上述操作,且都是高效的CPU和緩存操作。 列式存儲的缺點是更新數(shù)據(jù)的時候需要更新每一個列文件中的相應(yīng)數(shù)據(jù),一個常用的方法就是類似LSM那種批量內(nèi)存寫的方式。 當查詢只是返回某幾列數(shù)據(jù),列式存儲可以大規(guī)模減少磁盤IO。除此之外,列式存儲的數(shù)據(jù)往往屬于同一類型,可以進行高效的壓縮,一些低延遲,高壓縮率的掃描寬度、位填充算法都試用。即使對于未壓縮的數(shù)據(jù)流,同時可以進行針對其編碼格式的預(yù)取。 列式存儲尤其適用于大表掃描,求均值、最大最小值、分組等聚合查詢場景。 列式存儲天然的保持了一列中數(shù)據(jù)的順序性,方便兩列數(shù)據(jù)進行關(guān)聯(lián),而heap-file index結(jié)構(gòu)關(guān)聯(lián)時候,一份數(shù)據(jù)可以按順序讀取,則另一份數(shù)據(jù)就會有隨機讀取了。 典型優(yōu)勢總結(jié):
上面討論的數(shù)據(jù)順序存取的幾種方案,在很多數(shù)據(jù)處理平臺的最優(yōu)技術(shù)方案中大都有參考。 通過heap-file結(jié)構(gòu)把索引存儲在內(nèi)存,是很多NoSQL數(shù)據(jù)庫及一些關(guān)系型數(shù)據(jù)庫的首選,例如Riak,CouchBase和MongoDB,模型簡單并且運行良好。 要處理更大量的數(shù)據(jù),LSM技術(shù)應(yīng)用更為廣泛,提供了同時滿足高效存儲和讀取效率的基于磁盤的存取結(jié)構(gòu)。HBase、Cassandra、RocksDB, LevelDB,甚至MongoDB最新版也支持這種技術(shù)。 列式存儲在MPP數(shù)據(jù)庫里面應(yīng)用廣泛,例如RedShift、Vertica及hadoop上的Parquet等。這種結(jié)構(gòu)適合需要大表掃描的數(shù)據(jù)處理問題,數(shù)據(jù)聚合類操作(最大最小值)更是他的主戰(zhàn)場。 Kafka 通過追加式的文件或者預(yù)定義的offset集來存儲消息隊列。你來消費消息,或者重新消費消息都是很高效的順序IO操作。這和其他的消息中間件架構(gòu)上有所不同,JMS和AMQP都需要上面提到過的額外的索引來管理選擇器和session信息。他們最終性能表現(xiàn)像一個數(shù)據(jù)庫而非文件。 為滿足讀和寫不同業(yè)務(wù)場景的優(yōu)化,以上這些技術(shù)多少都有某些方面的折中,或者把問題簡化,或者需要硬件支持,作為一種拓展的方法。 二、分布式集群存儲設(shè)計(并行化) 把數(shù)據(jù)放到分布式集群中運算,有兩點最為重要:分區(qū)(partition)和副本(replication)。 分區(qū)又被稱為sharding,在隨機訪問和暴力掃描任務(wù)下都表現(xiàn)不錯。 通過hash函數(shù)把數(shù)據(jù)分布到多個機器上,很像單機上使用的hashtable,只不過這兒每一個數(shù)據(jù)桶都被放到了不同的機器上。 這樣可以通過hash函數(shù)直接去存儲數(shù)據(jù)的機器上把數(shù)據(jù)取出來,這種模式有很強的擴展性,也是唯一可以根據(jù)客戶端請求數(shù)線性擴展的模式。請求會被獨立分發(fā)到某一機器上單獨處理。 我們通過分區(qū)可以實現(xiàn)批量任務(wù)的并行化,例如聚合函數(shù)或者更復(fù)雜的聚類或者其他機器學習算法,我們通過廣播的方式在所有機器上使任務(wù)同時執(zhí)行。我們還可以運行分治策略來使得高計算的任務(wù)在一個更短的時間內(nèi)解決。 這種批處理系統(tǒng)在處理大型的計算問題時有不錯的效果,但只能提供有限并發(fā),,因為執(zhí)行任務(wù)時會非常消耗集群的資源。 所以分區(qū)方式在兩個極端情況非常簡單:
二級索引是指不是構(gòu)建在主鍵上的索引,意味著數(shù)據(jù)不會因為索引的值而進行分區(qū)。不能直接通過hash函數(shù)去路由到數(shù)據(jù)本身。我們必須把請求廣播到所有節(jié)點上,這樣會限制了并發(fā)性,每一個請求都會卷入所有的節(jié)點。 因此好多基于key-value的數(shù)據(jù)庫拒絕引入二級索引,雖然它很有價值,例如Hbase和Voldemort。 也有些數(shù)據(jù)庫系統(tǒng)包含它了,因為它有用,例如Cassandra、MongoDB、Riak等。重要的是我們要理解好他的效益及他對并發(fā)性所造成的影響。 解決上述并發(fā)性瓶頸的一個途徑是數(shù)據(jù)副本,例如異步從數(shù)據(jù)庫和Cassandra、MongoDB中的數(shù)據(jù)副本。 實際上副本數(shù)據(jù)可以是透明的(只是數(shù)據(jù)恢復(fù)時候使用)、只讀的(增加讀的并發(fā)性),可讀寫的(增加分區(qū)容錯性)。這些選擇都會對系統(tǒng)的一致性造成影響,這是CAP理論中的一個研究課題(也許CAP理論不像你想象中的那么簡單)。 這些對一致性的折中,給我們帶來一個值得思考的問題?一致性到底有什么用? 實現(xiàn)一致性的代價非常昂貴。在數(shù)據(jù)庫中是用串行化來保證ACID的。他的基本保證是所有操作都是順序排列的。這樣實現(xiàn)起來的代價非常昂貴,所以好多關(guān)系型數(shù)據(jù)庫也不把他當成默認選項。 所以說要想在包含分布式寫操作的系統(tǒng)上實現(xiàn)強一致性,如同墜入深淵。(補充說明,Consistency, 在ACID和CAP中同時出現(xiàn),但是意義不一樣,我這兒說的是在CAP中的定義:所有的節(jié)點在同一時間看到的是同樣的數(shù)據(jù)) 解決一致性問題的方案也很簡單,避免他。假如不能避免它把他隔離到盡可能少的寫入和盡可能少的機器上。 避免一致性問題其實很簡單,尤其是你的數(shù)據(jù)是一串不再改變的事實描述。web日志就是一個很好的例子,不用擔心一致性問題,因為日志存下來后就是不變的事實描述。 當然有些業(yè)務(wù)場景是必須要保證數(shù)據(jù)一致性的,例如銀行轉(zhuǎn)賬時候。有些業(yè)務(wù)場景感覺上是必須保持一致性的,但實際上不是,例如標記一個交易是否有潛在的欺詐,我們可以先把它更新到一個新的字段里面,另外再用一條單獨的記錄數(shù)據(jù)去關(guān)聯(lián)最開始的那個交易。 所以對一個數(shù)據(jù)平臺來說有效的方式是去避免或者孤立需要一致性的請求,一種孤立的方法是采取單一寫入者的策略,Datamic就是典型的例子。另一種物理隔離的方法就是去區(qū)分請求中可變和不可變的字段,分別查詢。 Bloom/CALM把這種理念走的更遠,默認的配置選項就是無序執(zhí)行的策略,只有在必要的時候才啟用順序執(zhí)行讀寫語句。 前面是我們必須考慮的一些點,現(xiàn)在思考如何把這些設(shè)計組裝在一起做成一個數(shù)據(jù)處理平臺? 三、架構(gòu) 1、命令查詢職責分離架構(gòu)(CQRS) 最常用的架構(gòu)就是用傳統(tǒng)關(guān)系型數(shù)據(jù)庫存取數(shù)據(jù),上層承接各種應(yīng)用。這種架構(gòu)會遇到一些瓶頸,比如當數(shù)據(jù)吞吐量大到一定程度,就會遇到消息傳遞、負載均衡、擴容、并發(fā)性能降低等問題。為保持ACID特性,擴容問題尤其嚴峻。 一種解決方案是CQRS(Command Query Responsibility Segregation),命令查詢職責分離)架構(gòu),該模式從業(yè)務(wù)上分離修改 (Command,增,刪,改,會對系統(tǒng)狀態(tài)進行修改)和查詢(Query,查,不會對系統(tǒng)狀態(tài)進行修改)的行為。從而使得邏輯更加清晰,便于對不同部分進行針對性的優(yōu)化。 還有一種簡單的方式是把讀和寫的請求進行分離,寫數(shù)據(jù)側(cè)進行寫優(yōu)化處理,類似于日志文件結(jié)構(gòu)。讀數(shù)據(jù)側(cè)進行讀優(yōu)化處理。比較代表性的實現(xiàn)如Oracle的GoldenGate和MongoDB的Replica Sets .
還有一些數(shù)據(jù)庫,采用增加一層引擎的方式來實現(xiàn)上述思想。Druid就是一個很典型的例子,他是一個開源的、分布式的、實時的、列式存儲的分析引擎。列式存儲特別適合需要加載大的數(shù)據(jù)塊,且數(shù)據(jù)塊分到多個文件中的場景。Druid把一些近線實時數(shù)據(jù)放到寫優(yōu)化的存儲中,然后隨著時間的推移逐步把這些數(shù)據(jù)遷移到讀優(yōu)化的存儲中。當Druid接收到請求,會同時把請求轉(zhuǎn)發(fā)給讀、寫優(yōu)化的存儲,然后把返回的查詢結(jié)果根據(jù)時間標記進行排序反饋給用戶。像Druid這 種類似的系統(tǒng),通過一層抽象實現(xiàn)了CQRS的優(yōu)點。
2、操作/分析橋(Operational/Analytic Bridge)架構(gòu) 另一種相似的處理方式是操作/分析橋(Operational/Analytic Bridge),讀和寫優(yōu)化視圖被事件流所區(qū)分,數(shù)據(jù)流的狀態(tài)是被永久保存的,所以異步視圖可以通過后來的更新被重組或增強。 這樣前端模塊可以提供同步的讀和寫,這樣可以簡單高效的讀取剛被寫入的數(shù)據(jù),或者保持復(fù)雜的ACID事物管理。 后端模塊利用異步性、狀態(tài)不變性、去擴展離線處理進程,具體方式可以采用副本、異化、或者完全使用不同的存儲引擎。信息橋,連接前端與后端,允許上層應(yīng)用使用訪問數(shù)據(jù)處理平臺的數(shù)據(jù)。 這種模試比較適合中級數(shù)量的部署,尤其是至少包含部分的、不可 避免的動態(tài)視圖請求。
3、批處理架構(gòu)(Hadoop) 如果我們的數(shù)據(jù)是一次寫入,多次讀,不在改變的場景,上面可以部署各種復(fù)雜的分析型應(yīng)用。采取批處理模式的hadoop無疑是這種平臺最廣用和出色的代表了。 Hadoop平臺提供快速的讀寫訪問,廉價的存儲,批處理流程,高吞吐信息流,和其他抽取、分析、處理數(shù)據(jù)的工具。 批處理平臺可以主動拉取或者被推進來多種數(shù)據(jù)源的數(shù)據(jù),將其存儲進HDFS,后續(xù)可以處理成多種優(yōu)化的數(shù)據(jù)格式。數(shù)據(jù)可以被壓縮,清洗,去結(jié)構(gòu)化,聚合,處理為一種讀優(yōu)化的格式例如Parquet,或者直接被加載到數(shù)據(jù)服務(wù)層或者數(shù)據(jù)集市。通過這些過程,數(shù)據(jù)可以被查詢或者處理。 這種結(jié)構(gòu)在大批量的、數(shù)據(jù)不再改變的場景表現(xiàn)良好,一般可以到100TB以上,這種結(jié)構(gòu)的進化是緩慢的,數(shù)據(jù)處理速度一般也是以小時為單位的。 4、lambda架構(gòu) 有時候我們并不想等待小時后才得到結(jié)果,這是該架構(gòu)的一個缺陷。一種解決方法就是加一個流處理層,就是常說的lambda架構(gòu)。 lambda架構(gòu)在批處理的架構(gòu)上增加了一個流處理層,如同在一個擁擠城鎮(zhèn)新建一條高架橋。流處理層可以用主流的Storm或者Samza實現(xiàn)。lambda架構(gòu)的本質(zhì)是可以快速的返回一個近似的結(jié)果,精確的結(jié)果在后續(xù)返回。 所以流處理旁路提供一個流處理窗口期內(nèi)最好的結(jié)果,可以先被上層應(yīng)用所使用,后續(xù)批處理流程計算出精確結(jié)果在覆蓋掉前面的近似結(jié)果。這種架構(gòu)是對精準度和反饋時間做了一個聰明的平衡,作為后續(xù)發(fā)展,Spark平臺同時提供了批處理和流處理模塊(雖然流處理實際上市用微型批處理來實現(xiàn)的)。這種架構(gòu)也可以滿足 100TB以上數(shù)據(jù)的處理。 這種架構(gòu)的另一種代表叫kappa架構(gòu),但是本文作者沒看中那種架構(gòu),覺得叫kappa屬于吃飽了撐的。 5、流式處理架構(gòu) 不像是批處理架構(gòu),把數(shù)據(jù)存儲到HDFS上,然后在上面執(zhí)行各種跑批任務(wù)。流處理架構(gòu)把數(shù)據(jù)存儲到可擴展的消息或者日志隊列,例如kafka,這樣數(shù)據(jù)就可以被實時的處理成三級視圖、索引, 被數(shù)據(jù)服務(wù)層或者數(shù)據(jù)集市供上層應(yīng)用使用。 這和去掉批處理層的lambda架構(gòu)很相似,在消息層可以存儲處理海量的數(shù)據(jù),有足夠強大的流處理引擎可以hold住這些數(shù)據(jù)處理進程。 流處理結(jié)構(gòu)可以用來解決“應(yīng)用集成”問題,這是個頭疼復(fù)雜的問題,IT傳統(tǒng)大佬:Oracle,Tibco,Informatica都曾經(jīng)試圖想解決,一些部分結(jié)果是有用的,但不是真的解決,始終在尋找一套真正可用的解決方案。 流式處理平臺提供了一種解決該問題的可能性,他繼承了O/A橋平臺的優(yōu)點:多樣化的異步存儲形式和重新計算視圖的能力,把一致性請求給隔離。系統(tǒng)保存的數(shù)據(jù)是日志的話,很天然的擁有不變性。Kafka可以保存高容量和吞吐量的歷史記錄,意味著可以重新計算數(shù)據(jù)狀態(tài),而不是持續(xù)的設(shè)置檢查點。 類似流處理架構(gòu)的工具還有Goldengate,用來向大型數(shù)據(jù)倉庫同步數(shù)據(jù),不過他在數(shù)據(jù)副本層缺乏高吞吐量支持,在數(shù)據(jù)模型管理層過于復(fù)雜。 四、小結(jié): 我們開始于數(shù)據(jù)的位置,用來讀寫數(shù)據(jù)的順序地址,從而說明了我們用到組件對該問題的折衷。我們討論了對一些組件的拓展,通過分區(qū)和副本構(gòu)建分布式的數(shù)據(jù)處理平臺。最后我們闡述了觀點:盡量在數(shù)據(jù)處理平臺中把一致性的請求隔離。 數(shù)據(jù)處理平臺自身也是一個動態(tài)調(diào)整變化的平臺,依據(jù)業(yè)務(wù)需求,會把寫優(yōu)化轉(zhuǎn)為讀優(yōu)化,把強一致性依賴轉(zhuǎn)為開放的流式、異步、不變的狀態(tài)。 有些東西我們必須留在思想中,順序的結(jié)構(gòu)化模式是一種,時序、分布式、異步是另一種。 我們要堅信:經(jīng)過認真的解決,這些問題都是可控的。
附(知識補充): 簡單介紹一下heap-file結(jié)構(gòu)(和鏈表結(jié)構(gòu)很相似):
下面是Heap file自有的一些特性:
頁面可以被分割在某個存儲體的不同的物理區(qū)域,也可以分布在不同的存儲體上,甚至是不同的網(wǎng)絡(luò)節(jié)點中。我們可以簡單假設(shè)每一個page都有一個唯一的地址標識符PageAddress,并且操作系統(tǒng)可以根據(jù)PageAddress為我們定位該Page。 一般情況下,使用page在其所在文件中的偏移量就可以表示了。 |
|