HDFS 06 - HDFS 常用的 Java API 操作

0 - 配置 Hadoop 環境(Windows系統)

下述步驟適用於 Windows 系統,其他系統可忽略。

在 Windows 系統直接運行 Hadoop 相關代碼,會提示缺少 winutils.exehadoop.dll 文件:

java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.
WARN util.NativeCodeLoader: Unable to load native-hadoop library for your platform... using builtin-java classes where applicable

原因:通過代碼訪問 Hadoop 集羣,本地開發環境相當於 Hadoop 客戶端,需要有 Hadoop 相關軟件纔可正常運行。

配置步驟:

1)到 https://github.com/cdarlint/winutils 下載與集羣版本相匹配的文件夾,然後將此文件夾拷貝到沒有中文和空格的路徑下,比如 D:\software\hadoop-3.2.1

2)在 Windows 的環境變量中添加 HADOOP_HOME,值爲上面的路徑,並將 %HADOOP_HOME%\bin 添加到 path 中;

3)把上述文件夾 bin目錄下的 hadoop.dll 文件拷貝到系統盤 C:\Windows\System32 目錄;

4)重啓 Windows 電腦。

1 - 導入 Maven 依賴

鑑於篇幅有限,相關 Maven 依賴請參見:《https://github.com/healchow/bigdata-study/blob/main/pom.xml》

2 - 常用類介紹

通過 Java API 操作 HDFS,主要涉及以下 class:

1)Configuration

主要用來封裝客戶端 / 服務端的配置。

2)FileSystem

這個類的對象是一個文件系統對象,可以用該對象的一些方法來對文件進行操作。

可通過靜態方法獲得該對象:

// 通過 conf 中的 “fs.defaultFS” 參數的值來確定文件系統的具體類型
FileSystem fs = FileSystem.get(conf);

如果代碼中沒有指定 fs.defaultFS,並且工程的 ClassPath 下也沒有相應的配置,此參數的默認值就由 Hadoop Jar 包中的 core-default.xml 文件來確定:

默認值是 file:/// ,獲取的不是 DistributedFileSystem 的實例,而是一個本地文件系統的客戶端對象。

3 - 常見 API 操作

3.1 獲取文件系統(重要)

方式1:FileSystem.get(conf)

/**
 * 獲取 FileSystem - FileSystem.get()
 */
@Test
public void testGetFileSystem1() throws IOException {
    // 創建 Configuration 對象
    Configuration conf = new Configuration();

    // 指定文件系統類型
    conf.set("fs.defaultFS", "hdfs://hadoop:9000");

    // 獲取指定的文件系統
    FileSystem fileSystem = FileSystem.get(conf);
    // FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop:9000"), new Configuration());

    // 結果:DFS[DFSClient[clientName=DFSClient_NONMAPREDUCE_1219793882_1, ugi=healchow (auth:SIMPLE)]]
    System.out.println(fileSystem);

    // 關閉文件系統
    fileSystem.close();
}

方式2:FileSystem.newInstance(conf)

/**
 * 獲取 FileSystem - FileSystem.newInstance()
 */
@Test
public void testGetFileSystem2() throws IOException {
    // 創建 Configuration 對象
    Configuration conf = new Configuration();

    // 指定文件系統類型
    conf.set("fs.defaultFS", "hdfs://hadoop:9000");

    // 獲取指定的文件系統
    FileSystem fileSystem = FileSystem.newInstance(conf);
    // FileSystem fileSystem = FileSystem.newInstance(new URI("hdfs://hadoop:9000"), new Configuration());

    System.out.println(fileSystem);
    fileSystem.close();
}

3.2 創建目錄、寫入文件

/**
 * 通過 HDFS URL 創建目錄、寫入文件
 */
@Test
public void testPutFile() throws IOException, URISyntaxException {
    // 創建測試目錄(可創建多級目錄)
    FileSystem fileSystem = FileSystem.newInstance(new URI("hdfs://hadoop:9000"), new Configuration());
    boolean result = fileSystem.mkdirs(new Path("/test/input"));
    System.out.println("mkdir result: " + result);

    // 創建文件,若存在則覆蓋,返回的是寫入文件的輸出流
    FSDataOutputStream outputStream = fileSystem.create(new Path("/test/input/hello.txt"), true);
    String content = "hello,hadoop\nhello,hdfs";
    outputStream.write(content.getBytes(StandardCharsets.UTF_8));

    // 關閉流(不拋出異常)
    IOUtils.closeQuietly(outputStream);
}

3.3 上傳文件

/**
 * 向 HDFS 上傳文件 - copyFromLocalFile()
 */
@Test
public void testUploadFile() throws URISyntaxException, IOException {
    // 獲取 FileSystem
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop:9000"), new Configuration());

    // 從本地上傳文件,兩個參數都要指定到具體的文件
    fileSystem.copyFromLocalFile(new Path("/Users/healchow/bigdata/core-site.xml"),
            new Path("/test/upload/core-site.xml"));

    // 關閉FileSystem
    fileSystem.close();
}

3.4 下載文件

HDFS URL 打開 InputStream 的方式:

/**
 * 通過 HDFS URL 獲取文件並下載 - IOUtils.copy() 方法
 */
