HDFS短路讀詳解

本文介紹了HDFS的短路讀演進、安全的短路讀以及小米在安全短路讀的優化。
上篇文章回顧:HDFS Decommission問題分析

背景

Hadoop的一個重要思想就是移動計算,而不是移動數據。我們更願意儘可能將計算移動到數據所在節點。因此,HDFS中經常出現客戶端和數據在一個節點上,當客戶端讀取一個數據塊時,就會出現本地讀取。例如HBase場景,ResionServer寫數據一般在HDFS中都會存儲三備份副本並且肯定會往本地節點寫一備份,當ResionServer讀取該數據時也會優先選擇同一節點的數據進行讀取。

短路讀的演進

1、網絡讀

最初,HDFS中本地讀取的處理方式和遠程讀取相同,也是通過網絡讀來實現。客戶端通過TCP套接字連接到DataNode,並通過DataTransferProtocol協議傳輸數據(如下圖):

這種方式簡單,但有明顯的問題:

DataNode必須爲每個正在讀取數據塊的客戶端保留一個線程和一個TCP套接字。內核中會有TCP協議的開銷,以及DataTransferProtocol協議的開銷,因此這裏有很大的優化空間。

2、HDFS-2246 不安全短路讀

短路讀關鍵思想是因爲客戶端和數據塊在同一節點上,所以DataNode不需要出現在讀取數據路徑中。而客戶端本身可以直接從本地磁盤讀取數據。這樣會使讀取性能得到很大的提高。在HDFS-2246中實現的短路讀是DataNode將所有數據塊路徑的權限開放給客戶端,客戶端直接通過本地磁盤路徑來讀取數據,見下圖:

 

但這種方式引入了很多問題:

(1)系統管理員必須更改DataNode數據目錄的權限,以允許客戶端打開相關文件。將能夠使用短路讀的用戶專門列入白名單,不允許其他用戶使用。通常,這些用戶也必須放在特殊的Unix組中。

(2)這些權限更改會引入了一個安全漏洞,具有讀取DataNode節點上數據塊文件權限的用戶可以任意讀取路徑上所有數據塊,而不僅僅是他們所需訪問的數據塊,這好像讓用戶變成了超級用戶,對於少數用戶來說可能是可以接受的,例如HBase用戶。但總的來說,它會帶來很大安全隱患。

3、HDFS-347 安全短路讀

HDFS-2246的主要問題是向客戶端打開了DataNode的數據目錄,而我們真正需要讀取的只是一部分數據塊文件。Unix有一種機制是可以做到這一點,稱爲文件描述符傳遞。HDFS-347使用這種機制來實現安全短路讀。DataNode不是將目錄傳遞給客戶端,而是打開塊文件和元數據文件,並將它們的文件描述符通過domain socket傳遞給客戶端(如圖3):

基於以下兩方面,安全短路讀解決了HDFS-2246存在的安全性問題。

(1)文件描述符是隻讀的,因此客戶端無法修改傳遞描述符的文件。

(2)客戶端無法訪問數據塊目錄本身,所以也無法讀取它不應該訪問的任何其他數據塊文件。

HDFS安全短路讀

1、短路讀共享內存

瞭解了HDFS短路讀的演進,我們來看下HDFS是如何實現安全短路讀的。DataNode將短路讀副本的文件描述符傳給DFSClient,DFSClient緩存副本文件描述符。由於副本的狀態可能隨時發生改變,所以需要DFSClient和DataNode實時同步副本狀態。同時,DFSClient和DataNode在同一臺機器上,共享內存可以通過POSIX提供的 mmap接口實現將文件映射到內存,並且映射數據是實時同步的(如圖4),所以共享內存可以維護所有短路讀副本的狀態,使得DFSClient和DataNode通過共享內存來實時同步副本信息。

共享內存會有很多槽位,每個槽位對應一個短路讀副本的信息。共享內存保存了所有槽位的二進制信息。但是映射數據中的二進制槽位信息不便於管理,所以定義了Slot對象操作映射數據中的一個槽位,如下圖:

Slot槽位大小是64字節,槽位數據格式,前4字節是Slot標誌位,5到8字節是錨計數位,剩餘字節保留將來使用,例如統計信息等。

兩個標誌位:

(1)VALID_FLAG:表示槽位是否有效。

DFSClient在共享內存中分配新的槽位時設置此標誌位。當與此槽位關聯的副本不再有效時,DataNode將會消除此標誌位。DFSClient本身也會消除此槽位,認爲DataNode不再使用此槽位進行通信。

