1. 程式人生 > >[分散式檔案系統] Sun's Network File System

[分散式檔案系統] Sun's Network File System

第一次使用分散式客戶端伺服器模式的計算的其中一個領域是分散式檔案系統。在這樣一個環境中,有許多客戶機和一個伺服器(伺服器或者更多),伺服器將資料儲存在它的本地磁碟上,客戶機通過組織良好的協議訊息來獲取資料。


正如上面圖片中看到的,伺服器有磁碟,客戶機發送訊息來獲取它們需要的目錄和檔案。為什麼我們要使用這種很麻煩的佈局呢?(比如,為什麼不直接使用客戶機的本地磁碟呢?)主要的原因是這種佈局使得在客戶機之間的資料共享更加容易。比如,如果你訪問Client 0上的一個檔案,然後你使用Client 2,你會看到相同的檔案系統檢視。資料可以很自然地在不同機器上共享。第二個原因是集中管理,比如,要備份檔案,可以只備份少數幾個伺服器上的資料,而不用備份許多客戶機上的資料。另一個好處是安全,將所有的伺服器放在一個鎖好的機房裡可以防止特定型別的問題發生。

關鍵點:如何構建一個分散式檔案系統?
我們如何構建一個分散式檔案系統?需要思考哪些關鍵因素?什麼容易導致錯誤?可以從現在的系統上學到什麼?

8.1 一個基本的分散式檔案系統

現在,讓我們來研究一個基本的分散式檔案系統的架構。一個簡單的客戶機伺服器分散式檔案系統比我們已經研究過的檔案系統有更多的元件。在客戶端,客戶端程式通過客戶端檔案系統訪問檔案和目錄。客戶端程式為了獲取伺服器上的資料可以向客戶機發送系統呼叫。這樣,對於客戶端程式而言,分散式檔案系統跟本地檔案系統沒什麼不同,除了效能,因此,從這個方面說,分散式檔案系統的一個目標是可以透明地訪問檔案,畢竟,誰想用一個需要另外一套API來使用的檔案系統呢?

客戶端的檔案系統需要對這些系統呼叫進行響應。比如,如果客戶端程式發出一個read()請求,客戶端檔案系統可能會想伺服器檔案系統傳送一個訊息來讀取一個特定的塊,然後,檔案伺服器會從磁碟(或者快取)中讀取塊,然後將請求的資料構成一條訊息傳送給客戶端。客戶端檔案系統會將資料拷貝到read()系統呼叫提供的使用者緩衝區中,一個請求過程就結束了。注意,客戶端在接下來請求的同一個塊可能會快取在記憶體或者本地磁碟中,因此,最好的情況下,沒有產生任何網路流量。



從這個簡單的試圖可以看出,你必須知道在一個分散式檔案系統中有兩種不同的軟體:客戶端檔案系統和檔案伺服器。它們的行為共同決定分散式檔案系統的行為。現在,是時候來研究一個特定的系統了:Sun的NFS。

旁白:伺服器為什麼會崩潰?
在深入瞭解NFSv2協議之前,你可能會奇怪:伺服器為什麼會崩潰?確實,你可能會猜,有許多原因導致伺服器崩潰。伺服器會因為斷電而暫停服務,不過那是暫時的;當電力恢復時,機器就可以重啟。伺服器軟體可能包括十幾萬行或者幾百萬行程式碼;因此,可能會有bugs,甚至好的軟體每一百萬行程式碼就會有一些bugs,伺服器軟體可能會觸發一個導致它們崩潰的bug。伺服器軟體可能會有記憶體洩露;即使是一個很小的記憶體洩露也可能導致記憶體耗盡,然後就會崩潰。最後,在一個分散式系統中,客戶端和伺服器之間用網路連線,如果網路的行為異常(比如,如果網路被隔離了,客戶端和伺服器可以工作,但是不能通訊)於是,它們的行為就好像遠端機器崩潰了,但是事實是不能通過網路到達。

48.2 On to NFS

最早的且非常成功的一個分散式系統是Sun開發的NFS。在定義NFS時,Sun採用了一種不同尋常的方法:沒有建立一個專有的封閉的系統,而是開發了一種開放的協議,該協議只是指定了客戶端和伺服器通訊的精確的訊息格式。不同的組織可以開發它們自己的NFS伺服器,然後在保留互操作性的情況下爭奪NFS市場。NFS確實很好:今天,許多公司在銷售它們的NFS伺服器(包括Oracle,NetApp,EMC,IBM等等),NFS的成功可能要歸功於這個開放市場的方法。

