HDFS 05 - HDFS 常用的 Java API 操作

瘦風發表於2021-06-12

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)

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

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

相關文章