HDFS短路讀詳解

小米運維發表於2019-06-04
本文介紹了HDFS的短路讀演進、安全的短路讀以及小米在安全短路讀的優化。
上篇文章回顧:HDFS Decommission問題分析

背景

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

短路讀的演進

1、網路讀

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

HDFS短路讀詳解

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

DataNode必須為每個正在讀取資料塊的客戶端保留一個執行緒和一個TCP套接字。核心中會有TCP協議的開銷,以及DataTransferProtocol協議的開銷,因此這裡有很大的優化空間。

2、HDFS-2246 不安全短路讀

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

HDFS短路讀詳解


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

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

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

3、HDFS-347 安全短路讀

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

HDFS短路讀詳解

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

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

(2)客戶端無法訪問資料塊目錄本身,所以也無法讀取它不應該訪問的任何其他資料塊檔案。

HDFS安全短路讀

1、短路讀共享記憶體

瞭解了HDFS短路讀的演進,我們來看下HDFS是如何實現安全短路讀的。DataNode將短路讀副本的檔案描述符傳給DFSClient,DFSClient快取副本檔案描述符。由於副本的狀態可能隨時發生改變,所以需要DFSClient和DataNode實時同步副本狀態。同時,DFSClient和DataNode在同一臺機器上,共享記憶體可以通過POSIX提供的 mmap介面實現將檔案對映到記憶體,並且對映資料是實時同步的(如圖4),所以共享記憶體可以維護所有短路讀副本的狀態,使得DFSClient和DataNode通過共享記憶體來實時同步副本資訊。

HDFS短路讀詳解

共享記憶體會有很多槽位,每個槽位對應一個短路讀副本的資訊。共享記憶體儲存了所有槽位的二進位制資訊。但是對映資料中的二進位制槽位資訊不便於管理,所以定義了Slot物件操作對映資料中的一個槽位,如下圖:

HDFS短路讀詳解

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端的共享記憶體,如下圖所示:

HDFS短路讀詳解

在安全短路讀中,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,見下圖:

HDFS短路讀詳解

2、短路讀流程

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

HDFS短路讀詳解

(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的分配。

HDFS短路讀詳解

HDFS短路讀詳解

通過統計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執行緒花了大量的時間在建立和清理這些連線上。

HDFS短路讀詳解

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

HDFS短路讀詳解

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對短路度影響,使得延遲更為平滑。

HDFS短路讀詳解

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

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

HDFS短路讀詳解
附錄

(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/

本文首發於“小米雲技術”,點選檢視原文


相關文章