講給普通人聽的分布式數據存儲
關系型數據庫到底有什么問題?
正如你們中的很多人可能已經知道的,關系型數據庫(RDB)技術自從1970年代就已經存在,直到1990年代末一直是結構化存儲的事實標準。RDB幾十年來很出色地支持了高度一致性事務的工作負載,并依然保持強勁。隨著時間的推移,該項古老的技術為應對客戶的需求獲得了新的能力,比如BLOB存儲、XML/文檔存儲、全文檢索、在數據庫中執行代碼、使用星形數據結構的數據倉庫、以及地理空間擴展。只要一切都能擠進關系型數據結構的定義中,并且適合于單機,就可以在關系型數據庫中實現。
然后,互聯網的商業化發生了,并且徹底改變了一切,使得關系型數據庫不再能夠滿足所有的存儲需求。相比于一致性,可用性、性能和擴展正在變得同樣重要——有時甚至更重要。
性能一直很重要,但是隨著互聯網商業化的出現,改變的是規模。事實證明,要達到規模化的性能,要求的技巧和技術是前互聯網時代無法接受的。關系型數據庫圍繞著ACID(原子性Atomicity、一致性Consistency、隔離性Isolation和持久性Durability)的概念而建立,實現ACID最簡單的方法就是把一切保持在單機上。因此,傳統的RDB規模化的方法是垂直擴展(scale up),用白話說,就是使用更大的機器。
哦-哦,我想我需要一臺更大的機器使用一臺更大的機器的解決方案一直很好,直到互聯網帶來的負載大到單機無法處理。這迫使工程師們想出巧妙的技術來克服單機的限制。有許多不同的方法,各有其優缺點:主—副、集群、表聯合與分區(table federation and partitioning)、水平分區(sharding,可以認為是分區的特例)。
導致數據存儲選項增加的另外一個因素是可用性。前互聯網時代的系統,其用戶通常是來自組織的內部,這就有可能在非工作時段設置有計劃的停機時間,甚至計劃外的宕機也只會造成有限的影響。商業化互聯網也改變了這一點:現在每個能夠訪問互聯網的人都是潛在用戶,所以計劃外的宕機會造成很可能更大的影響,而且互聯網的全球性導致很難確定非工作時段,并安排有計劃的停機。
我曾探討了冗余在實現高可用性中所起的作用。不過,當應用到數據存儲層時,冗余帶來了一系列新的有趣的挑戰。在數據庫層應用冗余最常用的方式是主/副配置。
這個看似簡單的設置,在與傳統的單機關系型數據庫比較時,有一個巨大的差異:我們現在有網絡隔離的多臺機器。當數據庫的寫操作發生時,我們現在要決定何時認為它完成了:只要保存到主數據庫,或者只要保存到副數據庫(或者甚至是n個副數據庫,如果我們想要獲得更高的可用性--欲知增加另一臺機器對整個可用性的影響,請參看本博客系列的第一部分)。如果我們決定保存到主數據庫就足夠了,在復制數據之前如果主數據庫失效,我們要承擔丟失數據的風險。如果我們決定等到數據復制完成,我們就要接受延遲的代價。在副數據庫宕機的罕見情況下,我們需要決定是繼續接受寫操作的請求,還是拒絕它。
因此,我們從一個默認一致性的世界,進入了一個一致性是一種選擇的世界。在這個世界里,我們可以選擇接受所謂的最終一致性,即,狀態在多個節點之間復制,但是并非每個節點都有整個狀態的完整視圖。在我們上面的示例配置中,如果我們選擇認為達到主數據庫就是寫操作完成(或者到達主數據庫和任一副數據庫,但不一定是兩個副數據庫),那么我們就是選擇了最終一致性。最終,因為每個寫操作會被復制到每個副數據庫。但是在任一時間點,如果我們查詢某一個副數據庫,我們無法保證它包含截止到那個時刻為止的所有寫操作。
讓我們試試新的CAP理論總而言之,當數據存儲被復制(也稱為分隔(partitioned))時,系統的狀態被分散。這意味著我們離開了舒適的ACID領域,進入CAP的美麗新世界。CAP理論是由加州伯克利分校的Eric Brewer博士在2000年提出的。它最簡單的形式是這樣的:一個分布式系統必須在一致性、可用性和分隔容忍度(Partition Tolerance)之間取舍,并且只能做到三者中的兩者。
CAP理論把關于數據存儲的討論擴展到超出ACID的范圍,激發了許多非關系型數據庫技術的誕生。在提出他的CAP理論的10年之后,Brewer博士發表了一份聲明,澄清他最初的“三選二”的觀點被極大地簡化,是為了引起討論,并有助于超越ACID。不過,這種極大的簡化,引發了無數的曲解和誤會。在對CAP更精細的解釋中,所有三個維度應當理解為范圍,而不是布爾值。此外,應當理解,分布式系統大部分時間工作在非分隔模式,在這種情況下,需要做出一致性和性能/延遲之間的折中。在分隔真的發生的罕見情況下,系統必須在一致性和可用性之間做出選擇。
聯系到我們之前的主/副例子,如果選擇認為只有當數據在所有地方被復制(也稱作同步復制)之后寫操作才算完成,我們就是以寫操作延遲為代價選擇了一致性。另一方面,如果選擇認為一旦數據保存到主數據庫中,就認為寫操作完成,并讓復制在后臺進行(也稱作異步復制),我們就是以犧牲一致性為代價選擇了性能。
當網絡分隔發生時,分布式系統進入特殊的分隔模式,在一致性和可用性之間取舍。回到我們的例子:多個副數據庫在失去與主數據庫的連接之后,可能仍然繼續提供查詢服務,就是以犧牲一致性為代價選擇了可用性。要么,我們可以選擇,主數據庫如果失去與副數據庫的連接,就應當停止接受寫操作的請求,因此就是以犧牲可用性為代價選擇了一致性。在商業化互聯網時代,選擇一致性通常意味著收入的損失,所以很多系統選擇可用性。在這種情況下,當系統恢復到正常狀態時,它可以進入恢復模式,所有積累的不一致性得到解決和復制。
趁我們還在談論恢復模式,值得說一說一種稱為主—主(或主動—主動)的分布式數據存儲配置。在這種設置中,寫操作可以發送到多個節點,然后再互相復制。在這樣的系統中,即使是正常的模式也變得復雜了。因為,如果對同一條數據的兩個更新在大致相同的時間發生在兩個不同的主節點上,要如何協調呢?不僅如此,如果這樣的系統不得不從一個分隔的狀態恢復,事情就變得更糟了。雖然有可能存在可行的主—主配置,而且也有一些產品使之更容易,我的建議是除非絕對必要,否則盡量避免。有很多方法可以實現性能和可用性的良好平衡,而不必需要負擔主—主配置的高復雜度性的成本。
許多現代數據存儲的常見模式提供的性能/規模和可用性良好搭配的一種常見方法,是結合分隔和復制形成一種配置(或者說是模式)。這有時被稱為分隔的副本集合(partitioned replica set)。
不論是Hadoop、Cassandra或者MongoDB集群,所有這些基本上都符合這種模式,許多AWS數據服務也是如此。讓我們了解一下分隔的副本集合的一些共同特征:
數據是跨多個節點(或者多個節點集群)分隔的(即,分開的)。沒有單一分區擁有所有的數據。單個寫操作只發送到一個分區。多個寫操作有可能發送到多個分區,因此應當彼此獨立。復雜的、事務性、多條記錄(因此可能涉及多分區)的寫操作應當避免,因為這樣可能影響整個系統。
單個分區能夠處理的最大數據量可能成為潛在的瓶頸。如果一個分區達到它的帶寬上限,增加更多的分區以及拆分橫跨其間的流量,有助于解決該問題。因此,可以通過增加更多的分區來擴展這種類型的系統。
一個分區的索引(key)用來分配各個分區的數據。你需要小心選擇分區的索引,這樣讓讀操作和寫操作盡可能平均“分布”在所有的分區。如果讀/寫操作發生聚集,這些操作可能超出某個分區的帶寬,進而影響整個系統的性能,而其它分區則并未充分利用。這被稱為“熱分區”問題。
數據在多臺主機之間復制。這可以是,每個分區是完全分開的副本集合,或者在同一組主機之上的多個副本集合。一條數據被復制的次數通常被稱為復制因子。
這樣的配置擁有內置的高可用性:數據被復制到多個主機。理論上,若干小于復制因子數量的主機發生故障,不會影響整個系統的可用性。
所有這些好處,以及內置的可擴展性和高可用性,伴隨著相應的代價:這不再是你的瑞士軍刀,單機的關系型數據庫管理系統(RDBMS)了。這是復雜的系統,有很多需要管理的可變動的部分和需要微調的參數。需要專業知識來設置、配置和維護這些系統。此外,需要監測和報警的基礎設施來確保它們的正常運作。你當然可以自己做,但不容易,你可能短時間無法搞定。
豐富的數據存儲,雖然引起一些選擇困難,但其實是好事。我們只需超越傳統的整個系統只有單個數據存儲的想法,接受系統中使用多種數據存儲、每個為它最適合的工作負載提供服務這樣的思維方式。例如,我們可以使用下面的組合:
高性能攝入隊列,來獲取輸入點擊流量
基于Hadoop的點擊流量處理系統
基于云的對象存儲,用來低成本、長期地存儲經過壓縮的日常點擊流量摘要
保存元數據的關系型數據庫,可供我們用于充實點擊流量的數據
用于分析的數據倉庫集群
用于自然語言查詢的搜索集群
上面所有這些都可以是某個單一子系統的組成部分,比如叫做網站分析平臺。
總結商業化互聯網帶來擴展和可用性的需求,而RDBMS這樣的瑞士軍刀再也無法滿足這樣的需求。
對數據存儲增加水平擴展和冗余加大了系統復雜度,使得ACID更加難以保證,迫使我們按照CAP理論考慮取舍,創造了許多優化和專業化的有趣機會。
在系統中使用多個數據存儲,每個為與其最適當的工作負載提供服務。
現代數據存儲是復雜的系統,要求特殊的知識和管理開銷。