01

為什麼需要分散式檔案系統

釐清檔案、屬性、目錄、metadata 等基本概念,以及檔案系統的分層模組結構。

先讀原文開場,旁邊就是白話

這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。

原文 · 分散式檔案系統 521 12 DISTRIBUTED FILE SYSTEMS 12. 2 File service architecture 12. 3 Case study: Sun Network File System 12. 4 Case study: The Andrew File System 12.
白話導讀

釐清檔案、屬性、目錄、metadata 等基本概念,以及檔案系統的分層模組結構。

檔案系統到底在做什麼

釐清檔案、屬性、目錄、metadata 等基本概念,以及檔案系統的分層模組結構。

STEP 1

深度探秘

檔案、屬性與目錄三件套

檔案系統的工作

檔案系統負責檔案的組織、儲存、取用、命名、共享與保護。它給程式一個乾淨的介面,讓你不用煩惱資料實際放在磁碟的哪一塊。

一個檔案其實有兩個部分:

  • 資料(data):一連串的位元組,你可以讀寫其中任意片段。
  • 屬性(attributes):一筆描述檔案的紀錄,例如長度、各種時間戳記、檔案型別、擁有者、存取控制清單(ACL)。

有些屬性(像長度、時間戳記)是檔案系統自己維護的,使用者程式不能亂改。

目錄(directory)本身也是一種特殊的檔案,它做的事很單純:把人看得懂的文字名稱對應到內部的檔案識別碼。目錄裡又可以放其他目錄,於是就長出我們熟悉的階層式樹狀路徑,例如 /usr/students/jon

所有檔案系統為了管理檔案而額外存的資訊(屬性、目錄等),統稱為 metadata(中介資料)

💡
關鍵

檔案 = 資料 + 屬性;目錄負責把名字翻成內部檔案識別碼。

STEP 2

生活妙喻

圖書館的書、書背標籤與索引卡

把檔案系統想成一座圖書館

  • 書本內容 = 檔案的資料
  • 書背上的標籤(出版年、分類號、借閱次數) = 檔案的屬性
  • 索引卡櫃(書名 → 架位編號) = 目錄

你走進圖書館不會直接衝到第 3 排第 5 格找書,而是先查索引卡:「書名叫《分散式系統》→ 它的編號是 005.276」。這個「書名 → 編號」的對應,正是目錄做的事。

書架的擺放規則、編目系統這些幕後管理資訊,就是 metadata。你看不到、也不需要看到它們,但少了它們圖書館就亂成一團。

最棒的是:你完全不需要知道書是放在三樓鋼架還是地下室密集櫃——圖書館(檔案系統)把這些細節藏起來了。

💡
關鍵

目錄像圖書館的索引卡:把名字對應到位置,細節對你隱藏。

STEP 3

實用超能力

分層模組:每一層只依賴下一層

檔案系統的分層結構

真實的檔案系統是一疊分層模組堆起來的,每一層只依賴它下面那層。這讓設計者可以各自獨立修改、抽換。從上到下大致是:

flowchart TD
    A[目錄模組 名字對應到檔案ID] --> B[檔案模組 檔案ID對應到實際檔案]
    B --> C[存取控制模組 檢查操作權限]
    C --> D[檔案存取模組 讀寫檔案資料或屬性]
    D --> E[區塊模組 配置與存取磁碟區塊]
    E --> F[裝置模組 執行磁碟IO與緩衝]

為什麼要分層?因為關注點分離(separation of concerns)

  • 上層程式只想說「給我 report.txt 的第 100~200 個位元組」。
  • 它不需要知道這對應到磁碟的哪個磁區、要不要先進緩衝區。

要做分散式檔案系統時,你會發現這套分層幾乎都還用得上,只是要再加上「處理用戶端-伺服器通訊」與「分散式命名與定位」的元件。這也是為什麼先搞懂單機檔案系統,是理解分散式版本的最佳起點。

💡
關鍵

分層讓每層只管自己的事;分散式版本只是在上面再加通訊與定位。

🔆
生活妙喻:目錄 ≈ 圖書館的索引卡櫃

索引卡把「書名」對應到「架位編號」,就像目錄把「檔名」對應到「檔案識別碼」,你不用記實際位置。

🔆
生活妙喻:分層模組結構 ≈ 餐廳的外場、廚房、備料區

外場只管接單與上菜(上層介面),廚房負責烹調,備料區處理食材。每一層只跟相鄰的層打交道,換掉備料流程不會影響外場。

本節字彙

屬性 (attributes)
描述檔案本身的資訊,例如長度、時間戳記、擁有者、存取控制清單。
🧠 把屬性想成書背上的「身分標籤」,不是書的內容。
metadata(中介資料)
檔案系統為了管理檔案而額外儲存的所有資訊,包含屬性與目錄。
🧠 meta = 關於資料的資料;是『管理用』的幕後資訊。
目錄 (directory)
一種特殊檔案,提供從文字名稱到內部檔案識別碼的對應。
🧠 directory 像通訊錄:名字 → 號碼。
你寫了一個程式想知道某檔案是誰建立的、上次什麼時候被修改。這些資訊屬於檔案的哪一部分?
為什麼說「目錄本身其實也是一種檔案」?
檔案系統採用分層模組結構(每層只依賴下層),最主要的好處是什麼?

把檔案搬上網路的七大需求

透明性、並行更新、複製、異質性、容錯、一致性、安全與效率:分散式檔案系統必須面對的設計要求。

STEP 1

深度探秘

透明性:讓你感覺不到『遠端』的存在

一切從『透明性』開始

分散式檔案系統最初的目標,就是讓你感覺不到檔案其實放在別台機器上。書中列出幾種透明性:

透明性類型 白話意思
存取透明 (Access) 同一套操作存取本機與遠端檔案;舊程式不用改就能用遠端檔案。
位置透明 (Location) 看到統一的名稱空間;檔案搬家但路徑不變。
行動透明 (Mobility) 檔案被搬移時,用戶端程式與設定表都不用改。
效能透明 (Performance) 負載在一定範圍內變動時,效能仍可接受。
規模透明 (Scaling) 服務能逐步擴充,應付更大的負載與網路。

設計時要在『透明帶來的彈性與擴展性』與『軟體複雜度、效能』之間取得平衡——透明不是免費的。

💡
關鍵

透明性的核心:讓遠端檔案用起來像本機檔案,而你毫無感覺。

STEP 2

深度探秘

另外五個必須面對的需求

除了透明,還有這些挑戰

  • 並行更新 (Concurrent updates):多個用戶端同時改同一個檔案,彼此不能互相干擾——這就是並行控制問題,通常用檔案層級或紀錄層級的鎖來處理。
  • 檔案複製 (Replication):一個檔案在多處存多份副本,可分攤負載、提升容錯。但完整支援複製的系統很少;多數只支援快取(一種有限的複製)。
  • 硬體與作業系統異質性 (Heterogeneity):介面要設計成能在不同 OS、不同硬體上實作——這是開放性的重要面向。
  • 容錯 (Fault tolerance):用戶端或伺服器壞掉時,服務要能撐下去。
  • 一致性 (Consistency):傳統 UNIX 提供一對複本更新語意(one-copy update semantics)——所有程式看到的內容,就像只存在單一份檔案一樣。但只要有快取或複製,修改傳播就有延遲,難免偏離這個理想。
  • 安全 (Security):要驗證用戶端身分、保護訊息(數位簽章、加密)。
  • 效率 (Efficiency):功能與效能至少要不輸傳統檔案系統。
💡
關鍵

快取與複製帶來效能,卻也帶來一致性的難題——這是貫穿全章的張力。

STEP 3

生活妙喻

無狀態伺服器:失憶的便利商店店員

容錯的秘訣:讓伺服器『無狀態』

書中指出,簡單伺服器要做到還不錯的容錯其實不難,靠兩招:

  1. at-most-once 語意,或更簡單的 at-least-once 語意 + 幂等操作:確保重送的請求不會把資料搞壞。
  2. 無狀態伺服器 (stateless server):伺服器不記得任何用戶端的狀態,於是壞掉重啟後不需要任何回復程序,服務就能繼續。

比喻:失憶的便利商店店員

想像一位每講完一句話就忘光的店員。你不能說「就照剛剛那個」——因為他不記得。你每次都要講完整:「我要一杯中杯美式、去冰」。

好處是:就算店員突然換班(伺服器重啟),新店員也能無縫接手,因為所有需要的資訊都寫在你的每一句話裡,不依賴他腦中的記憶。

flowchart LR
    A[用戶端送出完整請求] --> B[無狀態伺服器處理]
    B --> C[伺服器當機重啟]
    C --> D[用戶端重送同一個完整請求]
    D --> E[幂等操作 重做也不出錯]