(2)ANCHORABLE_FLAG:表示槽位對應的副本是否已經緩存。

DataNode將槽位對應的副本通過POSIX提供的mlock接口緩存時會設置該標誌位。當標誌位已設置,DFSClient短路讀取該副本時不再需要進行校驗,因爲副本緩存時已經做了檢驗操作,並且這種副本還支持零拷貝讀取。DFSClient對這樣的副本進行讀取時,需要在對應的槽位錨計數加1,只有當槽位的錨計數爲0時,DataNode纔可以從緩存中刪除此副本。

共享內存段的最大是8192字節,當DFSClient進行大量短路讀時, DFSClient和DataNode之間可能會有多段共享內存。HDFS中DFSClient定義了DFSClientShm類抽象了DFSClient端一段共享內存,DFSClientShmManager類管理所有的DFSClientShm,而DataNode端定義了RegisteredShm類抽象DataNode端的一段共享內存,ShortCircuitRegistry類管理所有DataNode端的共享內存,如下圖所示:

在安全短路讀中,DFSClient和DataNode是通過domain socket來同步共享內存槽位信息的。

  • DFSClient申請一段共享內存保存短路讀副本的狀態。DataNode會創建共享內存,並將共享內存文件映射到DataNode內存中,並創建RegisteredShm管理這段共享內存,之後會將共享內存文件的文件描述符通過domain socket返回給DFSClient。

  • DFSClient根據文件描述符打開共享內存文件,將該文件映射到DFSClient的內存中,並創建DfsClientShm對象管理這段共享內存。

  • DFSClient通過domain socket向DataNode申請數據塊文件以及元數據文件的文件描述符,並且同步共享內存中slot槽位的狀態。DFSClient會在DfsClientShm管理的共享內存中爲數據塊申請一個slot槽位,之後通過domain socket向DataNode同步信息,DataNode會在RegisteredShm管理的共享內存中創建相應的slot槽位,然後獲取數據塊文件以及元數據文件的文件描述符,並通過domain socket發送給DFSClient,見下圖:

     

2、短路讀流程

當客戶端執行數據塊副本短路讀時,DFSClient與DataNode的交互過程如下:

(1)DFSClient通過requestShortCircuitShm()接口向DataNode請求創建共享內存,DataNode創建共享內存文件並將共享內存文件描述符返回給DFSClient。

(2)DFSClient通過allocShmSlot()接口申請共享內存中的槽位,並通過requestShortCircuitFds()接口向DataNode請求要讀取的副本文件描述符,DataNode打開副本文件並將數據塊文件和元數據文件的文件描述符返回給DFSClient。

(3)DFSClient讀取完副本後,異步通過releaseShortCircuitFds()接口向DataNode請求釋放文件描述符及相應槽位。

小米對HDFS安全短路讀的優化

1、Slot槽位釋放緩慢

幾次壓力場景中,我們發現Hbase ResionServer多個短路讀線程經常會阻塞在domain socket的讀寫上。從DataNode 的dump中發現大量的用於短路讀的ShortCircuitShm。於是我們通過YCSB模擬線上的情況,發現短路讀請求量較大時,BlockReaderLocal分配的QPS很高,並且BlockReaderLocal的分配依賴於同步讀取塊的ShortCircuitShm和slot的分配。

通過統計slot分配和釋放的QPS,我們發現slot分配的QPS能達到3000+,而釋放的QPS只能達到1000+,並且在YCSB測試經過約1小時,DataNode出現FULL GC。由此可看出,DataNode中積累的,來不及釋放的slot,是導致GC的主要有原因。

現在的短路讀實現中,每次釋放slot,都會新建一個domain socket連接。而DataNode對於每個新建立的domain socket 連接,都會重新初始化一個DataXceiver去處理這個請求。通過profile DataNode發現,SlotReleaser線程花了大量的時間在建立和清理這些連接上。

於是,我們對SlotReleaser的domain socket連接進行了複用。通過複用domain socket,在同樣的測試集上,slot 釋放的QPS能和分配的QPS達到一致。從而消除了過期slot在DataNode中的擠壓。同時,由於DataNode Young GC減少,YCSB的GET的QPS也提升了約20%左右。

2、共享內存分配效率低

