在Hadoop框架源代碼org.apache.hadoop.fs包中,都是關於Hadoop文件系統實現的相關類,主要包括文件系統模型的建立,及其在該文件系統定義、實現基本的文件操作。例如給出文件系統抽象,對文件系統上存儲的文件執行基本操作進行抽象,等等。
在該包中,類的繼承關係如下所示:
- ◦java.lang.Object
- ◦org.apache.hadoop.fs.BlockLocation (implements org.apache.hadoop.io.Writable)
- ◦org.apache.hadoop.conf.Configured (implements org.apache.hadoop.conf.Configurable)
- ◦org.apache.hadoop.fs.FileSystem (implements java.io.Closeable)
- ◦org.apache.hadoop.fs.FilterFileSystem
- ◦org.apache.hadoop.fs.ChecksumFileSystem
- ◦org.apache.hadoop.fs.InMemoryFileSystem
- ◦org.apache.hadoop.fs.LocalFileSystem
- ◦org.apache.hadoop.fs.HarFileSystem
- ◦org.apache.hadoop.fs.RawLocalFileSystem
- ◦org.apache.hadoop.fs.FsShell (implements org.apache.hadoop.util.Tool)
- ◦org.apache.hadoop.fs.Trash
- ◦org.apache.hadoop.fs.ContentSummary (implements org.apache.hadoop.io.Writable)
- ◦org.apache.hadoop.fs.FileChecksum (implements org.apache.hadoop.io.Writable)
- ◦org.apache.hadoop.fs.MD5MD5CRC32FileChecksum
- ◦org.apache.hadoop.fs.FileStatus (implements java.lang.Comparable<T>, org.apache.hadoop.io.Writable)
- ◦org.apache.hadoop.fs.FileSystem.Statistics
- ◦org.apache.hadoop.fs.FileUtil
- ◦org.apache.hadoop.fs.FileUtil.HardLink
- ◦org.apache.hadoop.fs.FsUrlStreamHandlerFactory (implements java.net.URLStreamHandlerFactory)
- ◦java.io.InputStream (implements java.io.Closeable)
- ◦java.io.FilterInputStream
- ◦java.io.BufferedInputStream
- ◦org.apache.hadoop.fs.BufferedFSInputStream (implements org.apache.hadoop.fs.PositionedReadable, org.apache.hadoop.fs.Seekable)
- ◦java.io.DataInputStream (implements java.io.DataInput)
- ◦org.apache.hadoop.fs.FSDataInputStream (implements org.apache.hadoop.fs.PositionedReadable, org.apache.hadoop.fs.Seekable)
- ◦org.apache.hadoop.fs.FSInputStream (implements org.apache.hadoop.fs.PositionedReadable, org.apache.hadoop.fs.Seekable)
- ◦org.apache.hadoop.fs.FSInputChecker
- ◦org.apache.hadoop.fs.LocalDirAllocator
- ◦java.io.OutputStream (implements java.io.Closeable, java.io.Flushable)
- ◦java.io.FilterOutputStream
- ◦java.io.DataOutputStream (implements java.io.DataOutput)
- ◦org.apache.hadoop.fs.FSDataOutputStream (implements org.apache.hadoop.fs.Syncable)
- ◦org.apache.hadoop.fs.FSOutputSummer
- ◦org.apache.hadoop.fs.Path (implements java.lang.Comparable<T>)
- ◦org.apache.hadoop.util.Shell
- ◦org.apache.hadoop.fs.DF
- ◦org.apache.hadoop.fs.DU
- ◦java.lang.Throwable (implements java.io.Serializable)
- ◦java.lang.Error
- ◦org.apache.hadoop.fs.FSError
- ◦java.lang.Exception
- ◦java.io.IOException
- ◦org.apache.hadoop.fs.ChecksumException
首先對文件系統最頂層抽象類FileSystem進行源代碼的閱讀分析。
FileSystem抽象類繼承自org.apache.hadoop.conf.Configured配置基類,實現了java.io.Closeable接口,通過這一點,可以瞭解到,FileSystem抽象類作爲一個文件系統的抽象定義,它是可配置的,也就是說可以通過指定的配置文件中的一些配置項來描述一個文件系統,實際上,最重要的配置類是org.apache.hadoop.conf.Configuration,org.apache.hadoop.conf.Configured中定義的方法就是對org.apache.hadoop.conf.Configuration配置類進行設置或獲取,滿足一個基於org.apache.hadoop.conf.Configuration配置類的其它類的需要。
FileSystem抽象類定義了文件系統所具有的基本特徵和基本操作。首先從該抽象類的屬性定義來看,這些屬性描述了文件系統的靜態特性。該類中定義瞭如下屬性:
- private static final String FS_DEFAULT_NAME_KEY = "fs.default.name";
- /** 文件系統緩存 */
- private static final Cache CACHE = new Cache();
- /** 該文件系統(this)在緩存中的鍵實例 */
- private Cache.Key key;
- /** 記錄文件系統類的統計信息的Map */
- private static final Map<Class<? extends FileSystem>, Statistics> statisticsTable = new IdentityHashMap<Class<? extends FileSystem>, Statistics>();
- /**
- * 該文件系統(this)的統計信息的實例
- */
- protected Statistics statistics;
- /**
- * 當文件系統關閉或者JVM退出以後,需要將緩存中的文件清空。該Set<Path>中的內容是,對緩存中文件的Path,並且是排好序的。
- */
- private Set<Path> deleteOnExit = new TreeSet<Path>();
Hadoop框架實現的文件系統,從FileSystem的Cache CACHE的含義可以看出,一個文件系統可以管理與它相關的並被緩存的多個文件系統的實例,這一組文件系統協調存儲工作,併爲Hadoop實現的MapReduce並行計算框架的機制提供便利的存儲基礎。
文件系統緩存
FileSystem抽象類定義了一個文件系統緩存Cache CACHE,用來緩存文件系統對象。也就是可能存在多個文件系統對象,從而可知,每個文件系統除了管理基於其上的內容之外,還可能要管理緩存的一組文件系統實例,這要看具體的文件系統是如何實現的。
當然,也可能是在分佈式環境中,一個文件系統管理遠程的和本地的文件系統實例。
爲了能夠快速獲取到一個存在於緩存中的文件系統對象,Hadoop採用了Hash算法,將文件系統對象以鍵值對的方式存儲到HashMap中,也就是org.apache.hadoop.fs.FileSystem.Cache緩存類定義的map屬性,如下所示:
- private final Map<Key, FileSystem> map = new HashMap<Key, FileSystem>();
其中,org.apache.hadoop.fs.FileSystem.Cache.Key是org.apache.hadoop.fs.FileSystem.Cache的一個內部靜態類,作爲緩存Cache中Map的鍵,一個Key所包含的內容就是一個URI的信息及其用戶名,下面是Key類的屬性:
- final String scheme;
- final String authority;
- final String username;
緩存org.apache.hadoop.fs.FileSystem.Cache的Map的值是繼承自FileSystem抽象類的子類。可以看出,可以通過一個合法的URI信息與用戶名快速獲取到緩存中存在的一個文件系統的對象,從而能夠獲取到指定文件系統中文件信息。該緩存類提供了3個基本的操作,如下所示:
- /** 根據URI與Configuration,從緩存中取出一個FileSystem實例,要求同步緩存操作。 */
- synchronized FileSystem get(URI uri, Configuration conf) throws IOException;
- /** 根據指定的緩存Key實例,從緩存中刪除該Key對應的FileSystem實例,要求同步緩存操作。 */
- synchronized void remove(Key key, FileSystem fs);
- /** 迭代緩存Map,刪除緩存中的緩存的全部文件系統實例,要求同步緩存操作。 */
- synchronized void closeAll() throws IOException;
文件系統統計信息
上面statisticsTable是一個IdentityHashMap<Class<? extends FileSystem>, Statistics>,鍵是繼承自FileSystem的Class,值是統計信息Statistics類。爲了在一個並行計算環境中進行安全的計算,Statistics類使用了java.util.concurrent.atomic包中的原子變量屬性,保證線程安全的原子讀寫操作的同時,提高並行性能。如下所示:
- private AtomicLong bytesRead = new AtomicLong();
- private AtomicLong bytesWritten = new AtomicLong();
其中,bytesRead是從統計數據中讀取指定數量的字節,加到當前讀取字節數上。同理,bytesRead是基於原子寫操作的。
另外一個統計數據屬性protected Statistics statistics,是對當前(this)的FileSystem的統計信息實例。該屬性是在該文件系統(this)的實例被構造完成之後被初始化的,通過調用initialize方法實現統計信息初始化:
- public void initialize(URI name, Configuration conf) throws IOException {
- statistics = getStatistics(name.getScheme(), getClass());
- }
然後又在initialize方法內部調用了getStatistics方法獲取到一個初始化的Statistics實例。在該方法中,在實例化一個Statistics實例以後,需要將它加入到統計信息實例的緩存statisticsTable中,以便能夠通過給定的URI快速獲取到對應的文件系統的統計信息。
爲了便捷操作文件系統的統計信息,Filesystem類實現了幾個非常方便的方法,下面只列出方法聲明:
- public static synchronized Map<String, Statistics> getStatistics();
- public static synchronized List<Statistics> getAllStatistics();
- public static synchronized Statistics getStatistics(String scheme, Class<? extends FileSystem> cls);
- public static synchronized void clearStatistics();
- public static synchronized void printStatistics() throws IOException;
這幾個方法,都是從statisticsTable中獲取到文件系統的統計信息。
文件緩存
屬性Set<Path> deleteOnExit是一個文件緩存,它用來收集當前緩存中的文件Path。當文件系統關閉,或者JVM退出的時候,需要將緩存中的文件全部刪除。刪除緩存文件的方法是在processDeleteOnExit方法中,如下所示:
- /**
- * 刪除緩存deleteOnExit中的全部文件,需要同步deleteOnExit。
- */
- protected void processDeleteOnExit() {
- synchronized (deleteOnExit) {
- for (Iterator<Path> iter = deleteOnExit.iterator(); iter.hasNext();) {
- Path path = iter.next();
- try {
- delete(path, true); // 調用,刪除目錄,及其子目錄和文件
- }
- catch (IOException e) {
- LOG.info("Ignoring failure to deleteOnExit for path " + path);
- }
- iter.remove();
- }
- }
- }
當一個FileSystem關閉以後,需要將該文件系統對應的Path加入到文件緩存deleteOnExit中,以便在文件系統關閉或JVM退出時,調用processDeleteOnExit方法刪除這些文件。向文件緩存中加入一個可能在文件系統關閉或JVM退出時刪除的文件,在deleteOnExit方法中實現的。
文件系統抽象
下面,從FileSystem抽象類“抽象”的切面橫向瞭解一個FileSystem定義了哪些基於文件系統的操作,使我們能夠知道如果實現一個基於文件系統,需要實現哪些基本操作。如下所示,FileSystem抽象類中定義了12個抽象方法:
- /** 獲取能夠唯一標識一個FileSystem的URI*/
- public abstract URI getUri();
- /**
- * 根據給定的Path f,打開一個文件的FSDataInputStream輸入流。
- * @param f 待打開的文件
- * @param bufferSize 緩衝區大小
- */
- public abstract FSDataInputStream open(Path f, int bufferSize) throws IOException;
- /**
- * 爲寫入進程打開一個FSDataOutputStream。
- * @param f 待寫入的文件
- * @param permission 權限
- * @param overwrite 是否重寫
- * @param bufferSize 緩衝區大小
- * @param replication 文件的塊副本數量
- * @param blockSize 塊大小
- * @param progress 用於報告Hadoop框架工作狀況的進程
- * @throws IOException
- */
- public abstract FSDataOutputStream create(Path f,
- FsPermission permission,
- boolean overwrite,
- int bufferSize,
- short replication,
- long blockSize,
- Progressable progress) throws IOException;
- /**
- * 向一個已經存在的文件中執行追加操作
- * @param f 存在的文件
- * @param bufferSize 緩衝區大小
- * @param progress 報告進程
- * @throws IOException
- */
- public abstract FSDataOutputStream append(Path f, int bufferSize, Progressable progress) throws IOException;
- /**
- * 重命名文件src爲dst
- */
- public abstract boolean rename(Path src, Path dst) throws IOException;
- /**
- * 刪除文件
- */
- public abstract boolean delete(Path f) throws IOException;
- /**
- * 刪除文件
- */
- public abstract boolean delete(Path f, boolean recursive) throws IOException;
- /**
- * 如果f是一個目錄,列出該目錄中的文件
- */
- public abstract FileStatus[] listStatus(Path f) throws IOException;
- /**
- * 爲給定的文件系統設置當前工作目錄
- */
- public abstract void setWorkingDirectory(Path new_dir);
- /**
- * 獲取文件系統的當前工作目錄
- */
- public abstract Path getWorkingDirectory();
- /**
- * 創建一個目錄f
- */
- public abstract boolean mkdirs(Path f, FsPermission permission) throws IOException;
- /**
- * 獲取與f對應的統計信息實例
- */
- public abstract FileStatus getFileStatus(Path f) throws IOException;
上面這些抽象方法應該是一個文件系統應該具備的基本操作,可能根據不同的需要設計一個基於FileSystem抽象類的子類實現類,這個文件系統的實現中,對於某些操作的實現細節可能因爲文件系統的特點而不全相同。因此,可以靈活設計你所需要的文件系統。
文件操作
在Filesystem文件系統上,與文件相關的操作很多,主要包括文件的創建、讀寫、重命名、拷貝、刪除這幾個基本操作。
文件的創建,包括目錄的創建和非目錄文件的創建,創建目錄的方法如下:
- public boolean mkdirs(Path f) throws IOException {
- return mkdirs(f, FsPermission.getDefault());
- }
- public abstract boolean mkdirs(Path f, FsPermission permission) throws IOException;
Filesystem抽象類沒有實現如何創建目錄的細節。
另外,還有一個跨文件系統執行創建目錄操作的實現:
- public static boolean mkdirs(FileSystem fs, Path dir, FsPermission permission) throws IOException {
- boolean result = fs.mkdirs(dir); // 基於默認權限創建一個目錄,返回文件輸出流對象
- fs.setPermission(dir, permission); // 設置fs中創建dir目錄的權限
- return result;
- }
通過這個方法可以看出,是在當前文件系統(this)中,在另一個文件系統fs中根據指定的權限來創建一個目錄,顯然這是在分佈式地進行目錄的遠程創建操作。
對於非目錄文件的創建,主要是爲了讀或寫操作而打開一個文件,返回文件的流對象,可以進行流式讀寫與追加。對創建文件的操作,有10個重載的方法都是基於一個create抽象方法的:
- public abstract FSDataOutputStream create(Path f,
- FsPermission permission,
- boolean overwrite,
- int bufferSize,
- short replication,
- long blockSize,
- Progressable progress) throws IOException;
還有一個比較特殊的create方法,如下所示:
- public static FSDataOutputStream create(FileSystem fs,
- Path file, FsPermission permission) throws IOException {
- FSDataOutputStream out = fs.create(file); // 基於默認權限創建一個文件,返回文件輸出流對象
- fs.setPermission(file, permission); // 設置fs中創建file文件的權限
- return out;
- }
通過這個方法的參數可以看出,是在當前文件系統(this)中,在另一個文件系統fs中根據指定的權限來創建一個文件,顯然這是在分佈式地進行文件的遠程創建操作。只要該文件系統的的權限滿足遠程文件系統fs的創建要求,並滿足必要的通信條件,就可以執行分佈式文件操作。
另外還有兩個open方法是用來打開已經存在的文件而且返回文件流對象;一個createNewFile方法內部實現也是調用了create方法。
文件的追加操作,是通過三個重載的append方法實現的,追加寫操作成功完成之後,返回org.apache.hadoop.fs.FSDataOutputStream流對象。
文件的重命名操作,是通過抽象方法rename(Path, Path)定義的。
文件的刪除操作,是通過delete方法定義的。
本地文件的拷貝操作,主要是通過兩組重載的方法實現的。一組是重載的copyFromLocalFile方法:拷貝源文件到目的文件,保留源文件(複製操作);另一組是重載的moveFromLocalFile方法:拷貝源文件到目的文件,刪除源文件,這是文件的移動操作(就是剪切操作)。
文件、塊、副本
關於文件和塊,可以通過Hadoop的架構設計中瞭解到一些相關信息,一些參數的含義及其設置。
關於塊(Block),FileSystem中定義瞭如下兩個方法:
- /**
- * 獲取文件f的塊大小
- */
- public long getBlockSize(Path f) throws IOException {
- return getFileStatus(f).getBlockSize();
- }
- /**
- * 獲取默認塊大小
- */
- public long getDefaultBlockSize() {
- // default to 32MB
- return getConf().getLong("fs.local.block.size", 32 * 1024 * 1024);
- }
爲了保證Hadoop分佈式文件系統的可靠性與可用性,使用了文件副本冗餘存儲和流水線複製技術。那麼對於文件副本的設置也是有一定要求的。下面是關於副本的一些參數的操作:
- /**
- * 設置文件src的replication因子爲replication
- */
- public boolean setReplication(Path src, short replication) hrows IOException {
- return true;
- }
- /**
- * 獲取文件src的replication因子
- */
- @Deprecated
- public short getReplication(Path src) throws IOException {
- return getFileStatus(src).getReplication();
- }
- /**
- * 獲取文件的默認副本個數,亦即replication因子
- */
- public short getDefaultReplication() { return 1; }
關於文件的狀態信息,可以通過一組重載的listStatus方法來獲取,文件狀態信息通過org.apache.hadoop.fs.FileStatus實體類來統計,該類實現了org.apache.hadoop.io.Writable接口,因此是可序列化的。它主要包含文件的下述信息:
- private Path path; // 文件路徑
- private long length; // 文件長度
- private boolean isdir; // 是否是目錄
- private short block_replication; // 塊副本因子
- private long blocksize; // 塊大小
- private long modification_time; // 修改時間
- private long access_time; // 訪問時間
- private FsPermission permission; //在指定文件系統中的操作權限
- private String owner; // 文件屬主
- private String group; // 所屬組
對於塊,塊是組成文件的基本單位,那麼給定一個文件,它就應該具有一個塊的列表,可以通過getFileBlockLocations方法獲取到一個文件對應的塊所在主機的列表、所在文件中的偏移位置等信息,如下:
- /**
- * 返回一個BlockLocation[],它包含了主機名列表、偏移位置、文件大小的信息
- */
- public BlockLocation[] getFileBlockLocations(FileStatus file, long start, long len) throws IOException {
- if (file == null) {
- return null;
- }
- if ( (start<0) || (len < 0) ) {
- throw new IllegalArgumentException("Invalid start or len parameter");
- }
- if (file.getLen() < start) {
- return new BlockLocation[0];
- }
- String[] name = { "localhost:50010" };
- String[] host = { "localhost" };
- return new BlockLocation[] { new BlockLocation(name, host, 0, file.getLen()) };
- }
其中,org.apache.hadoop.fs.BlockLocation類具有一個指定文件的塊的信息,它實現了org.apache.hadoop.io.Writable接口,因此是可序列化的,它具有的信息如下所示:
- private String[] hosts; // hostnames of datanodes
- private String[] names; // hostname:portNumber of datanodes
- private String[] topologyPaths; // full path name in network topology
- private long offset; // 塊在文件中的偏移位置
- private long length;
另外,Filesystem類中還定義了globStatus方法,用於根據指定的PathFilter來過濾文件系統中的文件Path,從而返回滿足過濾條件的Path的文件狀態信息的數組FileStatus[]。