HDFS短路讀詳解

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

背景

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

短路讀的演進

一、網絡讀

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

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

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

二、HDFS-2246 不安全短路讀

短路讀關鍵思想是由於客戶端和數據塊在同一節點上,因此DataNode不須要出如今讀取數據路徑中。而客戶端自己能夠直接從本地磁盤讀取數據。這樣會使讀取性能獲得很大的提升。在HDFS-2246中實現的短路讀是DataNode將全部數據塊路徑的權限開放給客戶端,客戶端直接經過本地磁盤路徑來讀取數據,見下圖:架構


但這種方式引入了不少問題:dom

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

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

三、HDFS-347 安全短路讀

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

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

(1)文件描述符是隻讀的,所以客戶端沒法修改傳遞描述符的文件。

(2)客戶端沒法訪問數據塊目錄自己,因此也沒法讀取它不該該訪問的任何其餘數據塊文件。

HDFS安全短路讀

一、短路讀共享內存

瞭解了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,見下圖:

二、短路讀流程

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

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

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

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

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

一、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%左右。

二、共享內存分配效率低

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

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

禁止了正在構建塊進行短路讀,也就是最後一個塊禁止短路讀。這個問題因爲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/

本文首發於「小米雲技術」, 點擊查看原文
相關文章
相關標籤/搜索