HDFS新特性Centralized Cache Management介紹

  • 概述

    HDFS作爲Hadoop底層存儲架構實現,提供了高可容錯性,以及較高的吞吐量等特性。在Hadoop 2.3版本里,HDFS提供了一個新特性——Centralized Cache Management。該特性能夠讓用戶顯式地把某些HDFS文件強制映射到內存中,防止被操作系統換出內存頁,提高內存利用效率,有效加快文件訪問速度。對於Hive來說,如果對某些SQL查詢裏需要經常讀取的表格文件進行緩存,通常能夠提升運行效率。

  • 架構介紹

Caching Architecture

    NameNode主要負責協調所有DataNode的堆外內存。DataNode會按照一定的時間間隔通過heartbeat向NameNode彙報所緩存的文件block塊記錄。NameNode會接收用戶緩存或清空某個路徑的緩存命令,然後通過heartbeat通知含有對應文件block的DataNode進行緩存。

    NameNode提供一個緩存池(cache pools)來方便管理緩存命令(cache directives),緩存命令負責決定具體進行緩存的路徑。

    目前緩存只能夠在文件夾或者文件的粒度進行控制。文件塊和子塊緩存仍然在計劃開發中。

  • 緩存命令接口

    具體用法和參數意義參見官網介紹,這裏只作簡單介紹。

Cache directive命令

  • addDirective

    添加一個新的cache directive。如: hdfs cacheadmin -addDirective -path <path> -pool <pool-name>

  •     removeDirective

    移除一個cache directive。如:hdfs cacheadmin -removeDirective <id>

  • removeDirectives

    移除指定路徑的每個cache directive。如:hdfs cacheadmin -removeDirectives <path>

  • listDirectives

     列出cache directive。如: hdfs cacheadmin -listDirectives

Cache pool命令

  • addPool

    添加新的內存池。如:hdfs cacheadmin -addPool <name>

  • modifyPool

    修改緩存池的metadata。如:hdfs cacheadmin -modifyPool <name> [-owner <owner>] [-group <group>] [-mode <mode>] [-limit <limit>] [-maxTtl <maxTtl>]

  • removePool

    移除緩存池,同時也會移除在緩存池裏的路徑。如: hdfs cacheadmin -removePool <name>

  • listPools

    顯示緩存池信息。如:hdfs cacheadmin -listPools [-stats] [<name>]

  • 實踐與思考

    上面的內容基本從官網簡單摘取過來,這節纔是本文章的重點,也是真正的乾貨所在。

    首先要注意的是,官網沒有說明的一點,要讀取到cache到內存的文件block,必須要首先開啓Short-Circuit Local Reads特性。該特性簡單來說,就是對於在讀取文件位於相同節點的時候,能夠避免建立TCP socket,直接讀取位於磁盤上的文件block。需要注意的是,該特性有兩種不同實現方案(HDFS-2246HDFS-347),其中,HDFS-347是Hadoop 2.x的重新實現方案,並且只有這個方案纔可以讀取Cache到內存的文件block。

    在配置Cache的過程中,由於DataNode需要強制鎖定文件映射到內存中,需要設置hdfs-site.xml參數dfs.datanode.max.locked.memory參數(單位bytes)。另外要注意的是通常還需要更改操作系統參數ulimit -l(單位KB),否則DataNode在啓動的時候會拋出異常。

    爲了能夠更加深入理解這個特性帶來的性能影響,在測試環境下進行了簡單的測試。

   測試環境是一個有3個節點的小集羣。2個NameNode(啓用NameNode HA特性),3個DataNode。其中Active NameNode(93)總共有12G內存,8個Intel(R) Xeon(R) CPU  2.50GHz;另外backup NameNode(24)和DataNode(25)均只有4GB內存,4個Intel(R) Xeon(R) CPU  2.00GHz。

    爲了能夠減少干擾,更加清晰地瞭解該特性帶來的性能變化,並且注意到要使用該特性,必須使用一個全新的API來進行讀取操作,因此寫了一個簡單的讀HDFS文件的測試程序來測量耗時。

    對於一個約400MB大小的HDFS文件,進行讀取,得出數據如下:

 
Non Short-Circuit(ms)
Short-Circuit(ms)
Short-Circuit + Centralized Cache Management(ms)
93 2993 1059 1014
24 2155

1137

1170
25 2751 1264 1199

    從上面數據可以看到,開啓了Short-Circuit特性的時候,讀取文件至少快了2-3倍;當啓用Centralized Cache Management特性的時候,讀取文件並沒有太大的速度提升。

    關於這個Cache Management不甚明顯的原因,查找相關資料(interactive-hadoop-via-flash-and-memory.pptx),可能說明了部分問題的原因:開啓了Short-Circuit本地讀取後,操作系統可能會在讀取的過程中同時緩存文件到內存裏,但是,如果此時節點內存使用比較緊張的時候(可能由於多個MR子任務的執行),操作系統可能會隨時把這些緩存換出,導致緩存無效。使用Cache Management顯式指定文件緩存後,可以保證該部分內存不被換出,同時由於使用的是off-heap memory,免於JVM的管理,避免產生GC。

    因此考慮到目前測試環境上的僅僅執行簡單測試,沒有模擬出激烈的內存使用情況,可能導致效果不甚明顯。

    另外,還嘗試過做Hive、HBase的讀取測試,無論是開啓Short-Circuit還是Short-Circuit + Centralized Cache Management都暫時沒有發現太大的性能提升,暫時懷疑是由於測試環境的節點實在太少,而且其中兩個節點的4G內存實在少得可憐,開啓任務後,空閒情況下只有5-600MB可用,而這兩個特性極度依賴於內存的使用情況,因此沒有辦法體現出真正的性能提升優勢。

     當然,這只是一個推論,最好的做法還是,待仔細查看源碼理解具體實現,明白更加深層次的原因。

  • 源代碼實現分析(Hadoop 2.6.0)

