本文基於Hadoop1.X
概述
分布式文件系統主要用來解決如下幾個問題:
- 讀寫大文件
- 加速運算
對於某些體積巨大的文件,比如其大小超過了計算機文件系統所能存放的最大限制或者是其大小甚至超過了計算機整個硬盤的容量的文件,這時需要將文件分割為若干較小的塊,然后將這些塊按照一定的規則分放在集群中若干台節點計算機里。
分布式文件系統的另一個作用是加速運算,在多台計算機上對每個子文件進行計算最后再匯總結果通常比在一台計算機上處理大量文件的運算要塊。這種分而治之的思想倡導:與其追求造價昂貴的高性能計算機,不如多找幾個性能普通的廉價機器來搞。
廉價的機器集群還有一個比單個高性能計算機更有優勢的地方是靈活性,可以非常方便的增加或減少集群中節點的數量,比起單個高性能機器,這種設計在計規模小時也不會造成資源(CPU,內存)的浪費,在規模膨脹時又能通過簡單的增加節點的動作輕松應對。
然而,任何事情都有兩面性,集群的弱點在於出現故障的幾率變大了,假設單台機器故障率是p,那么具有n台機器節點的集群故障率就是pn,為了能夠在某些節點出現故障的情況下不影響整個集群的健康,集群中不得不將每個節點中的數據備份多份放在別的節點,這樣當某個節點的數據不可用時還可以讀取備份節點的數據。集群以冗余為代價換取高可用性。
HDFS(Hadoop Distrbuted File System)是當今世界上最流行的分布式大數據處理系統Hadoop所依賴的文件系統,它的思想源頭是偉大的Google公司發表的論文:The Google File System,受此啟發,雅虎的若干大神包括Doug Cutting等把它搞出來的。
HDFS的特點
- 適合處理超大文件:超大文件是指大小超過幾百M,幾百G甚至幾百T的文件。HDFS不適合處理小文件,首先,HDFS的文件塊(block)默認為64M,太小的文件會造成磁盤的浪費;其次,HDFS的元數據信息(記錄文件、路徑和分塊情況等信息)保存在某個節點(NameNode)的內存中,大量的小文件需要大量的元素據來記錄,大量的元素據需要大量的內存;
- 流式數據訪問:一次寫入數據,然后多次讀取數據的大部分甚至全部進行分析,不支持隨機讀取操作
- 對節點計算機的要求較低:無需昂貴的高性能機器,集群可由廉價機器組成
- 不適合對數據讀寫延時有嚴格要求的場景:HDFS重在高吞吐量,付出的代價之一就是提高了延遲時間
- 不支持多用戶寫入,不支持隨機寫操作:只能順序將數據添加到文件結尾
想要實現隨機讀寫以及延時較低的數據讀寫,請考慮使用基於HDFS的HBase數據庫。
HDFS的結構
HDFS是一個基於操作系統本身的文件系統之上的虛擬文件系統,和常見的文件系統一樣,HDFS文件存儲的最小單元是塊(block),默認的塊的大小是64M,一個文件的若干塊可分布在不同的機器上(DataNode),所以才“分布式”了。
集群節點由三部分組成,分別是
- NameNode (只有一個)
- DataNode
- Secondary NameNode(只有一個)
NameNode節點負責保存和維護整個集群的元數據(Hadoop2並不是這樣,希望后續文章會涉及),包括文件名、文件分塊(block)、文件的塊分布在哪些節點、整個文件系統的運行狀態等信息,一個集群中只有一個節點是NameNode節點。
DataNode節點負責實際存放數據。
由於整個集群只有一個NameNode節點,該節點如果掛了整個集群也就完蛋了,所有又搞了一個Secondary NameNode出來,它會定時獲取NameNode的快照,當NameNode掛掉后可以通過Secondary NameNode快速恢復(Hadoop2並不是這樣,希望后續文章會涉及)。
下圖來自《Hadoop 實戰》:
文件data1和data2的元數據信息保存在NameNode節點中,元數據中記錄了data1文件由3個文件塊1,2和3組成,文件data2由2個文件塊4和5組成,並且記錄了每個塊所在的DataNode節點。文件塊分布在4個DataNode節點上,每個文件塊有都保存3份(3份是默認的設置)。DataNode還定時給NameNode發送心跳數據,如果NameNode長時間沒有收到心跳數據,則認為DataNode掛掉了。
HDFS的命令行接口
下載並安裝Hadoop后,即可使用HDFS了,Hadoop的安裝和配置請參考Hadoop學習之旅一:Hello Hadoop。HDFS類似於Unix的文件系統,權限的設定也是分w、r和x(可執行ls命令查看,該命令類似Unix的ls -l 命令),常用命令和Unix的文件操作命令相同或類似,下面介紹幾個常用的命令:
- ls:
bin/hadoop fs -ls /user
#列出路徑/user下的所有文件和路徑 - lsr: `bin/hadoop fs -lsr /user #列出路徑/user下的所有文件並遞歸列出子路徑及子路經下的文件
- mkdir: bin/hadoop fs -mkdir /user/hdfs_test #創建路徑 /user/hdfs_test
- rmr: bin/hadoop fs -rmr /user/hdfs_test #刪除路徑 /user/hdfs_test
- put: bin/hadoop fs -put hello.txt /user/hdfs_test #拷貝本地文件到HDFS路徑/user/hdfs_test
- get: bin/hadoop fs -get /user/hdfs_test/hello.txt /home/joey #將hello.txt文件從HDFS拷貝到本地文件系統
- cat: bin/hadoop fs -cat /user/hdfs_test/hello.txt #查看文件內容
更多命令請查看官方文檔。
HDFS的Java編程接口
實際上,上面的命令行接口就是一個Java應用,下面利用編程接口寫一個示例程序,該程序使用FileSysgem類讀取本地參數指定的路徑下以“hadoop”開頭的文件並將其復制到參數指定的HDFS路徑:
package test.joye.com;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
public class Copy {
public static void main(String[] args) {
Path localPath = new Path(args[0]); // /home/joey/test"
String hdfsPath = args[1]; // /user/hdfs
Configuration conf = new Configuration();
conf.set("fs.default.name", "hdfs://localhost:9000");
try {
FileSystem hdfs = FileSystem.get(conf);
FileSystem local = FileSystem.getLocal(conf);
FileStatus[] inputFiles = local.listStatus(localPath);
for(int i = 0; i < inputFiles.length; i++){
String fileName = inputFiles[i].getPath().getName();
if(fileName.startsWith("hadoop")){
FSDataOutputStream out = hdfs.create(new Path(hdfsPath + "/" + fileName));
FSDataInputStream in = local.open(inputFiles[i].getPath());
byte[] buffer = new byte[256];
int bytesRead = 0;
while((bytesRead = in.read(buffer)) > 0){
out.write(buffer, 0, bytesRead);
}
in.close();
out.close();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
該程序引用了如下包:
關於更多的API可以參考官方文檔
參考資料
-
《Hadoop權威指南》
-
《Hadoop實戰》
-
網易雲課堂:大數據工程師