48.3 重點:簡單和伺服器快速宕機恢復

在這節中,我們會討論傳統的NFS協議(NFSv2),它在過去許多年來都是標準。從NFSv2到NFSv3變化不大,從NFSv2到NFSv4有大規模的協議變化。然而,NFSv2是如此奇妙,而且它令人不快,因此,它是我們的研究重點。
NFSv2設計的主要目標是簡單和伺服器快速宕機恢復。在多客戶端、單伺服器環境下,這個目標很有意義,任何時候伺服器宕機會使得所有的客戶失望,造成所有的客戶端不能正常工作。因此,伺服器對於整個系統是個關鍵。

48.4 快速宕機恢復的關鍵:無狀態性

為了實現這個簡單的目標,NFSv2被設計為一個無狀態性的協議。伺服器並不跟蹤每個客戶端所發生的事。比如,伺服器不知道哪個客戶機快取了哪些塊,或者每個客戶機當前打開了哪些檔案,或者某個檔案的當前檔案指標位置等等。因此,伺服器並不知道客戶端當前正在做的事,而且,協議被設計用來傳送完成請求所需要的所有資訊。如果它不知道,正如我們接下來討論的,這種無狀態的方法更有意義。


來看一個有狀態的協議的例子,考慮open()系統呼叫。給定一個路徑,open()返回一個檔案描述符。這個檔案描述符在接下來的read()或者write()操作中用來訪問檔案塊。
現在,假設,客戶端檔案系統開啟一個檔案,向伺服器傳送一個協議訊息,說“開啟檔案foo,返回給我檔案描述符”。然後,檔案伺服器開啟檔案,將檔案描述符回送給客戶端。在接下來的讀操作中,客戶端程式使用檔案描述符呼叫read()系統呼叫;客戶端檔案系統將檔案描述符封裝成一個訊息,然後將訊息傳送給檔案伺服器,說“從我發給你的檔案描述符指向的檔案讀取一些位元組”。


在這個例子中,檔案描述符是客戶機和伺服器共享的狀態。正如我們在上面提到的共享狀態使得宕機恢復複雜。假如,伺服器在第一個讀操作完成後,但是在客戶機發送第二個請求前崩潰了。在伺服器重啟後,客戶機發送第二個讀請求。不幸的是,伺服器不知道檔案描述符fd指向哪個檔案,該資訊是個瞬時資訊,因此,當伺服器崩潰後,該資訊就丟失了。為了處理這種情況,客戶機和伺服器會啟動某種恢復協議,客戶端要確保它本身儲存了記憶體中足夠的資訊,這樣它就能夠告訴伺服器發生了什麼。
如果考慮一個狀態伺服器要處理客戶機的宕機,情況會變得更糟。比如說,客戶端打開了一個檔案,然後崩潰了。open()操作使用的是伺服器上的一個檔案描述符,伺服器如何能夠知道可以關閉這個檔案?在正常情況下,客戶端最後會呼叫close()告訴伺服器應該關閉這個檔案。然而,如果客戶機崩潰了,伺服器就接收不到close(),因此,為了關閉這個檔案,伺服器必須知道客戶機已經崩潰了。


基於這些原因,NFS的設計者們提出了一種無狀態的方法:每個客戶端操作包含用於完成請求的所有資訊。不需要代價較高的宕機恢復;伺服器只需要重啟,在更糟的情況下,客戶端可能重新發送請求。

48.5 NFSv2

總算可以看看NFSv2協議了。我們的問題很簡單:
關鍵點:如何定義無狀態的檔案協議?
我們如何才能定義網路協議來實現無狀態操作呢?我們已經知道,有狀態的呼叫(比如open())不是我們討論的部分(因為它需要伺服器跟蹤已經開啟的檔案),然而,客戶端程式仍然希望使用open()、read()、write()、close()等其它標準API來訪問檔案和目錄。因此,這個問題可以改為:我們如何才能定義一個協議,它是無狀態的,同時它支援POSIX檔案系統API。

理解NFS的一個關鍵是理解檔案控制代碼(file handle)。檔案控制代碼用於唯一地描述一個特定的檔案操作所操作的檔案或者目錄;因此,許多協議請求包含一個檔案控制代碼。