代價是每個請求都比較囉嗦(要帶完整資訊),但換來的是極佳的容錯性與簡單性。

💡
關鍵

無狀態 + 幂等 = 伺服器隨時可重啟、請求可重送都不出事。

🔆
生活妙喻:無狀態伺服器 ≈ 每句話就失憶的便利商店店員

店員不記得你剛剛說過什麼,所以你每次都要講完整需求;好處是換班(重啟)也能無縫接手。

🔆
生活妙喻:一對複本更新語意 (one-copy semantics) ≈ 全班共用同一份公告板

理想上不管誰看、誰改,都像只有一份公告板;但若每人各抄一份回家,更新就會有延遲落差。

本節字彙

存取透明 (access transparency)
用同一套操作存取本機與遠端檔案,舊程式不用修改就能用遠端檔案。
🧠 你『存取』時感覺不到本機/遠端的差別。
無狀態伺服器 (stateless server)
伺服器不為個別用戶端保存任何狀態,因此當機重啟後不需回復程序即可繼續服務。
🧠 stateless = 失憶店員,每次請求都要講完整。
幂等操作 (idempotent operation)
同一個操作執行一次或多次結果都相同,因此可安全地重送請求。
🧠 幂等 = 做幾次都一樣,像按電梯按鈕按幾下都同一層。
一對複本更新語意 (one-copy update semantics)
所有程式看到的檔案內容,就像系統中只存在單一份檔案一樣。
🧠 大家看到的永遠是『同一份』,沒有副本落差。
一個多年前寫好、只會操作本機檔案的舊程式,在某分散式檔案系統上不用修改就能正常存取遠端檔案。這體現了哪一種透明性?
系統管理員把一批檔案從伺服器 A 搬到伺服器 B,而用戶端程式與其設定表完全不需更動就能繼續存取。這最符合哪一種透明性?
為什麼一旦在分散式檔案系統中引入快取或複製,就很難維持嚴格的『一對複本更新語意』?
02

抽象的檔案服務架構

平面檔案服務、目錄服務、用戶端模組各自負責什麼,以及 UFID 如何串起它們。

三個模組的分工

平面檔案服務、目錄服務、用戶端模組各自負責什麼,以及 UFID 如何串起它們。

STEP 1

深度探秘

把檔案服務拆成三塊

為什麼要拆成三個模組?

為了把『存取檔案』這件事的各種關注點清楚切開,抽象的檔案服務架構把系統拆成三個元件:

  • 平面檔案服務 (Flat file service):只負責對檔案『內容』做操作(讀、寫、建立、刪除、讀寫屬性)。它用 UFID(唯一檔案識別碼) 來指稱檔案,完全不管檔案叫什麼名字。
  • 目錄服務 (Directory service):負責『文字名稱 ↔ UFID』的對應。它其實是平面檔案服務的用戶端——它的目錄資料就存成平面檔案服務裡的檔案。
  • 用戶端模組 (Client module):跑在每台用戶端電腦上,把上面兩個服務組合並擴充成一個熟悉的程式介面(例如模擬整套 UNIX 檔案操作),還負責記住伺服器位置、並做本機快取。
flowchart TD
    A[應用程式] --> B[用戶端模組]
    B --> C[目錄服務 名字對應到UFID]
    B --> D[平面檔案服務 對UFID內容做讀寫]
    C --> D
💡
關鍵

平面檔案服務只認 UFID 管內容;目錄服務管名字;用戶端模組把兩者組成你熟悉的介面。

STEP 2

生活妙喻

倉庫、櫃台與你的私人助理

一座沒有名字、只有編號的倉庫

想像一間超大型倉庫:

  • 平面檔案服務 = 倉庫本身。倉庫工人只認貨架編號(UFID)。你給他編號 #A7F3...,他就把那箱貨的內容拿給你或幫你換內容。他完全不在乎這箱貨『叫什麼名字』。
  • 目錄服務 = 櫃台的登記簿。登記簿記著『生日蛋糕 → 編號 #A7F3...』。你報得出名字,櫃台才查得到編號。而這本登記簿,其實也是倉庫裡的一箱貨(目錄服務是平面檔案服務的用戶端)。
  • 用戶端模組 = 你的私人助理。你只想說一句『幫我把生日蛋糕換成草莓口味』,助理就會:先去櫃台查名字拿到編號 → 再拿編號去倉庫換內容。他還會把常用的東西先放你家(快取),下次更快。

UFID 之所以能切開兩個服務,正是因為它是『名字無關』的純編號——倉庫只要顧編號,命名的事交給登記簿。

💡
關鍵

倉庫只認編號(UFID),登記簿管名字,助理(用戶端模組)幫你串起整個流程。

STEP 3

實用超能力

這個分工帶來的彈性

為什麼這樣切很聰明

這個設計的『開放性』體現在:可以換不同的用戶端模組,做出不同的程式介面

  • 在 UNIX 主機上,用戶端模組可以模擬整套 UNIX 檔案操作,把多段路徑用一連串對目錄服務的請求逐步翻譯出來。
  • 在另一種作業系統上,可以提供另一套介面——但底下的平面檔案服務與目錄服務不變。

還有兩個重點責任落在用戶端模組身上:

  1. 記住伺服器位置:平面檔案伺服器與目錄伺服器各在哪裡,由用戶端模組掌握。
  2. 快取:在用戶端快取最近用過的檔案區塊,這是達成良好效能的關鍵。
# 用戶端模組做的事(概念示意)
ufid = directory.lookup(dir, '報告檔')        # 先問目錄服務
data = flatfile.read(ufid, start, n)         # 再用 UFID 讀內容

這套抽象模型不是空談——稍後你會看到 NFS 與 AFS 都是它的具體化,理解它就等於拿到看懂兩大案例的鑰匙。

💡
關鍵

換用戶端模組就能換介面;快取與定位伺服器也是它的職責——這正是 NFS/AFS 的共同骨架。

🔆
生活妙喻:平面檔案服務 ≈ 只認貨架編號的倉庫工人

工人只看 UFID 編號取貨換貨,不管貨叫什麼名字,把『內容操作』與『命名』徹底分開。

🔆
生活妙喻:用戶端模組 ≈ 你的私人助理

你只下一句模糊指令,助理自動先查登記簿(目錄服務)再去倉庫(平面檔案服務),還會把常用品放你家(快取)。

本節字彙

UFID(唯一檔案識別碼)
在整個分散式系統中唯一指稱一個檔案的長位元串,與檔名無關。
🧠 U = Unique;像倉庫貨架的『全宇宙唯一編號』。
平面檔案服務 (flat file service)
只負責對檔案內容做操作的服務,用 UFID 指稱檔案,不處理命名。
🧠 flat = 沒有目錄階層,只有一堆用編號標示的檔案。
目錄服務 (directory service)
提供文字名稱與 UFID 之間對應的服務;它本身是平面檔案服務的用戶端。
🧠 目錄服務 = 登記簿:名字 → 編號。
在抽象檔案服務架構中,平面檔案服務(flat file service)的核心職責是什麼?
為什麼說『目錄服務是平面檔案服務的用戶端』?
一個應用程式想讀取 `report.txt` 的某段內容。依抽象架構,用戶端模組典型的處理順序是?

幂等操作與無狀態伺服器

為什麼介面刻意拿掉 open/close 與讀寫指標,讓操作可重複、伺服器可重啟。

STEP 1

深度探秘

為什麼介面要拿掉 open 和 close

一個沒有 open/close 的檔案介面

抽象模型的平面檔案服務介面,跟 UNIX 比起來少了 openclose:檔案可以直接用 UFID 立刻存取。而且它的 Read / Write 都帶一個明確的起始位置 i

Read(FileId, i, n)  -> Data     # 從第 i 項起讀 n 項
Write(FileId, i, Data)          # 從第 i 項起寫入 Data
Create() -> FileId              # 建新空檔,回傳新 UFID
Delete(FileId)
GetAttributes(FileId) -> Attr
SetAttributes(FileId, Attr)

UNIX 則不同:每個 read/write 從**讀寫指標(read-write pointer)**的目前位置開始,做完還會自動把指標往前移。

這個差異是刻意為了容錯而設計的,帶來兩個關鍵性質:

  • 可重複(幂等)操作:除了 Create 以外,操作都是幂等的,因此可用 at-least-once RPC 語意——沒收到回覆就放心重送。
  • 可無狀態實作:伺服器可在當機後重啟並繼續,不需要用戶端或伺服器回復任何狀態。
💡
關鍵

Read/Write 自帶位置 i,所以不需要 open/close,也不需要伺服器記住讀寫指標。

STEP 2

深度探秘

讀寫指標:那個害人不淺的隱藏狀態

