分佈式文件系統下的本地緩存

在分佈式文件系統中,爲了提高系統的性能,常常會引入不同類型的緩存存儲系統(算法優化所帶來的的效果可能遠遠不如緩存帶來的優化效果)。在軟件中緩存存儲系統一般可分爲了兩類:一、分佈式緩存,例如:Memcached、Redis、淘寶的Tair等,二、本地緩存,例如:Facebook的flushcache等,其中本地緩存又可以分爲兩種:本地磁盤緩存(現在一般都採用讀寫性能比較優異的SSD來做存儲)和本地內存緩存。在系統中,爲了提高緩存存儲系統的性能以及熱點數據的命中率,一般在本地磁盤緩存中也會引入內存緩存用於存儲經常訪問的數據。有時爲了減少客戶端對服務器的請求,也會在客戶端上使用緩存,當然這是要考慮安全問題的。在這裏,本文主要討論分佈式文件系統下的本地磁盤緩存。
    1. 本地磁盤緩存與操作系統的關係
    根據操作系統對硬件的管理以及文件系統的存儲邏輯,本地磁盤緩存在操作系統中的位置可大概分爲一下三種:
                         
        一、處於Storage Controller層,OS與應用相互獨立,與硬件相關聯,由硬件來做讀寫保護
        二、處於Device Driver層,與應用獨立,與硬件獨立,需要第三方來完成讀寫保護,處於這一層的本地緩存系統有Facebook的flushcache
        三、處於File System層,OS與應用相關聯,與硬件獨立,需要第三方來完成讀寫保護,這一層的本地緩存是在應用中使用最爲廣泛的,本文所討論的本地緩存也是基於此層

   2. 存儲設備以及空間
    根據現有的硬件存儲技術,SSD的隨機讀寫性能以非常優異,並且其使用壽命也得到了很好的保證,因此被廣泛的應用於數據需要持久化存儲和高速讀寫的服務器中。根據性價比來考慮,一般的分佈式文件系統的服務器機器中不會放置太大的SSD來做緩存存儲設備。——64GB至128GB的SSD是主流。

   3. 磁盤緩存系統在分佈式文件系統中的地位
    緩存系統在整個分佈式中以多種形態存在,常見的有以單獨一個進程運行的方式存在,這種方式類似於C/S模式,需要進程間的通信;另外就是直接寄宿於分佈式文件系統的進程中,由分佈式文件系統的進程直接調用緩存的接口來進行數據讀寫操作,本文主要討論後一種。

   4. 分佈式文件系統中,本地磁盤緩存的存儲策略大概有一下所示的三種:
                          
        一、Write Around策略,該策略主要是讀緩存,不直接寫本地磁盤緩存,而是直接將數據寫入分佈式持久化存儲當中,在讀取數據時,如果發現緩存中沒有對應的數據則向分佈式持久化存儲請求,然後再將請求後的數據寫入緩存中。優點在於沒有一致性問題,數據不會丟失,利於數據集中讀取。缺點在於,分佈式文件系統中直接將數據寫入持久化存儲速度會比較慢,對於用戶體驗會有很大的影響。
        二、Write Through策略,該策略在讀取數據方面與Write Around差不多,但是在數據寫入方面有很大的區別,數據在寫入的過程中,寫入本地緩存的同時也將數據寫入分佈式持久化存儲中。優點在於沒有一致性問題,數據不會丟失,利於數據集中讀取。缺點在於寫入速度將會比較慢,不過這取決於分佈式持久化存儲的寫入性能。
        三、Write Back策略,該策略主要是讀寫緩存共用,將數據先寫到本地緩存中,然後再通過異步的方式將髒數據同步到分佈式持久化存儲中,在本地讀取數據Miss後才向分佈式持久化存儲請求,並回寫到本地緩存中。優點在於寫入速度快,利於數據集中讀取,缺點在於會產生一致性問題以及數據可能會丟失。雖然這種策略容會導致數據的丟失,但是在不同層次的存儲方案中其是常常被採用個的一種。

    5. 存儲數據類型
    在分佈式文件系統中,本地緩存效率極高,數據直接存儲於服務器本地,對數據的操作就是對本地文件的操作,但是由於分佈式的特徵,本地緩存中的數據其它節點無法訪問,那麼如果相同的一份數據被多臺機器緩存於本地,一旦該數據在其中一臺機器中被修改,這就會導致一定數據的不一致,因此本地緩存最好只用於存儲靜態數據——一旦寫入就再也不會修改的數據,例如文件的數據塊內容、圖片等等。

   6. 索引
    根據項目實踐,在分佈式文件系統中數據塊的網絡傳輸佔用了絕大多數的時間,根據對數據隨機訪問概率的統計數據來分析,當一個文件被連續多次訪問之後,其在最近一段時間內被訪問到的概率非常之高,因此,爲了減少對同一文件的網絡IO,設計一個高效且符合實際的場景的本地磁盤緩存非常有必要。高效的緩存系統,必然少不了索引,利用索引快速定位對應數據的文件路徑,然後儘可能的挖掘磁盤的讀寫潛力,讓系統瓶頸落在對磁盤的讀寫上。同時,索引還可以很好的用於控制磁盤當前的使用空間;如果採用Write Back策略,還可以高效計算還未被同步到分佈式持久化存儲的存儲大小;對索引的管理還可以提高緩存的命中率,什麼樣的數據可以被刪除,什麼時候刪除等等。
    緩存中一般都採用Key-Value的鍵值對形式將數據存儲,Key由應用傳入爲了區分Value唯一的標誌,爲了能加速查找和減少索引的內存佔用空間,緩存內部會將Key的內容進行MD5計算,然後存儲在索引的數據結構中;Value的內容則以一個文件,或者多個Value存入一個文件中。由於使用SSD作爲最後的Value存儲,這裏我們就簡單的將每一個Key作爲一個文件存儲。另外,爲了處理大量的小文件導致文件系統的inode耗盡的問題,可以將小於8KB的數據用leveldb來存儲(leveldb對小數據的存儲性能非常高)。
    索引的組織方式有很多種,常用的有標準庫的map,list,其它效率更高的有紅黑樹、B+樹等等,這些也都是一種索引在內存中的存儲和組織方式,其核心還是離不開對Key值的查找比較。在操作系統中,字符串的比較效率往往要低於系統整數類型,比如對兩個32位MD5字符串的比較需要系統進行32次的比較,如果我們將MD5的值用4個unsigned int或者2個unsigned long long來存儲的話(這裏是在64位操作系統上的),那麼直接利用CPU能對整型數進行計算的特性只需要比較4次或者2次即可,這樣既可以節省16個字節的內存空間,同時還能加速比較,快速定位。
    在索引的內存佔用方面我們不必太過於擔心,現在的服務中一般的內存配置都是十幾GB,用1GB的內存來存儲索引也不算太坑老闆。假如每個索引所佔字節數爲32,那麼1024 * 1024 * 1024 / 32 = 33554432個索引,如果每個索引所對應內容的平均大小爲4KB,那麼需要的磁盤空間爲33554432 * 4KB = 134217728KB ≈ 128GB,128GB的容量正好與第2點中所說的存儲相符。當然如果你開發的分佈式文件系統就是用於存儲小文件的,那麼這些算法就需要額外考慮了。
    
   7. 緩存的更新
    在緩存系統中,由於容量有限,因此,在還有數據繼續寫入的情況下必須刪除一些已存在緩存,騰出空間。在我們已知的常用緩存老化方法中大概有如下幾種:
        先進先出(FIFO: First In, First Out)
       最近最少使用(LRU: Least Recently Used)
       最少使用頻率(LFU: Least Frequently Used)
        隨機刪除(Random)
    在實際開發過程中,有時還會採用一些其它的方式,比如,在每個數據中加入生命週期的管理——設置該數據在一定時間內自動失效並將其刪除,這種方式需要對系統的時間有很精確的要求,系統時間不能隨便被更改。
    在分佈式系統中,由於緩存會被老化或者刪除,那麼就會產生當緩存被老化後,某一時刻有同一數據被多人從同一臺服務器中訪問,所有的訪問都會到緩存系統中查找,此時,發現緩存中數據不存在,那麼都到分佈式持久化存儲中讀取,返回之後,所有請求的操作會將同一份數據再回寫到緩存系統中的問題。對於這個問題我暫時還沒有找到什麼比較好的處理方法,只能從緩存存儲的數據以及業務邏輯上處理。像第5點所說的,緩存只用於存儲靜態數據,那樣數據就可以覆蓋,而不會產生一致性的問題。
    如果在緩存策略中採用了Write Back策略,那麼就需要做,在緩存中還未被持久化到分佈式持久化存儲的數據的特殊處理,這一類數據在備份持久化之前不能被內部的主動老化策略所刪除。

   8. 性能和命中率
    在支持多線訪問的緩存系統中,爲提高索引的查找性能,避免鎖的高頻率碰撞,一般在設計索引的組織方式時會採用hash table的方法,使用多個hash桶,根據對應的hash算法來分散索引的存儲,這種方法簡單,易於實現,但其也存在無法動態添加hash桶的缺點。每個hash桶中放置的是一個LRU鏈表,LRU是緩存系統中最常見的數據替換算法,每次淘汰最久沒有被訪問的數據。每一個hash桶中只對LRU進行上鎖,這樣就能減少一部分的鎖碰撞。memcached中也才用了hash table加LRU鏈表來管理內存的方法,其高效的管理,使得memcached被廣泛用於網站的緩存中,其內存管理示意圖如下:
                 
