iGuard和NFS文件同步的解決方案

一般來說,從文件系統中獲得文件變化資訊,調用作業系統提供的 API 即可。Windows 作業系統上有個名為 ReadDirectoryChangesW 的 API 介面,只要監視一個目錄路徑就可以獲得包括其子目錄下的所有文件變化資訊,簡單高效;介面的支援度也很廣,現有主流的 Windows 作業系統都支援,往前還可以追溯到 Windows 2000。對碼農來說,能提供穩定有效且好用的 API 的系統就是好系統。而本文將討論 iGuard 網頁防篡改系統在 Linux 上獲取文件變化資訊的方法及從 NFS 網路文件系統中獲取文件變化時遇到的困難和心得。

在這裡插入圖片描述

▲ReadDirectoryChangesW

在 Linux 系統上獲取文件變更資訊,就沒有這樣的好運了,想要一個像 Windows 上一樣提供 ReadDirectoryChangesW 功能的 API,似乎是一種奢望。Linux 內核版本 2.4.0 (2001) 中引入了一個叫 dnotify 的目錄檢測機制,不怎麼好用;內核 2.6.13 (2005) 引入了新方法 inotfiy,但它與 ReadDirectoryChangesW 相比還是差一大截,一次調用只能監視當前目錄下的文件變更,子目錄里的變更則是無法感知的。如果要獲取整個目錄下的所有文件變化,應用程式需要遍歷整個目錄,並把所有的目錄監視起來。通過 inotify 介面獲得一個目錄創建事件時,需要把這個新建的目錄及時添加到監視列表,才有可能獲得新目錄下的文件變化。應用程式處理變化資訊較慢時,在把新建目錄添加到監視列表前,新目錄下的文件事件是極有可能丟失的。對於一個巨型文件系統來說,遍歷出所有的目錄也是件費事耗資源的任務。如此看來,inotify 機制並不完善,小規模文件數量的場景尚能勝任,像集約化平台,文件規模太龐大,就難以滿足了。

在這裡插入圖片描述

▲dnotify

在這裡插入圖片描述

▲inotify

隨著 Linux 系統的演化,獲取文件變化資訊的手段也在發展,但一直不完善,inotify 的缺陷同樣表現在 NFS 系統上。NFS 系統是天存資訊的用戶中一種常見的應用場景,它共享海量文件。我們的 iGuard 網頁防篡改系統在 NFS 上需要一種可靠的獲取文件變化的手段,來保障安全業務的 7×24h 運轉。鑒於 Linux 系統公開的 API 似乎不能滿足我們的要求,只有另闢蹊徑。幸好 Linux 是開源的,沒有現成的就改一個出來。

我們的改造目標指向了 NFS 系統的服務模組 nfsd。在 Linux 內核源程式碼樹下的文件系統 fs 目錄中很容易找到 nfsd 模組的同名目錄。在 Linux 系統中,NFS 服務透過虛擬文件系統 VFS 介面來訪問真實的文件系統,文件的新建、改寫、改名和刪除等動作是非常清晰的。我們很快就把這些文件更改相關的事件傳遞出來並為我所用。

最初,我們的這種 nfsd 解決方案和 iGuard 網頁防篡改系統看起來是可以一起工作的,在用戶生產環境下可以穩定地跑上幾周幾個月,基本上沒有問題。隨著集約化平台的興起,大量網站集中到統一的管理平台下進行內容編輯和運維,這樣的單一管理平台發布文件的規模每天可達百萬級別。我們的 iGuard 系統在超大規模的文件發布量下也暴露出一些問題,文件同步任務阻塞、滯後或者遺漏等;這些問題以前可能沒有出現或缺少關注,隨著規模變大,這些問題現今被放大了。這些問題中,最難排解的就是文件遺漏,明明磁碟文件已經更新,但系統就是沒有把文件內容同步到遠端。後來追查發現,在某些情況下,我們無法獲得 NFS 服務所寫文件對象的完整文件路徑,進而無法輸出對應文件的變更消息。

在 Linux 文件系統中,inodedentry 是兩個重要的數據結構 。前者對應於磁碟文件的元數據 (類型、尺寸、許可權等,但不包括文件路徑) 和文件數據塊索引,每個 inode 都有一個編號,在文件系統中是唯一的;後者是文件系統運行過程中創建的記憶體對象,組合成目錄項高速快取 dcache,每個 dentry 對應文件路徑上的一個節點並和一個 inode 相關聯,目錄樹由這些 dentry 組成,可以通過遍歷目錄樹來獲取文件路徑,dentry 可以被視作某種快取資訊,讓文件系統運行得更快更高效。

image

通過 NFS 對外提供文件訪問的系統需要符合文件系統可導出規範,這個規範在 Linux 的內核文檔 Making Filesystems Exportable 中有簡要說明,其中提到 NFS 通訊協議中使用文件句柄來標識文件,而不是平時所想像的按文件路徑來定位文件。這個句柄資訊跟符合可導出規範的文件系統相關,包含 inode 的編號、文件系統標識等資訊。這裡可以看出,我們需要的文件變化消息是基於文件路徑,而 NFS 操作文件是基於這種文件句柄,這裡就存在從文件句柄到文件路徑的轉譯過程。

image

在一般情況下,這個轉譯過程是正常的,每一個 NFS 文件句柄都可以在 dcache 中找到對應的文件。文檔 Making Filesystems Exportable 中還提到 dcache 構建中的 2 個注意事項,大致是:

  • dcache 包含的對象有時候是沒有合適前綴的節點 (可以理解為孤立的),該節點沒有與根節點相連。
  • 新遍歷出來的節點可能是已存在於 dcache 的孤立節點,這種情況需要將孤立節點移動到合適的位置 (可以理解為孤立節點回歸到大目錄樹下)。

這就解釋了我們在 NFS 系統中遇到的問題原因——無法獲取變更文件的完整路徑,因為它沒有和根節點相連。我們也能重現問題,在 NFS 服務和客戶端工作了一段時間後,重啟 NFS 伺服器,當 NFS 客戶端繼續讀寫曾經訪問過的文件時,由於 NFS 伺服器上的 dcache 已經複位,客戶端請求過來的文件句柄是合法的,並在伺服器端形成一個沒有合適前綴的節點,這樣的節點是無法解析出完整路徑的。dcache 畢竟是一個快取系統,不可能把磁碟上的目錄樹全部保存到記憶體,當記憶體不夠用時,dcache 會釋放一部分數據並進行記憶體回收。

NFS 服務的這個問題看似無解,是 NFS 工作模式引發的。為了解決問題,我們嘗試採用一種看似笨拙但有效的方法,創造性地構建一張持久化的超大表來跟蹤所有的 NFS 文件句柄,記錄它們的文件路徑資訊,通過這張大錶轉譯出那些沒頭腦的節點。用磁碟空間來換取 NFS 文件句柄的路徑檢索。方法雖不完美,但我們儘力讓 iGauard 網頁防篡改系統運行更加完美。(徐品華 | 天存資訊)

Ref

  1. Filesystem notification series by Michael Kerrisk
  2. Making Filesystems Exportable