如何打敗CAP理論

一篇談使用讀寫分離方式實現如何打敗CAP定理文章,可以認爲是Event Sourcing的一個變種。

CAP定理認爲一致性 可用性和分區容錯性同時不能獲得,通常我們不能喪失分區容錯性,那麼你就只有在可用性和一致性之間選擇,這就催生了NoSQL運行。

一致性意味着你實現一個成功的寫以後,將來的讀到的總是寫的最新結果;可用性意味着你總是能對系統進行讀寫,在一個分區分佈式系統,你只能擁有他們其中一個。

兩者權衡時,如果一致性超過可用性,那麼如果數據庫不能用怎麼辦?你所做的是將寫緩衝起來後來再寫,但是所冒風險當這臺服務器當機緩衝就丟失,當一個客戶端認爲寫已經成功,但是實際在緩衝中沒有寫到數據庫,就會發生不一致性. 替代方案是你返回錯誤給這個客戶端,說數據庫不可用,try again,用戶使用這樣的產品感受是如何呢?

如果你選擇可用性高於一致性, 通過最終一致性實現,使用最終一致性情況下,你可能讀取到和你剛纔寫入的不是同一個數據,有時多個讀者讀取同樣的Key總是得到不同的結果,更新也許不可能傳播到所有副本,這樣你的一些副本更新了,另外的也許沒有更新,這就需要跟蹤歷史,使用vector clocks 或將更新融合的方式(稱爲 "read repair").

維護一個最終一致性的應用是一個非常沉重的認爲,read repair將受開發人員的粗心等錯誤影響,一旦read repair有問題,將引入數據庫不可逆轉的腐敗性。

這樣,逃避可用性系統不能使用有問題,最終一致性又帶來複雜性,又有什麼替代方案呢?

你不能逃避CAP,但是能夠隔離複雜性,將其不再影響你的大部分系統;CAP引起的複雜性其實來自於我們數據系統,數據系統的根本問題是:數據庫中保存的是可變數據,然後有一個增量算法在不斷更新這個數據狀態,這個交互過程本身帶來了複雜性。

CAP定理是數據系統相對機器出錯後的容錯級別,還有一種容錯方式:人工容錯,那是開發人員不夠完美,Bugs等被帶入系統產品,我們數據系統必須忍受有Bug的程序寫入壞數據,作者展示的一個能夠打敗CAP的系統也將展示如何達到更好的人工容錯。

作者認爲他的方案更加優雅 可擴展性和健壯性。

作者首先發問:什麼是數據系統,他認爲可以用下面公式簡單定義:
Query = Function(All Data)

所有數據系統都可以用這個公式表達,數據系統是回答關於數據集的問題,這些問題是查詢Queries, 查詢的都是數據,因此Query和Data是兩個重要概念。

數據有兩個重要屬性:首先數據是基於時間的,數據是表達一段時間內一個邏輯爲真的事實。另外一個屬性是數據本質上是不可變的,因爲和時間有關,我們是不能回到過去改變數據的真實性。

這兩個屬性就意味着:對數據你其實只有兩個主要的操作:讀取現有數據,並(隨着時間)添加更多新的數據,CRUD(增刪改查)稱爲CR(增讀)。

這樣,CRUD其實沒有U修改,因爲修改對不可變數據是不其作用的(非常類似DDD中值對象不可變,不能修改,只能更換)。

CRUD中也沒有刪除Delete,其實大部分刪除其實是一種創建新數據,如果Bob停止跟隨Mary,但是他們不能改變他曾經跟隨過他的事實,刪除那個他不跟隨她的數據,你會增加一個數據記錄,說他在某個時刻不再跟隨她了。

作者隨後解釋了他的這套數據定義和普通沒有什麼不同(banq認爲實際是從業務領域帶有OO概念或者說業務邏輯去理解了,對於我們理解了面向對象,事件和狀態以及與事實之間關係,這些定義非常容易理解和得到認同)。

下面是對Query查詢,查詢是一種計算功能,你可以通過查詢實現很多功能,聚合,join不同數據類型等等。查詢是對整個數據集的一種功能,當然很多查詢不需要整個數據集,僅僅需要一個子集,這也不影響查詢這個定義。

查詢可以看成不可變數據的讀,對於一個分佈式系統大數據,如果一個每次都是從頭開始查詢的響應時間又在允許的延遲內
(從頭查詢因爲有新數據加入),那麼是否可以認爲我們實際通過不可變數據和查詢避免了CAP定理?

當然CAP定理還會起作用,關鍵是不可變數據,這樣就避免了數據更新,那就不可能有那麼多數據片變成不一致,那就意味着沒有vector clocks, or read-repair,只有數據和數據上的查詢功能,你就不必面對最終一致性。