可以認為檔案控制代碼包含三個重要部分:卷識別符號、索引節點號和生成號;這三個部分共同構成了客戶端希望訪問的檔案或者目錄的唯一識別符號。卷識別符號告訴伺服器這個請求指向哪個檔案系統;索引節點號告訴伺服器這個請求訪問的是該分割槽的哪個檔案。最後,當要重用一個索引節點號時,生成號是必要的;當要重用一個索引節點號時,就對生成號遞增;伺服器保證了客戶端不能使用一箇舊的檔案控制代碼訪問新建立的檔案。

下面是該協議的一些重要部分的總結;整個協議的內容在網上都能夠找到。



我們對該協議的重要部分設定了高亮。首先,LOOKUP協議訊息用於獲取一個檔案控制代碼,之後就可以使用這個檔案控制代碼進行檔案訪問。客戶端傳遞一個目錄檔案控制代碼和檔案的名字進行查詢,檔案控制代碼和它的屬性被返回給客戶端。
比如,假設客戶端已經有了一個檔案系統的根目錄的檔案控制代碼(事實上,根目錄的檔案控制代碼是通過NFS掛載協議獲得的,NFS掛載協議是客戶端和伺服器開始是如何連線的)。如果客戶端的一個程式打開了一個檔案/foo.txt,客戶端檔案傳送一個lookup請求給伺服器,將根目錄的控制代碼和名字foo.txt包裝到訊息中;如果操作成功,伺服器就會返回foo.txt的檔案控制代碼和屬性給客戶端。

在上面提到了檔案屬性,其實,檔案屬性就是檔案系統跟蹤每個檔案的元資料,包括建立時間、最後更改時間、檔案大小、所有者、訪問許可權資訊和其它資訊,就像呼叫stat()獲得的資訊。

好了,現在可以使用檔案控制代碼了,客戶端可以傳送READ和WRITE協議訊息來讀寫這個檔案。READ協議訊息還要傳送檔案讀取的偏移量和讀取位元組數。然後,伺服器就會發送read操作(畢竟,描述符告訴了伺服器要讀取哪個卷中的哪個檔案,偏移量和位元組數告訴伺服器該讀取哪部分資料),然後將資料返回給客戶端。WRITE操作類似,除了資料是從客戶端傳送到伺服器,返回一個成功碼。

最後一個有趣的協議訊息是GETATTR請求;給定一個檔案控制代碼,它可以獲得該檔案的屬性,包括檔案的最後更改時間。下面我們會看到為什麼在討論快取時這個協議訊息在NFSv2中如此重要?

48.6 從協議到分散式檔案系統

現在,你很想知道如何將該協議轉換成跨越客戶端檔案系統和檔案伺服器的檔案系統。客戶端檔案系統跟蹤開啟的檔案,將應用程式請求轉換成對應的協議訊息。伺服器只需要響應每個協議訊息,每個協議訊息包含完成請求的所有資訊。

比如,我們來看看一個讀檔案的簡單程式。在圖48.1中,顯示了程式呼叫了哪些系統呼叫,客戶端檔案系統和檔案伺服器對這些呼叫做了什麼操作。

圖中包含一些註釋。首先,需要注意的是,客戶端如何跟蹤檔案訪問操作的對應狀態,包括檔案描述符與NFS檔案控制代碼的對應和當前檔案指標。這能夠使得客戶端將每個讀請求轉換成合適的讀協議訊息,該協議訊息告訴伺服器從檔案中讀取哪些位元組。讀取操作成功了,客戶端會更新當前檔案位置;接下來的讀取操作針對同一個檔案控制代碼,但是偏移量不同。

其次,你可能注意到了伺服器交互發生的位置。當檔案第一次開啟時,客戶端檔案系統傳送LOOKUP請求訊息。事實上,如果檔名包含一個很長的路徑,比如/home/remzi/foo.txt,客戶端會發送三次LOOKUP:第一次在根目錄查詢home,第二次在home目錄下查詢remzi,第三次在remzi目錄下查詢foo.txt。

最後,你可能注意到每個伺服器請求包含了了需要完成請求的所有資訊。這個設計在伺服器宕機後的恢復過程中十分重要;它保證了伺服器不需要儲存狀態來響應請求。


訣竅:冪等性是有用的
冪等性在構建可靠系統時是一個非常有用的屬性。當一個操作可以執行多次,那麼,處理操作的失敗就十分容易;你可以進行重試。如果一個操作不是冪等的,生活就會變得如此艱難。

48.7 用冪等操作處理伺服器故障

當客戶端向伺服器傳送一條訊息,有時收不到迴應。造成響應失敗的原因有很多。有時,這條訊息可能被網路丟棄了;網路丟掉了訊息,那麼,無論是請求還是響應都丟失了,客戶端永遠也收不到響應。

