最近,我有一個複製大量檔案的需求,雖然我已經在Unix各種變種上有超過20年的工作經驗,但是我仍然被cp指令的行為震撼,我認為這些心得很值得和大家分享下。
首先介紹下機器:一臺戴爾伺服器(雙核,記憶體最初是2G,後來擴充套件到10G,執行ubuntu系統),伺服器配備的是全新戴爾儲存套件MD1200,該套件包括12個4TB硬碟,其中40TB採用RAID 6配置,這樣整個系統就可以容忍兩塊硬碟同時down掉。這臺伺服器主要用作異地備份,唯一的操作是IO寫,由於我使用rsnapshot完成這項工作,因此大部分檔案有較高的連結計數(30+)。
一天早上,我被告知一個硬碟down掉了。這不是什麼大事,這種事情經常發生。我打電話給戴爾,戴爾第二天就給送來了一塊替換盤。但我安裝替換盤時發現,替換盤根本不能工作,並且另一塊磁碟也down掉了。戴爾技術服務部門富有經驗地建議我不要只換down掉的硬碟,因為整個磁碟陣列可能已經損毀了。就我所知,磁碟只有在有足夠多壞塊的情況下才會告警,設想這樣一個場景:短時間內,一個檔案位於三個磁碟上的備份塊全部壞掉,那麼很不幸,RAID引擎短時間內難以完成檢測壞塊、重新計算備份資料並儲存這樣一個流程,這樣你就有丟失資料的風險了。因此,如果兩個硬碟顯式告警,你的資料可能已經丟失了。
現有儲存套件容量已經難以達到要求,我們決定擴容,把檔案從舊儲存套件拷貝到新套件中。正常情況下,我會在塊級別拷貝用dd指令或者pvmove指令拷貝資料,但是考慮到壞塊,我決定採用檔案級別的拷貝,因為這樣的話,我就可以知道那些檔案包含壞塊了。我上網搜了相關經驗,發現cp可以完美地解決這個問題。若想儲存硬連結資訊,需要記錄有哪些檔案已經被複制,所以我預訂了8G的RAM,並配置了更大的交換區。
當新的儲存套件到貨,我就著手複製了,起初,根據iotop的測量資料,複製速度接近於300-400MB/s。不一會兒速度便顯著下降了,因為大部分時間花在了建立硬連結上,並且要花時間去處理檔案系統的一致性問題。為保持一致性,我們使用了XFS,由於沒有關閉寫屏障,我們深受效能之苦,但如果RAID控制器配置了帶有備份電源的寫快取,一致性問題便可更好地解決。正如所料,cp的記憶體佔用量穩步增加,很快便達到了GB級。
經歷了幾天的拷貝,問題來了:我發現系統已經停止拷貝,根據strace的顯示,cp指令沒有呼叫任何的系統呼叫函式。閱讀相關原始碼後發現,cp指令會以一個雜湊表跟蹤有哪些檔案被複制了,這個表需要時常地擴充套件自身容量以避免太多的衝突。當RAM被耗盡,雜湊表的擴容過程會成為效能瓶頸。
我們相信cp的雜湊表擴容後,cp指令會繼續執行,果然不一會它又“復活”了,但是它會進入週期性的“擴容-拷貝-擴容-拷貝”的迴圈中,且擴容的時間變得越來越長。經過10天的拷貝,根據dd結果,新檔案系統的塊數量和inode節點的數量已經和原系統一樣,但是讓人吃驚的是cp指令並沒有退出。再次閱讀原始碼,我發現cp正在認真地解構雜湊表(使用forget_all函式),由於cp程式需要的虛擬記憶體大小達到了17GB+,但伺服器總共才10G記憶體,所以解構過程執行了很多RAM和swap區間的換頁操作。
上面整個過程中,我使用了cp的-v選項,並使用tee把日誌重定向到日誌檔案(很大的檔案)中。cp的輸出資訊會被快取,為使這些快取資訊全部重新整理到日誌檔案中,我給了cp多於一天的時間進行解構雜湊表。
執行”ls –laR”指令,檢視下兩個檔案系統中的檔案是否一致,發現除了日誌檔案的一些錯誤外,只有一個檔案發生了io錯誤(還好,我們有它的另一個備份)。
這種錯誤不會馬上覆現,但是如果cp能夠設計一種資料結構,在等待io的時候處理那些已經被記錄的檔案,那樣處理效率會更高。此外,對於cp最後解構雜湊表的forget_all函式,除了那些缺少可用記憶體管理模組的老式伺服器一定需要它之外,我還沒發現去掉這個函式會對整個拷貝過程產生什麼不好的影響。
總結一下:
1、拷貝整個檔案系統時,如果確定你的硬體和檔案系統都OK,應該使用塊級別的拷貝。這種拷貝方式更快,除非你有許多空閒塊,但是無論如何它耗費的記憶體都是較少的;
2、如果要拷貝許多檔案,並想要保留硬連結的資訊,可以採用檔案級拷貝。拷貝之前需要保證機器有足夠多的記憶體;
3、比起簡單粗暴地停止程式,認真地解構資料結構會花費更多的時間;
4、告警的硬碟的數量並不等同於有壞塊的硬碟的數量。如果不走運,就算配置了RAID6,沒來得及等3塊磁碟告警,你的資料可能已經丟失。這個時候我們只能賭一把了。同樣的情況適用於RAID 5,那怕只有一塊磁碟甚至沒有磁碟告警,如果不走運,你的資料照樣丟!
希望這篇文章對你有益!