之前引起複雜性是增量更新和CAP定理,這兩個真的無法很好在一起工作,可變的值需要read=repair,通過拒絕增量更新,強迫不可變數據,從頭計算每次查詢,你能避免複雜性。

這個方案中挑戰性工作是每次都從頭計算的查詢,這種查詢是一種預計算的批處理查詢,所幸的是我們有Hadoop,它是進行批處理的最好工具。

使用Thrift和Protocol Buffers可以讓Hadoop處理結構化數據,Hadoop由兩個部分:分佈式文件系統HDF和批處理框架MapReduce,我們將數據不斷加入HDFS中,一種Append方式;而預先計算查詢依靠MapReduce,也有更易使用的工具: Cascalog, Cascading, and Pig

最好,你需要將預計算的結果索引,這樣結果能夠被應用很快訪問,有一個數據庫可以做到這點:ElephantDB and Voldemort read-only

這兩個是能夠爲查詢從Hadoop中將key/value數據導出,這些數據庫支持批量寫和隨機讀,但是不支持隨機寫,隨機寫是數據庫中最複雜的,通過不支持能夠實現更加簡單健壯,ElephantDB只有幾千行代碼。


案例:如果你正在建立一個通過跟蹤pageView實現的Web分析應用。你需要每隔一段時間查詢PageView的數值:



每個數據記錄包含一個page view. 這些數據都保存在HDFS文件中,每個小時通過URL來統計PageView,這作爲MapReduce jobs. 發出key是[URL, hour],每個value值死頁面訪問量,這些key/value數據被導出到ElephantDB數據庫中,這樣應用程序能夠更快地獲得[URL, hour]的值. 當應用系統需要知道一段時間內的pageView時,它會查詢那段時間內每個小時的PageView數值,然後將它們加在一起得到最後結果。

批處理可以計算有關任何數據的任何功能,這樣就可以解決大部分問題,更重要的是它簡單可擴展,你只要思考數據和功能,Hadoop爲你考慮並行處理。

關於人工容錯,因爲數據是不可變的,數據集只能append追加,即使有bug的應用程序寫入壞數據,也不會覆蓋好數據,這是因爲沒有更新update。

即使MVCC 和 HBase row versioning也不能永遠實現人工容錯,一旦數據庫影響到了行,舊數據已經丟失。

(banq注:不斷append追加的好像應該是事件這樣的數據,這樣新事件不會覆蓋舊事件,我們通過事件回放能夠找到某個時間段的數據。見Martin fowler的Evetn sourcingLMAX架構)

以上查詢是幾個小時前的預處理查詢,如何實現實時查詢呢?需要一個實時系統和前面提到的批處理系統並行運行:



實時系統可以使用依賴修改的 Riak 或 Cassandra, 這些都依賴於增量算法和狀態更新。

模擬Hadoop的實時計算是Storm,下面是這樣的一個結合並行系統:


Hadoop 和 ElephantDB預先計算幾個小時前的數據,最近幾個小時數據都在實時系統中計算。

雖然實時系統我們也使用了NoSQL,但是是否又回到了CAP定理的複雜性呢?非也,因爲數據只是最近幾個小時內的,當然,如果你在實時系統範了錯誤,也不可能完全丟失數據,因爲批處理系統會幫助你糾正。

這種實時系統使用Storm + Cassandra;批處理系統使用Hadoop + ElephantDB方式可以打敗CAP定理,因爲它隔離降低了CAP定理的複雜性原因。

作者以親身經歷說明這種方式的人工容錯性:作者也沒有什麼系統監視工具,一天醒來,發現 Cassandra已經超出空間,每個請求都超時出錯,這導致Storm當機,數據流被備份在消息隊列中,因爲消息發不出,一個消息 在那裏不停地重複試圖發出。(banq注:很顯然是一種事件消息隊列方式)

因爲有批處理系統,作者清空這個隊列中消息,重新部署Cassandra,批處理系統象順時針鍾一樣幾個小時內又恢復正常工作。無數據丟失和不正常查詢結果。

垃圾數據回收可以避免數據集隨着時間推移越來越大。

最後,作者總結了這種批處理/實時( batch/realtime)結合的架構的好處。

相關其他文章:
爲什麼要用Event Sourcing?

LMAX架構

閒話淘寶網和新浪微博架構

羅素摹狀詞理論與面向對象OO(討論數據與事實的關係,與時間有關的數據準確稱是狀態,事件是觸發狀態的因,因此事件與事實最接近)




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