UNIX 為什麼不幂等?

問題出在那個隱藏的、與用戶端相關的讀寫指標

在 UNIX,一旦 open,系統就為這個開啟的檔案產生一個讀寫指標,並保留到 close 為止。每次 read/write 都會自動推進它。

想像 RPC 因網路問題重送了一次 write:

  • 第一次 write 成功,指標前進。
  • 用戶端沒收到回覆,重送第二次 write。
  • 結果指標又前進一次——同一筆資料被寫到了不同位置!這就不是幂等。

要在伺服器模擬這個指標,就得加上 open/close,並在檔案開啟期間替每個用戶端保存指標的值——伺服器立刻就有狀態了。

抽象模型的做法很乾脆:把讀寫指標砍掉,改由每個請求自己帶位置 i。指標消失,伺服器替特定用戶端保存狀態的需求也大半消失。

比較項 UNIX read/write 抽象模型 Read/Write
起始位置 來自隱藏的讀寫指標 由參數 i 明確指定
重送是否安全 否(指標會亂跑) 是(幂等)
伺服器需保存狀態 需要(指標) 不需要
💡
關鍵

讀寫指標是隱藏的用戶端狀態,砍掉它就同時換來幂等與無狀態。

STEP 3

生活妙喻

影印店:『接著上次』vs『每次講清楚』

兩種影印店店員

UNIX 式店員(有狀態):你說『接著上次那頁繼續印 10 張』。他得記得你『上次印到第幾頁』。如果你的對講機訊號斷了、你以為沒成功又喊一次『接著印 10 張』,他就會從新的位置再印一批——印錯地方了!而且他若下班換人(伺服器重啟),新店員根本不知道你上次印到哪。

抽象模型式店員(無狀態 + 幂等):你每次都講完整:『從第 51 頁開始印 10 張』。

  • 訊號斷了重喊一次?沒關係,他還是從第 51 頁印 10 張,結果一模一樣(幂等)。
  • 店員換班?新店員照樣能做,因為位置寫在你的指令裡,不靠他的記憶。
flowchart TD
    A[用戶端 Write FileId 第51項 資料] --> B{收到回覆了嗎}
    B -- 沒有 --> A
    B -- 有 --> C[完成]

唯一的例外是 Create:每次呼叫都會產生一個全新檔案,所以它不是幂等的——就像你每喊一次『開一份新檔案』就真的多開一份。

💡
關鍵

每次把位置講清楚,重送也安全、換人也能接手;只有『開新檔案』天生不幂等。

🔆
生活妙喻:讀寫指標 (read-write pointer) ≈ 影印店店員記在腦中的『上次印到第幾頁』

這是隱藏在伺服器腦中的用戶端狀態,一旦重送或換班就會出錯,所以抽象模型乾脆砍掉它。

🔆
生活妙喻:幂等操作 ≈ 『從第 51 頁印 10 張』這種完整指令

不管喊幾次結果都一樣,因為位置寫死在指令裡,不依賴店員的記憶。

本節字彙

讀寫指標 (read-write pointer)
UNIX 在開啟檔案時建立、記錄下次讀寫位置的隱藏狀態,每次讀寫後自動前進。
🧠 像書籤:標著『讀到哪了』,但這書籤存在伺服器腦中很危險。
at-least-once 語意
RPC 可能被執行一次或多次的呼叫語意;搭配幂等操作才安全,沒收到回覆就重送。
🧠 至少一次:可能重做,所以操作必須『做幾次都一樣』。
可無狀態實作 (stateless implementation)
伺服器不為個別用戶端保存狀態,當機重啟後無需回復即可繼續服務。
🧠 砍掉讀寫指標,伺服器就不用替你『記東西』。
抽象模型的 Read/Write 操作刻意帶一個明確的起始位置參數 i,最關鍵的好處是什麼?
為什麼 UNIX 的 read/write 操作不是幂等的?
下列哪一個操作在抽象模型中『不是』幂等的?

存取控制、目錄與檔案群組

在分散式環境如何做存取檢查、階層式命名如何建構,以及檔案群組為何要全域唯一。

STEP 1

深度探秘

存取控制:為什麼非得在伺服器端檢查

在分散式環境,門禁要設在伺服器

單機 UNIX 在 open 時檢查一次權限,之後直到 close 都不再檢查。但在分散式系統,伺服器的 RPC 介面是一個沒有保護的入口——任何人都能送請求進來。所以:

  • 存取檢查必須在伺服器端做。
  • 使用者身分要隨請求一起送出,伺服器因此可能收到偽造的身分
  • 如果伺服器把檢查結果留著給後續請求用,它就不再無狀態了。

為了既能無狀態、又能做檢查,有兩種常見做法:

  1. 能力 (capability):把名字翻成 UFID 時做一次檢查,把結果編碼成一張『能力憑證』回給用戶端,之後每個請求附帶它。
  2. 每次請求都帶身分:每個檔案操作都附上使用者身分,伺服器每次都檢查。(NFS 與 AFS 都採這種,較常見。)

這兩種都沒解決『偽造身分』的根本問題——那要靠數位簽章與像 Kerberos 這樣的驗證機制來補強。

💡
關鍵

分散式環境下 RPC 介面是裸露入口,存取檢查必須在伺服器端做,且常用『每次帶身分』。

STEP 2

深度探秘

目錄服務與階層式命名

從單層查詢,堆出整棵樹

目錄服務的核心操作其實很少:

Lookup(Dir, Name)            -> FileId   # 單步:在某目錄查一個名字
AddName(Dir, Name, FileId)              # 加一筆,並使參考計數 +1
UnName(Dir, Name)                       # 移除一筆,參考計數 -1,歸零就刪檔
GetNames(Dir, Pattern)       -> NameSeq # 用正規表示式比對檔名

注意 Lookup 只做單步翻譯——它是個積木。要解析像 /usr/students/jon 這種多段路徑,是由用戶端模組從根目錄開始,一段一段反覆呼叫 Lookup 拼出來的:

flowchart LR
    A[根目錄] -->|Lookup usr| B[usr目錄UFID]
    B -->|Lookup students| C[students目錄UFID]
    C -->|Lookup jon| D[jon檔案UFID]
  • 參考計數 (reference count):一個檔案可以有多個名字(UNIX 的 link)。AddName 讓計數 +1、UnName 讓計數 -1;歸零時檔案才真正被刪。
  • 屬性裡需要一個型別欄位區分『普通檔案』與『目錄』,這樣沿路徑往下走時,才能確保中間每一段都是目錄。
💡
關鍵

Lookup 是單步積木,多段路徑由用戶端模組從根逐段拼出;參考計數讓多名字共用同一檔案。

STEP 3

實用超能力

檔案群組:搬得動的檔案箱子,要有全球唯一標籤

檔案群組與全域唯一識別碼

檔案群組 (file group) 是放在某台伺服器上的一批檔案(UNIX 裡類似的概念叫 filesystem)。重點特性:

  • 一台伺服器可放多個群組;群組可在伺服器間搬移,但單一檔案不能換群組。
  • UFID 裡會含一個檔案群組識別碼欄位,用戶端模組就能據此把請求送到持有該群組的伺服器。

為什麼識別碼要『全域唯一』?

因為群組會搬移,而且兩個原本獨立的系統可能合併成一個。要保證識別碼永遠不撞號,唯一辦法是用能確保全域唯一的演算法產生。書中範例:把建立者主機的 32 位元 IP 位址 接上一個由日期導出的 16 位元整數,組成唯一的 48 位元整數:

file group identifier = [ 32-bit IP 位址 | 16-bit 日期 ]

關鍵陷阱:這個 IP 位址不能拿來『定位』群組!因為群組可能已經被搬到別台伺服器。定位要靠檔案服務另外維護的『群組識別碼 → 伺服器』對應表。IP 只用來『產生唯一名字』,不用來『找路』。

💡
關鍵

檔案群組可搬移,所以識別碼要全域唯一;但裡頭的 IP 只用於產生唯一名字,不能用來定位。

🔆
生活妙喻:在伺服器端做存取檢查 ≈ 夜店每位進場者都要驗一次證件

因為入口(RPC 介面)誰都能靠近,所以每個請求都得驗身分,不能只在第一次進門時驗一次就放行整晚。

🔆
生活妙喻:檔案群組識別碼裡的 IP 不能用來定位 ≈ 用出生地當你姓名的一部分,但你早就搬家了

出生地讓你的名字夠獨特(唯一性),但要找到你現在住哪,得查戶政系統的最新住址(對應表),不能照出生地去找。

本節字彙

