Linux 網絡文件系統的數據備份及恢復機制實現
Linux 網絡文件系統簡介
網絡文件系統(NFS)協議是由 Sun MicroSystem 公司在 20 世紀 80 年代為了提供對共享文件的遠程訪問而設計和實現的,它采用了經典的客戶機/服務器模式提供服務。為了達到如同 NFS 協議通過使用 Sun 公司開發的遠在本機上使用本地文件系統一樣便捷的效果,NFS 通過使用遠程過程調用協議(RPC Protocol)來實現運行在一臺計算機上的程序來調用在另一臺遠程機器上運行的子程序。同時,為了解決不同平臺上的數據交互問題,它提供了外部數據表示(XDR)來解決這個問題。為了靈活地提供文件共享服務,該協議可以在 TCP 協議或者是 UDP 協議上運行,典型的情況是在 UDP 協議上運行。在此基礎上,NFS 在數據的傳送過程中需要 RPC 命令得到確認,而且在需要的時候會要重傳,這樣既可以通過 UDP 協議獲得較高的通信效率,也能通過 RPC 來獲得較高的通信可靠性。
由于 NFS 基于 C/S 模式提供服務,所以它的核心組件主要包括客戶機和服務器兩部分。圖 1 詳細說明了 NFS 的主要組件以及主要的配置文件。在服務器端,portmap、mountd、nfsd 三個監控程序將在后臺運行。portmap 監控程序用來注冊基于 RPC 的服務。當一個 RPC 的監控程序啟動的時候,它告訴 portmap 監控程序它在哪一個端口進行偵聽,并且它在進行什么樣的 RPC 服務。當一個客戶機向服務器提出一個 RPC 請求,那么它就會和 portmap 監控程序取得聯系以確定 RPC 消息應該發往的端口號。而 Mountd 監控程序的功能是來讀取服務器端的/etc/exportfs 文件并且創建一個將服務器的本地文件系統導出的主機和網絡列表,因而客戶機的掛接(mount)請求都被定位到 mountd 監控程序(daemon)。當驗證了服務器確實具有掛接所請求的文件系統的權限以后,mountd 為請求的掛接點返回一個文件句柄。而 nfsd 監控程序則被服務器用來處理客戶機端發送過來的請求。由于服務器需要同時處理多個客戶機的請求,所以在缺省情況下,操作系統將會自動啟動八個 nfsd 線程。當然,如果 NFS 服務器特別忙的時候,系統有可能根據實際情況啟動更多的線程。
圖 1 網絡文件系統簡圖
NFS 的客戶機與服務器之間通過 RPC 進行通信,通信過程如下所示:
- 用戶將 NFS 服務器的共享目錄掛載到本地文件系統中。
- 客戶訪問 NFS 目錄中的文件時,NFS 客戶端向 NFS 服務器端發送 RPC 請求。
- NFS 服務端接收客戶端發來的 RPC 請求,并將這個請求傳遞給本地文件訪問程序,然后訪問服務器主機上的一個本地的磁盤文件。NFS 服務器可以同時接收多個 NFS 客戶端的請求,并對其進行并發控制。
- NFS 客戶端向服務器主機發出一個 RPC 調用,然后等待服務器的應答。NFS 客戶端收到服務器的應答后,把結果信息展現給用戶或應用程序。
NFS 下的數據備份、恢復的主要功能
對數據進行備份與恢復是保證數據安全和業務連續性的非常成熟的做法。在 Linux 下的本地文件系統(例如 Ext2、Ext3 等)中,數據備份和恢復一般采用常規的辦法來進行操作,例如使用 Tar、Archive 等。而對于 NFS 來說,其數據備份需要采用量身定制的方法來進行。
為了保證數據在災難環境中的可用性和業務連續性,針對它的數據備份、恢復方案應具備如下重要功能:
- 通過對系統重要數據的快速備份,切實保證系統數據的安全;
- 可以根據指令完成備份系統的實時切入,保證服務不被中斷,保持系統持續運行的能力;
- 通過實時記錄所有文件的操作日志,系統管理員能夠在發生災難的情況下對日志進行分析和取證,從而發現入侵者的蛛絲馬跡。
NFS 多版本備份技術
為了保證服務器出現故障后能迅速恢復,要求系統數據能快速恢復到一個最近的正確狀態,所有這些都需要多版本技術的支持,通過同步記錄文件的在某些時刻的狀態,在整個系統范圍內建立起類似于數據庫系統的”檢查點”,以保證上述目標的實現。
對于多版本系統而言,需要較好地解決兩個方面的問題:性能和空間利用率。對于前者,最主要的是保證在生成版本的時候能夠快速完成,同時恢復時也具有較好的性能。此外,系統引入多版本造成的整體開銷也應該比較理想。對于第二點,主要考慮是節約磁盤空間,雖然隨著硬件技術的不斷發展,磁盤空間越來越大,性價比也越來越高,但是當版本較多而且文件數量較多、較大時,引入多版本增加的開銷也可能相當可觀,同時,較大的空間也意味著版本生成時可能需要更多的寫操作,這樣也必將影響總體性能。
為了保證引入多版本特性后文件系統仍具有較好的性能,以及保證較高的空間利用率,我們開發了一種高效的惰性版本生成算法。主要思想是:生成版本時不進行文件的復制,僅復制目錄結構,在新版本生成后到下一版本生成前,如果有文件需要修改,則第一次修改時對該文件進行復制,從而保證該文件狀態與對應的版本保持一致。
在一般情況下,目錄結構的數據量遠遠小于文件的數據量,因而這種方法可以大大降低版本生成時需要復制的數據量,因而具有較高的性能。同時,這種把單個文件版本生成的實際操作推后到非做不可的時候,并且任意文件在兩次版本之間最多生成一次版本,因此這種惰性策略可以使需要實際生成版本的文件數量達到最少,同時還可以把多個文件版本生成操作分散到具體的文件操作中,從而避免了集中的一次性版本生成方法可能造成的服務暫時停頓的問題。
版本生成后的結構如圖 2 所示。
圖 2 多版本生成示意圖
具體算法包括兩個部分,即版本生成算法和文件第一次修改處理算法,版本生成算法主要完成版本生成工作,主要過程如下:
- 找到需要形成版本的最高層目錄作為原目錄;
- 利用文件系統提供的函數,生成新的目錄節點,稱為新目錄;
- 把原目錄中的結構復制到新目錄;
- 在原目錄中找到所有的子目錄,重復 2、3 步;
- 把新的子目錄對應的 inode 號替換上一層目錄中的老 inode 號;
- 重復上述過程,及到目錄樹中的所有目錄得到復制為止。
在上述策略中,新版本并沒有復制所有的文件,只是在復制的目錄結構中記錄下了該文件的 inode 號(即復制了目錄的結構,而不是把文件都進行復制,從而節省了存儲和計算資源),因此,當有 NFS 請求需要對文件進行版本生成后的第一次修改時,需要復制該文件,生成新的版本。該實現過程參見如下流程圖:
圖 3 寫時復制算法示意圖
這種文件復制策略其實是一種惰性算法,也即我們常說的寫時復制的方法,這個方法在 Linux 操作系統的子進程對父進程資源的繼承中有所體現。這個策略一方面可以最大限度減少復制文件的數量,另一方面則可以避免瞬間過大的文件復制工作量,影響文件服務的性能。該算法的過程如下:當文件操作為寫操作時,判斷該文件是否版本生成后的第一次寫操作;若是則利用文件系統提供的底層函數生成一個新的文件,復制源文件的數據到新生成的文件,同時把該文件當前版本的 inode 節點中的版本號置為當前版本號,這樣新文件就成為該文件的最新版本。
雖然我們采用的算法可以有較好的性能,存儲開銷也是最優,但是,每次版本生成肯定會造成服務性能的下降和空間的占用,而這些代價在一個比較安全可靠的環境中是可以適當降低的,即當系統比較安全的時候,可以選擇讓系統以更低的頻率進行版本生成,相反,當系統安全狀況比較糟糕的時候,可以通過提高版本生成頻率適當降低服務性能來獲得更高的數據安全性能,當系統處于緊急狀態時,甚至可以要求立即進行版本生成。
基于這些考慮,我們采用了自適應的備份策略,災情評估系統可以動態評估系統的災情程度,然后可以立即修改版本生成策略,以適應當時的安全要求。
NFS 數據恢復技術
企業應用 NFS 的一個重要目標就是要保證系統的高可用性,即使在出現嚴重災難、故障、攻擊等情況下能具有較好的生存能力。因此,當一個系統出現故障時,如何快速地恢復系統,迅速投入到服務備份中去是相當重要的,所以,對于文件系統數據的恢復而言,也需要專門的考慮和設計。
本方案被配置成多個站點互為備份的情況,即平時只有一個主站點在服務,其他站點處于同步備份狀態,當某個站點出現故障或災難時,或者是被非法入侵者攻破時,系統可以立即分配新的主站點把被破壞的站點替換下來,進入恢復狀態,其他正常的站點仍可提供正常的服務。
當然,也存在所有站點均出現故障的情況,但是由于我們采用了多種措施,如動態隨機遷移、災情評估與響應策略等,再配合傳統的防火墻、IDS 等安全系統,可以極大限度地減少這種幾率。因此,我們的數據恢復問題主要考慮上述這種情形,即個別服務器出現故障退出服務而其他系統依然正常的情況。
首先,我們來分析一下系統退出后數據的情形,主要涉及到退出的服務器和正常的主服務器與備份服務器,如圖 4 所示:
圖 4 一個系統退出后數據狀態示意圖
在上圖中,退出服務器最后生成的版本號為 i,系統退出后,一方面主文件服務器會察覺到同步數據無法從退出服務器返回結果,這樣的話它就會重發同步請求,經過 3 次重發后,如果依然沒有返回信息,則認為該服務器退出服務,因此會把同步數據備份到磁盤文件中,并記錄下該服務器在同步數據文件中的起始位置,這當由多個文件服務器退出時可以分別識別出來。由于退出系統無法繼續保持同步,因此其狀態會與工作的文件服務器不一致,具體表現在以下幾個方面:
- 當退出時間很短時,數據不一致僅存在于緩沖區中,這時如果退出服務器能立即重新投入使用,則不需要進行額外的數據恢復,數據同步可以通過主服務器同步請求的重試來達到。
- 當主服務器確認退出服務器退出后,會把未同步的數據寫入特定的同步數據文件中,這時的不一致性包括了緩沖區中的數據和同步數據文件中的數據,這時的數據恢復需要做兩方面的工作:
- 把同步數據文件中的正確數據一次性發送給退出服務器,退出服務器把它寫入本地的同步數據文件;
- 建立本地的緩沖區,建立起同步機制,接收同步數據,同時啟動數據同步進程,先同步數據文件中的數據,當緩沖區數據因沒有處理而達到一定程度時,會自動把部分數據追加到同步數據文件的后面,這時,退出服務器已經恢復了正常工作,實際上也不需要過多的數據恢復工作。
由于主文件服務器一般需要處理文件的讀寫請求,寫請求僅占一部分,需要同步而執行的操作造成的負載要小于主服務器,因此可能在較短的時間內完成同步。當需要退出服務器(此時已經進入同步階段)成為主服務器時,則必須等所有同步數據同步完成后才能開始服務。
- 如果退出服務器是因為較嚴重的故障或災難而退出的,則可能需要較長時間的處理,如更換硬件、系統重啟、甚至重裝系統等,這時就可能出現一些困難的情況,一種是如上圖所示的,工作正常的系統已經生成了新的版本,如服務器退出時的最新版本是 i,經過一段時間后,正常系統生成了新的版本,這時主系統會清空同步數據文件,重新從版本生成后進行記錄。對于這種情況,可以有兩種處理辦法:
- 基于本地版本的快速恢復:當退出文件服務器本地至少存在一個版本與其他正常機器上的版本相同時,可以采用這種恢復策略。具體而言,先確定一個最新的正確版本,用本地版本恢復,這一過程非常簡單快捷,僅涉及到兩次 inode 的修改;然后選擇一臺正常服務器,請求它生成一個正常系統上最新版本與恢復版本的增量升級數據,這樣的數據量不會很大,而且不需要象基于操作的同步那樣逐步進行,同步效率非常高,因此可以大大提高恢復速度。同步到正常系統的最新版本后,然后就按照上述第 2 條的情況進行同步數據文件的同步。
- 基于分布版本的快速恢復:當停頓時間太長而不存在一個相同的版本,或文件服務器數據出現損壞(如磁盤故障造成數據損毀)時,需要采用此種方法。具體辦法如下:直接把正常服務器上的最新版本傳送到退出服務器,然后按照上述的第 2 種情況進行同步數據文件的同步。
正如上面所述,全部服務器均出現問題的概率是很小的,但是,不能簡單的排除這種情況的出現,特別是本方案采用數據同步機制,即多個站點的數據是保持快速同步的,這雖然能保證動態遷移的順利完成,但是也帶來較大的風險,就是會出現數據”污染”的自動傳播,當主文件服務器中的文件數據因為某些原因(主要是對文件的非法訪問)造成數據非法修改時,會立即傳播到其他備份節點,這樣的話,不管服務遷移到哪臺機器均會出現錯誤。
針對這種情況,我們采取了以下措施:當發現非法修改造成數據污染時,系統可以自動命令各站點恢復到指定的版本,如前一版本(可以由管理員配置成前一、二、三個版本);管理員也可以干預這一過程,強制各站點恢復到同一指定的版本,從而保證全局文件系統使用同一正確版本。
NFS 文件細粒度恢復技術
在傳統恢復技術中,一方面由于數據備份不是實時進行的,當出現事故需要恢復時,最新的備份數據與最新數據之間存在一個時間差,這樣就造成了該時間段內數據的丟失(見圖 5);同時,傳統的數據備份是一定時間段后數據的增量備份,是一段時間內所有文件操作疊加后的結果,因而無法精確知道在這段時間內實際數據的變化過程,因而也無法從所有這些操作中定位非法操作,并進行選擇性的恢復,以保證數據的正確性。
圖 5 因非實時備份造成恢復時的數據丟失示意圖
基于上述考慮,我們不但采用了增量方式的多版本備份恢復技術,同時還對文件的修改日志進行了實時的備份,這樣就可以在事故發生后進行基于文件操作的精確恢復,并支持允許剔除非法操作的選擇性恢復,這樣既能盡量避免因事故造成的數據丟失問題,又能通過選擇性恢復較好保證數據的正確性,同時,還可以通過對日志的分析,結合數據的精確恢復,達到發現犯罪線索、獲得有效證據的目的,為打擊網絡犯罪提供有力的技術手段。在這里,精確性恢復指的是恢復某一時段的所有操作,一般是在某一版本后的所有操作,不由用戶進行選擇,而選擇性恢復則指的是某一時間段內的所有操作構成的集合的子集,需要恢復的操作由用戶通過查詢、瀏覽等工具來進行選擇。在我們的定義中,實際上可以認為精確恢復為選擇性恢復的一個特例。
我們首先需要有相關的解決方法來記錄下具體的操作信息,形成操作日志文件,從而作為分析的證據(參見圖 6)。我們使用的策略是通過修改服務器操作系統內核調用 nfsd_write()、nfsd_create()……。從中取到調用處理對象的文件、目錄的全路徑名,寫進文件,在內核中截獲相應的文件操作請求。下面以 nfsd_rename()系統調用為例,進行擴充、修改而實現記錄操作日志的功能。
- int nfsd_rename(struct svc_rqst *rqstp, struct svc_fh *ffhp, char *fname,
- int flen,struct svc_fh *tfhp, char *tname, int tlen)
- {
- struct dentry *fdentry, *tdentry, *odentry, *ndentry;
- struct inode *fdir, *tdir;
- int err;
- char *name;
- mem_segment_t oldfs;
- int fd;
- err = fh_verify(rqstp, ffhp, S_IFDIR, MAY_REMOVE);
- if (err)
- goto out;
- rqstp->rq_path1 = rqstp->rq_path2;
- err = fh_verify(rqstp, tfhp, S_IFDIR, MAY_CREATE);
- if (err)
- goto out;
- fdentry = ffhp->fh_dentry;
- fdir = fdentry->d_inode;
- tdentry = tfhp->fh_dentry;
- tdir = tdentry->d_inode;
- ……
- //加入的代碼進行處理工作
- if((!rqstp->rq_recover)&&(!S_ISDIR(odentry->d_inode->i_mode))
- &&(odentry->d_inode->i_nlink>1)){
- rqstp->rq_copy->wait = 1;
- rqstp->rq_copy->done = 0;
- name = get_total_name(dentry,NULL);
- oldfs = get_fs();
- set_fs(KERNEL_DS); //進入內核模式
- fd = sys_open("/backupserv/changfilename.c",0,31681);
- sys_write(fd,name,strlen(name));
- sys_close(fd);
- set_fs(oldfs); //從內核模式返回
- while(!rqstp->rq_copy->done){
- schedule_timeout((HZ+99)/100);
- ……
- }
- }
該文件是在 nfs 服務器端執行 nfs 客戶機發送過來的修改文件或者是目錄的原函數。在這里,我們可以通過添加自己的代碼,來將創建的目錄和文件名存入一個磁盤文件當中,以備后面的備份和恢復操作。
圖 6 NFS 文件細粒度恢復日志產生示意圖
獲得了操作日志信息,然后就可以進行精確恢復和選擇性恢復時。首先由用戶利用數據查詢、瀏覽工具確定需要恢復的文件操作集,然后利用相應的日志數據按記錄產生順序逐條生成恢復請求,發送給文件服務器端的代理程序,由它通過 proc 文件請求 NFS 文件系統恢復模塊進行恢復,恢復模塊收到請求后,取出相關數據,然后通過調用底層 ext3 文件系統基本操作完成該次文件操作的”重放”,最后返回執行結果,通過 proc 文件通知代理程序,代理程序再通知管理端,管理端再發送下一條恢復請求,及到所有選中的操作全完成為止。具體實現模式請參看圖 7:
圖 7 恢復流程示意圖
數據快速同步技術
在系統中,各文件服務器之間的數據需要及時同步更新,這樣才能保證服務遷移后到新的環境后相關數據環境的一致性,從而保證服務遷移在語義上的正確性。在本方案中,每個文件服務器均采用 NFSv3 協議向外提供文件服務,當系統開始工作時,管理員會指定一臺主服務器,由該服務器負責向外提供服務,其他文件服務器為備份服務器,接收同步數據,進行數據的同步更新,并不對外提供服務,只有當系統決定遷移后,選定的遷移目標對應的文件服務器才成為主文件服務器。
由于主文件服務器負責對外的文件服務,因此,數據同步的發起者應該是主文件服務器,而所有的備份服務器均為被動的同步數據接收者。因此,數據的快速同步包含兩方面的工作:主文件服務器產生同步數據和備份文件服務器接收同步數據完成同步。具體的數據流向如圖 8 所示:
圖 8 同步數據的產生與流動示意圖
為了達到數據快速同步的目的,我們采用了記錄文件寫操作(包括創建、修改、刪除、改名、屬性修改等所有的改變文件或目錄屬性、內容的操作)的具體參數的方法來生成同步數據,這樣每次生成的數據量比較少,而且可以滿足及時更新的目的。同步數據的格式及相關代碼段如下:
- struct Log {
- int length; //整個數據包的長度
- int ops; //操作的類型
- char* data; //與操作相關數據
- };
- //下面代碼段從內核將同步數據包發往其他文件服務器
- long send(struct socket* sock, void * buff,size_t len)
- {
- int err;
- mm_segment_t oldfs;
- struct msghdr msg;
- struct iovec iov;
- static int total = 0;
- down(&log_sem);
- iov.iov_base=buff;
- iov.iov_len=len;
- msg.msg_name=NULL;
- msg.msg_iov=&iov;
- msg.msg_iovlen=1;
- msg.msg_control=NULL;
- msg.msg_controllen=0;
- msg.msg_namelen=0;
- total += len;
- msg.msg_flags = MSG_SYN;//DONTWAIT;
- oldfs=get_fs();
- set_fs(KERNEL_DS);
- err = sock_sendmsg(sock, &msg, len);
- set_fs(oldfs);
- if(err<0){
- dprintk("send err(errNo=%d len = %d)\n",err,len);
- netbroken = 1;
- }
- ……
- up(&log_sem);
- return(err);
- }
同步數據產生后,先放入一個緩沖區中,而不是立即發送到備份文件服務器,這樣可以較大程度改善系統的總體性能。緩沖區中的數據由同步管理進程管理,當達到一定數據量時,同步管理程序負責把緩沖區中的數據發送到備份文件服務器上,并根據返回的應答結果決定是否需要把重發數據,當確認某個服務器無法響應后,自動把同步數據定期寫入一個僅可追加的文件,以便于隨后可能需要的恢復階段同步的需要,當這個寫入的文件數據量超出一定限制時,并且系統確認已經至少有一個新的版本生成,可以把該文件清空。
當數據到達備份文件服務器時,由獨立的接收進程負責把數據放入接收緩沖區,經核對數據無誤后給主服務器發送確認信號,另一個獨立進程即更新管理進程把接收緩沖區作為輸入,從中解析出一個個的順序的操作日志,從每個日志中得到操作類型,然后在剩余的數據中按照特定的操作類型提取所需的參數,利用文件系統調用完成相應操作。
總結
Linux 網絡文件系統已經為企業在數據備份和共享領域得到了廣泛應用。如何保證其多版本備份、實時恢復是一個非常關鍵的問題,本文將詳細介紹針對該網絡文件系統的數據備份、恢復及同步機制在內核的具體實現,給廣大系統管理員和研發人員提供技術參考。