Hadoop讀書(shū)筆記2-HDFS-read write file
HDFS是一個(gè)分布式文件系統(tǒng),在HDFS上寫文件的過(guò)程與我們平時(shí)使用的單機(jī)文件系統(tǒng)非常不同,從宏觀上來(lái)看,在HDFS文件系統(tǒng)上創(chuàng)建并寫一個(gè)文件,流程如下圖(來(lái)自《Hadoop:The Definitive Guide》一書(shū))所示:
(注意L:這是讀文件的示意圖 。 貼錯(cuò)圖了)
具體過(guò)程描述如下:
- Client調(diào)用DistributedFileSystem對(duì)象的create方法,創(chuàng)建一個(gè)文件輸出流(FSDataOutputStream)對(duì)象
- 通過(guò)DistributedFileSystem對(duì)象與Hadoop集群的NameNode進(jìn)行一次RPC遠(yuǎn)程調(diào)用,在HDFS的Namespace中創(chuàng)建一個(gè)文件條目(Entry),該條目沒(méi)有任何的Block
- 通過(guò)FSDataOutputStream對(duì)象,向DataNode寫入數(shù)據(jù),數(shù)據(jù)首先被寫入FSDataOutputStream對(duì)象內(nèi)部的Buffer中,然后數(shù)據(jù)被分割成一個(gè)個(gè)Packet數(shù)據(jù)包
- 以Packet最小單位,基于Socket連接發(fā)送到按特定算法選擇的HDFS集群中一組DataNode(正常是3個(gè),可能大于等于1)中的一個(gè)節(jié)點(diǎn)上,在這組DataNode組成的Pipeline上依次傳輸Packet
- 這組DataNode組成的Pipeline反方向上,發(fā)送ack,最終由Pipeline中第一個(gè)DataNode節(jié)點(diǎn)將Pipeline ack發(fā)送給Client
- 完成向文件寫入數(shù)據(jù),Client在文件輸出流(FSDataOutputStream)對(duì)象上調(diào)用close方法,關(guān)閉流
- 調(diào)用DistributedFileSystem對(duì)象的complete方法,通知NameNode文件寫入成功
下面代碼使用Hadoop的API來(lái)實(shí)現(xiàn)向HDFS的文件寫入數(shù)據(jù),同樣也包括創(chuàng)建一個(gè)文件和寫數(shù)據(jù)兩個(gè)主要過(guò)程,代碼如下所示:
static String[] contents = new String[] {
"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"cccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
"dddddddddddddddddddddddddddddddd",
"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
};
public static void main(String[] args) {
String file = "hdfs://h1:8020/data/test/test.log";
Path path = new Path(file);
Configuration conf = new Configuration();
FileSystem fs = null;
FSDataOutputStream output = null;
try {
fs = path.getFileSystem(conf);
output = fs.create(path); // 創(chuàng)建文件
for(String line : contents) { // 寫入數(shù)據(jù)
output.write(line.getBytes("UTF-8"));
output.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
結(jié)合上面的示例代碼,我們先從fs.create(path);開(kāi)始,可以看到FileSystem的實(shí)現(xiàn)DistributedFileSystem中給出了最終返回FSDataOutputStream對(duì)象的抽象邏輯,代碼如下所示:
public FSDataOutputStream create(Path f, FsPermission permission,
boolean overwrite,
int bufferSize, short replication, long blockSize,
Progressable progress) throws IOException {
statistics.incrementWriteOps(1);
return new FSDataOutputStream
(dfs.create(getPathName(f), permission, overwrite, true, replication, blockSize, progress, bufferSize), statistics);
}
下面,我們從DFSOutputStream類開(kāi)始,說(shuō)明其內(nèi)部實(shí)現(xiàn)原理。
DFSOutputStream內(nèi)部原理
打開(kāi)一個(gè)DFSOutputStream流,Client會(huì)寫數(shù)據(jù)到流內(nèi)部的一個(gè)緩沖區(qū)中,然后數(shù)據(jù)被分解成多個(gè)Packet,每個(gè)Packet大小為64k字節(jié),每個(gè)Packet又由一組chunk和這組chunk對(duì)應(yīng)的checksum數(shù)據(jù)組成,默認(rèn)chunk大小為512字節(jié),每個(gè)checksum是對(duì)512字節(jié)數(shù)據(jù)計(jì)算的校驗(yàn)和數(shù)據(jù)。
當(dāng)Client寫入的字節(jié)流數(shù)據(jù)達(dá)到一個(gè)Packet的長(zhǎng)度,這個(gè)Packet會(huì)被構(gòu)建出來(lái),然后會(huì)被放到隊(duì)列dataQueue中,接著DataStreamer線程會(huì)不斷地從dataQueue隊(duì)列中取出Packet,發(fā)送到復(fù)制Pipeline中的第一個(gè)DataNode上,并將該P(yáng)acket從dataQueue隊(duì)列中移到ackQueue隊(duì)列中。ResponseProcessor線程接收從Datanode發(fā)送過(guò)來(lái)的ack,如果是一個(gè)成功的ack,表示復(fù)制Pipeline中的所有Datanode都已經(jīng)接收到這個(gè)Packet,ResponseProcessor線程將packet從隊(duì)列ackQueue中刪除。
在發(fā)送過(guò)程中,如果發(fā)生錯(cuò)誤,所有未完成的Packet都會(huì)從ackQueue隊(duì)列中移除掉,然后重新創(chuàng)建一個(gè)新的Pipeline,排除掉出錯(cuò)的那些DataNode節(jié)點(diǎn),接著DataStreamer線程繼續(xù)從dataQueue隊(duì)列中發(fā)送Packet。
下面是DFSOutputStream的結(jié)構(gòu)及其原理,如圖所示:
我們從下面3個(gè)方面來(lái)描述內(nèi)部流程:
- 創(chuàng)建Packet
Client寫數(shù)據(jù)時(shí),會(huì)將字節(jié)流數(shù)據(jù)緩存到內(nèi)部的緩沖區(qū)中,當(dāng)長(zhǎng)度滿足一個(gè)Chunk大小(512B)時(shí),便會(huì)創(chuàng)建一個(gè)Packet對(duì)象,然后向該P(yáng)acket對(duì)象中寫Chunk Checksum校驗(yàn)和數(shù)據(jù),以及實(shí)際數(shù)據(jù)塊Chunk Data,校驗(yàn)和數(shù)據(jù)是基于實(shí)際數(shù)據(jù)塊計(jì)算得到的。每次滿足一個(gè)Chunk大小時(shí),都會(huì)向Packet中寫上述數(shù)據(jù)內(nèi)容,直到達(dá)到一個(gè)Packet對(duì)象大小(64K),就會(huì)將該P(yáng)acket對(duì)象放入到dataQueue隊(duì)列中,等待DataStreamer線程取出并發(fā)送到DataNode節(jié)點(diǎn)。
- 發(fā)送Packet
DataStreamer線程從dataQueue隊(duì)列中取出Packet對(duì)象,放到ackQueue隊(duì)列中,然后向DataNode節(jié)點(diǎn)發(fā)送這個(gè)Packet對(duì)象所對(duì)應(yīng)的數(shù)據(jù)。
- 接收ack
發(fā)送一個(gè)Packet數(shù)據(jù)包以后,會(huì)有一個(gè)用來(lái)接收ack的ResponseProcessor線程,如果收到成功的ack,則表示一個(gè)Packet發(fā)送成功。如果成功,則ResponseProcessor線程會(huì)將ackQueue隊(duì)列中對(duì)應(yīng)的Packet刪除。