也淺談下分佈式存儲要點

本篇文章謝絕轉載,歡迎轉發java

幾年以前,曾蚍蜉撼樹的想要寫一個兼容RDBMS和NoSQL的數據庫,結果僅實現了一個Raft協議,寫了一棵BTree,就放棄了。使用Golang寫這個算是比較簡單的了,但過程難以言訴,有點螞蟻撼大樹了。算法

而我的,因爲工做的關係,也已經有四五年沒有和SQL打交道了。最近重拾,感慨良多。sql

MySQL這種RDBMS,天生是存在分佈式缺陷的,在海量數據的今天,很容易就達到瓶頸。過去這麼多年,一點長進都沒有。因此常常的操做就是換存儲引擎、分庫分表、引入中間件,閹割功能。數據庫

分佈式存儲特徵

能讓你不忍割捨的,一個就是MySQL協議,你用慣了;一個就是事務,你怕丟數據。編程

幸運的是,大多數互聯網業務不須要強事務,甚至連MySQL協議都不須要。接下來咱們看一下要將一個傳統的MySQL改形成分佈式的存儲,是有多麼的困難。安全

CAP理論應該是人盡皆知的事情了,在此很少提。併發

單機上的任何數據都是不可信的,由於硬盤會壞,會斷電,會被挖光纜。因此通常經過冗餘多個副原本保證數據的安全。副本的另一個做用,就是提供額外的計算能力,好比某些請求,會落到副本上。副本越多,可用性越高框架

而加入副本之後,就涉及到數據的同步問題。即便是最快的局域網,也會存在延遲,更不用說機器性能差別引發的同步延遲。這就存在一個問題,讀副本的請求讀到的數據,可能不是最新的,這就是數據的一致性發生了改變。固然有些手段能保證數據的一致性,但 副本越多,延遲越大

副本的加入還會引入主從的問題。主節點死掉之後,要有副本節點頂上去,這個過程的協調須要時間,其間部分不可用。

而當一類數據足夠大(好比說某張表),在其上的操做已經很是耗時的狀況下,就須要對此類數據進行切割,將其分佈到多臺機器上。這個切割過程就是Sharding,經過必定規則的分片來減小單次查詢數據的規模,增長集羣容量。分佈式

當某些查詢涉及到多個分片,這個過程就比較緩慢了。協調節點須要與每一個節點進行溝通,而後聚合查詢的結果,分片數越多,時間越長高併發

通常,在一個維度上的分庫都會遇到上述問題,可怕的是你可能會有多個維度的需求。對於一些NoSQL來講,每個維度,都須要冗餘一份數據,這通常是膨脹性的。


集羣的規劃並非一成不變的,你的集羣可能會加入新的節點;也可能有節點由於事故離線;也可能由於分片維度的問題,數據發生了傾斜。當這種狀況發生,集羣間的數據會發生遷移,以便達到平衡。這個過程有些是自動的,也有些是手動進行觸發。這個過程也是最困難的:既要保證數據的增量遷移,又要保證集羣的正確服務。

若是你想要事務(不少狀況是你不懂技術的Leader決定),那就集中存儲,不要分片。事務是不少性能場景和擴展場景的萬惡之源,流量大了你會急着去掉它。

副本

針對一個分片的數據,只能有一個寫入的地方,這就是master,其餘副本都是從master複製數據。

副本可以增長讀操做的並行讀,但會讀到髒數據。若是你想要讀到的數據是一致的,能夠採用同步寫副本的方式,好比KAFKA的ack=-1,只有所有同步成功了,才認爲本次提交成功。

但若是你的副本太多,這個過程會很是的慢。你可能想要經過分配寫入和讀取的副本個數來協調寫入和讀取的效率,QuorumR+W>N就是一個權衡策略。

這個過程能夠簡單的用抽屜原理來解釋。

上面的這個過程比較簡單,因此須要有點複雜的壓下軸。一個名門就是Paxos,複雜的很,之前看了一個星期也沒所有搞懂 -.-。 ZAB協議是ZooKeeperPaxos協議的基礎上進行擴展而來的,說實話也沒看懂,並且ZK的源代碼也很是的... 惟一看得懂的就是Raft協議,這個是EtcdConsul的基礎,是簡化版的Paxos,目前來看是高效且可靠的。