@Test
public void testDownFileByUrl() throws IOException {
    // 註冊 HDFS URL
    URL.setURLStreamHandlerFactory(new FsUrlStreamHandlerFactory());

    // 獲取 HDFS 文件的輸入流
    InputStream inputStream = new URL("hdfs://hadoop:9000/test/input/hello.txt").openStream();
    // 獲取本地文件的輸出流(絕對路徑,文件夾必須存在)
    FileOutputStream outputStream = new FileOutputStream("/Users/healchow/bigdata/test/hello.txt");

    // 拷貝文件
    IOUtils.copy(inputStream, outputStream);

    // 關閉流(不拋出異常)
    IOUtils.closeQuietly(inputStream);
    IOUtils.closeQuietly(outputStream);
}

FileSystem 打開 InputStream 的方式:

/**
 * 通過 FileSystem 獲取文件並下載 - IOUtils.copy() 方法
 */
@Test
public void testDownloadFile() throws URISyntaxException, IOException {
    // 獲取 FileSystem
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop:9000"), new Configuration());

    // 獲取 HDFS 文件的輸入流
    FSDataInputStream inputStream = fileSystem.open(new Path("/test/input/hello.txt"));

    // 獲取本地文件的輸出流
    FileOutputStream outputStream = new FileOutputStream("/Users/healchow/bigdata/test/hello1.txt");

    // 拷貝文件
    IOUtils.copy(inputStream, outputStream);

    // 關閉流
    IOUtils.closeQuietly(inputStream);
    IOUtils.closeQuietly(outputStream);
    fileSystem.close();
}

FileSystem#copyToLocalFile() 的方式:

/**
 * 通過 FileSystem 獲取文件並下載 - copyToLocalFile() 方法
 */
@Test
public void testDownloadFileByCopyTo() throws URISyntaxException, IOException, InterruptedException {
    // 獲取 FileSystem
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop:9000"), new Configuration(), "root");

    // copyToLocalFile 拷貝文件到本地,會下載 CRC 校驗文件
    fileSystem.copyToLocalFile(new Path("/test/input/hello.txt"),
            new Path("/Users/healchow/bigdata/test/hello2.txt"));

    // 關閉 FileSystem
    fileSystem.close();
}

3.5 遍歷 HDFS 的文件

/**
 * 遍歷 HDFS 文件
 */
@Test
public void testListFiles() throws URISyntaxException, IOException {
    // 獲取FileSystem實例
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop:9000"), new Configuration());

    // 遞歸獲取 /test 目錄下所有的文件信息
    RemoteIterator<LocatedFileStatus> iterator = fileSystem.listFiles(new Path("/test"), true);

    // 遍歷文件
    while (iterator.hasNext()) {
        LocatedFileStatus fileStatus = iterator.next();

        // 獲取文件的絕對路徑:hdfs://hadoop:9000/xxx
        System.out.println("filePath: " + fileStatus.getPath());

        // 文件的 block 信息
        BlockLocation[] blockLocations = fileStatus.getBlockLocations();
        for (BlockLocation blockLocation : blockLocations) {
            String[] hosts = blockLocation.getHosts();
            for (String host : hosts) {
                System.out.println("blockHost: " + host);
            }
        }
        System.out.println("blockSize: " + blockLocations.length);
    }
}

4 - HDFS 的訪問權限控制

從上面的 API 練習,不難發現:只要得到了 HDFS 的 URL(即 fs.defaultFS)配置項,能訪問到集羣的任何人都能讀寫 HDFS 上的數據,這會導致數據的安全性完全無法得到保障。

爲了解決這個問題,HDFS 有 訪問權限控制的方法,只有通過認證的用戶,按照其所擁有的權限,讀取或寫入某些目錄下的文件。

開啓 HDFS 訪問權限控制的方法如下:

1)停止 HDFS 集羣:

cd ~/bigdata/hadoop-3.2.1
sbin/stop-dfs.sh

2)修改 ~/bigdata/hadoop-3.2.1/etc/hadoop/hdfs-site.xml 中的配置,添加如下內容:

<property>
    <name>dfs.permissions.enabled</name>
    <value>true</value>
</property>

4)重啓 HDFS 集羣:

cd ~/bigdata/hadoop-3.2.1
sbin/start-dfs.sh

5)上傳測試文件到 HDFS 集羣,這裏將上傳後的一個文件的權限修改爲 600,即只能所有者讀寫:

cd ~/bigdata/hadoop-3.2.1/etc/hadoop
hdfs dfs -mkdir /test/config
hdfs dfs -put *.xml /test/config
hdfs dfs -chmod 600 /test/config/core-site.xml

6)通過代碼下載文件:

/**
 * 通過下載文件,測試訪問權限控制
 */
@Test
public void testAccessControl() throws Exception {
    // 開啓權限控制後,當前用戶(啓動 NameNode 的用戶)應當能成功訪問
    // FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop:9000"), new Configuration());
    // 僞造其他用戶訪問,應當訪問失敗
    FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop:9000"), new Configuration(), "testuser");

    fileSystem.copyToLocalFile(new Path("/test/config/core-site.xml"),
            new Path("file:/Users/healchow/bigdata/core-site.xml"));

    fileSystem.close();
}

說明:本地測試失敗。無論用哪個用戶,訪問都成功。

查了很多資料,沒有說得通的。勞煩有了解的大佬,留言告知我呀🙏



版權聲明

作者:瘦風(https://healchow.com)

出處:博客園-瘦風的南牆(https://www.cnblogs.com/shoufeng)

感謝閱讀,公衆號 「瘦風的南牆」 ,手機端閱讀更佳,還有其他福利和心得輸出,歡迎掃碼關注🤝

本文版權歸博主所有,歡迎轉載,但 [必須在頁面明顯位置標明原文鏈接],否則博主保留追究相關人士法律責任的權利。

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