還有可能是伺服器崩潰了,伺服器就不會響應訊息。之後,伺服器重啟,但是,所有的請求已經丟失了。在所有的情況下,客戶端留下了一個問題:當伺服器沒有及時地響應,客戶端應該怎麼做呢?

在NFSv2中,客戶端使用一種一致的優雅的方式:重新發送請求。在客戶端傳送請求後,設定一個定時器。如果在定時器超時之前,收到了響應,就取消定時器,操作成功。然而,如果定時器超時了,沒有收到任何響應,客戶端就會認為請求沒有被處理,然後重新發送請求。如果伺服器響應了,一切是多麼完美啊,客戶端如此靈活地處理了這個問題。

客戶端可以重新發送請求(無論造成故障的原因是什麼),這要歸功於大多數NFS請求的一個重要特性:它們是冪等的。當一個操作執行多次的效果跟執行一次的效果一樣,就稱這個操作是冪等的。比如,如果你在一個記憶體地址儲存一個值三次,它的效果於在該地址儲存這個值一次的效果一樣;因此,在記憶體中儲存值這個操作就是一個冪等操作。然而,如果你在一個計數器上遞增三次,它帶來的結果與執行一次的結果不同;因此,計數器遞增不是冪等的。更一般地,任何只讀取資料的操作是冪等的;但是,更新資料的操作必須小心地考慮,看它是否有這個性質。

NFS的宕機恢復的設計核心就是大多數操作都是冪等的。LOOKUP和READ是冪等的,因為它們只從伺服器讀取資料,而不更新。更加有趣的是,WRITE請求通常也是冪等的。比如,如果WRITE失敗了,客戶端就重新發送WRITE請求。WRITE訊息包含資料,資料的個數和寫資料的偏移量。這樣,執行多次寫操作的結果與執行一次的結果相同。

使用這種方法,客戶端可以以一種統一的方式處理所有的超時。如果WRITE請求丟失了(上圖的情況1),客戶端重新發送WRITE請求,伺服器執行寫操作,一切都很美好。如果請求已經發送,伺服器碰巧宕機了,當第二次請求傳送時,所有的操作重新執行(情況2)。最後,伺服器事實上收到了WRITE請求,然後,伺服器向磁碟傳送write操作,將結果傳送給客戶端。這個響應可能丟失(情況3),客戶端重新發送請求。當伺服器再次收到了這個請求,它會做同樣的事:向磁碟寫資料,向客戶端傳送響應。如果客戶端收到了響應,一切工作順利,客戶端以一種統一的方式處理了訊息丟失和伺服器鼓掌。多麼乾淨利落啊!

還剩下一部分:一些操作很難使它變成冪等的。比如,當你想建立一個已經存在的目錄,你知道這個mkdir操作失敗了。因此,在NFS中,如果檔案伺服器接收到了MKDIR訊息,執行成功了,但是響應丟失,客戶端可能會重新發送該訊息,然後該操作失敗,事實上,這個操作在第一次成功了,只是在重試時失敗了。當然,任何事情都不是完美的!!!

訣竅:沒有任何事情是完美的
即使當你設計一個漂亮的系統,有時候,所有的細節情況不會如你預期地執行。看看上面的mkdir的例子;可以將mkdir重新設計,使得它有不同的語義,將它變成冪等的;然而,為什麼如此煩惱呢?NFS的設計哲學包含了大多數的重要情況,它使得系統設計在應對故障時十分簡潔和簡單。因此,請接受吧,沒有任何事情是完美的,設計一個系統是一個好的工程的標誌。顯然,這來自於Voltaire,他說:一個聰明的義大利人說沒有任何事情是完美的。因此,我們稱它為Voltaire定律。

48.8 提高效能:客戶端快取

基於許多原因,分散式檔案系統是很有用的,但是,將所有的讀寫請求都發送到網路上會帶來一個嚴重的效能問題:通常,網路頻寬並不大,特別是相對於本地記憶體或者磁碟而言。因此,另一個問題是:我們如何才能提高分散式檔案系統的效能。

正如你從上面的標題中看到的,答案是:使用客戶端快取。NFS客戶端檔案系統將從伺服器讀取到的資料和元資料快取到本地記憶體中。這樣,第一次訪問的代價較高(比如,它需要網路通訊),接下來的訪問就很快,因為使用的是客戶端記憶體。