副本是用來作HA的,因此master死了,要有副本頂上來。這個過程就涉及到master的選舉。

kafka,藉助zookeeper來進行主分區的選舉。而ES是使用Bully算法,經過選出ID最大的節點看成master。不管什麼方式,都是要從一堆機器中,找到一個惟一的master節點,並且在選舉的過程當中,都須要注意一個腦裂問題(也就是不當心找到倆了)。master選舉一般都是投票機制,因此最小組集羣的臺數通常都設置成n/2+1。 這也是爲何不少集羣推薦奇數臺的緣由!

cassandra採用了另一種協議來維護集羣的狀態,那就是gossip,是最終一致性的典範。

副本機制在傳統的DB上也工做的很好。好比MySQL經過binlog完成副本的同步;Postgresql採用WAL日誌完成同步。但涉及到主從的切換,尤爲是有多個從庫的狀況下,通常都不可以自動化執行。

分片

分片就是對資料的切割,也就是一套主從已經裝不下了。分片的邏輯能夠放在客戶端,好比驅動層的數據庫中間件,Memcache等;也能夠放在服務端,好比ES、Mongo等。

分片的信息組成了一組元數據,存放了切割的規則。這些信息能夠藉助外部的存儲好比KAFKA;也有的直接同步在集羣每一個節點的內存中,好比ES。比較流行的NoSQL主從信息最小維度通常都是分片,一個節點上同時會有master分片和其餘分片的副本。


分片的規則通常有下面幾種:

Round-Robin 資料輪流落進不一樣的機器,數據比較平均,適合弱相關性的數據存儲。壞處是聚合查詢可能會很是慢,擴容、縮容難。

Hash 使用某些信息的Hash進行尋路,客戶端依照一樣的規則能夠方便的找到服務端數據。問題與輪詢相似,數據過於分散且擴容、縮容難。Hash一樣適合弱相關的數據,並可經過一致性哈希來解決數據的遷移問題。

Range 根據範圍來分片數據,好比日期範圍。能夠將一類數據歸檔到特定的節點,以增長查詢速度。此類分片會遇到熱點問題,會冷落不少機器。

自定義 自定義一些分片規則。好比經過用戶的年齡,區域等進行切分。你須要維護大量的路由表,而後本身控制數據和訪問的傾斜問題。

嵌套 屬於自定義的一種,路由規則能夠嵌套。好比首先使用Range進行虛擬分片,而後再使用Hash進行實際分片。在實際操做中,這頗有用,須要客戶端和服務端的結合才能完成。

路由的元數據不能太多,不然它自己就是一個訪問瓶頸;也不可以太複雜,不然數據的去向將成爲謎底。分佈式系統的數據驗證和測試是困難的,緣由就在於此。


惋惜的是,使用用戶的年齡,和使用用戶的地域進行分片,數據的分佈徹底不一樣。增長了一個維度的查詢速度,會減慢另外一個維度的性能,這是不可避免的。切分字段的選擇很是重要,若是幾個維度都很必要,解決的方式就是冗餘---按照每一個切分維度,都寫一份數據。

大部分互聯網業務通常經過用戶ID便可找到用戶的全部相關信息,規劃一個分層的路由結構即能知足需求。但數據統計類的需求就困難的多,你看到的不少年度報告,多是算了個把月纔出來的。

通常組成結構

數據寫入簡單,由於是按條寫的。但數據的讀取就複雜多了,由於可能涉及到大量分片,尤爲是AGG查詢業務。通常會引入中間節點負責數據的聚合,由於大量的計算會影響master的穩定,這是不能忍受的。

經過區分節點的職責,能夠保證集羣的穩定。根據不一樣的須要,會有更多的協調節點被加入。

在作分佈式以前,先要確保在單機場景可以最優。除了一些緩衝區優化,還有索引。但分佈式是一直缺乏一個索引的,曾經想設計一種基於內存的分佈式索引,但仍是賺錢養家要緊。

