alluxio文件讀取交互

總流程

時序圖

alluxio文件讀取流程總時序圖

交互流程概覽

在這裏插入圖片描述
如上圖所示,alluxio文件系統的交互流程主要由三部分組成:client(發起請求)、master(響應client和worker的請求)、worker(提供數據的存儲與訪問)。

Client - Master

1.client通過rpc連接master,獲取文件path對應的元數據信息status

Client端,負責發送請求是 RetryHandlingFileSystemMasterClient
Master端,負責接收請求並處理是FileSystemMasterClientServiceHandler
client 和 master的rpc連接由Thrift框架實現,以上兩個類都是Thrift定製生成。

2.基於status、path、options,創建文件輸入流對象FileInstream

屬性說明:
stauts 從上面master返回(再次通過rpc與master進行交互);
options 從命令行提交的請求,默認爲null,使用 default options來填充。
context 所有filesystem對象共享的上下文,此流程主要獲取client端 和 master 、worker 的客戶端對象。
blockStore 文件的各種塊操作的管理對象。特別注意,其初始化時,寫入了當前Client所在節點的層級化屬性:
  默認值=>TierIdentity(node=cleint節點本地ip,rack=null),該屬性在後續爲block選取worker節點時使用。

3.將文件(FileInStream) 拆分爲各個塊(BlockInstream)來讀取

補充:
    alluxio的文件讀取會轉化爲文件的塊讀取;
    在alluxio中,block是 文件讀取的最小基本單位, 通過BlockIntream實現文件讀取。
    文件讀取的傳輸方式,還需要根據命中情況,拆分成短路讀和netty遠程讀2種方式。

byte[] buf = new byte[512];
try (FileInStream is = mFileSystem.openFile(path, options)) {//使用默認的options打開文件,詳見 openFile
int read = is.read(buf);//讀取 512 bytes, 詳見read方法內部
while (read != -1) {
  System.out.write(buf, 0, read);
  read = is.read(buf);
}

源碼說明:

a、從上面的 is.read(buf)看到,每次循環都是讀取buf=512Byte 數據。
   這個讀取過程中,每次都會記錄/更新 當前讀取到文件的哪個位置position,目的就是計算這部分的數據是屬於文件的哪個blockId。blockId記錄在上面提及的status.blockIds列表。
   每個blockId 都會創建一個block輸入流對象BlockInStream 來進行後續的數據塊的數據讀取。
   每讀完一個block,則會創建一個新的blockInstream去讀取下一個block。
b、block輸入流對象blockInstream 封裝了 block的基本信息BlockInfo,每個block的讀取行爲(即 與worker節點的選定直接相關),以及讀取數據時的通訊信息。

下面是每個block的讀取流程描述:

3.1 構建blockInStream:爲block選定worker節點,根據讀命中類型,創建對應數據傳輸方式

通過position確定要status.blockIds列表的下標,從而取出下一個要讀取的blockId。

通過塊管理對象BlockStore,創建一個block輸入流對象BlockInStream, 並設置流對象開始讀取文件的位置。

具體創建 BlockInStream的過程,實際就是爲block選定worker節點,並根據命中類型創建對應的數據傳輸方式的過程。

1)client通過rpc連接master,獲取blockId對應的blockInfo信息:
獲取到的locations 就是block所在的worker列表。 目前block 僅保存在一個worker上。 10.37.129.2
從locations 可以 提取出worker的網絡位置信息:workerNetAdderss;
workerNetAdderss 包含了worker的層級化屬性:node=ip,rack=null, 可用於判斷各種命中類型的worker節點選取。
2)blockInfo 選取最優worker節點

各個節點都有層級化屬性 —— 層級化屬性可以從腳本/configuration獲取,默認都不設置時,僅獲取進程所在的ip來填充,即

TierIdentity:node=主機Ip,rack=null

所以有如下三個節點的位置信息:

client節點的層級化屬性: TierIdentity:node=本地主機Ip,rack=null
blockInfo的層級化屬性: 當前要讀取的block所在的worker列表,封裝在blockInfo.localtions裏面。

下文是block的woker節點選取路線:
情況一:當數據存在於alluxio本地資源(MEM,ssd,hhd)時,即blockInfo.localtions列表非空。

 將client的TierIdentity 和 blockInfo.localtions列表的各個worker的TierIdentity進行比較。
 當且僅當client和 待選worker的node相同(即ip相同)時,該worker節點纔是本地命中worker節點,標記爲“LOCAL”。 (注:rack=null 不參與最優worker節點評選)
 如果找不到“本地命中worker節點”時,返回blockInfo.localtions列表的第一個節點作爲最優worker節點,此時標註爲“REMOTE”(遠程命中節點)