快取通常作為一個用於讀的臨時緩衝區。當客戶端程式第一次寫一個檔案時,客戶端在將資料傳送到伺服器之前將資料快取在客戶端記憶體中(與從檔案伺服器讀取的資料快取的位置相同)。這樣的寫快取是很有用的,因為它減少了write()延遲,比如,應用程式呼叫write()立即成功(只是將資料存放到客戶端檔案系統的快取中);之後再將資料寫到檔案伺服器。

這樣,NFS客戶端快取資料,效能得到了提升,一切都很好,不是嗎?其實,情況並沒有那麼好。在任何有多個客戶端的系統中加入快取會帶來一個嚴峻而有趣的挑戰,我們稱之為快取一致性問題。

48.9 快取一致性問題

快取一致性問題可以用兩個客戶端和一個伺服器的系統來解釋。假如客戶端C1讀一個檔案F,然後在它的本地快取中儲存這個檔案的一個副本。現在,另一個客戶端C2重寫了檔案F;我們將這個F檔案的新版本稱為F(v2),之前的檔案F稱為F(v1)。最後,第三個客戶端C3,它還沒有訪問過F。

你可能看到問題所在了。事實上,這裡有兩個子問題。第一個子問題是,C2可能在將資料寫到伺服器之前,將它快取一段時間;在這種情況下,F(v2)儲存在C2的記憶體中,從其它客戶端訪問F時讀取的資料是F(v1)。這樣,將寫操作的資料快取在客戶端,其它的客戶端可能會讀取到老版本的資料,這並不是所期望的行為;事實上,假如你登陸了C2,更新F,然後登陸C3,嘗試讀取這個檔案,所讀取的檔案是老版本的副本。當然,這種情況讓人不爽。我們稱快取一致性問題的這個方面為更新可見性;何時在一臺客戶端上進行更新操作,然後在其它客戶端上可見呢?

快取一致性的第二個子問題是快取失效;在這種情況下,最終C2會將資料寫到檔案伺服器,檔案伺服器就有了F(v2)。然而,C1仍然儲存了F(v1)而不是F(v2),這不是所期望的行為。

NFSv2以兩種方式解決快取一致性問題。首先,為了解決更新可見性問題,客戶端的實現通常稱為flush-on-close一致性語義(也就是close-to-open);特別地,當一個客戶端程式寫檔案,然後關閉檔案,客戶端會將所有的更新刷寫到伺服器。有了flush-on-close一致性,NFS可以保證之後從另一個客戶端開啟檔案能夠得到該檔案的最新版本。

其次,為了解決快取失效問題,NFSv2客戶端在使用快取內容之前會檢查檔案是否已經改變了。特別地,當開啟檔案時,客戶端檔案系統會向伺服器傳送一個GETATTR請求來獲得檔案的屬性資訊。更重要的是,屬性資訊包含伺服器上檔案上次的修改時間;如果更改時間比客戶端快取的檔案的時間要新,客戶端就使快取的檔案失效,並將它從客戶端快取中刪除,保證之後的讀操作會發送到伺服器,以便獲得檔案的最新版本。另一方面,如果客戶端發現快取的檔案是最新版本,它就會使用快取檔案,這樣能夠提高效能。

當Sun的團隊在實現快取失效問題的解決方案時,他們遇到了一個新的問題;NFS伺服器被GETEATTR請求淹沒了。一個良好的工程設計原則是儘量針對一般情況,這樣也能工作得很好。這裡,雖然通常情況是一個檔案只被一個客戶端訪問,客戶端還是經常需要向伺服器傳送GETATTR訊息來保證沒有其它客戶端改變了這個檔案。客戶端就會發送許多訊息,都是詢問“是否有其他人已經改變了該檔案”,但是,大多數時候並沒有人改變這個檔案。

為了改善這個情況,在每個客戶端中添加了屬性快取。客戶端在訪問檔案之前仍然需要檢視檔案是否有效,但是,大多數情況下,只需要從屬性快取中獲取屬性。當檔案第一次訪問時,該檔案的屬性存放在快取中,過一段時間,會超時(3秒)。這樣,在3秒以內,所有的檔案訪問都被認為可以使用快取檔案,這樣的話,就沒有與伺服器的網路互動。

48.10 評估NFS的快取一致性

flush-on-close的加入確實有點作用,但是也帶來一個性能問題。特別地,如果在客戶端建立一個臨時的或者短期的檔案,之後馬上刪除,該行為仍然要傳送到伺服器。一種方法是將這種短期的檔案儲存在記憶體中,直到它們被刪除,這樣就能夠完全消除與伺服器的就互動,或許能夠提高效能。

