LWN大作:NFS 的早期時(shí)代!
關(guān)注了就能看到更多這么棒的文章哦~
NFS: the early years
June 20, 2022
This article was contributed by Neil Brown
DeepL assisted translation
https://lwn.net/Articles/897917/
我最近因?yàn)橐恍┰蛐枰獙?NFS(網(wǎng)絡(luò)文件系統(tǒng))協(xié)議多年來的改動進(jìn)行一下回溯和反思,而且發(fā)現(xiàn)這是一個(gè)很值得給大家講講的故事。這樣的故事很容易被過多的細(xì)節(jié)所淹沒,因?yàn)榇_實(shí)有非常多這樣的細(xì)節(jié),但確實(shí)有一個(gè)想法是比其他的那些更加明顯和突出的。NFS 的最早期版本被描述為一個(gè) "stateless (無狀態(tài)的) "協(xié)議,這個(gè)術(shù)語我現(xiàn)在還偶爾聽到有人會這么說。NFS 的大部分歷史都是承認(rèn)有 state 以及添加支持而逐步演進(jìn)的。本文著眼于 NFS 早期階段的演變(以及它對 state 的處理方式)。后續(xù)會有第二部分內(nèi)容來一直講述到當(dāng)前情況。
我所說的 "state",就是指客戶端和服務(wù)器端需要共同記住的那些信息,它們?nèi)绻谝环桨l(fā)生了改變的話,就需要讓另一方也產(chǎn)生改變。正如我們將看到的,state 中包含很多內(nèi)容。其中一個(gè)很簡單的例子就是文件的內(nèi)容,當(dāng)它被緩存(cache)在客戶端一側(cè)時(shí),其目的要么是希望能不要進(jìn)行 read requests,要么就是為了能把 write request 合并起來發(fā)出去。客戶端需要知道什么時(shí)候所緩存的數(shù)據(jù)必須要被 flush 出去或者清除掉,從而讓客戶端和服務(wù)器端基本保持同步。另一種明顯的 state 就是文件鎖(file locks)了,對于這種鎖,服務(wù)器和客戶端必須始終針對客戶端在某個(gè)時(shí)刻持有什么鎖要能確保一致。每一方都必須能夠發(fā)現(xiàn)另一方出現(xiàn)了 crash 的情況,從而能讓 lock 被丟棄(discard)或恢復(fù)(recover)。
NFSv2 — the first version
據(jù)推測,在 Sun Microsystems 內(nèi)部曾有一個(gè) NFS 的 "version 1",但第一個(gè)公開出來的版本就是 version 2 了,它出現(xiàn)在 1984 年。該協(xié)議在 RFC 1094 中有介紹,但這個(gè)文檔并不被視為權(quán)威性文件;相反,Sun 公司的實(shí)現(xiàn)本身定義了該協(xié)議。在同一時(shí)期,還有其他網(wǎng)絡(luò)文件系統(tǒng)被開發(fā)出來,如 AFS(the Andrew File System)和 RFS(Remote File Sharing)。與這些系統(tǒng)相比,NFS 有一個(gè)明顯的區(qū)別,那就是它很簡單。有人可能會說,它太簡單了,完全無法正確地實(shí)現(xiàn)一些 POSIX 語義。然而,這種簡單性意味著它可以為許多常見的工作場景提供良好的性能。
20 世紀(jì) 80 年代初是 "3M Computer" 的時(shí)代,當(dāng)時(shí)個(gè)人工作站的經(jīng)常是只有 1M 字節(jié)的內(nèi)存、1 MIPS 的處理能力和 1 M 像素(單色)的顯示器。以今天的標(biāo)準(zhǔn)來看,這也太弱了,完全沒法用,而且當(dāng)時(shí)人們還認(rèn)為一百萬便士(10,000 美元)的價(jià)格是可以接受的。但這些就是 NFSv2 所必須能夠運(yùn)行的硬件環(huán)境,而且必須運(yùn)行良好才能被人們接納。歷史表明,它足以完成這項(xiàng)任務(wù)。
Consequence of being "stateless"
NFSv2 協(xié)議沒有對狀態(tài)管理的明確支持。沒有 "打開" 一個(gè)文件的概念,不支持 locking,也沒有在 RFC 中提到任何 caching 機(jī)制。只有簡單的、一個(gè)個(gè)互相獨(dú)立的訪問請求,所有這些都是利用文件句柄(file handle)來做的。
"file handle" 是 NFSv2 的最核心的把其他東西統(tǒng)一起來的機(jī)制:它是一個(gè)不透明的(opaque)、32 字節(jié)的文件標(biāo)識符,在某個(gè)特定 NFS 服務(wù)器中,在所有時(shí)間內(nèi)這些 file handle 都是固定且唯一的。NFSv2 允許客戶端在一個(gè)特定目錄(由其他的 file handle 來識別)中為某個(gè)指定的名字查找文件句柄,檢查和改變這個(gè)文件句柄的屬性(所有權(quán)、大小、時(shí)間戳等),并在某個(gè)文件句柄的某個(gè)偏移位置來進(jìn)行數(shù)據(jù)塊的讀寫。
為 NFSv2 選擇的操作是盡可能要滿足冪等的(idempotent)條件,也就是說如果某個(gè)請求被重復(fù)發(fā)送了,它在第二次或第三次執(zhí)行中的結(jié)果將會是與第一次相同的。這對于在不穩(wěn)定的網(wǎng)絡(luò)上進(jìn)行真正的無狀態(tài)操作是個(gè)必要條件。NFS 最初是通過 UDP 實(shí)現(xiàn)的,UDP 不保證數(shù)據(jù)一定能到達(dá),所以客戶端必須準(zhǔn)備好在沒有得到回復(fù)時(shí)來重新發(fā)送請求。客戶端不能知道是請求丟失了,還是回復(fù)丟失了,而真正的無狀態(tài)服務(wù)器是不能記住某個(gè)特定請求是否已經(jīng)被看到的,也就無法避免觸發(fā)重復(fù)動作。因此,當(dāng)客戶端重新發(fā)送一個(gè)請求時(shí),它可能會重復(fù)一個(gè)已經(jīng)執(zhí)行過的操作,所以必須是要 idempotent 的操作才行。
不幸的是,并不是所有 POSIX 下的文件系統(tǒng)操作都可以是 idempotent 的。一個(gè)很好的例子就是 MKDIR,如果這個(gè)名字的目錄尚不存在,那么就應(yīng)該創(chuàng)建一個(gè)目錄;如果這個(gè)名字已經(jīng)被使用了,即使本身就是一個(gè)目錄了,也會需要返回錯(cuò)誤。這意味著重復(fù)請求可能導(dǎo)致會導(dǎo)致返回錯(cuò)誤。盡量減少這個(gè)問題的標(biāo)準(zhǔn)做法就是在服務(wù)器上實(shí)現(xiàn)一個(gè)重復(fù)請求緩存(DRC, Duplicate Request Cache)。這是對最近處理過的 non-idempotent 請求的歷史記錄,也包括返回的結(jié)果。實(shí)際上,這意味著客戶端(必須要跟蹤記錄尚未收到回復(fù)的請求)和服務(wù)器都維護(hù)一個(gè)隨時(shí)間不斷變化的未完成的請求列表。這些列表符合我們對 "state" 的定義,所以最初的 NFSv2 實(shí)際上并不是無狀態(tài)的,盡管根據(jù)規(guī)范來說它是無狀態(tài)的。
由于服務(wù)器無法知道客戶端何時(shí)能看到自己的回復(fù),也就無法知道一個(gè)請求何時(shí)才能算是處理完成的,所以它必須使用一些啟發(fā)式規(guī)則來把舊的 cache 條目丟棄掉。不可避免地會記住許多不需要記住的請求,并可能會丟棄一些很快就會需要的請求。雖然這顯然不是最理想的方案,但經(jīng)驗(yàn)表明,這對正常的工作負(fù)荷來說已經(jīng)是相當(dāng)有效了。
維護(hù)這個(gè) cache 就需要服務(wù)器知道每個(gè)請求來自哪個(gè)客戶端,所以它需要一些可靠的方法來識別客戶。隨著協(xié)議的發(fā)展,狀態(tài)管理變得更加明確,我們會看到這種需求反復(fù)出現(xiàn)。對于 DRC 來說,所使用的客戶端標(biāo)識符是由客戶端的 IP 地址和端口號得出的。當(dāng)后續(xù)添加了 TCP 支持的時(shí)候,協(xié)議類型也就需要與主機(jī)地址和端口號一起用上了。由于 TCP 提供了可靠的傳輸,似乎不需要 DRC,但這并不完全正確。如果網(wǎng)絡(luò)問題導(dǎo)致客戶端和服務(wù)器在很長一段時(shí)間內(nèi)無法通信,TCP 連接有可能 "中斷(break)"。NFS 做好了無限期等待的準(zhǔn)備,但是 TCP 不是這樣的。如果 TCP 確實(shí)中斷了連接,客戶端就不能知道那些未決請求的狀態(tài)了,它必須在一個(gè)新的連接上重新傳輸這些請求,這樣服務(wù)器端就可能仍然看到重復(fù)的請求了。為了確保這一點(diǎn),NFS 客戶端要注意使用與先前連接相同的 source 端口來重新建立連接。
這個(gè) DRC 機(jī)制并不完美,部分原因是由于啟發(fā)式方法可能會在客戶端實(shí)際收到回復(fù)之前就丟棄了這些條目,還有可能的原因是在服務(wù)器重啟時(shí)沒有保留這些內(nèi)容,因此一個(gè)請求可能在服務(wù)器 crash 之前和之后都會被執(zhí)行。在許多情況下,這只是個(gè)無傷大雅的小問題,如果 "mkdir" 偶爾返回 EEXIST (本來不應(yīng)該有這個(gè)返回值),那么會有人真正受到影響嗎?但是有一種情況就被證明是會有很大問題的,而且 DRC 根本沒有對它進(jìn)行處理,那就是獨(dú)占創(chuàng)建(exclusive create)。
在 Unix 有文件鎖的概念之前(因?yàn)樗?Edition 7 Unix 中沒有,這正是 BSD 的基礎(chǔ)),會經(jīng)常使用 lock file。如果需要獨(dú)占訪問某些文件,比如/usr/spool/mail/neilb,慣例是應(yīng)用程序必須首先創(chuàng)建一個(gè)相關(guān)名稱的 lock file,比如/usr/spool/mail/neilb.lock。這必須是一個(gè)使用 O_CREAT|O_EXCL 標(biāo)志的 "exclusive" 方式創(chuàng)建出來的,如果文件已經(jīng)存在就會失敗。如果某個(gè)應(yīng)用程序發(fā)現(xiàn)它不能創(chuàng)建該文件,因?yàn)槠渌麘?yīng)用程序已經(jīng)這樣做了,它就會等待著重新嘗試。
Exclusive create 天生就不是一個(gè) idemotent 操作,而且 NFSv2 根本就不支持它。客戶端可以進(jìn)行查詢,如果報(bào)告說沒有現(xiàn)存的文件,他們就可以創(chuàng)建該文件。這個(gè)兩步程序顯然容易受到競態(tài)沖突影響,所以并不可靠。NFS 的這一缺陷似乎并沒有讓它變得不受歡迎了,但多年來肯定有很多人在詛咒這一點(diǎn)。這也引出了一些創(chuàng)新,使用其他方法來創(chuàng)建 lock file。
一種方法是生成一個(gè)在所有客戶端都是唯一的字符串(可能包括主機(jī)名、進(jìn)程 ID 和時(shí)間戳),然后用這個(gè)字符串作為名稱和內(nèi)容來創(chuàng)建一個(gè)臨時(shí)文件。這個(gè)文件需要(hard)link 來創(chuàng)建 lock file 的文件名。如果 hard link 成功了,就說明拿到了鎖。如果失敗了,也就是這個(gè)名字已經(jīng)存在了,那么應(yīng)用程序可以讀取該文件的內(nèi)容。假如文件內(nèi)容與我們剛生成的唯一字符串相匹配,那么這個(gè)錯(cuò)誤就是由于重傳造成的,而且 lock 也已經(jīng)獲取到了。否則的話,應(yīng)用程序就需要休眠并再次嘗試。
不做狀態(tài)管理的另一個(gè)不幸的后果是,文件在打開之后被 unlink 了。POSIX 對這些 unlink 但仍然保持打開狀態(tài)文件沒有什么意見,并會保證該文件能繼續(xù)正常運(yùn)行,直到它最終被關(guān)閉,此時(shí)該文件就會徹底消失。在 NFS 服務(wù)器看來,因?yàn)樗恢滥男┪募窃谀膫€(gè)客戶端上打開的,就很難做到這么好,所以 NFS 客戶端的實(shí)現(xiàn)并不依賴于服務(wù)器的幫助。而是在進(jìn)行 unlink 處理(刪除文件)時(shí),客戶端會將這個(gè)已經(jīng)打開的文件重命名為一些特殊且唯一的名字,如.nfs-xyzzy,然后在文件最終關(guān)閉時(shí)再刪掉這個(gè)名字。這使得服務(wù)端無需跟蹤客戶端的狀態(tài),但對客戶端來說偶爾會有一些不便。如果一個(gè)應(yīng)用程序打開了某個(gè)目錄中唯一的文件,unlink 該文件,然后試圖刪除該目錄,那么最后一步將會失敗,因?yàn)樵撃夸洿藭r(shí)還不是空目錄,而是包含了一個(gè)帶有特殊的.nfs-XX 名稱的文件,除非客戶端先把這個(gè)特殊文件名的文件移到父目錄中,或者將 RMDIR 也改成一個(gè)重命名操作。在實(shí)踐中,這種操作順序是非常少見的,所以 NFS 客戶端都不打算滿足這個(gè)功能。
The NFS ecosystem
當(dāng)我在上面說 NFSv2 不支持 file locking 時(shí),其實(shí)只講了故事的一半。也就是說這個(gè)說法是準(zhǔn)確的,但并不完整。事實(shí)上,NFS 是一套協(xié)議的一部分,所有這些協(xié)議配合在一起使用的時(shí)候,可以提供更完整的服務(wù)。NFS 不支持鎖,但有其他協(xié)議是支持鎖的。可以與 NFS 一起使用的協(xié)議包括:
NLM(the Network Lock Manager)。其允許客戶端對一個(gè)給定文件(使用 NFS 文件句柄來標(biāo)明)請求一個(gè) byte-range 的鎖,并允許服務(wù)器授予(或不授予),可以是立即授予,也可以是今后授予。當(dāng)然,這是一個(gè)明確的有狀態(tài)協(xié)議,因?yàn)榭蛻舳撕头?wù)器必須為每個(gè)客戶端維護(hù)相同的 lock 列表。
STATMON(the Status Monitor)。當(dāng)一個(gè)節(jié)點(diǎn)–無論是客戶端還是服務(wù)器端–crash 或以其他方式重啟時(shí),之前的所有暫時(shí)狀態(tài),如文件鎖等都會丟失,所以它的對端就需要對此進(jìn)行響應(yīng)。服務(wù)器端將清除掉該客戶端所持有的鎖,而客戶將試圖重新獲得丟失的鎖。在 NLM 中選擇的方法是讓每一端都在可靠的存儲設(shè)備中記錄對端列表,并在重啟時(shí)通知到所有對端;然后他們就可以自己進(jìn)行清理。這項(xiàng)記錄然后通知對等體的任務(wù)就是 STATMON 完成的了。當(dāng)然,如果一個(gè)客戶端在持有一個(gè)鎖的時(shí)候崩潰了,并且沒有重啟,服務(wù)器就永遠(yuǎn)不會知道這個(gè)鎖不再被人所持有了。這有時(shí)就會引入麻煩。
MOUNT。當(dāng)掛載一個(gè) NFSv2 文件系統(tǒng)時(shí),你需要知道該文件系統(tǒng) root 位置的文件句柄,而 NFS 沒有辦法提供。這是由 MOUNT 協(xié)議來處理的。該協(xié)議希望服務(wù)器能夠跟蹤記錄哪些客戶已經(jīng) mount 了哪些文件系統(tǒng),因此可以把這些有用的信息報(bào)告出來。然而,由于 MOUNT 并不與 STATMON 交互,客戶端可以重新啟動也就是事實(shí)上 umount 了文件系統(tǒng),并未告訴服務(wù)器端。雖然這種軟件仍在記錄當(dāng)前 active mount 的列表,但沒有人相信它們。
在后來的版本中,MOUNT 還會處理 security negotiation。服務(wù)器可能需要某種 cryptographic security(如 Kerberos)才能訪問某些文件系統(tǒng),這個(gè)要求會通過 MOUNT 協(xié)議傳達(dá)給客戶端。
RQUOTA(remote quotas)。NFS 可以報(bào)告文件和文件系統(tǒng)的各種屬性,但有一個(gè)屬性是未被支持的,那就是 quota。可能是因?yàn)檫@些是用戶的屬性,不是文件的屬性。為了填補(bǔ)這一空白,就出現(xiàn)了 RQUOTA 協(xié)議。
NFSACL(POSIX draft ACL)。正如我們有 RQUOTA 來實(shí)現(xiàn) quota 功能,我們也有 NFSACL 來實(shí)現(xiàn)訪問控制列表(access control lists)。這允許檢查 ACL 并進(jìn)行設(shè)置(這一點(diǎn)跟 RQUOTA 不同)。
除了這些,還有其他一些協(xié)議只是松散地聯(lián)系在一起,比如 "Yellow Pages",也被稱為網(wǎng)絡(luò)信息服務(wù)器(NIS),它可以讓一組機(jī)器能有完全一致的用戶名到 UID 的映射;"rpc.ugid",也可以提供幫助;甚至可能 NTP 也算,它確保了 NFS 客戶端和服務(wù)器對當(dāng)前時(shí)間的判斷是一致的。無論如何,這些都不是 NFS 的真正組成部分,但卻是讓 NFS 如此繁榮的生態(tài)系統(tǒng)的一部分。
NFSv3 - bigger is better.
NFSv3 是在大約十年后(1995 年)出現(xiàn)的。這時(shí),工作站的速度更快了(而且色彩更豐富了),磁盤驅(qū)動器也更大。32 位不夠代表文件中的字節(jié)數(shù)、文件系統(tǒng)中的 block 數(shù)或文件系統(tǒng)中的 inode 數(shù)了,而 32 字節(jié)也不再足以代表文件句柄,因此這些 size 都被翻倍了。NFSv3 還獲得了 READDIRPLUS 操作,用來獲取一個(gè)目錄中的所有 name 和文件屬性,這樣可以更有效地實(shí)現(xiàn) ls -l。請注意,決定何時(shí)使用 READDIRPLUS 和何時(shí)使用更簡單的 READDIR,并不是一件容易的事情。在 2022 年,Linux NFS 客戶端仍然在采用啟發(fā)式方法來進(jìn)行改進(jìn)。
有兩個(gè)改動是專門跟 state 管理有關(guān)的,其中之一是解決上面討論的 exclusive-create 的問題,另一個(gè)是幫助維護(hù)客戶端數(shù)據(jù)的 cache。其中第一個(gè)對 CREATE 操作進(jìn)行了擴(kuò)展。
在 NFSv3 中,一個(gè) CREATE 請求可以指定該請求是 UNCHECKED、GUARDED 還是 EXCLUSIVE 的。其中第一個(gè)是無論文件是否存在都會讓操作成功。但是如果文件存在的話,第二種方式必須要失敗,但它就像 MKDIR 一樣,可能會因?yàn)橹貍鞫鴮?dǎo)致出現(xiàn)不應(yīng)該出現(xiàn)的錯(cuò)誤,所以它不是特別有用。EXCLUSIVE 則更有用一些。
EXCLUSIVE 這個(gè)創(chuàng)建請求會帶有 8 個(gè)字節(jié)的每個(gè)客戶端各不相同的標(biāo)識(這是我們反復(fù)用到的方式),稱為 "verifier"。RFC(RFC 1813)中建議說,"也許" 這個(gè) verifier 可以包含客戶的 IP 地址或其他一些獨(dú)特的數(shù)據(jù)。Linux NFS 客戶端使用了 jiffies 這個(gè) internal timer 的四個(gè)字節(jié)以及發(fā)出請求的進(jìn)程的 PID 的四個(gè)字節(jié)。服務(wù)器端需要在創(chuàng)建文件時(shí)將這個(gè) verifier 采用原子操作方式存儲到可靠的存儲位置。如果服務(wù)器后來被要求再創(chuàng)建一個(gè)已經(jīng)存在的文件,那么必須要對比存儲中的客戶端標(biāo)識符和請求中的標(biāo)識符,在匹配的情況下,服務(wù)器必須報(bào)告說 exclusive create 成功了,也就是判定這是之前請求的一次重復(fù)發(fā)起。
Linux NFS 服務(wù)器在其創(chuàng)建的文件的 mtime 和 atime 字段中會存儲這個(gè) verifier。NFSv3 協(xié)議承認(rèn)這種可能性,并要求一旦客戶端收到表示成功創(chuàng)建的回復(fù),就必須發(fā)出 SETATTR 請求,從而讓服務(wù)器這邊可以把存儲了 verifier 的這些文件屬性改成正確的值。這個(gè) SETATTR 步驟向服務(wù)器確認(rèn)了一些非冪等的請求已經(jīng)完成了,這樣就應(yīng)該可能對 DRC 實(shí)現(xiàn)有幫助了。
Client-side caching and close-to-open cache consistency
NFSv2 RFC 并沒有描述客戶端緩存,但這并不意味著實(shí)現(xiàn)方案里面也沒有做任何緩存。他們必須要非常小心。只有當(dāng)有充分的理由認(rèn)為數(shù)據(jù)在服務(wù)器上沒有變化時(shí),cache 數(shù)據(jù)才是安全的。NFS 的實(shí)現(xiàn)方案里給客戶端提供了兩種方法,用來讓其相信 cache 的數(shù)據(jù)是可以安全使用的。
NFS 服務(wù)器可以把一個(gè)文件的各種屬性報(bào)告上來,尤其是 size 和最后更改時(shí)間。如果這些值跟以前的一樣,那么基本可以判定文件內(nèi)容沒有改變。NFSv2 允許將更改的時(shí)間戳按微秒為單位來上報(bào),但這并不意味著服務(wù)端也可以保持這種精度水平。甚至在 NFSv2 首次使用起來的二十年后,還有一些很重要的 Linux 文件系統(tǒng)只能按秒為精度單位來提供時(shí)間戳。因此,如果一個(gè) NFS 客戶端看到一個(gè)至少過去一秒鐘的時(shí)間戳,然后讀取數(shù)據(jù),它就可以判定這些緩存數(shù)據(jù)是安全的,這樣一直到它看到時(shí)間戳變化為止。如果它看到的時(shí)間戳是在 "當(dāng)前時(shí)間" 的一秒鐘之內(nèi),那么就不太好確定是否安全了。
NFSv3 引入了 FSINFO 請求,可以供服務(wù)器上報(bào)各種限制和設(shè)置信息,并包括了一個(gè) "time_delta",這是假設(shè)文件修改時(shí)間以及其他時(shí)間戳中的時(shí)間精度應(yīng)該到什么水平。這樣客戶端的 cache 維護(hù)就可以更精確一些。
如上所述,在看到文件的屬性發(fā)生變化之前,可以安全地使用文件的緩存數(shù)據(jù)。客戶端可能實(shí)現(xiàn)成不再查看文件的屬性,因此就永遠(yuǎn)看不到文件變動了,但這是不允許的。確認(rèn)數(shù)據(jù)安全的話,需要客戶端進(jìn)行屬性檢查的時(shí)機(jī)方面遵守兩條規(guī)則:
第一條規(guī)則很簡單:就是偶爾檢查一下。協(xié)議中沒有規(guī)定最小或最大的 timeout 時(shí)間,但大多數(shù)實(shí)現(xiàn)中都允許配置這些 timeout 值。Linux 默認(rèn)的是三秒鐘的超時(shí),只要沒有任何變動的話,超時(shí)時(shí)間就會成倍地增長,最長會增加到一分鐘。這意味著客戶端可以從緩存中提供最多 60 秒的數(shù)據(jù),但不會更長了。第二條規(guī)則是建立在一個(gè)假設(shè)上,也就是多個(gè)應(yīng)用程序永遠(yuǎn)不會同時(shí)打開同一個(gè)文件,除非它們使用了 locking 鎖定或者都是只讀訪問。
在客戶端打開一個(gè)文件時(shí),它必須驗(yàn)證緩存中的數(shù)據(jù)(通過檢查時(shí)間戳來判斷),并丟棄那些它不能確定的數(shù)據(jù)。只要文件保持 open 狀態(tài),客戶端就可以認(rèn)為服務(wù)器上不會再發(fā)生變動了(除了它自己要求變動之外)。當(dāng)它關(guān)閉文件時(shí),客戶端必須在關(guān)閉完成前將所有的變動傳遞到服務(wù)器上。如果每個(gè)客戶端都這樣做,那么任何一個(gè)打開文件的應(yīng)用程序都會看到其他應(yīng)用程序在這次打開之前其他客戶端對文件進(jìn)行 close 操作時(shí)完成的所有修改了,所以這種模式有時(shí)被稱為 "close-to-open consistency"。
當(dāng)使用 byte-range locking 的時(shí)候,也可以使用類似的基本模型,但 open 操作變成了客戶端被授予鎖的時(shí)刻,而 close 則是它釋放鎖的時(shí)刻。在被授予鎖之后,客戶端必須重新驗(yàn)證或清除鎖范圍內(nèi)的任何已經(jīng)緩存的數(shù)據(jù),在釋放鎖之前,它必須將這個(gè)區(qū)域的緩存變化傳遞合并到服務(wù)器上。
由于上面說的都是依靠修改時(shí)間點(diǎn)來驗(yàn)證 cache 的,而這個(gè)時(shí)間戳在任何客戶端寫入文件時(shí)都會更新,因此邏輯上的含義是,當(dāng)客戶端寫入文件時(shí),它必須清除自己的 cache,因?yàn)闀r(shí)間戳已經(jīng)改變了。在文件關(guān)閉(或這個(gè)區(qū)域被解鎖)之前都是可以繼續(xù)使用緩存的,但不能超過這個(gè)時(shí)間段。在使用 byte-range 鎖時(shí),這種需求尤其明顯。一個(gè)客戶可能會 lock 一個(gè)區(qū)域,進(jìn)行寫入,然后再 unlock。另一個(gè)客戶端可能會 lock、寫入和 unlock 另一個(gè)不同的區(qū)域,而寫入請求正好發(fā)生在同一時(shí)間。任何一個(gè)客戶端都不可能知道另一個(gè)客戶端是否寫了這個(gè)文件,因?yàn)闀r(shí)間戳是涵蓋整個(gè)文件而不僅僅是一個(gè)范圍的。所以他們都必須在下次打開或 lock 文件之前清除他們的相應(yīng) cache。
至少,在 NFSv3 中引入 weak cache consistencies(wcc)屬性之前是沒有辦法判斷的。在 NFSv3 WRITE 請求的回復(fù)中,在寫請求之前和之后允許服務(wù)端上報(bào)一些屬性(如 size 和時(shí)間戳),并且要求,如果它確實(shí)報(bào)告了這些屬性,那么在這兩組屬性之間是沒有發(fā)生其他 write 操作的。客戶端可以使用這些信息來檢測時(shí)間戳的變化是純粹是由于它自己的寫入導(dǎo)致的,還是由于其他客戶端的寫入導(dǎo)致的。因此,它可以確定它是否是唯一一個(gè)向文件寫入的客戶端(這是最常見的情況),并且在這種情況下,哪怕時(shí)間戳在變化了,也可以保留其 cache。在對 SETATTR 和修改目錄的請求(如 CREATE 或 REMOVE)的回復(fù)中也是可以使用 Wcc 屬性的,所以客戶端也可以判斷它是否是一個(gè)目錄中唯一進(jìn)行操作的一方,并相應(yīng)地管理它的緩存。
這被稱為 "weak" cache consistency,因?yàn)樗匀恍枰蛻舳伺紶杹頇z查時(shí)間戳。強(qiáng)緩存一致性則要求服務(wù)器明確地告訴客戶端這里即將發(fā)生改動,不過要等到 NFS 的后面的版本才會帶有這個(gè)支持了。盡管是 weak 方式的,但它仍然是一個(gè)明顯的進(jìn)步,可以允許客戶端保持對服務(wù)器狀態(tài)的了解,因此是對無狀態(tài)協(xié)議這個(gè)說法的又一個(gè)打擊。
順便說一句,Linux NFS 服務(wù)端并沒有在對文件進(jìn)行 write 時(shí)提供這些 wcc 屬性。要做到這一點(diǎn)的話,它需要在收集文件屬性以及進(jìn)行寫入時(shí)必須持有文件鎖。從 Linux 2.3.7 開始,底層文件系統(tǒng)負(fù)責(zé)在寫入過程中加鎖,所以 nfsd 不能以原子操作方式來提供這些屬性。不過,Linux NFS 確實(shí)為目錄內(nèi)的修改提供了 wcc 屬性。
NFS - the next generation
這些早期版本的 NFS,都是在 Sun Microsystems 內(nèi)部開發(fā)的。這些代碼可供其他 Unix 廠商在他們的產(chǎn)品中使用,雖然這些廠商能夠根據(jù)需要來調(diào)整實(shí)現(xiàn),但他們不能改變協(xié)議,畢竟那是由 Sun 公司控制的。
隨著新千年的到來,人們對 NFS 的興趣不斷增加,就出現(xiàn)了獨(dú)立的第三方的實(shí)現(xiàn)。這導(dǎo)致了更多的開發(fā)者對如何改進(jìn) NFS 提出了意見,還是非常了解細(xì)節(jié)并且深思熟慮過的意見。為了滿足這些開發(fā)者,同時(shí)又不至于引出分裂的危機(jī),就需要一個(gè)機(jī)制可以用來聽取和回答這些意見。這個(gè)機(jī)制的性質(zhì),以及在 NFS 協(xié)議的后續(xù)版本中出現(xiàn)的改動,將是我們后續(xù)需要講述的主題。
全文完
LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。
長按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~