情況二:當數據不存在於alluxio時,先爲block選定一個最優worker節點, 後續會通過這個worker從ufs讀取數據

 block不在alluxio的任意一個worker,首先需要通過rpc連接master,獲取當前所有worker節點的列表。
 然後通過LocalFirstPolicy 策略,從所有worker列表中選出一個最優worker。
 LocalFirstPolicy 一個優先返回本地主機的定位策略,如果本地worker沒有足夠的容量,那麼就會從活躍有效的workers列表隨機選擇一個worker用於每個塊的寫入。
     關鍵成員變量mLocalHostName,即本地主機名,它是用於選擇本地本機worker的關鍵變量。
     核心方法getWorkerForNextBlock(), 爲下一個block選取一個worker,返回Worker的網絡地址WorkerNetAddress。
     具體邏輯,分爲兩大步驟,如下:
    (1)首先嚐試從本地主機中選擇:
       先遍歷worker列表,獲取每個workerInfo:【迴應一下,這裏的worker列表,指的是上面master返回的所有worker節點列表】
       然後判斷workerInfo所在主機名是否與mLocalHostName,且workerInfo的總字節數是否滿足當前塊所需要的大小blockSizeBytes,
       如果滿足條件則返回,否則繼續遍歷;
    (2)如果步驟1始終未選擇到滿足條件的worker,則隨機挑選一個容量足夠滿足要求的worker:
        先對workerInfoList進行shuffle,避免熱點問題;
        然後遍歷worker列表,獲取每個BlockWorkerInfo,即workerInfo:
       只要workerInfo的總字節數是否滿足塊需要的大小blockSizeBytes,就是我們需要的worker,否則繼續遍歷;
    (3)連步驟2都不滿足,即所有worker的容量都足以保存這個塊,直接返回null。
 補充,從UFS讀取數據,這種命中類型標記爲“UFS”

情況三:上面兩種情況都無法找到block對應可用的worker節點,則拋異常。

3)基於不同命中類型的worker節點,創建不同的數據傳輸流BlockInStream

從上面的步驟,我們得到一個BlockInStream的基本信息:

BlockInStream.create(mContext, info, dataSource, dataSourceType, options);
參數說明:
mContext = FileSystemContext , 用來獲取各種client對象
info = blockInfo, 當前塊的信息
dataSource = 當前塊選定的worker的網絡地址,即命中的worker。
dataSourceType = 數據來源(也是命中類型);
例如 LOCAL,REMOTE,UFS,當僅當是從node選出的最優worker節點才滿足 本地命中的要求,讀取數據時走“短路讀”。其他都是遠程命中,或不命中。
options = 操作的選項

針對不同的命中類型,我們創建不同傳輸方式的BlockInStream,接下來就是client與worker之間不同的交互方式。

Client - Worker

情況一:本地命中 - LocalFilePacketReader讀取流程

client端
client識別到數據源所在worker與本地worker是同一臺機器,則下面進入短路讀的流程。
worker端
worker端對client的請求處理如下圖所示:

在這裏插入圖片描述
1.對於短路讀取的流程,如上圖中的4a所示,請求經由LocalFilePacketReader發起,到達worker端之後,在Netty的通道中流經各個Handler,最後被ShortCircuitBlockReadHandler捕獲(出入口由RPCMessageDecoder和RPCMessageEncoder擔當);
2.在ShortCircuitBlockReadHandler中進一步根據傳入的blockId進行塊的處理,如下圖:
在這裏插入圖片描述
→ BlockWorker會將目標塊移動到MEM中的指定目錄(移動之前會先判斷是否有必要移動);
→ 將處理後的目標塊的路徑返回給客戶端;

情況二:非本地命中的其他讀取行爲(Netty遠程數據傳輸)

client端

注:client端在向worker請求數據前的一系列步驟如下圖所示:
在這裏插入圖片描述

1. client在向master端獲取block所在的worker列表之前需要獲取對應的blockId列表,過程如下圖所示:
    1.1 client端通過RPC請求傳入要訪問的文件的路徑path
    1.2 master端根據該path查找其對應的Inode信息,並生成對應文件的FileInfo,該FileInfo包含了BlockId列表、UfsPath、MountId、Path等信息;

在這裏插入圖片描述

2. client端遍歷獲取到的目標文件的BlockId列表,再次通過RPC請求向master獲取對應blockId的worker列表(客戶端返回的blockInfo中有blockId、以及包含該blockId的worker地址列表):
3. client端對比master返回的對應的blockId的worker列表,找出目標worker,查找策略在上面已經提及過:
    3.1 當數據存在於alluxio本地worker中時(MEM、SSD、HDD),即blockInfo.locations列表非空;
    3.2 當數據不存在於alluxio本地worker中時,先爲block選定一個最優worker節點,後續會通過這個worker從ufs中讀取數據;
    3.3 上面兩種情況都找不到block所對應的可用的worker節點,則拋出異常。
