1. 分布式文件系統,即為管理網絡中跨多台計算機存儲的文件系統。HDFS以流式數據訪問模式來存儲超大文件,運行於商用硬件集群上。HDFS的構建思路為:一次寫入、多次讀取是最高效的訪問模式。數據集通常由數據源生成或從數據源賦值而來,接着長時間在此數據集上進行各類分析。每次分析都涉及該數據集的大部分數據甚至全部,因此讀取整個數據集的時間延遲比第一條記錄的時間延遲更重要。
2. HDFS是為高數據吞吐量應用優化的,這可能會以高時間延遲為代價。目前,對於低延遲的訪問需求,HBase是更好的選擇。
3. HDFS的塊默認為64M,與單一磁盤上的文件系統相似,HDFS上 的文件也被划分為塊大小的多個分塊(chunk),作為獨立的存儲單元。不同的是HDFS中小於一個塊大小的文件不會占據整個塊的空間。HDFS的塊比磁盤大,目的是為了最小化尋址開銷。如果塊設置得足夠大,從磁盤傳輸數據的時間可以明顯大於定位這個塊開始位置所需的時間。這樣,傳輸一個由多個塊組成的文件的時間取決於磁盤的傳輸速率。
4. 對分布式文件系統中的塊進行抽象會帶來很多好處。1) 一個文件的大小可以大於網絡中任意一個磁盤的容量;2) 使用塊抽象而非整個文件作為存儲單元,大大簡化了存儲子系統的設計。3) 塊非常適合用於數據備份進而提供數據容錯能力和可用性。將每個塊復制到少數幾個獨立的機器上(默認為3個),可以確保在發生塊、磁盤或機器故障后數據不丟失。如果發現一個塊不可用,系統會從其他地方讀取另一個副本,而這個過程對用戶是透明的。一個因損壞或機器故障而丟失的塊可以從其他候選地點復制到另一台可以正常運行的機器上,以保證復本的數量回到正常水平。HDFS中的fsck指令可以顯示塊的信息。% hadoop fsck / -files -blocks將列出文件系統中各個文件由哪些塊構成。
5. HDFS集群有兩類節點,並以管理者-工作者模式運行,即一個namenode(管理者)和多個datanode(工作者).
(1) namonode管理文件系統的命名空間。它維護着文件系統樹及整棵樹內所有的文件和目錄。這些信息以兩個文件形式永久保存在本地磁盤上:命名空間鏡像文件和編輯日志文件。namenode也記錄着每個文件中各個塊所在的數據節點信息,但它不會永久保存塊的位置信息。因為這些信息會在系統啟動時由數據節點重建。
(2) 客戶端代表用戶通過與namenode和datanode交互來訪問整個文件系統。
(3) datanode是文件系統的工作節點。它們根據需要存儲並檢索數據塊(受客戶端或namenode調度),並且定期向namenode發送它們所存儲的塊的列表。
6. 沒有namenode,文件系統將無法使用,因此對namenode實現容錯非常重要,Hadoop為此提供了兩種機制:
(1) 備份那些組成文件系統元數據持久狀態的文件。Hadoop可通過配置使namenode在多個文件系統上保存元數據的持久狀態,這些寫操作是實時同步的,一般的配置是,將持久狀態寫入本地磁盤的同時,寫入一個遠程掛載的網絡文件系統(NFS)。
(2) 運行一個輔助namenode,但它不能被用作namenode。其作用是定期通過編輯日志並命名空間鏡像,以防止編輯日志過大,且一般在另一台單獨的物理計算機上運行。它會保存合並后的命名空間鏡像的副本,並在namenode發生故障時啟用。但,輔助namenode保存的狀態總是滯后於主節點,所以在主節點全部失效時,難免會丟失部分數據。在這種情況下,一般把存儲在NFS上的namenode元數據復制到輔助namenode並作為新的主namenode運行。
7. 基本文件系統操作
可以通過hadoop fs -help命令獲取所有命令的詳細幫助文件。
(1) 從本地文件系統將一個文件復制到HDFS。
%hadoop fs -copyFromLocal input/docs/quangle.txt hdfs://localhost/user/tom/quangle.txt
該命令調用Hadoop文件系統的shell命令fs,本地文件quangle.txt被復制到運行在localhost上的HDFS實例中,路徑為/user/tom/quangle.txt。事實上,可以簡化命令格式以省略主機的URI並使用默認設置,即省略hdfs://localhost,因為該項已在core-site.xml中指定。
%hadoop fs -copyFromLocal input/docs/quangle.txt /user/tom/quangle.txt
也可以使用相對路徑,並將文件復制到HDFS的home目錄中,本例中為/usr/tom
%hadoop fs -copyFromLocal input/docs/quangle.txt quangle.txt
我們將文件復制回本地文件系統,並檢查是否一致:
% hadoop fs -copyToLocal quangle.txt quangle.copy.txt
% md5 input/docs/quangle.txt quangle.copy.txt
由於MD5鍵值相同,表明這個文件在HDFS之旅中得以幸存並保存完整。
(2) 創建一個目錄看它zai列表中是如何顯示的:
% hadoop fs -mkdir books
% hadoop fs -ls .
返回的結果與Linux的ls -l的輸出結果很相似,不同之處在於第2列是這個文件的備份數。由於在整個文件系統范圍內設置的默認復本數為1,所以這里顯示的也都是1,。新建的目錄為空,因為本例中沒有使用復本的概念-目錄作為元數據保存在namenode中,而非datanode中。
8. Hadoop文件系統
HDFS只是Hadoop的一個實現。Java抽象類org.apache.hadoop.fs.FileSystem定義了Hadoop中的一個文件系統接口。
文件系統 | URI方案 | Java實現 | 描述 |
Local | file | fs.LocalFileSystem | 使用了客戶端檢驗和的本地磁盤文件系統。沒有使用校驗和的本地磁盤文件系統RawLocalFileSystem |
HDFS | hdfs | hdfs.DistributedFileSystem | Hadoop的分布式文件系統,將HDFS設計成與MapReduce結合使用,可以實現高性能 |
HFTP | Hftp | hdfs.hftpFileSystem | 一個在HTTP上提供對HDFS只讀訪問的文件系統,通常與distcp結合使用,以實現並運行不同版本的HDFS的集群之間復制數據 |
HSFTP | hsftp | hdfs.HsftpFileSystem | 在HTTPS上提供對HDFS只讀訪問的文件系統 |
HAR | har | fs.HarFileSystem | 一個構建在其他文件系統之上用於文件存檔的文件系統。Hadoop存檔文件系統通常用於需要將HDFS中的文件進行存檔時,以減少namenode內存的使用 |
hfs(雲存儲) | kfs | fs.kgs.kosmosFileSystem | CloudStore是類似於HDFS或是谷歌的GFS文件系統 |
FTP | ftp | fs.ftp.FTPFileSystem | 由FTP服務器支持的文件系統 |
s3(原生) | s3n | fs.s3native.NativeS3FileSystem | 由Amazon S3支持的文件系統 |
s3(基於塊) | s3 | fs.sa.S3FileSystem | 由Amazon S3支持的文件系統,以塊格式存儲文件以解決S3的5GB文件大小限制 |
Hadoop對文件系統提供了很多接口,一般使用URI方案來選取合適的文件系統實例進行交互。例:要想列出本地文件系統根目錄下的文件,輸入命令:
%hadoop fs file:///
9 Hadoop文件系統的接口是通過Java API提供的,所以其他非Java應用程序訪問Hadoop文件系統會比較麻煩。thriftfs定制功能模塊中的Thrift API通過把Hadoop文件系統包裝一個Apache Thrift服務來彌補這個不足,從而使任何具有Thrift綁定的語言都能輕松地與Hadoop文件系統進行交互。
10 Hadoop提供了一個名為libhdfs的C語言庫,該語言庫是Java FileSystem接口類的一個鏡像。它可以使用Java原生接口(JNI)調用Java文件系統客戶端。
11 用戶空間文件系統(Filesystem in Userspace,FUSE)允許把按照用戶空間實現的文件系統整合成一個Unix文件系統,通過使用Hadoop的Fuse-DFS功能模塊,任意一個Hadoop文件系統均可以作為一個標准文件系統進行掛載。
12 Java接口:與Hadoop的某一文件系統進行交互的API。
(1) 從Hadoop URL中讀取數據
最簡單的方法是使用java.net.URL對象打開數據流,進而從中讀取數據。
例:通過URLStreamHandler實例以標准輸出方式顯示Hadoop文件系統的文件
public class URLCat{ static { // JVM只能調用一次該方法,因此第三方組件如已聲明一個URLStreamHandlerFactory實例 // 將無法再使用該方法從Hadoop中讀取數據 URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory()); } public static void main(String[] args) throws Exception{ InputStream in = null; try{ in = new URL(args[0]).openStream(); // 輸入流和輸出流之間復制數據 // 4096-設置復制的緩沖區大小,flase-設置復制結束后是否關閉數據流 IOUtils.copyBytes(in,System.out,4096,false); }finally{ IOUtils.closeStream(in); } } }
下面是一個運行示例:
% hadoop URLCat hdfs://localhost/user/tom/quangle.txt
(2) 通過FileSystem API讀取數據
Hadoop文件系統中通過Hadoop Path對象來代表文件,你可以將一條路徑視為一個Hadoop文件系統URI。
獲取FileSystem實例有兩種方法:
public static FileSystem get(Configuration conf) throws IOException public static FileSystem get(URI uri,Configuration conf) throws IOException
Configuration對象封裝了客戶端或服務器的配置,通過設置配置文件讀取類路徑來實現(如conf/core-site.xml)。第一個方法返回的是默認文件系統,第二個方法通過給定的URI方案和權限來確定要使用的文件系統。
有了FileSystem實例后,可調用open函數來讀取文件的輸入流:
public FSDataInputStream open(Path f) throws IOException public abstract FSDataInputStream open(Path f, int bufferSize) throws IOException
例:直接使用FileSystem以標准格式顯示Hadoop文件系統中的文件
public class FileSystemCat{ public static void main(String[] args) throws IOExcption{ String uri = args[0]; Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(URI.create(uri),conf); InputStream in = null; try{ in = fs.open(new Path(uri)); IOUtils.copyBytes(in,System.out,4096,false); }finally{ IOUtils.closeStream(in); } } }
運行示例:
% hadoop FileSystemCat hdfs://localhost/user/tom/quangle.txt
FileSystem對象中的open()方法返回的是FSDataInputStream對象,該類繼承了java.io.DataInputStream接口,並支持隨機訪問,由此可從流的任意位置讀取數據。
package org.apache.hadoop.fs; public class FSDataInputStream extends DataInputStream implements Seekable,PositionedReadable{ }
Seekable接口支持在文件中找到指定位置,並提供一個查詢當前位置相對於文件起始位置偏移量的查詢方法getPos()。
public interface Seekable{ void seek(long pos) throws IOException; long getPos() throws IOException; boolean seekToNewSource(long targetPos) throws IOException; }
調用seek()定位大於文件長度的位置會導致IOException異常,與java.io.InputStream中的skip()不停,seek()可以移到文件中任意一個絕對位置,skip()則只能相對於當前位置定位到另一個新位置。
例:將一個文件寫入標准輸出兩次:在第一次寫完之后,定位到文件的起始位置再次以流方式讀取該文件。
public class FileSystemDoubleCat{ String uri = args[0]; Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(URI.create(uri),conf); FSDataInputStream in = null; try{ in = fs.open(new Path(uri)); IOUtils.copyBytes(in,System.out,4096,false); in.seek(0); IOUtils.copyBytes(in,System.out,4096,false); }finally{ IOUtils.closeStream(in); } }
運行示例:
% hadoop FileSystemDoubleCat hdfs://local/user/tom/quangle.txt
FSDataInputStream也繼承了PositionedReadble接口,從一個指定偏移量處讀取文件的一部分:
public interface PositionedReadable{ public int read(long position, byte[] buffer, int offset, int length) throws IOException; public void readFully(long position, byte[] buffer, int offset, int length) throws IOException; public void readFully(long position, byte[] bufer) throws IOException; }
read()方法從文件的指定position處讀取至多為length字節的數據並存入緩沖區buffer指定的偏移量offset處。返回的是實際讀到的字節數。
所有這些方法會保留當前偏移量,因此它們提供了在讀取文件-可能是元數據-的主體時訪問文件的其他部分的便利方法。
13 寫入數據
最簡單的方法是給准備創建的文件指定一個Path對象,然后返回一個用於寫入數據的輸出流。
public FSDataOutputStream create(Path f) throws IOException;
還有一個重載方法Progressable,用於傳遞回調接口,如此一來,可以把數據寫入數據節點的進度通知到你的應用:
package org.apache.hadoop.util; public interface Progressable{ public void progress(); }
另一個新建文件的方法是使用append()方法在一個已有文件末尾追加數據:
public FSDataOutputStream append(Path f) throws IOException
該追加操作允許一個writter打開文件后在訪問文件的最后偏移量處追加數據。該API可以時某些應用創建無邊界文件。
例:顯示如何將本地文件復制到Hadoop文件系統,每次Hadoop調用progress()方法時,打印一個時間點來顯示整個運行過程。
public class FileCopyWithProgress{ public static void main(String[] args) throws IOException{ String localSrc = args[0]; String dst = args[1]; InputStream in = new BufferedInputStream(new FileInputStream(localSrc)); Configuration conf = new Configuration(); FileSystem fs = FileSystem.get(URI.create(dst),conf); OutputStream out = fs.create(new Path(dst),new Progressable(){ public void progress(){ System.out.print("."); } }); IOUtils.copyBytes(in,out,4096,true); } }
運行示例:
% hadoop FileCopyWithProgress input/docs/1400-8.txt hdfs://localhost/user/tom/1400-8.txt
FSDataOutputStream也有一個查詢文件當前位置的方法:
package org.apache.hadoop.fs; public class FSDataOutputStream extends DataOutputStream implements Syncable{ public long getPos() throws IOException{ } }
FSDataOutputStream類不允許在文件中定位,因為HDFS只允許對一個已打開的文件順序寫入或在現有文件的末尾追加數據。
14 FileSystem實例提供了創建目錄的方法:
boolean mkdirs(Path f) throws IOException
該方法可以一次性新建所有必要但還沒有的父目錄,如果目錄都已創建成功,則返回true。