能力 (capability)
把存取檢查結果編碼成的憑證,用戶端持有並隨請求附帶,讓伺服器免於保存狀態。
🧠 像一張通行證:拿到它就代表你被檢查過、有權限。
參考計數 (reference count)
記錄一個檔案目前有幾個名字指向它;歸零時檔案才真正被刪除。
🧠 幾個名字指著它就計幾;沒人指了才丟掉。
檔案群組 (file group)
放在某台伺服器上的一批檔案,可在伺服器間整批搬移,但單一檔案不能換群組。
🧠 像一整箱貨:可以整箱搬走,但箱內單品不能跳到別箱。
為什麼在分散式檔案系統中,存取控制檢查必須在『伺服器端』進行,而不能像單機 UNIX 那樣只在 open 時檢查一次?
NFS 與 AFS 採用的存取控制做法是下列哪一種?
要解析多段路徑 `/usr/students/jon`,依抽象模型是怎麼完成的?
03

案例一:Sun NFS

虛擬檔案系統如何分辨本機與遠端檔案,file handle 又是什麼。

VFS 與 file handle:無縫接軌的魔術

虛擬檔案系統如何分辨本機與遠端檔案,file handle 又是什麼。

STEP 1

深度探秘

NFS 的整體樣貌

NFS 怎麼把遠端檔案接進來

Sun NFS 自 1985 年問世後在業界與學界廣泛採用,是第一個以產品為目標設計的檔案服務。它遵循上一章的抽象模型,幾個關鍵特性:

  • 對稱關係:每台電腦都能同時當用戶端與伺服器;任何機器都可以**匯出(export)**自己的部分檔案供他人遠端存取。
  • 作業系統獨立:NFS 協定不綁特定 OS,幾乎所有平台都有實作。
  • 以 RPC 溝通:NFS 用 Sun RPC(可走 UDP 或 TCP),透過 port mapper 服務讓用戶端按名稱綁定服務。
  • 開放的 RPC 介面:任何程序都能送請求,只要附帶有效的使用者憑證就會被處理(可選用簽章與加密強化安全)。

NFS 伺服器模組住在**核心(kernel)**裡。用戶端對遠端檔案的請求,會被用戶端模組翻成 NFS 協定操作,送給持有該檔案系統的伺服器模組。

💡
關鍵

NFS 是抽象模型的具體化:對稱、OS 獨立、用 RPC 溝通、伺服器模組在核心裡。

STEP 2

深度探秘

VFS:核心裡的萬用轉接頭

虛擬檔案系統 (VFS) 是怎麼變魔術的

NFS 達成存取透明——程式對本機或遠端檔案發出同樣的 system call,毫無分別。這份魔術由加進 UNIX 核心的 虛擬檔案系統 (Virtual File System, VFS) 模組完成。VFS 做三件事:

  1. 分辨本機與遠端檔案,把請求交給對的模組(本機 UNIX 檔案系統、NFS 用戶端模組,或其他檔案系統)。
  2. 翻譯識別碼:在 NFS 用的 OS 無關識別碼與 UNIX 內部識別碼之間轉換。
  3. 追蹤目前可用的檔案系統(本機與遠端皆然)。
flowchart TD
    A[應用程式 system call] --> B[VFS 虛擬檔案系統]
    B -->|本機| C[本機 UNIX 檔案系統]
    B -->|遠端| D[NFS 用戶端模組]
    D -->|NFS 協定 RPC| E[遠端 NFS 伺服器]

VFS 為每個掛載的檔案系統保有一個 VFS 結構,為每個開啟的檔案保有一個 v-node。v-node 裡有個指標說明檔案是本機還是遠端:本機就指向本地索引(i-node),遠端就存著遠端檔案的 file handle

💡
關鍵

VFS 是核心裡的轉接層,靠 v-node 分辨本機/遠端,讓同一套 system call 通吃。

STEP 3

生活妙喻

file handle:寄物櫃的號碼牌

file handle 是什麼?

NFS 用來指稱檔案的識別碼叫 file handle(檔案控制代碼)。它對用戶端是不透明的(opaque)——用戶端拿到它只能原封不動帶著走,不該去解讀內容。它含有伺服器分辨某個檔案所需的一切資訊。在 UNIX 實作中,file handle 由三部分組成:

file handle = [ 檔案系統識別碼 | i-node 編號 | i-node 世代編號 ]
  • 檔案系統識別碼:每個可掛載檔案系統建立時分配的唯一編號。
  • i-node 編號:在該檔案系統內定位檔案。
  • i-node 世代編號:因為 UNIX 的 i-node 編號在檔案刪除後會被回收再用,世代編號每次重用就 +1,避免新舊檔案搞混。

比喻:寄物櫃號碼牌

file handle 就像火車站寄物櫃的號碼牌。你(用戶端)不需要懂牌子背後的機械原理(opaque),只要把牌子交回去,櫃台(伺服器)就能精準取出你的東西。而『世代編號』就像號碼牌上的啟用批次章——同一個櫃號被不同人租過,靠批次章區分『這是這一輪的租客』,避免你拿到上一位客人的行李。

file handle 由 lookup、create、mkdir 等操作從伺服器傳給用戶端,之後用戶端把它放進每個後續操作的參數裡。

💡
關鍵

file handle 是不透明的『寄物櫃號碼牌』;世代編號避免回收的 i-node 編號造成新舊檔案混淆。

🔆
生活妙喻:虛擬檔案系統 (VFS) ≈ 萬國旅行插座轉接頭

不管你的插頭(system call)面對的是本機還是遠端插座,VFS 都幫你接上對的孔,讓你完全不必換裝置。

🔆
生活妙喻:file handle ≈ 寄物櫃的號碼牌

用戶端不需懂牌子內部結構(opaque),交回去伺服器就能取出對的檔案;世代編號像啟用批次章,避免拿到前一位租客的東西。

本節字彙

虛擬檔案系統 (VFS)
加進 UNIX 核心的模組,負責分辨本機與遠端檔案、翻譯識別碼,讓同一套 system call 同時適用兩者。
🧠 VFS = 萬用轉接頭,把不同檔案系統接成同一個介面。
file handle(檔案控制代碼)
NFS 用來指稱檔案的不透明識別碼,含伺服器辨識檔案所需的一切資訊。
🧠 opaque 號碼牌:你只管帶著走,內容讓伺服器解讀。
i-node 世代編號 (generation number)
因 i-node 編號會在刪檔後被回收重用,每次重用就遞增的編號,用以區分新舊檔案。
🧠 同號碼牌的『第幾批租客』戳章。
NFS 達成『存取透明』——程式對本機或遠端檔案發出同樣的 system call 而無感差異。這主要靠核心裡的哪個模組實現?
為什麼說 file handle 對用戶端是『不透明 (opaque)』的?
UNIX 實作的 file handle 中包含『i-node 世代編號』,它存在的主要原因是什麼?

Mount、路徑翻譯與 Automounter

遠端檔案系統如何被掛載進本機名稱空間,路徑為何要逐段翻譯,automounter 又做了什麼。

STEP 1

深度探秘

Mount:把遠端子樹接到自己的名稱空間

掛載(mount)是怎麼一回事

遠端檔案系統要先被掛載,本機程式才能用。流程是:

  • 每台 NFS 伺服器上有個著名檔案 /etc/exports,列出可供遠端掛載的本機檔案系統,並附上存取清單指明哪些主機可以掛。
  • 用戶端用修改過的 mount 指令,指定遠端主機名、遠端目錄路徑、以及要掛在本機的哪個名稱。掛載服務會回傳該目錄的 file handle,連同伺服器位置交給 VFS 與 NFS 用戶端。

掛載後,本機路徑就能無縫指到遠端檔案。例如把 Server 1 的 /export/people 掛在本機 /usr/students,程式就能用 /usr/students/jon 存取遠端檔案。

硬掛載 vs 軟掛載

硬掛載 (hard) 軟掛載 (soft)
伺服器無回應時 程序被暫停,無限重試直到成功 重試幾次就回傳失敗
伺服器故障後 重啟後程序若無其事繼續 程式須自行偵測失敗並處理
風險 伺服器長期掛掉時程式卡死 沒檢查失敗的程式會行為怪異

因為許多 UNIX 工具不檢查檔案存取是否失敗,多數安裝採用硬掛載——代價是伺服器久久不回時程式無法優雅地恢復。

💡
關鍵

mount 把遠端子樹接到本機名稱空間;硬掛載卡死等待、軟掛載早早回傳失敗,多數選硬掛載。

STEP 2

深度探秘

路徑為什麼要在用戶端逐段翻譯

路徑翻譯:不能丟給伺服器一次搞定

在單機 UNIX,多段路徑是一步步翻成 i-node 參考。在 NFS,路徑不能在伺服器一次翻完,原因很關鍵:

