保障一個線上系統的可靠性和可用性的常用基本手段是多副本和主-從兩種方案。這兩種方案都的核心目的是消除單點。單點就是在一個系統中,某一個服務,或者功能模組,只有一個執行個體在運行。造成的問題就是,一旦這個執行個體下線,那麼整個系統將會宕機;一旦這個執行個體遺失資料,那麼整個系統將遺失資料。
消除單點的手段不外乎增加執行個體數,也就是我們常說的“冗餘”。但是冗餘並沒有那麼簡單。有些服務或模組沒有持久化的狀態(通俗地講,不儲存資料),增加冗餘很容易,按要求多重部署即可。但如果服務或模組需要儲存資料,那麼問題就複雜了。人們在實踐中發展出兩種常用的冗餘方案:主-從和多副本。
多副本方案很直觀:不想要單點,那就增加資料的份數,壞了一個,還有其他的。但是,考慮到服務下線或者損壞,多個副本之間資料可能不一樣。我們無法確定從一個副本上讀到的資料是最新的。因為任何一次對多個副本的資料寫入,都無法保證都成功。這就是我們常說的"一致性"問題。要得到正確的資料,必須整合所有的副本。通常的處理手段是規定成功寫入的副本數必須超過某個數量,成功讀取的份數也必須超過某個數量。只要兩個數值設定合理,就能確保讀取到最新的資料。
主-從方案更容易理解:用額外的從伺服器不停地備份主伺服器的資料,主伺服器宕了或者資料丟了,從伺服器可以接替主伺服器,繼續工作。主從之間的資料備份有兩種方式:非同步備份和同步備份。非同步模式下,資料寫入主伺服器後,隨即向用戶端反饋成功。隨後在恰當的時候,資料被同步到從伺服器中。同步備份是說資料寫入主伺服器後,主伺服器將資料轉寄至從伺服器,當從伺服器也寫入成功,再向用戶端反饋成功。
顯而易見,非同步主從備份無法保證可靠性。因為主伺服器遺失資料的時候,總會有一些資料沒有來得及備份到從伺服器,這些資料就永遠丟失了。
因此,同步資料備份比一部資料備份更好地保證可靠性。每次正確的寫入都會有兩份資料存在。主伺服器宕機,從伺服器那邊還有另一份。(當然,實際情況遠比這複雜得多,這裡就不展開了)。同步資料備份的一大問題是效能。資料寫入請求要通過主伺服器轉寄到從伺服器,並獲得反饋,才能響應使用者。大幅增加了請求的延遲。
現在,我們先拋開效能問題,看看其他方面。可靠性之後,我們還得考慮可用性。同步寫的模式要求主和從伺服器都寫入成功,才能告訴客戶成功。因此,當主從兩台伺服器有任何一台下線,系統就不可用。如果放鬆要求,從伺服器不一定要寫成功,那麼有些資料只有主伺服器上的一份資料,依然會遺失資料。
為了保證可靠性的同時,提高可用性,一個合理的選擇是增加從伺服器的數量,比如2台、3台,甚至5台。不一定要每台從伺服器都寫成功,只要保證有1台,保險點2台從伺服器寫入成功,便反饋客戶資料以妥善儲存。如此。可以保障足夠的可靠性,而可用性也不會受到單台伺服器下線的影響。
(Bad feelings I have about this, huh)
接下來的問題是一致性。主從模式最大的好處就是不用操心一致性問題,因為主伺服器的資料都是成功寫入的。同使用者的視角一致。
真的是這樣嗎?一般情況下,或者說沒有異常情況的時候是這樣。當主伺服器出現問題,比如硬體失效,或者系統崩潰,那麼就需要將一台從伺服器升格為主伺服器,以確保系統可用。
但是,前面說過了,從伺服器並不一定要每次都寫成功。於是,從伺服器升格而來的主伺服器可能資料不完整。相比原來的主伺服器少了資料。在使用者眼裡,此時的系統丟了資料。儘管實際上資料還在,只是不在當前的主伺服器上。
現在我們來解決這個問題。問題的根源是從伺服器的資料和主伺服器不一致,最直接的解決方案是設法讓它們一致。主伺服器在向從伺服器作同步寫的時候,可以多試幾次,提高成功率。但是,終究還是有怎麼重試也無法成功的時候。如果就此作罷,那麼資料不一致的情況依然存在。如果一直這麼重試下去,就無法響應使用者。唯一的做法是反饋使用者,同時接著重試。為減少系統的複雜性,通常會將這後續的重試操作放在一個單獨的服務模組裡非同步地執行。但是,重試這個步驟無論在主邏輯,還是在非同步獨立模組中執行,都無法保證完全成功。系統各類異常會打斷重試的努力。甚至會丟失重試任務。重試這個failover過程本身會fail。那麼如果要保證failover能夠正確及時的執行,那麼就必須處理好failover的failover。按這個邏輯下去,還有failover的failover的failover...。沒底了。所以,實際上是無法保證主從伺服器的資料完全一致,而不影響可用性。這條路是走不通的。
那麼,我們退而求其次,容忍主從伺服器的資料不一致,尋找消除資料不一致的問題。前面提到,我們不一定要每台從伺服器的每次寫入都成功,只需要保證有一定數量的從伺服器成功即可。所以,多台從伺服器的資料整合起來,就可以確保同主伺服器一致。這樣的話,一旦主伺服器宕機,從伺服器升格為主伺服器,新的主伺服器每收到一個資料讀取的請求,就去從伺服器那裡把相應的資料都取來,同自己的資料一起比對,找出正確的那份資料,返還給使用者。
等等,這不就變成了多副本的方案嗎?的確如此。根本而言,主-從方案同時滿足可靠性、可用性和一致性的前提是沒有伺服器宕機。這本身就違背了“任何東西都會失效”這個基本事實。(更血腥的事實是“任何不可能發生的事情也會發生”,而且總是在你認為不會發生的時候發生)。而且,主-從方案的一致性基準是建立在主伺服器上的,在發生了主從伺服器切換之後,整個叢集就失去了一致性基準,從而被迫在所有主-從伺服器上尋求一致性。而多副本方案一開始就明確伺服器是不可靠的,單台伺服器的資料是不可能完整和準確的。整個系統的可靠性、可用性和一致性建立在所有伺服器資料整合之上。
在接受了“任何東西都會失效”這個前提之後,我們看到主-從方案也不得不綜合所有伺服器的資料,以便獲得一致性。這也等同於多副本方案,但卻要付出更多的代價,比如主-從切換邏輯,多一次主從轉寄的資料訪問延遲。
但是,多副本方案並非完美。通常它只能用於非常簡單的資料存放區結構和資料操作邏輯,比如map和插入/刪除等簡單操作。涉及多個資料的操作,非等冪的操作,或者帶有事務的操作,使得各副本間的一致性比對非常困難。
對於可靠性可用性要求不高的情境,我們往往只能退而求其次,使用主-從模式,以犧牲可靠性、可用性為代價,確保商務邏輯得以實現。在使用主-從方案時,我們通常會設法提高裝置的可靠性,比如使用RAID,減少出錯的機率。
有時,我們對於可靠性、可用性和一致性的要求非常高,主-從方式無法滿足。那麼,只能改變商務邏輯,將其簡化成能夠適用於多副本架構的形式。大型物件儲存對可靠性、可用性和一致性有極高的要求,(它是線上服務的服務,依賴可靠性和可用性賺錢,此處絕對馬虎不得)。因而不得不放棄任何複雜的資料操作,將其簡化到適應多副本儲存的形態。所以說大型物件儲存系統採用<key,value>的形式,並非是自願,或者睿智的,完完全全是被迫的。雲端儲存系統不可避免地採用多副本的方案。
多副本和主-從方案之間的比較問題是非常典型雲端運算問題(或者說“坑”)。很多方案在單獨考慮某一種需求,比如可靠性,或者可用性,都可以應付。但一旦將這些需求綜合在一起考慮,這些方案將會遇到各種障礙。另一方面,一個線上系統的方案,不僅僅考慮在一般情況下的功能實現,必須時刻考慮在異常情況出現時,可能遇到的各種問題。後者佔據了雲端運算系統架構和設計的7、8成工作量。換句話說,雲端運算系統成功的難易程度,取決於你躲過了多少"坑"。