在profile HBase短路讀過程中,我們還發現另外一個問題,就是每隔一段時間,會有一批讀會有約200ms左右的延遲而且這些延遲幾乎同時出現。開始我們懷疑 Hbase ResionServer的Minor GC導致。但通過比對ResionServer的GC日誌,發現時間並不完全匹配。有一部分卻和DataNode Minor GC時間吻合。數據塊副本的真正讀取操作,是完全不通過DataNode的,如果是DataNode的影響,那問題只能出在ResionServer和DataNode建立短路讀時的交互上。通過進一步在短路讀過程中加trace log,我們發現這些延遲,是由於DataNode Minor GC導致ShortCircuitShm分配請求被阻塞。當分配一個ShortCircuitShm時,會導致很多slot的分配阻塞。slot的分配延遲,又會引起BlockReaderLocal的延遲,從而導致短路讀的延遲。這就是之前發現有一批讀,總是同時報相近的延遲。 爲了解決這個問題,我們對ShortCircuitShm進行了預分配,以減輕DataNode Minor GC對短路度影響,使得延遲更爲平滑。

3、短禁止正在構建塊的短路讀

禁止了正在構建塊進行短路讀,也就是最後一個塊禁止短路讀。這個問題由於HBase的讀寫模式 ,對其影響不是很大,但對基於HDFS流式服務影響很大。我們正在做的優化工作主要是通過保證短路讀只發生在flush操作之後,同時在讀取過程中檢查塊的有效性,處理讀異常,如果確實失敗,轉成遠程讀的方式。

附錄

(1)文件描述符(File Descriptor)

文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核爲每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。

Unix和Windows系統都允許在進程間傳遞文件描述符。一個進程打開一個文件,然後把該文件的文件描述符傳遞給另一個進程,另一個進程可以訪問該文件。文件描述符傳遞對於安全性是非常有用的,因爲它消除了對第二個進程需要擁有足夠訪問權限來打開文件限制,同時文件描述符是隻讀的,所以該方式還可以防止有問題的程序或者惡意的客戶端損壞文件。在Unix系統上,文件描述符傳遞只能通過Unix domain socket完成。

(2)Unix domain socket

Unix domain socket是用於在同一臺主機操作系統上執行的進程間交換數據的通信端點。有效的Unix domain socket類型是SOCK_STREAM(用於面向流的套接字)和SOCK_DGRAM(用於保留消息邊界的面向數據報的套接字),與大多數Unix實現一樣,Unix domain datagram socket始終可靠且不重新排序的數據報。Unix domain socket是POSIX操作系統的標準組件。  

Unix domain socket的API類似於網絡socket,但是不使用底層網絡協議,所有通信都完全在操作系統內核中進行。Unix domain socket使用文件系統作爲其地址名稱空間。進程引用Unix domain socket作爲文件系統inode,因此兩個進程可以通過打開相同的socket進行通信。 除了發送數據外,進程還可以使用sendmsg()和recvmsg()系統調用在Unix domain socket連接上發送文件描述符。並且只有發送進程授權給接收進程,接收進程纔可以訪問文件描述符的權限。

(3)共享內存(Shared Memory)

共享內存是進程間通信的方法,即在同時運行的程序之間交換數據的方法。一個進程將在RAM中創建一個其他進程可以訪問的區域。由於兩個進程可以像訪問自身內存一樣訪問共享內存區域,因此是一種非常快速的通信方式。但是它的擴展性較差,例如通信必須在同一臺機器上運行。而且必須要避免如果共享內存的進程在不同的CPU上運行,並且底層架構不是緩存一致的。

POSIX提供了使用共享內存的POSIX標準化API。使用sys/mman.h中的函數shm_open。POSIX進程間通信包含共享函數shmat,shmctl,shmdt和shmget。shm_open創建的共享內存是持久化的。它一直保留在系統中,直到被進程明確刪除。這有一個缺點,如果進程崩潰並且無法清理共享內存,它將一直保持到系統關閉。POSIX還提供了用於將文件映射到內存的mmap API,可以共享映射,允許將文件的內容用作共享內存。

引用

1. https://en.wikipedia.org/wiki/File_descriptor

2. https://en.wikipedia.org/wiki/Unix_domain_socket

3. https://en.wikipedia.org/wiki/Shared_memory

4.https://www.tutorialspoint.com/inter_process_communication/inter_process_communication_shared_memory.htm

5. https://blog.cloudera.com/blog/2013/08/how-improved-short-circuit-local-reads-bring-better-performance-and-security-to-hadoop/

本文首發於“小米雲技術”,點擊查看原文

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