一條多段路徑中,不同段的目錄可能位在不同伺服器上(因為某段是另一個掛載點)。

所以路徑由用戶端逐段解析:每一段若指向遠端掛載的目錄,就對該遠端伺服器發一次 lookup 請求,取回 file handle 與屬性,再用它當下一段的目錄繼續查。

flowchart LR
    A[根] -->|lookup usr| B[本機 usr]
    B -->|lookup students 跨掛載點| C[Server1 的目錄 fh]
    C -->|lookup jon| D[檔案 fh]

這看似沒效率(解析五段路徑要多次 lookup),但靠快取每一步的翻譯結果來緩解——人們通常集中存取少數幾個目錄,存取局部性讓快取很有效。事實上,研究指出 lookup 佔了將近 50% 的伺服器呼叫,正是這種逐段翻譯的後果。

💡
關鍵

一條路徑可能橫跨多台伺服器,所以必須由用戶端逐段 lookup;快取翻譯結果來補救效率。

STEP 3

實用超能力

Automounter:用到才掛、還能順手做容錯

Automounter:聰明的隨選掛載

Automounter 解決一個痛點:不必預先把所有遠端目錄都掛好,而是用到某個『空』掛載點時才動態掛載

運作方式:

  1. Automounter 維護一張表,把掛載點對應到一或多台 NFS 伺服器。它表現得像本機的一個小 NFS 伺服器。
  2. 當用戶端要解析含此掛載點的路徑時,automounter 對表上的每台伺服器發**探測(probe)**請求。
  3. 第一台回應的伺服器就被掛載;之後用符號連結(symbolic link)讓後續存取直接走,不再經過 automounter。若數分鐘沒人用,就自動卸載。

順手得到的好處:唯讀複製 + 容錯 + 負載平衡

如果在表上對同一個名字列出多台持有相同副本的伺服器(適合很少變動、又被大量讀取的檔案,例如 /usr/lib 系統二進位檔),就能達成一種簡單的唯讀複製

  • 第一次開檔時對所有伺服器送探測,第一個回應者被掛載。
  • 第一個回應的通常是沒當機、且負載較輕的那台——於是同時得到了有限的容錯負載平衡
# automounter 表(概念示意)
/usr/lib  ->  serverA, serverB, serverC   # 三台都有相同副本
💡
關鍵

automounter 用到才掛、第一個回應者勝出;列多台相同副本就順便換到唯讀複製、容錯與負載平衡。

🔆
生活妙喻:硬掛載 vs 軟掛載 ≈ 打電話:一直重撥 vs 撥幾次沒人接就放棄

硬掛載像對重要對象一直重撥直到接通(程序暫停等待);軟掛載像撥幾次沒接就掛掉去做別的(回傳失敗)。

🔆
生活妙喻:automounter 探測多台取第一個回應 ≈ 同時打給三家披薩店,誰先接就跟誰訂

先接電話的那家通常沒打烊(沒當機)且現在不忙(負載輕),於是你自動避開了壞掉或忙碌的店——順便得到容錯與負載平衡。

本節字彙

掛載 (mount)
把遠端檔案系統的子樹接到本機名稱空間某個掛載點,之後本機路徑即可指向遠端檔案。
🧠 像把外接硬碟『接到』你的資料夾樹上。
硬掛載 (hard mount)
伺服器無回應時程序被暫停並無限重試,伺服器恢復後程序若無其事繼續。
🧠 hard = 死等到底,重要對象一直重撥。
automounter
在引用到空掛載點時才動態掛載遠端目錄的機制;可列多台伺服器達成唯讀複製與容錯。
🧠 用到才掛、第一個回應者勝出的『隨選掛載員』。
為什麼 NFS 的多段路徑無法在伺服器端一次翻譯完,而必須由用戶端逐段 lookup?
某 NFS 安裝採用硬掛載 (hard mount)。當伺服器突然故障時,正在存取該檔案系統的使用者程序會發生什麼?
在 automounter 表中對同一個名字(如 /usr/lib)列出多台持有相同副本的伺服器,能順帶帶來什麼好處?

快取與一致性:時間戳記的妥協

伺服器端與用戶端快取如何運作,timestamp 驗證如何在一致性與效率間取得平衡。

STEP 1

深度探秘

伺服器端快取與 write 的兩種模式

為什麼 NFS 一定要快取

用戶端與伺服器兩端的快取,都是 NFS 達成足夠效能不可或缺的手段。伺服器端用快取保存最近讀過的磁碟區塊,不造成一致性問題;但寫入時就得確保資料真的持久化,即使伺服器當機。NFS v3 的 write 提供兩種模式:

  1. write-through(寫穿):資料先寫進伺服器記憶體快取,並寫到磁碟後才回覆用戶端。用戶端一收到回覆就確定資料已持久。
  2. commit 模式:資料只先放記憶體快取,等收到該檔案的 commit 操作才寫到磁碟。標準 NFS 用戶端採此模式——在開啟寫入的檔案被關閉時才發 commit。

commit 是 v3 新增的操作,用來解決 write-through 在大量寫入時造成的效能瓶頸。

為什麼分散式特別需要 write-through 的保證?因為獨立故障:伺服器掛了,用戶端還活著,程式可能基於『之前的寫入已落地』而繼續動作。這在單機很少發生(本機檔案系統壞了,程式通常也一起死了)。

💡
關鍵

伺服器讀快取沒問題;寫入要嘛 write-through 立刻落地,要嘛靠 commit 在關檔時落地。

STEP 2

深度探秘

用戶端快取與兩個時間戳記

用戶端快取怎麼判斷『還新鮮嗎』

用戶端會快取 read、write、getattr、lookup、readdir 的結果以減少對伺服器的請求。但這會讓不同用戶端持有不同版本——因為一個用戶端的寫入不會立刻更新別人的快取。所以用戶端要主動輪詢伺服器檢查快取是否還新鮮,方法是用兩個時間戳記:

  • Tc:這份快取項目上次被驗證的時間。
  • Tm:該區塊上次在伺服器被修改的時間。

在時間 T,快取項目有效的條件是:

( T - Tc < t )   或   ( Tm_client == Tm_server )

其中 t 是新鮮區間(freshness interval)。第一個條件不需連伺服器就能算;若成立就免查。若不成立,才透過 getattr 向伺服器取最新的 Tm 比對:相同就把快取視為有效並更新 Tc,不同則作廢快取、重新向伺服器要資料。

flowchart TD
    A[要用快取項目] --> B{T減Tc 小於 t 嗎}
    B -- 是 --> E[直接用快取]
    B -- 否 --> C[getattr 取伺服器 Tm]
    C --> D{Tm_client 等於 Tm_server 嗎}
    D -- 是 --> F[視為有效 更新 Tc]
    D -- 否 --> G[作廢快取 重新抓資料]
💡
關鍵

用兩個時間戳記 Tc 與 Tm 加新鮮區間 t 判斷快取是否有效,先算本地條件再決定要不要問伺服器。

STEP 3

實用超能力

新鮮區間的取捨與一致性的真相

t 的取捨:一致性 vs 效率

新鮮區間 t 是個典型的取捨旋鈕:

  • t 很短 → 很接近一對複本一致性,但要頻繁呼叫伺服器查 Tm,伺服器負載重。
  • t 較長 → 伺服器輕鬆,但用戶端可能用到較舊的資料。

Sun Solaris 用戶端採自適應設定:檔案依更新頻率落在 3~30 秒;目錄則是 30~60 秒(目錄較少並行更新,風險低)。

幾個降低 getattr 流量的小技巧

  • 一收到新的 Tm_server,就套用到該檔案所有相關快取項目。
  • 把最新屬性**搭便車(piggyback)**附在每個操作的回覆裡,順便更新快取。
  • 自適應的 t 已大幅降低多數檔案的流量。

誠實面對:NFS 的一致性沒那麼嚴格

這套驗證不保證與單機 UNIX 同等的一致性,因為近期更新不一定立刻被分享者看到。時間落差有兩個來源:寫入離開更新者核心快取前的延遲,以及那 3 秒的快取驗證『時間窗』。

結論:NFS 提供近似一對複本語意,足夠應付絕大多數應用;但不建議用 NFS 檔案共享來做跨機器的通訊或緊密協作。

💡
關鍵

t 在一致性與效率間取捨;NFS 只給近似一致性,不適合拿來做跨機緊密協作。

🔆
生活妙喻:新鮮區間 t 與快取驗證 ≈ 牛奶上的賞味期限

在賞味期限內(T - Tc < t)你直接喝(用快取);過了期限才打開聞一下(getattr 查伺服器)確認還能不能喝。期限越短越安全但越麻煩。

🔆
生活妙喻:write-through 的持久化保證 ≈ 重要文件寄出後一定要拿到簽收回執