存儲要有一個強大的查詢語法引擎,目前來看非SQL引擎莫屬。抽象成一棵巨大但語法樹,而後在其上編程。像Redis這樣簡單的文本協議,是一個特定領域的特例。

分佈式事務

ACID是強事務的單機RDBMS的特性。涉及到跨庫,會有二階段提交、三階段提交之類的分佈式事務處理。

數據庫的分佈式事務實現叫作XA,也是一種2PC,MySQL5.5版本開始已經支持這種協議。

2PC會嚴重影響性能,並非和高併發的場景,並且其實現複雜,犧牲了一部分可用性。


另外一種經常使用的方式就是TCC(補償事務)。TCC的本質是:對於每個操做,都須要一個與之對應的確認和撤銷操做。但惋惜的是,在確認和撤銷階段,也有必定機率發生問題,須要TCC的TCC;不少業務根本沒有相應的逆操做,好比刪除某些數據,TCC就無法玩了。

TCC須要大量編碼,適合在框架層統一處理。


還有一種思路是將分佈式事務合併成本地事務來處理。也就是一個事務包含一條消息+一堆數據庫操做,成功執行完畢後再設置消息的狀態,失敗後會重試。 此種方式將消息強制耦合到業務中,且消息系統自己的事務問題也是一個須要考慮的因素。


分佈式事務除了要寫多個分片的協調問題,還有併發讀寫某一個值的問題。

好比有不少請求同時在修改一個餘額。經常使用的方法就是加鎖,可是效率過低。咱們回憶一下java如何保證這種衝突。 對於讀遠大於小的操做,可使用CopyOnWrite這種方式優化;對於原子操做,可使用CompareAndSet的方式先比較再賦值。要想保證餘額的安全,使用後者是頗有必要的。

MVCC是行級別鎖的一種妥協,他用來保證一個值在某個事務中是一致的,避免了髒讀和幻讀,但並不能保證數據的安全,這點必定要注意。

最終一致性

舉個栗子:你的家庭資金共有500w,你私自借給好基友500萬。使出了洪荒之力在年末討回了借款,並追加了利息。在老婆查賬的時候,原封不動的展現給她看。這就是最終一致性。

我習慣性這樣描述:在可忍受的時間內,輕過程、重結果,達成一致便可。雖然回味起來心有餘悸。

在這種狀況下,不須要過多的使用分佈式事務來控制。你只管寫你的數據,不用管別人是否寫成功。咱們經過其餘的手段來保證數據的一致性。

一種方式是常見的定時任務,不斷的掃描最近生成的數據,進行補齊。若是程序實在沒法判斷,則寫入到異常表中人工介入。

另一種方式就是重放數據,將這個過程從新執行一遍,要求業務邏輯是可重入的(冪等)。若是依然有問題,仍是須要人工介入。

比較幸運的是,良好的設計下,這些異常情況產生的概率是比較小的,投入和產出會超出指望。採用了BASE的系統,選擇的是弱一致性,高度依賴業務監控組件來及時的發現問題。

這種思想已經被大多數研發所接受,除非你的老闆可忍受時間很短!

哦,BASE的全稱是: Basically Available(基本可用), Soft state(軟狀態), Eventually consistent(最終一致性)

總結

做爲研發人員,是不能對軟件有好惡傾向的,只有合適與不合適的區別。沒有精力去改進這些系統,只能經過不斷的取捨,組合它們的優勢。

Greenplum和ElasticSearch,在分佈式DB領域,是兩個典型實現,它們都以強大的分佈式能力著稱。

Greenplum表明了RDBMS是如何向分佈式發展的,固然它是創建在強大的Postgresql基礎上的。

ES是創建在Lucene上的全文檢索搜索引擎,但好像你們也拿它當數據庫使用。源碼是java的,有不少值得推敲的地方。

緩慢的I/O設備,再也沒法壓榨單機的性能,註定了要走向分佈式。但前路依然漫漫,看看五花八門的分佈式數據庫就知道了。

沒有誰,能一統江湖。

相關文章
相關標籤/搜索