GFS的写入一致性
目錄
- 一、命名空間修改的原子性——命名空間鎖保障原子性和正確性
- 二、Chunk位置信息的改變
- 三、Chunk內(nèi)容的一致性
- (1)數(shù)據(jù)修改方式
- (2)寫入結(jié)果和狀態(tài)
- (3)Record Append具體的處理流程:
- (4)過期失效的副本檢測
一、命名空間修改的原子性——命名空間鎖保障原子性和正確性
文件命名空間:
不同于許多傳統(tǒng)文件系統(tǒng),GFS沒有針對每個目錄實(shí)現(xiàn)能夠列出目錄下所有文件的數(shù)據(jù)結(jié)構(gòu)。GFS也不支持文件或者目錄的鏈接(即Unix術(shù)語中的硬鏈接或者符號鏈接)。在邏輯上,GFS的命名空間就是一個全路徑和元數(shù)據(jù)映射關(guān)系的查找表。利用前綴壓縮,這個表可以高效的存儲在內(nèi)存中。在存儲命名空間的樹型結(jié)構(gòu)上,每個節(jié)點(diǎn)(絕對路徑的文件名或絕對路徑的目錄名)都有一個關(guān)聯(lián)的讀寫鎖。
如上圖所示,文件命名空間葉子節(jié)點(diǎn)是一個文件的全路徑,指向了該文件的元信息。
例子:我們演示一下在/home/user被快照到/save/user的時候,鎖機(jī)制如何防止創(chuàng)建文件/home/user/foo??煺詹僮鳙@取/home和/save的讀取鎖,以及/home/user和/save/user的寫入鎖。文件創(chuàng)建操作獲得/home和/home/user的讀取鎖,以及/home/user/foo的寫入鎖。這兩個操作要順序執(zhí)行,因?yàn)樗鼈冊噲D獲取的/home/user的鎖是相互沖突。文件創(chuàng)建操作不需要獲取父目錄的寫入鎖,因?yàn)檫@里沒有”目錄”,或者類似inode等用來禁止修改的數(shù)據(jù)結(jié)構(gòu)。文件名的讀取鎖足以防止父目錄被刪除。
采用這種鎖方案的優(yōu)點(diǎn)是支持對同一目錄的并行操作。比如,可以再同一個目錄下同時創(chuàng)建多個文件:每一個操作都獲取一個目錄名的上的讀取鎖和文件名上的寫入鎖。目錄名的讀取鎖足以的防止目錄被刪除、改名以及被快照。文件名的寫入鎖序列化文件創(chuàng)建操作,確保不會多次創(chuàng)建同名的文件。
因?yàn)槊Q空間可能有很多節(jié)點(diǎn),讀寫鎖采用惰性分配策略,在不再使用的時候立刻被刪除。同樣,鎖的獲取也要依據(jù)一個全局一致的順序來避免死鎖:首先按名稱空間的層次排序,在同一個層次內(nèi)按字典順序排序。
二、Chunk位置信息的改變
上圖右側(cè)即Chunk位置信息,Master不持久化保存,只是在啟動時輪詢Chunk服務(wù)器獲取位置信息——Chunk服務(wù)器來維護(hù)這些信息。
三、Chunk內(nèi)容的一致性
GFS一般是三副本,所以當(dāng)同一份數(shù)據(jù)要寫入不同的ChunkServer服務(wù)器時,會遇到部分寫入失敗的問題,這就是數(shù)據(jù)寫入一致性的問題。
Table1中的第一行Write和Record Append指的是兩種數(shù)據(jù)修改方式,第一列Serial success 、Concurrent successes、Failure指的是三種寫入的結(jié)果。對應(yīng)兩種數(shù)據(jù)修改方式和三種結(jié)果,產(chǎn)生了四種不同的狀態(tài)。
(1)數(shù)據(jù)修改方式
Write:是傳統(tǒng)的寫入方式,客戶程序會指定數(shù)據(jù)寫入的偏移量。對同一個region的寫入操作不是串行的:region尾部可能會包含多個不同客戶端寫入的數(shù)據(jù)片段。
Record Append:是將客戶端要寫入的數(shù)據(jù)追加到由GFS指定的位置,追加完成后GFS返回這個偏移量給客戶端。GFS保證至少有一次原子的寫入操作成功執(zhí)行(即寫入一個順序的byte流),即At Least Once”。
(2)寫入結(jié)果和狀態(tài)
consistent:一致的,多個副本Replica讀出來的數(shù)據(jù)都是一樣的。
defined:確定的,就是客戶端寫入的數(shù)據(jù)能夠完整地被讀到。即:每個客戶端寫入指定offset的數(shù)據(jù) 和 再從offset讀出來的數(shù)據(jù)是相同的。(不會被其他客戶端寫入的內(nèi)容覆蓋)
inconsistent:不一致的,如果數(shù)據(jù)寫入是失敗的(部分副本成功,部分副本失敗也是寫入失敗),那么GFS中的數(shù)據(jù)必然是不一致的inconsistent。
Serial success-Write-defined:**如果是順序?qū)懭?#xff0c;寫入成功,那么文件中的數(shù)據(jù)就是確定的defined,客戶端讀到的數(shù)據(jù)也是確定的defined。**串行寫入成功,不會有不同客戶端寫入的內(nèi)容交叉,即undefined的狀態(tài)出現(xiàn)。
Concurrent successes-Write-consistent but undefined:并發(fā)寫入,由于多個客戶端并發(fā)地向同一個文件中寫入數(shù)據(jù),三副本都寫入成功了,如果多個客戶端寫入的數(shù)據(jù)有可能出現(xiàn)交叉。
例子:
客戶端A寫入數(shù)據(jù)范圍[50, 80],客戶端B寫入數(shù)據(jù)范圍[70, 100],就會出現(xiàn)下面的情況:客戶端A寫入成功后,客戶端B有可能會覆蓋了客戶端A的數(shù)據(jù);也可能是客戶端B寫入成功后,客戶端A可能覆蓋了客戶端B的數(shù)據(jù)。但是客戶端A和客戶端B都寫成功了,其3個副本都是按同樣的順序?qū)懙臄?shù)據(jù),客戶端無論從哪個副本讀,數(shù)據(jù)都是一致的consistent,但是客戶端去讀自己寫入的數(shù)據(jù),就會出現(xiàn)讀出來的數(shù)據(jù)有部分交叉的內(nèi)容,這部分內(nèi)容是客戶端A寫入的,還是客戶端B寫入的是不確定的undefined。
為什么并發(fā)寫入成功時會出現(xiàn)consistent but undefined的狀態(tài)呢?
一是,數(shù)據(jù)的寫入順序并不需要通過Master來協(xié)調(diào),而是直接發(fā)送給ChunkServer,由ChunkServer來管理數(shù)據(jù)的寫入順序;二是,隨機(jī)寫操作大概率會橫跨多個Chunk。這就導(dǎo)致一個Chunk可能既包含數(shù)據(jù)A,又包含數(shù)據(jù)B。隨機(jī)數(shù)據(jù)的寫入并非原子性的,也沒有事務(wù)性保證,因此需要客戶端在寫入數(shù)據(jù)時是順序?qū)懭氲?#xff0c;避免并發(fā)寫入的出現(xiàn)。那么問題也來了,GFS允許幾百個客戶端讀寫訪問,那該怎么避免這種一致但非確定的狀態(tài)呢?GFS設(shè)計(jì)了記錄追加Record Append來解決這個問題。
為什么Record Append可以避免consistent but undefined的狀態(tài)?
GFS的Record Append操作:寫操作時,GFS并不指定在Chunk的哪個位置offset寫入數(shù)據(jù),而是告訴Chunk的主副本Primary Replica服務(wù)器“我要追加記錄”,由主副本ChunkServer根據(jù)當(dāng)前Chunk位置決定寫入的offset,在寫入成功后將該offset返回給客戶端,客戶端根據(jù)offset確切知道寫入結(jié)果,無論是串行寫入,還是并發(fā)寫入,其結(jié)果都是確定的defined。(從根本上避免了不同客戶端寫入內(nèi)容的覆蓋)
(3)Record Append具體的處理流程:
1)首先,主副本ChunkServer會校驗(yàn)當(dāng)前的Chunk能否容納下要追加的數(shù)據(jù),如果能,主副本ChunkServer就將數(shù)據(jù)追加到當(dāng)前Chunk中,然后給次副本ChunkServer發(fā)送指令,次副本ChunkServer也將數(shù)據(jù)追加到副本Chunk中。
2)如果當(dāng)前的Chunk不能容納,主副本ChunkServer就把當(dāng)前Chunk剩余空間填充空數(shù)據(jù),然后發(fā)送給次副本ChunkServer也把副本Chunk剩余空間填充空數(shù)據(jù);然后主副本ChunkServer回復(fù)客戶端“在下一個Chunk上寫入”,客戶端重新從Master節(jié)點(diǎn)獲取新的Chunk位置,數(shù)據(jù)傳輸完畢后,再次向新的Chunk所在的主副本ChunkServer發(fā)起寫指令。
3)因?yàn)閿?shù)據(jù)寫入的順序是由主副本來控制的,且數(shù)據(jù)寫入只有追加操作,因此主副本ChunkServer會將并發(fā)寫入的數(shù)據(jù)進(jìn)行排隊(duì),然后依次追加寫入,這樣就不會出現(xiàn)相互覆蓋的情況了。
4)為了確保Chunk剩下的空間能存的下需要追加的數(shù)據(jù),GFS限制了一次記錄追加的數(shù)據(jù)大小為16MB,而Chunk大小默認(rèn)是64MB,所以當(dāng)空間不足需要補(bǔ)空數(shù)據(jù)時,最多也就是16MB,也就是最多也就浪費(fèi)1/4的空間,不至于浪費(fèi)太多。
defined interspersed with inconsistent:確定的但夾雜著不一致的數(shù)據(jù)
當(dāng)某一副本寫失敗的時,會在失敗的Chunk offset處填充空數(shù)據(jù),三個副本一起重新寫入一次,這就是“At Least Once”
例子1:
無并發(fā)的Record Append
客戶端追加了一個數(shù)據(jù)a,第一次執(zhí)行一半Secondary2失敗了,那么此時這個Chunk的副本情況如下:
于是,客戶端重新發(fā)起一次追加寫操作,Primary先操作offset2后再將請求發(fā)給Secondary操作offset2,由于Secondary2上次操作offset1未成功,所以會先補(bǔ)空數(shù)據(jù),然后再從offset2處寫數(shù)據(jù),那么此時副本情況如下:
并返回給客戶端offset2。于是中間的offset1部分就是不一致的inconsistent,但對于新追加的數(shù)據(jù)就是確定的defined,客戶端讀offset2,就可以確定的讀到a,這就是“defined interspersed with inconsistent確定的但夾雜著不一致的數(shù)據(jù)”。
例子2:并發(fā)記錄追加
兩個客戶端分別向同一個文件追加數(shù)據(jù)a和數(shù)據(jù)b,Client1追加數(shù)據(jù)a,Client2追加數(shù)據(jù)b。假如Primary排序b,a。然后執(zhí)行追加寫操作,假如執(zhí)行一半Secondary2失敗了,同理,Client2會再次追加一遍,那么這個Chunk的副本情況如下:
于是Client1收到offset2,Client2收到offset3,offset2和offset3都是確定的defined,而offset1是不一致的inconsistent,同樣是“defined interspersed with inconsistent確定的但夾雜著不一致的數(shù)據(jù)”。
不同的副本中可能被追加了不同的次數(shù),但“至少一次”是確定的defined,這就是“At Least Once”,這就是GFS承若的一致性。可見GFS對寫入數(shù)據(jù)的一致性保障相當(dāng)?shù)?#xff0c;它只是保障了所有數(shù)據(jù)追加至少被寫入一次。
GFS將校驗(yàn)和去重的工作交給了客戶端,GFS在客戶端自帶了對寫入的數(shù)據(jù)去添加校驗(yàn)和(checksum),在讀取數(shù)據(jù)的時候計(jì)算驗(yàn)證數(shù)據(jù)的完整性功能。而對于重復(fù)寫入多次的問題,也可以對每一條要寫入的數(shù)據(jù)生成一個唯一的ID,帶上時間戳,那么即便數(shù)據(jù)順序不對,有重復(fù),也很容易在數(shù)據(jù)處理中根據(jù)ID進(jìn)行排序和去重。
經(jīng)過了一系列的成功的修改操作之后,GFS確保被修改的文件region是已定義的,并且包含最后一次修改操作寫入的數(shù)據(jù)。GFS通過以下措施確保上述行為:
(a) 對Chunk的所有副本的修改操作順序一致,
(b)使用Chunk的版本號來檢測副本是否因?yàn)樗诘腃hunk服務(wù)器宕機(jī)而錯過了修改操作而導(dǎo)致其失效。
失效的副本不會再進(jìn)行任何修改操作,Master服務(wù)器也不再返回這個Chunk副本的位置信息給客戶端。它們會被垃圾收集系統(tǒng)盡快回收。
(4)過期失效的副本檢測
如上文所說,當(dāng)Chunk服務(wù)器失效時,Chunk的副本有可能因錯失了一些修改操作而過期失效。Master節(jié)點(diǎn)保存了每個Chunk的版本號,用來區(qū)分當(dāng)前的副本和過期副本。
無論何時,只要Master節(jié)點(diǎn)和Chunk簽訂一個新的租約,它就增加Chunk的版本號,然后通知最新的副本。Master節(jié)點(diǎn)和這些副本都把新的版本號記錄在它們持久化存儲的狀態(tài)信息中。這些發(fā)生在任何客戶端向Chunk寫入數(shù)據(jù)之前。如果某個副本所在的Chunk服務(wù)器失效,那么他就持有一個過期的版本號。Master節(jié)點(diǎn)在這個Chunk服務(wù)器重新啟動,并且向Master節(jié)點(diǎn)報告它擁有的Chunk的集合以及相應(yīng)的版本號的時候,就會檢測出它包含過期的Chunk。如果Master節(jié)點(diǎn)看到一個比它記錄的版本號更高的版本號,Master節(jié)點(diǎn)會認(rèn)為它和Chunk服務(wù)器簽訂租約的操作失敗了,因此會選擇更高的版本號作為當(dāng)前的版本號。
Master節(jié)點(diǎn)在例行的垃圾回收過程中移除所有的過期失效副本。在此之前,Master節(jié)點(diǎn)在回復(fù)客戶機(jī)的Chunk信息請求的時候,簡單的認(rèn)為那些過期的塊根本就不存在。另外一重保障措施是,Master節(jié)點(diǎn)在通知客戶機(jī)哪個Chunk服務(wù)器持有租約、或者指示Chunk服務(wù)器從哪個Chunk服務(wù)器進(jìn)行克隆時,消息中都附帶了Chunk的版本號??蛻魴C(jī)或者Chunk服務(wù)器在執(zhí)行操作時都會驗(yàn)證版本號以確??偸窃L問當(dāng)前版本的數(shù)據(jù)。
參考文章:
05| GFS論文解讀(2)
分布式系統(tǒng)論文精讀2:GFS
總結(jié)
- 上一篇: cewl工具(URL字典生成器)
- 下一篇: 计算圆柱面积