memcache以slab方式管理內存塊,從系統申請1MB大小的大塊內存並劃分爲不同大小的chunk,不同slab的chunk大小依次爲80字節,80 * 1.25,80 * 1.25^2, …。向memcache中添加item時,memcache會根據item的大小選擇合適的chunk。
    在項目中,爲了權衡性能和命中率之間的關係,有時候常常會降低命中率來換取性能。在數據老化時可以將LRU的最少使用的前幾個一起老化,減少每次只老化一個就得鎖住當前的LRU鏈表的頻率,從而加速索引的查找和寫入,其次是增加hash桶個數,減少hash衝突,同時也減少了對同一LRU鏈表的操作。

   9. 索引的持久化
    在Google的好文《Introduction to Distributed System Design》中有闡明關於分佈式的設計:design for failure。因此,在分佈式文件系統中做好故障處理是及其重要有極其困難的,但是做好進程內的狀態保存,當程序重啓後能及時恢復到崩潰前的狀態還是能夠容易實現的。在緩存系統中,數據的索引尤爲重要,因此可將其持久化,待程序重新啓動後就可以重建索引。這裏建議可以使用google的leveldb來做索引的持久化,其高效的讀寫性能,並不會對系統的性能太大的影響。——這裏不考慮機器突然斷電的情況。

   總結
    在現在的系統中,緩存無處不在,如果能合理的利用緩存,那麼整個系統的性能將會得到大大個提高。那麼如何判定一個緩存系統是高效的呢?在實際開發過程中,我們總結出:現在開源的緩存項目很多,但是符合自身項目需求的卻寥寥無幾,因此,設計一個滿足自身業務需求的緩存系統時,切記不要過度設計,簡單且滿足需求,夠用就行。


參考:
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章