本博客在http://doc001.com/同步更新。
本文主要內容翻譯自MySQL開發者Ulf Wendel在PHP Submmit 2013上所做的報告「Scaling database to million of nodes」。翻譯過程中沒有全盤照搬原PPT,按照自己的理解進行了部分改寫。水平有限,如有錯誤和疏漏,歡迎指正。
本文是系列的第四篇,本系列所有文章如下:
- 百萬節點數據庫擴展之道(5): NoSQL再討論
另外一些概念
組通信系統(group communication system)
設想一下,你被要求為MySQL設計一個新的副本系統。這個副本系統里有一組MySQL服務器,你會選擇何種通信方式?很顯然,XML-RPC、Apache、HTTP、PHP這樣的技術棧肯定會很慢。于是,你不得不直接處理網絡協議。你將會看到,這個處理并不是很繁瑣。除了通信問題外,另外一個重要的問題是如何確定組的成員。
組通信系統能夠極大地簡化上述的任務。它能夠提供不同保障的簡單、可理解的消息傳遞方法。
組通信系統提供以下功能:
- 一組節點間的消息交換
- 知曉哪個節點在組內
- 對網絡和路由細節進行了抽象
- 提供不同保障的通信原語(communication primitive)
虛同步(virtual synchrony)
虛同步將多播(multicast)消息和組的概念融合到一起。在虛同步中,一個消息要么傳遞給所有的組成員,要么一個都不傳遞。在消息被多播之前,組內的每一成員都同意它們是組的一部分,即形成一個組視圖(group view)。如果一個節點想加入或離開組,必須多播一個"視圖改變"(view change)消息。
例如,組G1={P1,P2,P3}內多播了三個消息M1、M2、M3。節點P4想加入到組中,于是多播了一個"視圖改變"消息.而此時M3還在傳遞過程中,虛同步要求要么M3在視圖改變生效前傳遞到G1的所有成員,要么一個都沒傳遞到。
一個多播消息產生后,只有一種情況下可以不被投遞成功,那就是發送節點失效。繼續上面的例子,組G2={P1,P2,P3,P4}多播了消息M5、M6、M7。P4剛將消息M7傳遞給{P3},然而,很不幸該節點崩潰了。P4的崩潰被P1注意到了,觸發了一個“視圖改變”。因為需同步要求消息被傳遞給所有組成員,因此,P3將M7丟棄掉,然后視圖改變才能生效。
虛同步提供可靠多播。可靠性可以由OSI模型中較高層次的協議提供,如TCP。ISIS,一個早期的虛同步實現框架,使用了TCP協議的點對點連接來實現可靠服務。TCP連接能夠自動處理網絡錯誤,保證消息按序到達。但是,對于多個并發的TCP連接,消息的順序無法得到保證,不同連接消息的排序只能在應用層實現。
原子廣播(atomic broadcast)將虛同步的所有消息進行全序排序。當虛同步在80年代中期被引入的時候,實際上明確允許其它的消息排序方法。例如,應該能夠支持有消息交換概念的分布式應用,利用該概念,可以改變消息發送的順序以提高性能。如果事件在不同的節點上以不同的順序執行,那么系統就不能稱之為同步了——應該稱之為虛擬同步。
上面這坨的邏輯實在沒搞清楚!翻譯也感覺不到位,附上原文:
Atomic broadcast means Virtual Synchrony used with total-order message ordering. When Virtual Synchrony was introduced back in the mid 80s, it was explicitly designed to allow other message orderings. For example, it should be able to support distributed applications that have a notion of finding messages that commute, and thus may be applied in an order different from the order sent to improve performace. If events are applied in different order on different processes, the system cannot be called synchronous any more – the inventors called it virtually synchronous.
CAP再討論
腦裂
集群中孤立的節點或數據庫副本失效的情況已經被討論過了。現在考慮這種情況:集群的一半節點與另一半節點失去了連接,哪半個集群會生存下來?答案通常是實現相關的。例如,ISIS指定了一條規則,新的組視圖不應該少于n/2+1個成員才能生效,其中n是當前組成員的數量。因此,在這個例子中,這兩個半數節點的集群都不會生存下來。
CAP中隱藏的D
CAP中其實就隱含了ACID中的D(持久化)。
CAP中的C要求所有的節點都獲得全部的更新。正如我們看到的一樣,事務更新必須序列化以達到節點間的一致性。因此,節點必須以定義好的順序獲得所有更新。進一步,節點必須從來不會丟失任何一個更新。那么,更新必須被持久化!
從D到A
CAP理論說我們不可能同時實現一致性、可用性和分區容忍性。為了實現分區容忍性和一致性,我們需要法定人數協議,這就是CP系統。選舉人數協議會使系統變得很慢,因為我們不能只從一個節點讀取消息,取而代之,我們必須從很多個節點讀取消息。這其實就是CAP中A的終極含義:數據最終會變得一致,只是整個過程很慢...
CP系統如果使用選舉人數協議,ACID中的A就被違反了,因為并非所有的節點都應用了更新,而A,原子性,意味著“要么都做,要么都不做”。但是,如果更新是持久化的,這個更新永遠不會被“忘記”,它總能被看到,我們得到了A。因此,D是A的前置條件。
C不是一個障礙
CAP中的C對于實現ACID來說也不是一個阻礙。
一致性意味著我們不得不在所有的節點上按照確定的順序應用更新。這很容易通過主副本方案做到。在該方案中,所有的更新都會被路由到主副本,主副本確定更新的順序,把更新發送給其它的副本。我們只需要在主副本和副本之間建立FIFO消息通道即可(例如TCP連接),這不會對延時造成負面影響。
在更新時需要鎖嗎?不需要!首先,我們有主副本,主副本對消息的排序能夠處理寫沖突;其次,我們可以引入多版本并發控制(multi version concurrency control,MVCC)消除讀寫沖突。
因此,ACI都不是問題。我們真正需要的是D!
避免慢的讀選舉人
如果我們能夠立刻移除失效節點,那么就不會受到慢讀選舉人的影響,失效節點也不會導致不一致的過期數據。
如果一個節點組里所有的節點都是最新、一致的,那么讀操作可以由任何一個成員服務。讀速度將比選舉人讀快很多。虛同步做到了這一點。
視圖改變
如果一個虛同步組的成員認為一個節點失效了,就會進行一次排除失效節點的投票,這個過程就是「視圖改變」。更新操作必須等待到「視圖改變」過程結束才能進行。BASE理論中也有類似的等待機制,BASE需要等待寫選舉人達成一致后,才能進行其它更新操作。無論在虛同步還是在BASE中,等待操作都是昂貴的。
FLP不可能理論告訴我們在異步網絡中無法區分「慢」節點和「失效」節點。實際系統采取的做法是設置一個超時時間,超過超時時間未收到應答就認為節點失效了。這個超時時間決定了更新操作被禁止的最大時長。禁止期間的更新操作可以先暫時緩存起來。注意,此時系統仍然是一致的,只不過,我們在一致狀態下等待操作結果。
D的成本
持久化的成本取決于網絡協議:
- 更新不能丟失,它們必須是持久的
- 強迫組通信系統在不可靠的網絡協議上使用Paxos
- 早期的ISIS^2在800節點、千兆網絡上的數據顯示更新操作的平均延時是25ms,最大100ms
- Google Spanner的數據表明,100個spanserver的平均延時是70ms,最高150ms;200個spanserver的平均延時是150ms,最高350ms
CAP與ACID結合
CAP并不禁止構建一個一致的、可用的、分區容忍的ACID兼容數據庫。Spanner和ISIS^2就是這樣的系統。
放寬一致性
在很多場景下,一致性能夠且必須被放寬,以換取更好的性能。最出名的例子就是ATM。即使一個ATM機和銀行失聯了,余額未知,你仍然能夠存款。但是,有個限制,就是,ATM業務的特點決定弱一致能容忍誤差范圍很小。
如果你能夠接受99.999%的ATM顯示準確的余額,不一致的窗口期最多1分鐘。那么系統可以在1分鐘內先使用類似于ICMP、Gossip之類的快速、不可靠協議將值傳播開,然后主副本使用慢速但是100%可靠的方法傳播正確的值。
一個明顯的數據對比就是,如果使用不可靠的協議,ISIS^2的延時只有2ms。對比下,之前列出的數據是25ms。
一般系統的經驗就是,軟狀態(soft-state)可以接受弱一致,硬狀態(hard-state)必須是強一致。弱一致分兩種,一種是時間限制的弱一致,即限制不一致的時間窗口;一種是誤差限制的弱一致,即限制不一致期間值的偏差。
ACID可以變得多快?
加快事務返回速度的一個方法是使用異步API。使用異步API時,客戶端發送完更新后,不等待確認消息就直接返回。客戶端并不知道事務是否成功了,必須做好一段時間后發現事務失敗的準備。
當MySQL使用異步客戶端后,在一個30節點的集群上,寫操作可以達到1950萬次/s的響應速度,讀操作可以達到7167萬次/s的響應速度。
CAP的演進
CAP理論推出已經有十幾年,其中的很多規則已經發生了變化,CAP的作者Eric Brewer說道(2012年3月):
「CAP提出后的十多年間,幾被濫用,設計師和研究者發展了很多新穎獨特的分布式系統。這些系統很多都可歸入NoSQL,NoSQL被認為站在傳統數據庫的對立面...CAP中三選二的公式其實是一個誤導,它將三者的關系過于簡單化了。現在這個觀點應該予以矯正。CAP事實上只禁止很微小的一部分設計空間,即完美的可用性和一致性在分區存在的情況下是不可能的,除此之外,別無其他...分區現象其實很罕見,因此CAP的C、A在大部分時間可以完美實現。當分區出現時,需要策略檢測到分區,并保證一切井然有序。這個策略對分區的處理實際上分為三個步驟:1) 檢測分區;2) 進入分區模式,禁用部分操作;3) 分區結束后,開始恢復進程,恢復一致性,處理分區期間的錯誤...」
co-location(協同定位)
我們該如何擴展數據庫規模?
分布式事務代價:
- 延時,而不是吞吐量,是一個很大的問題
- 受到同一數據中心、數據中心間的物理條件的約束(機架連線、距離等)
強一致性的代價如下:
- Spanner:3數據中心,4K寫,大約14ms
- Gaios:4K寫,小于10ms,其中超過9ms是磁盤日志耗時
- Spinnaker:寫操作比Cassandra選舉人機制慢5-10%
可以看到,實現分布式事務和強一致性都需要付出一定的代價。Spanner就是一個很明顯的例子。
在以太網中,可以通過廣播實現數據中心內或鄰近數據中心間的強一致副本。Paxos的研究系統Spinnaker的寫性能只比Cassandra略差,但是前者是強一致的,后者是最終一致。Birman結合了向量鐘和Paxos也是向這個方向努力。這些研究表明,只要操作的時間大于Paxos延時(即平均SQL查詢時間在10ms~15ms),那么Paxos適合用于保證強一致性。
co-location是必須的
從已有的Paxos實現看,一個較小的Paxos的平均延時大約在1ms。但是他們如果在運行時需要要維護狀態,延時會增加很多。即使使用fast Paxos,連ROWA(讀一寫全部)在數據量很大時都會成為擴展瓶頸。
因此,數據需要分區。一旦數據分區,我們又回到分布式事務的老問題。我們必須仔細考慮數據的分布式協同,以最小化分布式事務成本。
靜態co-location:實體組(entity group)
Megastore中實現co-location的方法是使用明確的模式信息。和Spanner一樣,Megastore讓用戶定義表/實體的具名、類型化的列。一組列形成主鍵。實體間形成層次關系,并使得它們在物理存儲上保存到一起。這對于email賬號、博客這類的業務是一個非常好的方法。
由于同一實體組的數據通常一起被查詢,實體組是數據分區的合適粒度。實體組的數據是按照鍵值排序好的,這使得分區分裂操作和合并操作都相當廉價。
靜態co-location:樹形模式(tree schema)
一個標準的樹形模式中,外鍵關聯的數據應該位于同一個分區中。樹形模式存在一個主表,其它的次表包含一個外鍵指向主表的主鍵。整個樹的深度沒有限制。
表之間的關系通過全局查找表來維護。全局查找表的數據很少發生變化,副本操作代價很低。
主表中所有屬于同一主鍵的行被稱之為一個行組(row group)。一個分區可存儲一個或多個行組。
靜態co-location:鍵值表組(keyed table group)
鍵值表組是將任意表co-locate到一起的通用方法。該方法中,同一個分區的表共享相同的分區鍵(partition key)。分區鍵是表的某一列,并不一定是主鍵或外鍵。鍵值表組中同樣有行組的概念,其含義與樹形模式相同。
該模型被高并發分布式的云SQL所使用,例如Microsoft Azure。
靜態co-location:全副本節點(full replication node)
如果數據的規模不過分大,co-location的粒度可以更粗一些,為此,引入全副本節點。全副本節點將從許多數據分區復制數據,組合成一個完整的數據副本。需要跨分區執行的查詢能夠高效地運行在全副本節點上。在某種意義上,全副本節點類似于物化視圖。
在這種方式中,數據分片負責處理OLTP客戶端,全副本節點用于加速OLAP/join查詢。
動態co-location:訪問驅動(access driven)
Curino等人提出了一個學術模型,該模型將應用訪問建模為一個圖。圖進行分區,通過將頻繁訪問的行復制多份來加速讀性能,同時保證更新操作所需要的分布式事務數量盡可能地少。
這種方法能夠達到很好的分區性能,但是客戶端路由變得很復雜。客戶端和路由服務器很難通過一個簡單的查找函數來找到一個包含指定分區的節點。許多改進路由的方法被提出,但是沒有一個方案簡潔明了。
動態co-location:應用驅動(applicatopn driven)
考慮一個游戲場景。玩家有很多游戲可選,但是通常,一旦選定了一個游戲,就會一直玩數個小時。在玩的期間,玩家數據和游戲數據應該被co-locate到一個節點上。按照應用程序要求,為需要的數據創建組,只在組內保證數據操作的一致性。當游戲結束時,組也跟著被釋放。提前切分數據,在該模式下并沒有必要。
擴展閱讀
「In Search of an Understandable Consensus Algorithm」論文
「Unbundling Transaction Services in the Cloud」論文
「MoSQL: an elastic storage engine for MySQL」論文
NuoDB:分布式關系型數據庫