本文主旨并非闡述一個可用的架構(gòu)設(shè)計,而是展示出對象存儲的元數(shù)據(jù)保障中將會遇到的問題。問題往往比答案更重要。多數(shù)問題只要認(rèn)識清楚,解決并非難事。但需要注意的是,沒有哪個問題存在十全十美的徹底解決方案。我們所采取的策略更多的是將一個關(guān)鍵性問題轉(zhuǎn)化成另一個次要和易于處理的問題,或者將一個問題發(fā)生的可能性盡可能降低。很多問題是無法徹底解決的,我們的現(xiàn)實目標(biāo)是將其發(fā)生的概率下降到業(yè)務(wù)可接受的范圍。因此,希望大家能夠更多地關(guān)注和發(fā)掘所存在的問題,而不是緊盯方案和設(shè)計。我們相信,認(rèn)清了問題之后,找到解決辦法并非難事。
在開始討論元數(shù)據(jù)保障的問題之前,我們先要牢牢記住一句話:“任何事物都會出錯。”這個真理時時刻刻在發(fā)揮著驚人的作用。
元數(shù)據(jù)
元數(shù)據(jù)系統(tǒng)的好壞關(guān)系到整個對象存儲系統(tǒng)的可靠性、可用性和一致性,并且會影響到性能。因此,元數(shù)據(jù)部分是對象存儲系統(tǒng)的核心,也是架構(gòu)和保障的重中之重。
元數(shù)據(jù)最基本的作用在于數(shù)據(jù)對象的定位。對象存儲的任務(wù)是保存用戶提交的數(shù)據(jù)對象,并以用戶指定的名稱(Key)對其標(biāo)識。用戶如需獲取一個數(shù)據(jù)對象,要向?qū)ο蟠鎯ο到y(tǒng)提交Key,存儲系統(tǒng)便會根據(jù)Key找到相應(yīng)的數(shù)據(jù)對象,然后反饋給用戶。存儲系統(tǒng)根據(jù)Key找到數(shù)據(jù)對象存放位置的過程,便是依托元數(shù)據(jù)完成的。圖1是元數(shù)據(jù)方案架構(gòu)圖。
元數(shù)據(jù)的核心內(nèi)容是Key=>Pos(存儲位置)的映射,本質(zhì)上是一個Map。此外,元數(shù)據(jù)還承載了數(shù)據(jù)對象歸屬(容器和用戶)、大小、校驗值等元信息,用于記賬、校對、修復(fù)和分析等輔助操作。
說到這里,大家或許會有疑問:現(xiàn)今的存儲系統(tǒng)都流行去中心化,設(shè)法去掉元數(shù)據(jù),為何還要談元數(shù)據(jù)的保障?原因其實很簡單,當(dāng)今各種去中心化的存儲設(shè)計,在云存儲中并不適用。其中原因頗為繁復(fù),已超出本文范疇,這里只做簡單說明。
去中心化的方案利用算法確定數(shù)據(jù)對象的位置。最常見的算法是一致性哈希。這些方案的問題在于,如果僅考慮數(shù)據(jù)對象的存取,邏輯很簡單,實現(xiàn)上也簡單。但如果考慮到可靠性、可用性、一致性,以及運維,就會變得復(fù)雜。
數(shù)據(jù)對象的存儲副本總會損壞和丟失。一旦丟失,需要盡快修復(fù)。最極端的情況是某一磁盤失效,進(jìn)而造成大量的數(shù)據(jù)副本需要修復(fù)。去中心化方案將數(shù)據(jù)對象的副本同磁盤一一對應(yīng)起來,結(jié)果就是修復(fù)的數(shù)據(jù)只能灌入一塊磁盤。單塊磁盤的吞吐能力有限,從而造成一塊磁盤的數(shù)據(jù)需要近一天的時間方能修復(fù)。而在大型存儲系統(tǒng)中,這段時間內(nèi)可能會有更多的磁盤發(fā)生損壞,因而可能造成數(shù)據(jù)的丟失。
同樣因為數(shù)據(jù)對象與服務(wù)器和磁盤綁定,一旦一臺服務(wù)器下線,便會降低相應(yīng)對象的存取可用性。有些方案臨時將數(shù)據(jù)對象映射到其他服務(wù)器,從而提高可用性,但臨時狀態(tài)的管理、其間的異常處理等問題的復(fù)雜性遠(yuǎn)遠(yuǎn)超出了去中心化所帶來的便利。
當(dāng)存儲系統(tǒng)擴(kuò)容時,去中心化方案需要在服務(wù)器之間遷移大量的數(shù)據(jù),以求各磁盤的可用容量達(dá)到平衡。遷移規(guī)模隨擴(kuò)容規(guī)模增大而增加。遷移過程消耗系統(tǒng)資源,并且存在中間狀態(tài),使得數(shù)據(jù)存取邏輯復(fù)雜化。而且漫長的遷移過程中一旦發(fā)生異常,其處理過程相當(dāng)繁復(fù)。
在一致性方面,去中心化方案礙于副本數(shù)較少,在某些特定情況下會造成一致性問題,而且難以修復(fù)。關(guān)于這一點,后面會具體論述。
對于list操作、數(shù)據(jù)校驗、記賬等輔助操作,各種去中心化的方案都很不友好。因為數(shù)據(jù)量龐大,無法通過遍歷所有數(shù)據(jù)計算用量、分析統(tǒng)計。因此,一些去中心化方案會設(shè)置數(shù)據(jù)對象和用戶的關(guān)聯(lián)數(shù)據(jù)庫,以方便使用量計算。這些數(shù)據(jù)庫實際上就是一種元數(shù)據(jù)庫,同樣面臨著元數(shù)據(jù)保障的問題。
去中心化方案的諸多問題并非無法解決,總能找到合適的方法應(yīng)對。但其實這只是將壓力轉(zhuǎn)移到運維階段,使得運維成為瓶頸。我們說存儲系統(tǒng)是“三分研發(fā),七分運維”,運維是重中之重,無論從設(shè)計還是研發(fā)角度,都應(yīng)當(dāng)以方便運維,減少運維壓力為先。表1中對比了去中心化方案和元數(shù)據(jù)方案。
元數(shù)據(jù)的特性
讓我們回到元數(shù)據(jù)上來。元數(shù)據(jù)相對于數(shù)據(jù)對象本身,總量較少。根據(jù)統(tǒng)計,元數(shù)據(jù)和數(shù)據(jù)對象的大小之比大體在1:100到1:10000之間,具體的比率,取決于數(shù)據(jù)對象的平均大小。因此,元數(shù)據(jù)相比數(shù)據(jù)對象本身更加容易操作和處理。
當(dāng)用戶索取數(shù)據(jù)對象時,存儲系統(tǒng)需要檢索元數(shù)據(jù)庫,找到相應(yīng)的元數(shù)據(jù)條目,獲得存儲信息后,進(jìn)行讀取。因此,元數(shù)據(jù)的操作是查詢操作,也就是數(shù)據(jù)庫操作。正因為元數(shù)據(jù)的量較少,并且都是結(jié)構(gòu)化數(shù)據(jù),使得這種檢索得以方便地實現(xiàn)。
即便如此,當(dāng)存儲量達(dá)到幾十PB級別時,元數(shù)據(jù)的數(shù)據(jù)量也將達(dá)到幾十上百TB的級別。這已遠(yuǎn)超出單臺服務(wù)器的存儲能力。因此,元數(shù)據(jù)庫天生便是一個分布式集群。在分布式數(shù)據(jù)庫集群中,很難維持復(fù)雜的數(shù)據(jù)結(jié)構(gòu),特別是在可靠性、可用性、一致性,以及性能的約束下,同時滿足這些要求是極難做到的。在動輒數(shù)以PB計的云存儲系統(tǒng)中,只得維持最簡單的對象存儲形態(tài),這也就是大型的云存儲系統(tǒng)不支持文件系統(tǒng)的原因。
元數(shù)據(jù)的重要性在于它是整個數(shù)據(jù)存儲的基準(zhǔn):整個系統(tǒng)擁有哪些數(shù)據(jù)對象,歸屬哪些容器、哪些用戶。一個數(shù)據(jù)對象,元數(shù)據(jù)說有,就有了;元數(shù)據(jù)說沒有,就沒有了。所以,元數(shù)據(jù)的可靠性代表著存儲系統(tǒng)的可靠性。數(shù)據(jù)校驗、空間回收等操作都依賴于元數(shù)據(jù)。元數(shù)據(jù)一旦有所差錯,必然造成整個數(shù)據(jù)存儲的錯失和混亂。
同時,幾乎所有操作都需要操作元數(shù)據(jù),或讀取,或?qū)懭?,是存儲系統(tǒng)的關(guān)鍵路徑。如果元數(shù)據(jù)系統(tǒng)下線,那么整個存儲系統(tǒng)就會宕機。因此,元數(shù)據(jù)部分的可用性決定了存儲系統(tǒng)的可用性。
性能方面,幾乎所有的操作都涉及到元數(shù)據(jù),整個元數(shù)據(jù)系統(tǒng)的訪問壓力遠(yuǎn)遠(yuǎn)超過存儲系統(tǒng)。因而,元數(shù)據(jù)的性能決定了存儲系統(tǒng)的整體響應(yīng)。
元數(shù)據(jù)的一致性也決定了存儲系統(tǒng)的一致性。一致性決定了用戶在不同時間對同一個對象訪問是否得到同樣的結(jié)果。當(dāng)一致性發(fā)生問題時,用戶在不同的時間可能獲得某個對象的不同版本,甚至無法獲得有效的對象。這些違背用戶預(yù)期的情況往往造成用戶業(yè)務(wù)邏輯的混亂,引發(fā)不可預(yù)期的結(jié)果。而在某些特定的情況下,一致性的錯亂會增加數(shù)據(jù)丟失的可能性。
作為存儲系統(tǒng)的樞紐,元數(shù)據(jù)占到整個系統(tǒng)大部分的維護(hù)工作量。元數(shù)據(jù)系統(tǒng)可維護(hù)性上的提升對于整體的運維有莫大的幫助。
元數(shù)據(jù)保障的真正困難在于上述這些特性的平衡和協(xié)調(diào)。這些特性之間有時會相互補充,但更多的時候會相互矛盾和沖突。試圖解決其中一個方面的問題,可能導(dǎo)致其他方面受到損害。解決問題的過程充滿了大量的折中手段。
接下來具體說一說云存儲元數(shù)據(jù)保障所面臨的問題和一般應(yīng)對手段。
主從模型
首先我們必須解決可靠性和可用性問題,盡可能保證保存下來的元數(shù)據(jù)不丟失,也盡可能讓服務(wù)始終在線。這里有一個關(guān)鍵點:怎樣才算保存下來?這個問題看似簡單,實則至關(guān)重要。一般我們會認(rèn)為所謂保存下來就是存儲到磁盤或其他持久存儲設(shè)備上。但在一個在線系統(tǒng)中無法以此界定。在一個在線服務(wù)中,數(shù)據(jù)完成保存是指明確向客戶端反饋數(shù)據(jù)已經(jīng)正確保存。一旦向客戶端發(fā)出這樣的反饋,那么便是承諾數(shù)據(jù)已經(jīng)保存下來。在這個界定之下,元數(shù)據(jù)的可靠性保障則頗為復(fù)雜。
前面說過,元數(shù)據(jù)系統(tǒng)是一個數(shù)據(jù)庫系統(tǒng),保障數(shù)據(jù)庫系統(tǒng)可靠性最常用手段就是主從模型(圖2),即讀寫主數(shù)據(jù)庫,而主數(shù)據(jù)庫將數(shù)據(jù)復(fù)制到從數(shù)據(jù)庫,從而確保數(shù)據(jù)庫沒有單點。主從模型同時也保障了可用性,當(dāng)主服務(wù)器下線時,從服務(wù)器可以切換成主服務(wù)器,繼續(xù)提供服務(wù)。這樣的保障手段對于一般的在線應(yīng)用大體上夠用了,但對于云存儲的元數(shù)據(jù)而言,則存在諸多問題。其中緣由頗為復(fù)雜,讓我們從最基本的可靠性開始。
主從模型是依靠主服務(wù)器向從服務(wù)器復(fù)制每次寫入的數(shù)據(jù),以確保數(shù)據(jù)存留不止一份。但這里有個同步和異步的問題。所謂“異步”是指主服務(wù)器完成數(shù)據(jù)寫入之后,即刻向客戶端反饋寫入成功信息,然后在適當(dāng)?shù)臅r候(通常都是盡快)向從服務(wù)器復(fù)制數(shù)據(jù)??蛻舳说玫奖4嫱瓿傻男畔⒑蟊銡g歡喜喜做后面的事情去了。而此刻相應(yīng)的數(shù)據(jù)只有主服務(wù)器上保存著,如果發(fā)生磁盤損壞之類的問題,便會丟失那些尚未來得及復(fù)制到從服務(wù)器的數(shù)據(jù)。之后如果客戶端來提取這條數(shù)據(jù),服務(wù)端卻無法給出,對存儲系統(tǒng)而言,是很丟人的事。
即便沒有發(fā)生硬件損壞的情況,依然可能丟失數(shù)據(jù)。由于硬盤之類的存儲介質(zhì)速度緩慢,無法跟上數(shù)據(jù)訪問的節(jié)奏,所以數(shù)據(jù)庫都會使用內(nèi)存進(jìn)行緩存。當(dāng)服務(wù)器發(fā)生掉電或者崩潰,數(shù)據(jù)庫發(fā)生非正常的退出時,都會造成緩存中沒有寫入磁盤的數(shù)據(jù)丟失,進(jìn)而造成數(shù)據(jù)的丟失。
在存儲系統(tǒng)長期的運行過程中,硬件肯定會壞,掉電必然會發(fā)生,系統(tǒng)也難保不會崩潰。所以這種異步的數(shù)據(jù)復(fù)制過程必然會有數(shù)據(jù)的丟失。這對于在線存儲服務(wù)而言是無法容忍的。
既然異步存在這樣的問題,那么同步地復(fù)制數(shù)據(jù)是否會好些呢?所謂“同步”是指主服務(wù)器完成數(shù)據(jù)寫入后,并不立即反饋用戶,而是先將數(shù)據(jù)復(fù)制到從服務(wù)器。等到從服務(wù)器正確寫入,再向用戶反饋數(shù)據(jù)寫入完成。這樣,任何時刻都會至少有兩臺服務(wù)器擁有某一條數(shù)據(jù),即便一臺服務(wù)器出了問題,也可以確保數(shù)據(jù)還在。如果有多臺從服務(wù)器自然就萬無一失了。
同步模式解決了可靠性問題,卻引入了新問題。最基本的問題就是同步的數(shù)據(jù)復(fù)制造成延遲的增加:主服務(wù)器需要等待從服務(wù)器完成寫入才能反饋。
讓我們先放下性能問題,畢竟在線對象存儲對于主從同步的延遲還沒有那么敏感。剩下的問題則是根本性的。同步模式解決了可靠性問題,下一步需要考慮可用性問題。對于在線存儲服務(wù)而言,必須要確保系統(tǒng)不受單點故障的影響。同步模式則恰恰受制于此。為了確??煽啃?,必須主從服務(wù)器都寫入成功,才能向用戶反饋成功。如果從服務(wù)器下線,那么這個條件就被破壞,不得不向用戶反饋失敗,直到從服務(wù)器重新恢復(fù)。而當(dāng)主服務(wù)器下線后,從服務(wù)器則設(shè)法升級為主服務(wù)器繼續(xù)服務(wù)。但此時只有一臺服務(wù)器,也不能滿足數(shù)據(jù)不少于兩份的要求。如果允許主從之間容忍從服務(wù)器的下線,那么可靠性又受到了削弱。這樣產(chǎn)生了令人啼笑皆非的結(jié)果,主從模型是為了提高可靠性和可用性,現(xiàn)在反到受制于單點故障。
解決這一問題的方法也不是很難:增加從服務(wù)器的數(shù)量,并且允許主從復(fù)制失敗。如此,當(dāng)一臺從服務(wù)器下線后,其他從服務(wù)器依然可以接收數(shù)據(jù),確保任何時刻一份數(shù)據(jù)都至少擁有兩個以上的副本,以維持可靠性。不過,具體操作上并沒有那么簡單,主從之間復(fù)制成功的數(shù)量必須滿足一個閾值。這個閾值也必須滿足一個條件才能保證數(shù)據(jù)一致性,關(guān)于這個要點后面會具體闡述。
又有新的問題出現(xiàn)。當(dāng)主服務(wù)器下線時,一臺從服務(wù)器便會被升級成為主服務(wù)器。但前面說了,為了可用性,并不要求主從服務(wù)器間的復(fù)制每次都成功,那么新的主服務(wù)器的數(shù)據(jù)可能是不完整的。這樣對于用戶而言,便會出現(xiàn)數(shù)據(jù)一致性的問題:原先成功寫入的數(shù)據(jù)卻讀不到了,或者讀到的都是很舊的版本,已被覆蓋掉的東西。給用戶造成的印象便是數(shù)據(jù)丟失了,這是嚴(yán)重的問題。
最直觀的解決途徑是設(shè)法補上那些數(shù)據(jù)。但考慮到元數(shù)據(jù)的量,這樣的修補是無法在短期內(nèi)完成的。另一個更復(fù)雜卻真正有效的方法是讀取數(shù)據(jù)時,同時讀所有的服務(wù)器,不管主從。然后將所得的結(jié)果整合在一起,找出正確的版本。
事情還沒有完結(jié),存放在數(shù)據(jù)庫中的數(shù)據(jù)經(jīng)年累月地運行之后,會逐步退化。磁盤的失效,磁道的損壞,乃至人為的操作失誤,都可能造成數(shù)據(jù)副本的丟失。換句話說,即便是主服務(wù)器,我們也無法完全保證數(shù)據(jù)的完整性和一致性。無論如何都無法回避主從服務(wù)器之間的一致性問題。
無論是主服務(wù)器,還是從服務(wù)器,終究需要對缺失的數(shù)據(jù)副本進(jìn)行修補,否則數(shù)據(jù)的退化終究會造成數(shù)據(jù)的遺失。這種修補的過程也是困難重重,后面會進(jìn)一步分析。
最后,最重要的就是運維。架構(gòu)、設(shè)計、開發(fā)之類的都是短期過程,雖說系統(tǒng)需要不斷演進(jìn),但畢竟都是階段性的工作。而運維,則是持久的、不間斷的任務(wù)。一個在線服務(wù)的正常運行最終還要依賴運維,運維決定了服務(wù)的品質(zhì)。因此,一個設(shè)計方案的可維護(hù)性具有很高的優(yōu)先級。
在可維護(hù)性方面,主從模型除了常規(guī)的系統(tǒng)維護(hù)事務(wù)外,還有很多額外的運維過程。主服務(wù)器下線,需要將某一臺從服務(wù)器切換為主服務(wù)器,這個過程是一個運維過程。理論上,一個主從集群可以實現(xiàn)自動切換。但實際使用中,這種切換并不能確保成功,往往需要人為干預(yù),切換失敗也是常有的事。對于計劃中的下線,如升級軟硬件等,運維強度尚不算大,但對于軟硬件故障造成的突發(fā)下線,主從切換往往是緊急運維事件。一旦失敗,便是服務(wù)下線的大事故。其中充滿了風(fēng)險和不可控因素,并不利于存儲服務(wù)的高可用。
一個在線系統(tǒng)的架構(gòu),最好的情況就是無論是正常運行,還是發(fā)生意外故障,都能夠以同一種方式運行,無須額外的應(yīng)急處理。用通俗的話說,在線系統(tǒng)必須能夠不動聲色地扛住局部異常,為運維人員贏得回旋處理的余地。對于云存儲的元數(shù)據(jù)系統(tǒng)尤是如此。元數(shù)據(jù)位居關(guān)鍵路徑,又存在持久化的狀態(tài),還受到各種特性要求的約束,它的架構(gòu)著實是一項富于挑戰(zhàn)的任務(wù)。
多副本模型
下面我們考查一個模型,暫且簡單地稱其為多副本模型。因為這種模型,就是利用眾多元數(shù)據(jù)副本來保證可靠性和可用性的。與主從模型不同的是,多副本模型的各副本之間沒有主次之分,所有的副本都處于相同的地位。因而,在向多副本模型寫入元數(shù)據(jù)時,是同時向這些副本發(fā)起寫入。而讀取元數(shù)據(jù)時,也是向這些副本同時讀(圖1)。
圖1 多副本模型
多副本模型基于這樣一個簡單而直觀的思路:單點會造成數(shù)據(jù)丟失,并引發(fā)可用性問題,那么就將數(shù)據(jù)同時寫入多個服務(wù)器,以防單點的出現(xiàn)。不同于主從模型中,讀寫都針對一臺主服務(wù)器,從服務(wù)器只是間接地參與可靠性和可用性的保障,多副本模型的每臺服務(wù)器任何時刻都在直接發(fā)揮著保障作用。
由于每次讀寫都施加在所有的副本服務(wù)器上,任何時刻都有不止一份數(shù)據(jù)被保存下來,所以可靠性自然就解決了。同樣,任何時刻都有不止一臺服務(wù)器在運行,它們同時下線的可能性極小(暫且不考慮機房級別的整體故障),所以可用性也無需額外措施,便可以得到保障。正是因為每臺副本服務(wù)器都是平等的,所以任何一臺服務(wù)器下線都不會對整個集群的可用性和可靠性產(chǎn)生影響,系統(tǒng)仍然依照正常情況下的方式運行,別無二致。
這是多副本模型相對于主從模型的最大優(yōu)勢。無論哪臺服務(wù)器下線,都不會引起系統(tǒng)運行狀態(tài)的變化,運維人員可以從容地進(jìn)行善后處理,沒有服務(wù)下線的顧慮。而軟硬件升級之類的日常維護(hù)工作,也不需要大動干戈地進(jìn)行主從切換之類的危險操作,直接將服務(wù)器下線,進(jìn)行更新便可。多副本模型的系統(tǒng)甚至可以容忍多臺服務(wù)器下線,而不會造成系統(tǒng)運行的波動。具體的容忍限度取決于配置設(shè)定,關(guān)于這一點,后文有詳細(xì)論述。
任何事物都有兩面,既然多副本模型有它的優(yōu)點,自然也有缺點。多副本的問題主要在一致性方面。由于我們允許副本服務(wù)器寫入失敗,再加上各種原因造成的數(shù)據(jù)退化,所以副本服務(wù)器之間的數(shù)據(jù)會不一樣。當(dāng)我們讀取的時候以哪個為準(zhǔn)呢?
這是一個值得考慮的問題。我們無法確定哪臺副本服務(wù)器包含了完整新鮮的數(shù)據(jù)。實際上不可能有這樣的服務(wù)器存在。因而我們也就無法從任何一臺服務(wù)器中準(zhǔn)確無誤地讀出所需的數(shù)據(jù)。唯一的辦法就是同時讀所有的副本服務(wù)器,綜合所得的副本數(shù)據(jù),以獲得所需的信息。
如何綜合副本數(shù)據(jù)呢?首先要確定基準(zhǔn)。基準(zhǔn)就是判定有效數(shù)據(jù)的標(biāo)準(zhǔn),有時間基準(zhǔn)和空間基準(zhǔn)之分。時間基準(zhǔn)用來處理元數(shù)據(jù)的先后覆蓋,而空間基準(zhǔn)用于處理副本之間的對應(yīng)關(guān)系。在描述如何確定基準(zhǔn)之前,先定下這樣一個準(zhǔn)則:對于一個數(shù)據(jù)對象的元數(shù)據(jù),只有時間最近的那條是有效的。有了這樣的準(zhǔn)則,建立基準(zhǔn)就容易了。我們可以為每一次元數(shù)據(jù)寫入加上一個時間戳,并且確保一次元數(shù)據(jù)寫入的各個副本擁有相同的時間戳,同時還需保證歷次寫入的元數(shù)據(jù)擁有不同的時間戳。只要能確保這兩個條件,數(shù)據(jù)的基準(zhǔn)就容易確立了。
具體的方法并不復(fù)雜,將讀取到的副本數(shù)據(jù)放在一起比對,時間戳上最近的那些副本,就是所需要的。但并非所有的最大時間戳都是有效的。假設(shè)這樣一種情況:一個元數(shù)據(jù)系統(tǒng)有5個副本,一次寫入時,由于種種原因,只有兩個副本寫成功了。在讀取這條元數(shù)據(jù)時,恰好那兩臺寫成功的副本服務(wù)器下了線。這種情況下,這條元數(shù)據(jù)就無法讀到了。其結(jié)果就是元數(shù)據(jù)不見了,直到那兩臺服務(wù)器重新上線。
為了避免這種情況的出現(xiàn),我們需要引入一個規(guī)則,以確保無論怎樣都能讀到正確的數(shù)據(jù)。
這樣的方法有不少,最常用的是WRN算法(圖2)。這種算法比較簡單,容易實現(xiàn),使用也較廣。算法的具體操作如下。
圖2 WRN算法
假設(shè)有N個副本,當(dāng)需要訪問數(shù)據(jù)時,同時寫或者讀所有副本。如果寫入時,有超過W個副本寫成功,那么就認(rèn)為這次寫入是成功的,否則就算失敗,向客戶端反饋寫入失敗。讀取時也一樣,如果有超過R個副本讀取成功,就認(rèn)為這次讀取是成功的,否則就算失敗,或者數(shù)據(jù)不存在(具體是失敗,還是不存在,需根據(jù)副本讀取的結(jié)果加以判別)。W和R必須滿足一個條件:W+R>N。只要滿足這個條件,成功寫和成功讀的副本之間必然存在重疊,因而肯定可以讀到至少一個有效的數(shù)據(jù)。
在具體使用中,WRN算法還有不少需要注意的細(xì)節(jié)。首先,具體讀取各副本時,最簡單的策略是取得該副本服務(wù)器中最新的那條元數(shù)據(jù),然后依靠WRN算法整合這些所得的數(shù)據(jù)。這種做法在一般情況下可以正常工作,但在一些異常情況中會存在一致性問題。一種情況是一次寫入時,沒有滿足成功寫入W份副本的條件,那么這次寫入算作失敗。但其中寫成功的副本因為時間戳更近,讀取該副本時,會覆蓋先前成功寫入的那些副本。于是,最新寫入失敗的那個元數(shù)據(jù)讀取時沒有達(dá)到足夠數(shù)量,而先前成功寫入的元數(shù)據(jù)因為某些副本被這次失敗的殘留副本遮蓋,也無法達(dá)到R數(shù)。于是便會出現(xiàn)無法取得有效元數(shù)據(jù)的情況。
這種情況屬于低概率事件,但云存儲系統(tǒng)經(jīng)年累月不間斷地運行,任何低概率的事件都會發(fā)生,而且必然發(fā)生。一旦發(fā)生,便會引發(fā)數(shù)據(jù)錯亂的情況,影響用戶的使用和服務(wù)的聲譽。
針對這樣的問題,有一些解決手段。最基本的手段是對于失敗的寫入操作,將各副本進(jìn)行回滾。也就是將那些已成功寫入的元數(shù)據(jù)條目副本刪除。這種做法可以在一定程度上有效地降低問題發(fā)生的概率。但基于“任何東西都會出錯”這樣一個事實,我們認(rèn)為回滾也會失敗。回滾失敗意味著依然會發(fā)生元數(shù)據(jù)無效的問題。
另外,還有一個解決方法:讀取副本時不是只讀最近的那一條元數(shù)據(jù),而是讀出幾條。把各副本讀到的元數(shù)據(jù)根據(jù)時間戳相互對位,還原出最原始的寫入狀態(tài)。在此基礎(chǔ)上,剔除那些失敗的寫入數(shù)據(jù),得到正確的數(shù)據(jù)。
還原寫入歷史的操作要求元數(shù)據(jù)在數(shù)據(jù)庫中采用只增的方式,以便保留歷史的條目。每次元數(shù)據(jù)的寫訪問,無論是新增還是覆蓋,都是增加一條記錄。與原有的元數(shù)據(jù)記錄不同的是,這條記錄攜帶新的時間戳。對于刪除操作,則同樣增加一條數(shù)據(jù),并且設(shè)置記錄中的“刪除”標(biāo)志,系統(tǒng)將根據(jù)此標(biāo)志判斷記錄的刪除操作。(只增也有助于避免事務(wù),減少數(shù)據(jù)庫鎖的使用,對提升性能有所幫助。)
但與任何一種問題解決手段一樣,這種還原歷史操作的方法也不能徹底消除一致性的問題,原因是數(shù)據(jù)退化。假設(shè)一次寫入正好成功了W個副本,那么這次寫入盡管成功了,但仍處于臨界狀態(tài)。如果有一個原先成功的副本發(fā)生了丟失,那么之后這次寫入將會被認(rèn)為是失敗的,而被忽略。盡管這是更加鮮有的情況,但確實會發(fā)生,而且無法直接消除。一般情況下,我們也只能容忍這種情況的存在,畢竟發(fā)生的概率已經(jīng)非常非常小了。
一些輔助手段可以進(jìn)一步減少這類問題發(fā)生的可能性。如果一次寫入的成功數(shù)超過了W,但依舊有少量失敗,那么要實時地對這些失敗寫入進(jìn)行修復(fù),例如重試,或者使用異步的重試隊列對失敗進(jìn)行修復(fù)。這種手段和失敗回滾一起可以很有效地減少容易產(chǎn)生混淆的臨界狀態(tài)的存在。
失敗修復(fù)和回滾之類的錯誤處理手段實際上并非很多人認(rèn)為的那么有效,錯誤處理本身也會失敗。如果試圖以錯誤處理消除問題的隱患,那么必須也要對錯誤處理的失敗情況加以處理。于是,就會有錯誤處理的錯誤處理,錯誤處理的錯誤處理的錯誤處理……如此便無窮無盡,這自然不是解決問題之道。錯誤處理的作用是減小問題發(fā)生的概率,最終將其減小到一個可以接受的范圍。
但考慮到數(shù)據(jù)退化,僅有簡單的修復(fù)是不夠的。一則錯誤處理不可能永遠(yuǎn)成功,二則并非每一次數(shù)據(jù)丟失都會被感知。我們需要一個最終的一致性修復(fù)手段,這個手段可以彌補其他異常處理失敗造成的問題。但這樣的手段也會失敗,為了能夠最終確保錯誤得到修復(fù),這種手段的失敗處理方式就是它自己。一般情況下,最終手段不需要有很強的實時性要求,它的任務(wù)在于應(yīng)對那些概率很低的情況。在對象存儲系統(tǒng)中,這個手段就是定期合成元數(shù)據(jù)快照。
元數(shù)據(jù)快照的做法是這樣的:定期導(dǎo)出所有副本的元數(shù)據(jù),按照時間戳匹配各條元數(shù)據(jù),然后依照WRN算法逐條將所有副本整合,拋棄那些失敗的殘留數(shù)據(jù),剔除被覆蓋的元數(shù)據(jù)。最終生成一個完整一致的快照。然后再重新載入元數(shù)據(jù)庫,替換快照所涵蓋的數(shù)據(jù)。
快照生成的主要問題是時間。元數(shù)據(jù)的量為數(shù)不少,通常都會在TB級別之上。對這樣的數(shù)據(jù)規(guī)模進(jìn)行匹配和計算,往往需要在一個集群中以Map-Reduce處理??紤]到成本,這樣的集群規(guī)模不可能很大,因而處理的時間將會比較長,可能達(dá)數(shù)小時。而且,數(shù)據(jù)的導(dǎo)出和加載也需要較長的時間。同時,作為一個定期的運維過程,如何自動化,減少人力參與也是非常重要的。總體上,如何快速并且自動地合成快照是頗有挑戰(zhàn)的任務(wù)。
快照還有一些額外的作用,包括:生成每個容器的數(shù)據(jù)量、對象數(shù)等統(tǒng)計信息;生成每塊硬盤的對象清單,用于磁盤失效后的數(shù)據(jù)恢復(fù)和數(shù)據(jù)對象的校對。
快照的生成周期通常設(shè)置在一天。那么一條元數(shù)據(jù)的不一致情況最多只會存在一天,一天之后,該條元數(shù)據(jù)的所有副本又會恢復(fù)到一致的狀態(tài)。而只要一天內(nèi)沒有發(fā)生多臺服務(wù)器同時丟失數(shù)據(jù)的情況,元數(shù)據(jù)的可靠性便可以得到很好的保障。我們相信一天內(nèi)2臺以上服務(wù)器發(fā)生數(shù)據(jù)丟失的可能性極小。反倒是人為的操作失誤更容易造成這類事故。而這就是另一個問題了,不在本文的討論范圍內(nèi)。
除了上述這些奇妙問題之外,還有一個在WRN理論中沒有被提及的問題。多數(shù)涉及WRN的論文都說這個算法可以保證強一致性,但實際上這是錯的。WRN算法如果要保證強一致性需要有一個條件,就是對每一個副本的寫入要么成功,要么失敗。在實際的使用過程中,副本的寫入?yún)s有第三種狀態(tài)。
對于一個副本的寫入必然是跨越網(wǎng)絡(luò)的。于是,一次副本寫入實際包含三個步驟:向副本服務(wù)器發(fā)送請求;副本服務(wù)器寫入數(shù)據(jù)庫;反饋寫入的結(jié)果。如果前兩個步驟發(fā)生錯誤,那么這次副本寫入操作就是完完全全的失敗。但如果前兩個步驟都成功了,但第三個步驟出現(xiàn)了問題,如網(wǎng)絡(luò)連接中斷,或者數(shù)據(jù)報文丟失等,客戶端會認(rèn)為這次操作是失敗的。但實際上這條數(shù)據(jù)已正確地寫入了數(shù)據(jù)庫。如果此時反饋信息恰巧達(dá)不到W數(shù),那么就會回復(fù)用戶這次元數(shù)據(jù)寫入失敗。而實際數(shù)據(jù)庫中,成功寫入的副本數(shù)卻是滿足W數(shù)的。在下一次讀取這個元數(shù)據(jù)時,卻讀到了被認(rèn)為失敗的元數(shù)據(jù)。也就是說,外界認(rèn)為一個不存在的元數(shù)據(jù),在系統(tǒng)中卻是真實存在的。這種一致性問題同樣是無法消除的。失敗回滾操作可以減少問題的發(fā)生概率,但終究無法徹底杜絕。
整體上,多副本模型輕而易舉地解決了可靠性和可用性,卻將問題集中到一致性上。一致性問題有很多既定的解決手段,每一種手段都有各自的問題,無法徹底將一致性問題解決。但綜合這些手段,可以很好地將一致性提升到近似的強一致性級別,從而滿足實際的需要。
WRN算法在具體使用中,N、W、R的選擇頗有些講究。首先,N數(shù)不能太少。我們來看幾種不同的WRN設(shè)置:
N=3,W=2,R=2,W+R-N=1
N=6,W=4,R=4,W+R-N=2
N=9,W=6,R=6,W+R-N=3
這三種配置都滿足W+R>N。但效果卻完全不同。對于N=3的配置,只能容忍1臺服務(wù)器下線。另外一個更麻煩的問題是,盡管寫入時有兩個副本成功了,一個副本失敗,這算是成功的。但如果其中一個成功寫入的副本發(fā)生了丟失,這時候殘存的只有一個副本,那么我們在讀取時,實際上無法判別這一個副本是寫入失敗殘存的副本,還是副本退化造成的。N=3時,這種混淆很容易發(fā)生。
而隨著N數(shù)的增大,所能夠容忍的副本下線和副本退化的數(shù)量就多了。在既定時間段內(nèi),同時下線多個副本,或者同時丟失多個副本的概率就小很多了。因此,從一致性保障角度而言,N數(shù)越大越好。當(dāng)然,考慮到資源,N數(shù)終究不可能無限多,一般6-9是比較容易接受的數(shù)字。盡管副本數(shù)如此多,但畢竟元數(shù)據(jù)相對數(shù)據(jù)存儲本身有著多個數(shù)量級的差異,所占用的服務(wù)器資源畢竟還是少數(shù)。
在這里,我們可以看到那些去中心化方案在一致性保障方面的缺陷:因為數(shù)據(jù)本身的量較大,所以考慮到成本,副本數(shù)不會太多,通常會選擇N=3。很明顯,3這個數(shù)字對于一致性保障而言還是相當(dāng)脆弱的。
至于W和R的具體數(shù)值,一般沒有太多的教條,但它們受制于存儲系統(tǒng)的部署。通常,對于云存儲系統(tǒng)而言,為了保障盡可能高的可靠性和可用性,會設(shè)法將存儲集群按照副本分散到不同的子集群中。每個子集群擁有獨立的交換機、獨立的配電線路,最好在物理位置上也相互獨立。最極端的情況就是分布在不同的IDC中(當(dāng)然不是所有的云計算服務(wù)商都有這樣的資源)。這樣的子集群通常稱為zone。設(shè)置這樣的zone的目的是為了消除在基礎(chǔ)設(shè)施上的單點故障。
在這樣的部署結(jié)構(gòu)下,W和R的選擇應(yīng)當(dāng)確保在一個zone里的副本服務(wù)器數(shù)量不大于W+R-N。這樣的設(shè)置,是為了保證一個zone下線后,系統(tǒng)依然有足夠的副本服務(wù)器滿足WRN算法。
主從模型vs.多副本模型
從前面關(guān)于主從模型和多副本模型的介紹可以看到兩者的差異。主從模型中,主服務(wù)器必然會由于各種原因而下線。一旦主服務(wù)器下線,那么就必須即刻采取行動,將從服務(wù)器切換成主服務(wù)器,保證系統(tǒng)依舊在線。但這種切換操作同樣也會失敗,而且失敗的可能性并不小。
多副本模型更易于保障可靠性和可用性。在多副本模型下,一個成功寫入的元數(shù)據(jù)任何時刻都會有多個副本存在,任何時刻都會有多個實例在提供服務(wù)。服務(wù)器單點故障不會對元數(shù)據(jù)系統(tǒng)的運行產(chǎn)生影響,整個系統(tǒng)還是按照正常的運作方式運行。這一點非常重要,這意味著可以將一個實時的在線運維事件變成了可以暫緩處理的離線運維事件。任何一臺元數(shù)據(jù)服務(wù)器出現(xiàn)故障,也不需要立刻做出反應(yīng)。系統(tǒng)繼續(xù)正常運行,運維人員則可以從容地處理問題。這是確保高可用性的關(guān)鍵所在。
主從模型本可以簡化程序的結(jié)構(gòu),因為客戶端只需要向主服務(wù)器發(fā)出請求,訪問元數(shù)據(jù)。但如同前面所描述的,可靠性、可用性和一致性的要求促使主從之間進(jìn)行并發(fā)訪問,以彌補這些特性方面的漏洞。這實際上就是將原本在元數(shù)據(jù)系統(tǒng)之外的多副本訪問邏輯移到主服務(wù)器中,卻憑空增加了延遲,以及主從切換的麻煩。
在一致性方面,主從模型在沒有考慮軟硬件異常和數(shù)據(jù)退化的情況下,基本上沒有什么難點。但因為現(xiàn)實中沒有軟硬件異常是不可能的,數(shù)據(jù)退化也是時刻在發(fā)生的。于是,無論主、從服務(wù)器都可能缺少數(shù)據(jù),更無法單純通過主服務(wù)器來維持?jǐn)?shù)據(jù)的一致性。為了獲得盡可能高的一致性,主從模型也需要通過整合主從副本來實現(xiàn)一致性保障,而副本修復(fù)也同樣是不可缺少的一部分。實際上,無論是主還是從,都應(yīng)當(dāng)看作是一個元數(shù)據(jù)的副本,既然是多個副本,那么也就存在一致性的問題。多副本遇到的問題,主從模型同樣也會遇到,這就是主從模型不如多副本模型來得合用的原因。
主從模型的主要優(yōu)勢是可以執(zhí)行復(fù)雜的數(shù)據(jù)庫邏輯,如關(guān)聯(lián)、聚合等。但這在多副本模型下非常難。主從模型下,一個復(fù)雜的查詢得到執(zhí)行后,將冪等的結(jié)果數(shù)據(jù)同步到從服務(wù)器。而對多副本模型來說,每個副本獨自處理查詢,會產(chǎn)生混亂的結(jié)果,因為復(fù)雜查詢往往是非冪等的。
對象存儲之所以可以使用多副本,是因為對象存儲的業(yè)務(wù)模型已被簡化到不需要復(fù)雜查詢的地步。所有寫入操作都只是增加一條元數(shù)據(jù)而已,天生的冪等操作。對象存儲放棄目錄結(jié)構(gòu),只實現(xiàn)Key/Value模型,也是為了避免復(fù)雜查詢的存在。
總結(jié)
在對象存儲中,元數(shù)據(jù)用于保存數(shù)據(jù)存儲位置、數(shù)據(jù)屬性等信息。元數(shù)據(jù)的好壞關(guān)系到整個對象存儲系統(tǒng)的可靠性、可用性、一致性等方面。元數(shù)據(jù)有如此重要的功能,必須有強有力的保障加以支撐。
元數(shù)據(jù)的存在雖然表面上增加了系統(tǒng)的復(fù)雜性,但實際上是將一致性保障、數(shù)據(jù)存儲基準(zhǔn)、可靠性檢測和修復(fù)、數(shù)據(jù)統(tǒng)計等重要的功能集中到更小的數(shù)據(jù)量上,更加易于實現(xiàn)和操作。這在去中心化的方案中是難以實現(xiàn)的。
元數(shù)據(jù)的兩種主要模型包括主從模型和多副本模型。主從模型,作為常規(guī)的數(shù)據(jù)庫高可用手段,并不能滿足云存儲系統(tǒng)的諸多特性。而多副本模型則能更好地平衡各方面需求,同時也很好地平衡了研發(fā)、部署和運維等方面的要求。
多副本模型的核心難點集中在一致性保障方面,針對這些問題都有相應(yīng)的解決手段,但都無法完全消除問題的存在。綜合運用各種保障方法,可以極大地減少一致性問題的發(fā)生概率,最終將其降低到服務(wù)可以接受的程度。