write-through 就像堅持等到對方簽收(資料寫到磁碟)才算數,這樣即使你那邊出事,也確定文件已安全送達。

本節字彙

write-through(寫穿)
寫入資料先進伺服器記憶體再寫到磁碟後才回覆用戶端,確保收到回覆即代表已持久化。
🧠 寫『穿透』到磁碟才算數,像一定要拿到簽收回執。
新鮮區間 t (freshness interval)
快取項目在不向伺服器查證下被視為新鮮的時間長度,是一致性與效率的取捨旋鈕。
🧠 像牛奶的賞味期限:期限內直接用。
搭便車 (piggyback)
把最新檔案屬性附在其他操作的回覆中一起傳回,順便更新用戶端快取以減少額外請求。
🧠 順路捎一程:回覆裡夾帶最新屬性。
NFS v3 的 write-through 模式下,用戶端收到 write 的回覆時可以確定什麼?
在 NFS 用戶端快取驗證中,若『T - Tc < t』這個條件成立,會發生什麼?
把新鮮區間 t 設得『很短』會帶來什麼取捨?
04

案例二:Andrew File System (AFS)

AFS 為何選擇傳整個檔案、存到本機磁碟,open/close 時各自發生什麼事。

整檔快取與 Vice/Venus 的劇本

AFS 為何選擇傳整個檔案、存到本機磁碟,open/close 時各自發生什麼事。

STEP 1

深度探秘

AFS 的核心賭注:整檔快取

AFS 為了『規模』而生

AFS 與 NFS 設計上差很多,根源在於 AFS 把**可擴展性(scalability)**當成最重要的目標——要能服務比其他系統更多的同時使用者。它的關鍵策略是兩個不尋常的設計:

  • 整檔服務 (Whole-file serving):伺服器把整個檔案(或整個目錄)的內容一次傳給用戶端(AFS-3 對大於 64 KB 的檔案分成 64 KB 區塊傳)。
  • 整檔快取 (Whole-file caching):傳來的檔案存進用戶端本機磁碟的快取,可容納數百個最近用過的檔案,而且這份快取是永久的——重開機後還在。本機副本優先被用來滿足 open 請求。

為什麼這賭注合理?因為 AFS 是根據真實 UNIX 工作負載的觀察設計的:

  • 檔案大多很小(多數小於 10 KB)。
  • 讀遠多於寫(約 6 倍)。
  • 多為循序存取,少有隨機存取。
  • 大多數檔案只被一個使用者讀寫;就算共享,通常也只有一人修改。
  • 檔案的存取是陣發的:最近用過的,近期很可能再用(局部性)。

例外:資料庫不適合 AFS——多人共享、頻繁更新、需要細緻並行控制,設計者明確把它排除在目標之外。

💡
關鍵

AFS 為規模而生,靠『整檔服務 + 整檔快取到本機磁碟』把工作集留在用戶端,大減伺服器往來。

STEP 2

深度探秘

Vice 與 Venus:一場 open/close 的劇本

兩個主角:Vice 與 Venus

AFS 由兩個 UNIX 行程實作:

  • Vice:跑在每台伺服器的伺服器軟體。
  • Venus:跑在每台用戶端的行程,對應抽象模型裡的『用戶端模組』。

核心被修改過,會攔截 open、close 等對共享名稱空間檔案的 system call,轉交給 Venus 處理。讓我們看一場完整劇本:

sequenceDiagram
    participant U as 使用者程序
    participant V as Venus 用戶端
    participant S as Vice 伺服器
    U->>V: open 共享檔
    V->>S: 本機快取沒有或無效 請求整檔
    S->>V: 傳整個檔案複本
    V->>U: 開本機副本 回傳檔案描述子
    U->>U: read write 都作用在本機副本
    U->>V: close
    V->>S: 若被改過 把整檔送回
    S->>S: 更新內容與時間戳記
  1. open:本機快取若沒有有效副本,向持有檔案的 Vice 要整份。
  2. 副本存進本機檔案系統,open 後回傳檔案描述子。
  3. 之後的 read/write 全部作用在本機副本——完全不碰伺服器!
  4. close:若本機副本被改過,就把內容送回 Vice,伺服器更新檔案與時間戳記;本機副本仍保留以備再用。
💡
關鍵

Vice 是伺服器、Venus 是用戶端;open 抓整檔、讀寫只碰本機副本、close 才把改動送回。

STEP 3

實用超能力

fid、volume,與這設計為何聰明

命名與分組:fid 與 volume

AFS 也對應抽象模型:Vice 實作平面檔案服務,目錄階層由各用戶端的 Venus 實作。每個檔案/目錄用一個 96 位元的 fid(檔案識別碼,類似 UFID) 標示。Venus 負責把使用者給的路徑名翻成 fid。fid 結構:

fid = [ 32-bit volume number | 32-bit file handle | 32-bit uniquifier ]
  • volume number:檔案所屬的 volume(卷冊)——AFS 的分組單位,通常比 NFS 的 filesystem 小(例如每個使用者一個 volume)。
  • file handle:在 volume 內定位檔案。
  • uniquifier:確保識別碼不被重用。

名稱空間切成『本機』與『共享』兩塊(共享子樹叫 cmu)。本機檔案只用於暫存與開機必需品;使用者目錄都放在共享空間,所以你能從任何工作站存取自己的檔案。

為什麼整檔快取在這些場景超有效?

  • 很少更新的共享檔(系統指令、函式庫)→ 本機副本長期有效。
  • 只有單一使用者用的檔(自己家目錄)→ 就算改了,新版也在自己機器的快取裡。

這兩類檔案佔了絕大多數的存取。一旦工作集進了本機磁碟快取(可分配上百 MB),工作站幾乎不必再麻煩伺服器——這正是 AFS 規模化的祕密。

💡
關鍵

fid 含 volume 編號;本機/共享分流加上整檔快取,讓常用檔長留本機,工作站幾乎自給自足。

🔆
生活妙喻:整檔快取到本機磁碟 ≈ 把整本書借回家影印一份留著

與其每次要看就跑圖書館(伺服器)翻一頁,不如一次把整本借回家影印(整檔快取),之後在家想怎麼看就怎麼看,還書(close)時才把改動交回去。

🔆
生活妙喻:Vice 與 Venus 的分工 ≈ 中央倉庫 Vice 與你家的管家 Venus

管家 Venus 攔下你所有的取物指令,需要時才去中央倉庫 Vice 整箱搬回家,平時你都只用家裡那箱,要還貨(close 改動)才回倉庫。

本節字彙

整檔快取 (whole-file caching)
把整個檔案傳到用戶端並存進本機磁碟快取,重開機後仍保留,open 時優先使用本機副本。
🧠 整本借回家影印,之後都在家看。
Vice / Venus
Vice 是跑在伺服器的 AFS 伺服器行程;Venus 是跑在用戶端、對應用戶端模組的行程。
🧠 Vice = 倉庫(伺服器);Venus = 你家管家(用戶端)。
volume(卷冊)
AFS 的檔案分組單位,通常比 NFS 的 filesystem 小,例如每個使用者一個 volume。
🧠 一個 volume 像一個人的私人置物櫃。
AFS 與 NFS 設計差異的根源,主要來自 AFS 把哪一項當成最重要的設計目標?
在 AFS 的典型運作中,使用者程序對一個已快取且有效的檔案執行 read 與 write,這些操作實際作用在哪裡?
在 AFS 中,當使用者程序對一個被修改過的共享檔案執行 close 時,會發生什麼?

Callback Promise:用通知取代輪詢

callback promise 如何維持快取一致性,以及它如何讓 AFS 比 NFS 更能擴展。

STEP 1

深度探秘

callback promise 是什麼

用『我會通知你』取代『你一直來問』

AFS 維持快取一致性的核心機制是 callback promise(回呼承諾)。當 Vice 把檔案副本給 Venus 時,會附上一個承諾:『只要有別人改了這個檔案,我一定通知你』

  • callback promise 與快取檔一起存在用戶端磁碟上,有兩種狀態:valid(有效)cancelled(取消)
  • 當伺服器執行某檔案的更新請求,它會對所有持有該檔 callback promise 的 Venus 各送一個 callback(這是一個從伺服器打到 Venus 的 RPC)。
  • Venus 收到 callback,就把該檔的承諾設成 cancelled

於是 Venus 處理 open 時:檢查快取 → 若檔案在快取中,看它的承諾 token:

  • valid → 直接用本機副本,完全不必聯絡 Vice
  • cancelled → 必須向 Vice 重新抓最新副本。