4. client端會判斷選擇出來的worker是否是本地worker進行相應的讀取策略。
worker端
注:worker端對client請求的處理如上圖client - worker交互流程所示:
1.對於遠端worker命中,client讀取數據的流程如上圖4b所示,請求經由NettyPacketReader發起,到達worker端之後,在Netty的通道中流經各個Handler,最後被BlockReadHandler捕獲(出入口由RPCMessageDecoder和RPCMessageEncoder擔當);
2.在BlockReadHandler中進一步根據傳入的blockId進行塊的處理,如下圖:

【此圖待修改】

2a.1 BlockReadHandler會先對該塊加讀鎖,如果該塊在Alluxio(MEM、SSD、HDD)中存在,則根據blockId、lockId獲取到的LocalFileBlockReader中包含了塊的存儲路徑(如:/Volumes/ramdisk/alluxioworker/50331648000 → 前半部分是塊所在的目錄的路徑;最後的50331648000則是blockId對應的塊),如下圖:

在這裏插入圖片描述

    2a.2 位於Alluxio中的數據會被寫入ByteBuf中,最後數據被封裝到RPCProtoMessage中,傳輸到client端;
    
    2b.1 要訪問的目標塊不在Alluxio中(MEM、SSD、HDD),則根據blockId獲取到的UnderFileSystemBlockReader過程中包含了在Alluxio中創建臨時塊、創建對臨時塊進行操作的BlockWriter、以及從ufs路徑加載目標blockId的InputStream,如下圖:

在這裏插入圖片描述
2b.2 對於UnderFileSystemBlockReader獲取(acquire)目標塊(blockId)對應的InputStream過程,Alluxio提供了緩存InputStream的概念:
即UfsInputStreamManager維護了一個映射表,資源號(blockId → fileId → resources)到CachedSeekableInputStream之間的一個映射,在配置允許緩存的前提下(alluxio.worker.ufs.instream.cache.enabled),優先查找已經緩存的InputStream並返回;
→ CachedSeekableInputStream封裝了FileInputStream(本地環境下由LocalUnderFileStream根據path生成並返回)
2b.3 位於ufs中的數據會被寫入ByteBuf中,最後數據被封裝到RPCProtoMessage中,傳輸到client端;

Worker - Master

非本地讀取完成後的同步行爲

注:文件讀取完成後,客戶端在關閉BlockInstram時,會根據本次讀文件是否是本地讀進行進一步的處理,如下圖:

1.短路讀取則直接關閉BlockInstream,並結束;

2.非短路讀取則在關閉BlockInstream後,由客戶端向worker(本地是worker則向本地worker請求,否則向文件讀取的worker請求)發送無響應數據同步請求,請求到達worker端後,主要有AsyncCacheHandler中列出的五種情況,如下圖:

在這裏插入圖片描述

3.以上五種情況可以分爲兩大類,處理數據源的worker與當前client請求同步的worker是否是同一個worker:
    3.1 是同一個worker,則該client請求同步的worker需要調用cacheBlockFromUfs進行同步,如下圖所示:
        → worker會從調用readUfsBlock()方法獲取UnderFileSystemBlockReader;
        → cacheBlockFromUfs中通過調用UnderFileSystemBlockReader的read()方法,將ufs中的數據複製到byte []數組中;
        → 獲取ufs數據之後,通過TieredBlockStore爲目標block申請存儲路徑;
        → 通過LocalFileBlockWriter將由byte []數組轉換成的Buffer寫入臨時塊;(臨時塊的路徑示例:/Volumes/ramdisk/alluxioworker/.tmp_blocks/708/5c0b3f1a9800d6c4-fa4000000)

在這裏插入圖片描述

    3.2 不是同一worker,則該client請求同步的worker需要調用cacheBlockFromRemoteWorker進行同步,如下圖所示:
        → 1. 通過BlockWorker先爲目標blockId在指定層級創建臨時塊空間(包括臨時塊元數據信息);
        → 2. 在創建RemoteBlockReader的時候,本地worker從遠端數據源worker請求到數據連接通道(封裝在RemoteBlockReader內);
        → 3. 通過BlockWorker獲取到的目標塊的BlockWriter和已經建立連接的RemoteBlockReader,在BufferUtils.fastCopy(reader.channel, writer.channel)下從遠端傳輸到臨時塊空間;
        → 4. 本地worker繼續通過BlockWorker將臨時塊中的數據移動到真正的塊中(完成後,臨時塊刪除);

在這裏插入圖片描述

→ 5. 本地worker處理完目標塊的所有的同步操作後,向Master端同步目標塊的元數據信息,Master端在收到worker端提交塊的元數據同步請求後,master端對元數據對一些處理如下:

在這裏插入圖片描述

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