(1)DataNode把文件block進行緩存並鎖定內存

    NameNode向DataNode請求把對應的HDFS的文件block進行cache的時候,DataNode將接收到的RPC命令是DatanodeProtocol.DNA_CACHE。然後會調用FsDataSetImpl.cache()把所有請求block塊進行緩存。接着調用cacheBlock()把每個Block進行逐個緩存。

     FsDataSetImpl.cacheBlock()函數主要是把每個block的緩存任務分配到每個Volume的線程中異步執行。通過找到對應的Volume後,其會調用FsDatasetCache.cacheBlock()進行具體的異步緩存任務執行。

     FsDatasetCache.cacheBlock()主要負責進行緩存異步任務提交。爲了追蹤緩存異步任務執行結果,首先把block id封裝成ExtendedBlockId對象,然後放入到mappableBlockMap裏,同時更新狀態爲CACHING。接着向FsVolumeImpl.cacheExecutor線程池提交一個CachingTask異步任務。

    CachingTask主要負責把具體的文件Block進行緩存和鎖定。具體緩存和鎖定的操作會調用MappableBlock.load()執行。然後把返回的MappableBlock保存到 FsDatasetCache的mappableBlockMap中,同時更新狀態爲Cached(如果之前狀態爲CACHING_CANCELLED時,則不會保留),並且更新引用計數。

    MappableBlock.load主要是調用FileChannelImpl.map和POSIX.mlock(最終會調用到libc的mmap和mlock),同時進行文件塊校驗。然後返回新建MappableBlock對象。

 (2)客戶端HDFS讀邏輯

    HDFS的讀主要有三種: 網絡I/O讀 -> short circuit read -> zero-copy read。網絡I/O讀就是傳統的HDFS讀,通過DFSClient和Block所在的DataNode建立網絡連接傳輸數據。short circuit read就是直接讀取本地文件。zero-copy read就是在short circuit read基礎上,直接讀取之前緩存了的文件block內存。

    這裏給出一個利用新API進行zero-copy read的例子。

Configuration confs = new Configuration();
String path = "your hdfs path";
FileSystem fs = FileSystem.get(URI.create(path), confs);
FSDataInputStream in = null;
int bufSize = 8192;
try {
    in = fs.open(new Path(path));
    ByteBuffer bb = null;
    ElasticByteBufferPool ebbp = new ElasticByteBufferPool();
 
    while ((bb = in.read(ebbp, bufSize, EnumSet.of(ReadOption.SKIP_CHECKSUMS))) != null) {
        //process buffer bb here
        in.releaseBuffer(bb);
    }
 
} catch (UnsupportedOperationException e) {
 
} finally {
    IOUtils.closeStream(in);
}

    爲了能夠進行short circuit read和zero-copy read,需要採用指定的API——ByteBuffer read(ByteBufferPool bufferPool,int maxLength, EnumSet<ReadOption> opts)。在調用函數FileSystem.get獲取具體的FileSystem子類(如果是HDFS,則是DistributedFileSystem),會創建DFSClient對象,DFSClient對象內部封裝了ClientProtocol對象,負責和NameNode進行RPC通信,獲取需要讀取的block信息。

    FSDataInputStream.read()函數負責HDFS文件block讀邏輯處理。真正的實現在DFSInputStream.tryReadZeroCopy()函數裏。具體主要調用BlockReader.getClientMmap()函數,如果是開啓了short circuit read特性,則具體調用的是BlockReaderLocal.getClientMmap()。getClientMmap()會調用到ShortCircuitCache.getOrCreateClientMmap(),函數實現會查看緩存是否已經進行過MMap過的文件block塊信息。如果不存在,則會調用ShortCircuitReplica.loadMmapInternal(),然後最終會調用到FileChannelImpl.map(),如果之前進行過文件緩存,並mmlock鎖定,則這裏就會把具體的物理內存映射到本進程的虛擬內存地址空間中,如果沒有,則會重新讀取文件進行mmap操作。

    可見,HDFS的這個zero copy read特性有一定限制,需要在本地上有需要讀取的block塊數據,也就是同樣需要考慮data-lock限制,因此綜合來看,主要是針對short circuit read的性能提升。目前的情況是在測試環境下,short circuit read時開啓後有較大的性能提升,zero copy read對比起short circuit read暫時沒有太大的明顯變化。

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