sequenceDiagram
    participant A as 用戶端A Venus
    participant S as Vice 伺服器
    participant B as 用戶端B Venus
    S->>A: 給檔案副本 加 callback promise valid
    B->>S: close 已修改的同一檔案
    S->>S: 更新檔案內容
    S->>A: 送 callback 取消承諾
    A->>A: 把 token 設為 cancelled
💡
關鍵

callback promise 是伺服器的承諾:別人改檔我就通知你;valid 就直接用本機、cancelled 才重抓。

STEP 2

深度探秘

為什麼比 NFS 輪詢更能擴展

callback vs timestamp:通知 vs 輪詢

AFS-1 原本用的是類似 NFS 的時間戳記法:Venus 每次 open 都去問 Vice『我這份還新嗎?』。AFS-2 改用 callback,原因是更能擴展

時間戳記法(NFS / AFS-1) callback 法(AFS-2+)
何時聯絡伺服器 每次 open 都問 只有檔案被更新
沒被改的檔案 仍要往返驗證 完全不必聯絡
伺服器負載 低很多

由於多數檔案不會被並行存取、且讀多於寫,callback 讓用戶端與伺服器的互動戲劇性地大幅減少——這正是 AFS 規模化的關鍵。實測中,18 個用戶端跑標準 benchmark,AFS 伺服器負載僅 40%,而 NFS 跑同樣的 benchmark 是 100%。

flowchart TD
    A[用戶端要 open 檔案] --> B{快取有且 token valid 嗎}
    B -- 是 --> C[直接用本機副本 零次伺服器往返]
    B -- 否 --> D[向 Vice 重抓最新副本]
💡
關鍵

callback 只在『檔案被改』時才通訊,沒改的檔零往返;讀多寫少讓伺服器負載大幅下降。

STEP 3

實用超能力

代價、漏訊處理與更新語意

天下沒有白吃的午餐

callback 機制要求 Vice 伺服器必須保存用戶端狀態——它得記著『每個檔案發了 callback promise 給哪些 Venus』。這跟無狀態的 NFS、AFS-1 與抽象模型不同。這些 callback 清單存在伺服器磁碟、用原子操作更新,才能撐過伺服器故障。

萬一 callback 訊息掉了怎麼辦?

網路故障可能讓 callback 遺失,於是 AFS 用兩道保險:

  1. 重啟後驗證:工作站重啟後,Venus 對每個快取檔在首次使用前發快取驗證請求(帶修改時間戳記)給伺服器,確認是 valid 還是 cancelled。
  2. 定期續約:若距上次與伺服器通訊已超過時間 T(通常約 10 分鐘),open 前必須先續約 callback。

更新語意:寬鬆但夠用

callback 機制只在 open 與 close 時作動。檔案一旦開啟,用戶端怎麼改本機副本,別人都不知道;close 時整檔送回覆蓋伺服器版本。

重要陷阱:若不同工作站同時開、寫、關同一檔案,除了最後一個 close,其餘更新會被無聲地丟失(不報錯)!需要並行控制的話,用戶端得自己實作。但同一台工作站上的兩個程序開同一檔,會共用同一份快取副本,按一般 UNIX 方式逐區塊更新。

💡
關鍵

callback 需伺服器保存狀態並用續約/重啟驗證防漏訊;跨機並行 close 只留最後一個,其餘無聲丟失。

🔆
生活妙喻:callback promise(通知取代輪詢) ≈ 餐廳的取餐呼叫器

拿到呼叫器(callback promise)你就安心坐著(用本機副本),不必一直跑去櫃台問『好了沒』(輪詢);餐點好了它才會震動通知你(callback)。

🔆
生活妙喻:跨機並行 close 只留最後一個 ≈ 兩人各自影印同一份文件改完、先後交回

兩人都拿影本回家改,交回時後交的那份直接蓋掉先交的,先交者的修改就無聲消失了——所以要協作得自己先講好規則。

本節字彙

callback promise(回呼承諾)
Vice 給 Venus 的承諾 token,保證該檔被他人修改時會通知;狀態為 valid 或 cancelled。
🧠 像取餐呼叫器:好了我震你,不用一直問。
callback(回呼)
從伺服器打到 Venus 的 RPC,用以取消某檔案的 callback promise。
🧠 伺服器『回打』給你:你那份過期了。
callback 續約 (renew)
若超過時間 T(約 10 分鐘)未與伺服器通訊,open 前須重新確認 callback,以防漏掉的通知。
🧠 呼叫器太久沒訊號,用前先確認它還連著。
在 AFS 中,Venus 處理 open 時若發現快取裡有該檔案且其 callback promise token 為 valid,會怎麼做?
相較於 NFS / AFS-1 的時間戳記輪詢法,AFS-2 的 callback 機制為什麼更能擴展?
callback 機制與 NFS、AFS-1 及抽象模型最關鍵的差異是什麼?
05

後續演進與新方向

Spritely NFS、NQNFS 如何加回 open/close 與 callback,WebNFS 與 NFSv4 如何走向 Internet。

讓 NFS 更強:一致性與廣域存取

Spritely NFS、NQNFS 如何加回 open/close 與 callback,WebNFS 與 NFSv4 如何走向 Internet。

STEP 1

深度探秘

把 open/close 與 callback 加回 NFS

用狀態換回嚴格一致性

NFS 的無狀態架構帶來穩健與好實作,卻換不到精確的一對複本更新語意,也用不了 callback,導致用戶端得頻繁發 getattr 來偵測檔案是否被改。兩個研究系統正是來補這個缺口:

Spritely NFS

它在 NFS 協定上加了 open 與 close。用戶端本機程序開檔時要送 open(帶模式:讀/寫/兩者,以及目前本機開檔讀者與寫者的計數);關檔時送 close 更新計數。伺服器把這些記在 open files table

伺服器收到 open 時,檢查有沒有別的用戶端也開著同檔,並送 callback 指示它們調整快取策略:

  • open 指定模式:若已有別人開著寫 → 失敗;開著讀的用戶端被指示作廢本機快取片段。
  • open 指定模式:對正在寫的用戶端送 callback 要它改成嚴格 write-through;對正在讀的用戶端要它停止快取(讀都直接問伺服器)。

結果:維持 UNIX 一對複本語意,代價是伺服器要帶一些用戶端相關狀態。若狀態放在揮發性記憶體就怕當機,所以 Spritely NFS 有回復協定:靠存在磁碟、較少更新、且『悲觀』(寧可多列)的用戶端清單,重建 open files table。

💡
關鍵

Spritely NFS 加上 open/close 與 callback,用伺服器狀態換回嚴格一對複本語意,並配回復協定防當機。

STEP 2

深度探秘

NQNFS 的租約,與走向 Internet 的 WebNFS

NQNFS:用租約簡化回復

NQNFS(Not Quite NFS) 目標與 Spritely NFS 類似,但改用 租約(lease) 來幫助當機回復:伺服器替開啟的檔案設一個租約時間上限,用戶端想繼續用就得續約。一樣用 callback 在寫入發生時要用戶端清快取;但若用戶端沒回應,伺服器只要等租約到期就能繼續處理新的寫入。

租約的妙處:狀態自帶『過期時間』,伺服器當機或用戶端失聯時,狀態會自然清掉,不必複雜的清理程序。

WebNFS:讓應用程式直連 NFS

Web 與 Java applet 興起後,有些 Internet 應用想直接存取 NFS 伺服器,而不必走標準 NFS 用戶端那套模擬 UNIX 操作的開銷。WebNFS 讓瀏覽器與應用程式能用一個公開 file handle、相對於公開根目錄來存取被『發佈』的檔案:

  • 繞過 mount 服務與 port mapper,直接連 NFS 伺服器的著名埠 2049
  • 多元件 lookup:一次請求就查完多段路徑,降低廣域網路高延遲的影響。

例如氣象服務可發佈 nfs://data.weather.gov/weatherdata/global.data,互動式地圖程式只讀它需要的那幾段,而不像 HTTP 得搬整個資料庫或另寫專用伺服器。

💡
關鍵

NQNFS 用租約讓狀態自動過期、簡化回復;WebNFS 用公開 handle 與多元件 lookup 讓應用直連 Internet 上的 NFS。

STEP 3

實用超能力

NFS v4 集大成,DCE/DFS 強化 AFS

NFS 版本 4:站在巨人肩膀上

NFS v4(2000 年)和 WebNFS 一樣以廣域網路與 Internet 應用為目標,但因為是全新協定,可做更根本的改良:

  • 納入 WebNFS 的功能(WebNFS 受限於不能新增協定操作)。
  • 採用近十年檔案伺服器研究成果,例如用 callback 或租約維持一致性。
  • 支援伺服器故障的即時回復:檔案系統可被透明地搬到新伺服器。
  • 用類似 Web 的**代理伺服器(proxy)**提升可擴展性。