更重要的是,屬性快取的加入使得我們很難知道我們獲得的檔案是哪個版本。有時,你會獲得最新版本;有時,你可能會獲取一個老版本,因為你的屬性快取還沒有超時,客戶端就會很高興地將客戶端記憶體中的資料發給你。雖然,這在大多數情況下工作得很好,但是,有時會導致奇怪的行為。

我們我已經說明了NFS客戶端快取的一些奇怪問題。

48.11 伺服器端寫快取的含義

目前為止,我們的重點都在客戶端快取,在客戶端快取有許多有趣的問題。然而,NFS伺服器也有記憶體,因此,也需要考慮快取的問題。當資料和元資料從磁碟中讀取出來,NFS會將它們快取在記憶體中,之後對這些資料和元資料的訪問就可以直接訪問快取,這對效能有稍許提高。

更有意思的情況是寫快取。針對一個WRITE請求,NFS伺服器只有等到資料已經寫到固定儲存器(磁碟或者其它持久儲存裝置)中才會返回成功。雖然能夠在伺服器記憶體中儲存一個副本,對WRITE請求返回成功會導致不正確的行為;你能夠找出這是為什麼嗎?

答案在於我們對於客戶端如何處理伺服器失效的假設。假設客戶端傳送了下列寫請求:
write(fd, a_buffer, size); // fill first block with a’s
write(fd, b_buffer, size); // fill second block with b’s
write(fd, c_buffer, size); // fill third block with c’s
這幾個寫操作用三個緩衝區的資料對檔案的三個塊進行重寫。如果,檔案開始是這樣的:

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz


我們可能會期望在經過三個寫操作之後結果是這樣,三個塊分別被a's,b's,c's重寫。
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc


現在,作為一個例子,我們假設這三個寫操作作為三個獨立的WRITE協議訊息傳送到伺服器。假設伺服器受到第一個WRITE訊息,對磁碟進行操作,通知客戶端操作成功。現在,假設第二個寫操作僅僅快取在記憶體中,在將資料寫到磁碟之前,伺服器通知客戶端操作成功;不幸的是,伺服器在將資料寫到磁碟之前崩潰了。伺服器立刻重啟,受到第三個寫請求,當然也成功了。

這樣,對於客戶端而言,所有的請求都成功了,但是,我們發現內容變成了這樣:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy <--- oops
cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc

因為伺服器在將資料寫到磁碟之前告訴客戶端第二個寫操作成功了,於是,檔案的這部分資料仍然是老資料,這可能導致災難性的後果。

為了解決這個問題,NFS伺服器在通知客戶端操作是否成功之前必須將資料寫到持久儲存裝置中;這樣做能夠使客戶端在寫操作過程中檢測伺服器是否崩潰,然後進行重試,直到成功。這樣做能夠保證我們永遠不會使資料發生上述例子中的混亂情況。

這個要求給NFS伺服器的實現帶來的一個問題是寫效能會成為主要的效能瓶頸。事實上,一些公司(比如NetApp)採用一種已經存在的方式,他們的目標是使得NFS伺服器能夠快速執行寫操作;他們使用的一個技巧是先把寫的資料放在一個帶後背電池的儲存器中,使得可以快速響應WRITE請求,而不用擔心資料的丟失,也消除了將資料寫到磁碟的開銷;第二個技巧是使用一個特別設計的檔案系統,這個檔案系統能夠在需要進行寫操作時快速地將資料寫到磁碟。

48.12 總結

我們已經瞭解了NFS分散式檔案系統的一些介紹。NFS的重心在於設計簡單,面對伺服器崩潰時的快速恢復,通過良好的協議設計來達到這個目標。操作的冪等性是必要的;因為客戶端可以安全地響應失敗的操作,無論伺服器是否執行了請求,這樣做都是可行的。

我們也瞭解了在多客戶端單伺服器環境下快取的加入如何使得情況變得複雜。特別地,為了使行為正確,系統必須解決快取一致性問題;然而,NFS採用的方式會導致奇怪的行為。最後,我們看到伺服器快取有也有問題:伺服器的寫操作在向客戶端回送成功訊息之前必須將資料寫到持久儲存器中(否則,資料可能會丟失)。

我們沒有討論其它的值得注意的與安全相關的問題。在早期的NFS實現中,安全的實現是很寬鬆的;客戶端上的任何使用者可以很輕易地偽裝成其他人,然後訪問任何檔案。與更加嚴密的認證服務的結合可以解決這些不足。