flowchart TD
    A[Spritely NFS 加 open close callback] --> D[NFS v4 集大成]
    B[NQNFS 用租約] --> D
    C[WebNFS 公開 handle 多元件 lookup] --> D

DCE/DFS:把 AFS 再往前推

以 AFS 為基礎的 DCE/DFS 在快取一致性上更進一步。AFS 只在伺服器收到已更新檔案的 close 時才發 callback;DFS 仿效 Spritely NFS / NQNFS,一更新就發 callback。要更新檔案,用戶端得先向伺服器取得一張寫 token(指定可寫的位元組範圍);此時持有讀副本的用戶端會收到撤銷 callback。各類 token 都有壽命,到期要續。

💡
關鍵

NFS v4 整合 open/close、callback、租約與 proxy 走向 Internet;DCE/DFS 用寫 token 在『更新時』就發 callback。

🔆
生活妙喻:租約 (lease) ≈ 停車場的計時票

票上印著到期時間,時間一到自動失效;就算你人不見了,車位也會被釋出——伺服器不必追著你清理狀態。

🔆
生活妙喻:WebNFS 多元件 lookup ≈ 一次把整張購物清單交給店員

與其一樣樣分開問(每段路徑一次 lookup、來回很慢),不如一次把整條多段路徑交出去,店員一趟就配齊——在高延遲的廣域網路特別省時。

本節字彙

Spritely NFS
在 NFS 協定加上 open/close 與 callback 的研究系統,可維持 UNIX 一對複本語意。
🧠 替無狀態 NFS『加裝記憶』換取嚴格一致性。
租約 (lease)
有到期時間的授權;用戶端要繼續使用須續約,過期狀態自動失效,簡化當機回復。
🧠 像停車計時票:到期自動失效。
WebNFS
讓應用程式用公開 file handle、經著名埠 2049 直接存取 NFS 伺服器的機制,支援多元件 lookup。
🧠 讓 NFS 直接上網,繞過 mount 與 port mapper。
Spritely NFS 為了在 NFS 上達成 UNIX 一對複本更新語意,做了什麼關鍵改動?
NQNFS 使用『租約 (lease)』而非單純的狀態紀錄,主要好處是什麼?
WebNFS 採用『多元件 lookup(一次請求查完多段路徑)』的主要動機是什麼?

儲存組織與無伺服器架構

RAID 與 log-structured 儲存如何提升效能與可靠度,xFS、Frangipani 如何把責任分散到多台機器。

STEP 1

深度探秘

RAID 與 log-structured 儲存

把資料在磁碟上重新組織

分散式檔案系統帶來更高的負載與可靠度需求,推動了磁碟資料組織的進步,兩大成果是:

RAID(廉價磁碟備援陣列)

把資料區塊切成固定大小的小塊,條帶化(striping)橫跨多顆磁碟儲存,並附上糾錯碼,讓某顆磁碟壞掉時仍能完整重建資料、正常運作。RAID 還比單顆磁碟更快——因為組成一個區塊的各條帶是並行讀寫的。

一句話:RAID 用『分散 + 冗餘』同時換到容錯與效能。

Log-structured 儲存 (LFS)

作者觀察到:記憶體越來越大,讀的快取命中率高、讀效能極佳,但效能依舊普通——因為寫個別資料區塊與更新 metadata(i-node)區塊的磁碟延遲很高。

LFS 的解法:先把一批寫入累積在記憶體,再一次以大塊、連續、固定大小的『日誌段(log segment)』寫到磁碟。資料與 metadata 嚴格按更新順序排列;一個日誌段 1 MB 以上,存在單一磁軌,省去逐區塊寫入的磁頭延遲。代價是要維護動態對應表、並由背景的 cleaner 做垃圾回收與壓實。

效果驚人:寫入吞吐量可達磁碟頻寬的 70%,傳統 UNIX 檔案系統不到 10%。後續的 Zebra 更把 log-structured 寫入結合分散式 RAID。

💡
關鍵

RAID 用條帶化加糾錯碼同時換來容錯與效能;LFS 把零散寫入聚成大塊日誌段,大幅提升寫效能。

STEP 2

深度探秘

新設計方向:把責任分散開

為什麼要走向『無伺服器』

高速交換式網路(ATM、交換式高速乙太網路)的出現,促成了把檔案資料高度可擴展、容錯地分散到許多節點的新設計。共同思路是:把『讀寫資料』的責任和『管理 metadata、服務用戶端請求』的責任分開

這些架構比前面的集中式伺服器更能擴展,但通常要求節點之間高度互信(因為它們用一種低階、類似『虛擬磁碟 API』的協定通訊),所以適用範圍多半限於單一區域網路。

flowchart TD
    A[傳統集中式檔案伺服器] -->|單點故障 擴展受限| B[瓶頸]
    C[把儲存責任分散] --> D[多節點並行傳輸 突破瓶頸]
    E[把管理責任分散] --> D

為什麼集中式伺服器有極限?書中點出:高效能 NFS 伺服器需要昂貴的多 CPU、多磁碟、多網路控制器硬體;把檔案空間切到不同伺服器的做法有上限;而且中央伺服器是單點故障

💡
關鍵

高速網路催生『分散儲存與管理責任』的設計,突破集中式伺服器的單點故障與擴展上限,但需節點間高度互信。

STEP 3

實用超能力

兩個代表:xFS 與 Frangipani

xFS:無伺服器網路檔案系統

UC Berkeley 的 xFS 之所以叫『無伺服器(serverless)』,是因為它把檔案伺服器的處理責任以單一檔案為粒度分散到區域網路上一群可用電腦。

  • 儲存責任獨立分散:實作軟體 RAID,把檔案資料條帶化跨多台電腦的磁碟,並用類似 Zebra 的 log-structuring。
  • 管理責任也分散:透過一個複製在所有節點上的 manager map——檔案識別碼裡有個欄位當索引,指出目前由哪台電腦負責管理該檔。

實測中,32 台工作站跑 xFS 的讀寫頻寬,約是單台雙處理器 NFS/AFS 伺服器的 10 倍

Frangipani:分兩層的設計

Frangipani 目標與 xFS 相似,但結構是兩個完全獨立的層

  • 下層:Petal —— 一個分散式虛擬磁碟,橫跨多台伺服器的多顆磁碟。它用資料副本容忍多數軟硬體故障,並自動搬移資料平衡負載;透過標準區塊 I/O 存取,因此能支撐多數檔案系統。
  • 上層:Frangipani 伺服器模組 —— 跑在核心內,動態指派檔案管理責任(含檔案鎖服務),所有機器看到統一名稱空間、對共享可寫檔案有近似一對複本的一致存取。資料以 log-structured + 條帶化存在 Petal 上。

Petal 幫 Frangipani 扛下實體磁碟空間管理,讓 Frangipani 的實作大為簡化——這就是『分層』威力的又一例。Frangipani 還能模擬 NFS、DCE/DFS 等既有服務介面。

💡
關鍵

xFS 以單檔為粒度把儲存與管理責任分散到多機;Frangipani 把儲存交給下層 Petal 虛擬磁碟,上層因而大幅簡化。

🔆
生活妙喻:RAID 條帶化加糾錯碼 ≈ 把重要文件影印分存多個保險箱並留備援頁

文件拆頁分存多個保險箱(條帶化),同時取存多箱可同時進行(更快);多印的備援頁(糾錯碼)讓某個保險箱壞了也能拼回完整文件(容錯)。

🔆
生活妙喻:Frangipani 與 Petal 的分層 ≈ 搬家公司把『開車運貨』外包給物流商

搬家公司(Frangipani)專心打包與安排(檔案管理),把實際的開車運送(實體磁碟管理)外包給物流商(Petal),自己的工作就簡單多了。

本節字彙

RAID(廉價磁碟備援陣列)
把資料條帶化跨多顆磁碟並加糾錯碼的儲存方式,同時提升容錯與並行讀寫效能。
🧠 分散存 + 留備援 = 又快又不怕單顆壞。
log-structured 儲存 (LFS)
把多筆寫入累積後一次以大塊連續『日誌段』寫到磁碟,大幅提升寫入效能。
🧠 把零散的寫入『打包成大箱』一次出貨。
Petal 虛擬磁碟
Frangipani 下層的分散式虛擬磁碟,橫跨多伺服器多磁碟,提供容錯的標準區塊 I/O 抽象。
🧠 把一堆實體磁碟偽裝成一顆超大虛擬磁碟。
RAID 既能提升容錯又能提升效能,這兩個好處分別來自什麼機制?
Log-structured 儲存 (LFS) 主要是為了改善哪一種效能問題?
xFS 與 Frangipani 這類『新設計方向』的共同核心思路是什麼?