1. 程式人生 > 實用技巧 >Hadoop叢集操作

Hadoop叢集操作

寫在前面

前文地址1 前文地址2我們搭建了hadoop叢集,接下來學習一下如何對HDFS進行操作(即hadoop的分散式檔案系統)。畢竟我們不能白搭建叢集嘛。主要可以分為shell操作和API操作。

shell操作

以下操作都在hadoop目錄下,如我的就在/opt/module/hadoop-2.7.2

這裡有兩種方法,**bin/hadoop fs 具體命令 **或者 bin/hdfs dfs 具體命令,二者區別不大,dfs是fs的實現類。這裡我配置了環境變數,故可以直接hadoop或者hdfs。

  • 啟動hadoop叢集

    sbin/start-dfs.sh
    sbin/start-yarn.sh
    
  • 顯示目錄資訊

    hadoop fs -ls /
    
  • 在HDFS上建立目錄

    hadoop fs -mkdir -p /sanguo/shuguo
    
  • 從本地剪下貼上到HDFS

    touch kongming.txt
    hadoop fs -moveFromLocal ./kongming.txt /sanguo/shuguo
    
  • 追加一個檔案到已經存在的檔案末尾

    touch liubei.txt
    vim liubei.txt
    輸入
    san gu mao lu
    hadoop fs -appendToFile liubei.txt /sanguo/shuguo/kongming.txt
    
  • 顯示檔案內容

    hadoop fs -cat /sanguo/kongming.txt
    
  • -chgrp、-chmod,-chown,用法與linux系統中一樣,修改檔案的所屬許可權

    hadoop fs -chmod 666 /sanguo/shuguo/kongming.txt
    hadoop fs -chown liuge:liuge /sanguo/shuguo/kongming.txt
    
  • 把本地系統檔案拷貝到HDFS上

    hadoop fs -copyFromLocal README.txt /
    
  • 從HDFS拷貝到本地

    hadoop fs -copyToLocal /sanguo/shuguo/kongming.txt ./
    
  • 從HDFS的一個路徑拷貝到另一個路徑

    hadoop fs -cp /sanguo/shuguo/kongming.txt /zhuge.txt
    
  • 在HDFS目錄中移動檔案

    hadoop fs -mv /zhuge.txt /sanguo/shuguo
    
  • -get等同於copyToLocal,-put等同於copyFromLocal

  • 合併多個下載檔案

hadoop fs -getmerge /user/liuge/test/* ./zaiyiqi.txt
  • 顯示一個檔案的末尾

    hadoop fs -tail /sanguo/shuguo/kongming.txt
    
  • 刪除檔案或者資料夾

    hadoop fs -rm /user/liuge/test/jinlian2.txt
    
  • 刪除空目錄

    hadoop fs -mkdir /test
    hadoop fs -rmdir /test
    
  • 統計資料夾的大小資訊

    hadoop fs -du -s -h /user/liuge/test
    

總的來說,shell操作可以滿足我們大多數的需求了。但有時候還是需要編寫JAVA API的。下面來看看使用JAVA API。

API操作

準備工作

首先準備一個已經編譯好的與linux上的hadoop一個版本的jar包,解壓到一個非中文目錄,配置HADOOP_HOME環境變數:

再接著去配置PATH環境變數,新增一條:%HADOOP_HOME%\bin

建立一個maven專案,匯入如下的依賴:

<dependencies>
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>RELEASE</version>
		</dependency>
		<dependency>
			<groupId>org.apache.logging.log4j</groupId>
			<artifactId>log4j-core</artifactId>
			<version>2.8.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-common</artifactId>
			<version>2.7.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-client</artifactId>
			<version>2.7.2</version>
		</dependency>
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-hdfs</artifactId>
			<version>2.7.2</version>
		</dependency>
</dependencies>

同時在resources目錄下新建一個log4j.properties,寫入以下內容:

log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n

編寫一個HDFSClient類,寫入如下程式碼作為測試:

        FileSystem fs = getFileSystem();
        // 2.在hdfs上建立路徑
        fs.mkdirs(new Path("/0529/liuge/banzhang"));
        // 3.關閉資源
        fs.close();
        System.out.println("over");

其中的getFileSystem()方法為:

    private static FileSystem getFileSystem() throws Exception {
        Configuration conf = new Configuration();
        return FileSystem.get(new URI("hdfs://hadoop03:9000"), conf, "liuge");
    }

解釋一下,在getFileSystem()方法裡,要寫入你的hadoop叢集地址,以及訪問的使用者,比如我這裡是liuge,就寫入liuge使用者。

之後我們獲取到檔案系統後,就可以在hdfs上進行操作了。

如果能正確輸出over,且在web端上觀察到已經建立了檔案,則說明能夠正確連線到叢集。

HDFS檔案上傳與下載

先直接上程式碼:

    /**
     * 1. 檔案上傳
     * 優先順序:程式碼配置>resources配置檔案>linux中的配置檔案>linux中的預設配置檔案
     */
    @Test
    public void testCopyFromLocalFile() throws Exception {
        // 1.獲取fs物件
        Configuration conf =new Configuration();
        conf.set("dfs.replication","2");
        FileSystem fs = FileSystem.get(new URI("hdfs://hadoop03:9000"), conf, "liuge");
        // 2.執行上傳程式碼
        fs.copyFromLocalFile(new Path("f:/update.txt"),new Path("/ignore.txt"));
        // 3.關閉資源
        fs.close();
    }

    /**
     * 2.檔案下載
     * @throws Exception
     */
    @Test
    public void testCopyToLocalFile() throws Exception{
        // 1.獲取物件
       FileSystem fs = getFileSystem();
        // 2.執行下載操作
//        fs.copyToLocalFile(new Path("/ignore.txt"),new Path("f:/ignore.txt"));
        fs.copyToLocalFile(false,new Path("/ignore.txt"),new Path("f:/ignore.txt"),true);
        // 3.關閉資源
        fs.close();
    }

這裡我們在檔案上傳那裡設定了一個引數,用來設定切片數。通過實驗得出瞭如上註釋的優先順序結論。

在檔案下載裡,我們發現copyToLocalFile有兩個過載方法,我們可以對第三個引數傳入true代表使用原生檔案系統,就不會生成本地的一些檔案了。

HDFS檔案刪除

    /**
     * 3. 檔案刪除
     * @throws Exception
     */
    @Test
    public void testDelete() throws Exception{
        // 1.獲取物件
        FileSystem fs=getFileSystem();
        // 2.檔案的刪除 第二個變量表示是否遞迴刪除
        fs.delete(new Path("/0529"),true);
        // 3.關閉資源
        fs.close();
    }

直接上程式碼,沒什麼好說的。

檔案更名

    /**
     * 4.檔案更名
     *
     * @throws Exception
     */
    @Test
    public void testRename() throws Exception {
        // 1.獲取物件
       FileSystem fs = getFileSystem();
        // 2.檔案的更名
        fs.rename(new Path("/update.txt"), new Path("/yanjing.txt"));
        // 3.關閉資源
    }

同上,也沒什麼好說的。

檢視檔案許可權資訊

先來看程式碼:

    /**
     * 5.檢視檔案許可權資訊
     *
     * @throws Exception
     */
    @Test
    public void testListFiles() throws Exception {
        // 1.獲取物件
       FileSystem fs = getFileSystem();
        // 2.檢視檔案詳情
        RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true);
        while (listFiles.hasNext()) {
            LocatedFileStatus fileStatus = listFiles.next();
            // 列印資訊
            // 名稱
            System.out.println(fileStatus.getPath().getName());
            // 許可權
            System.out.println(fileStatus.getPermission());
            // 長度
            System.out.println(fileStatus.getLen());
            // 儲存的塊資訊
            BlockLocation[] blockLocations = fileStatus.getBlockLocations();
            for (BlockLocation blockLocation : blockLocations) {
                String[] hosts = blockLocation.getHosts();
                for (String host : hosts) {
                    // 獲取塊儲存的主機節點
                    System.out.println(host);
                }
            }

            System.out.println("-------------分割線哦~--------------");
        }

        // 3.關閉資源
        fs.close();
    }

我們通過API獲得了很多資訊,這裡我們獲取了許可權,長度,塊資訊,並且在塊資訊中也有很多可以獲取的,這裡只獲取了塊儲存的主機節點。這個API還是很有用的。

判斷是資料夾還是檔案

/**
 * 判斷是檔案還是資料夾
 *
 * @throws Exception
 */
@Test
public void testListStatus() throws Exception {
    // 1.獲取fs
    FileSystem fs = getFileSystem();
    // 2.判斷操作
    FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
    for (FileStatus fileStatus : fileStatuses) {
        if (fileStatus.isFile()) {
            System.out.println("f:" + fileStatus.getPath().getName());

        }
        if (fileStatus.isDirectory()) {
            System.out.println("d:" + fileStatus.getPath().getName());
        }
    }
    // 3.關閉資源
    fs.close();
}

我們通過API可以判斷上傳的檔案是一個檔案還是一個資料夾,從而對其進行不同的操作。

總結

總的來說,用API操作還是十分簡單的。我們只需要獲取一個檔案系統,然後呼叫API就可以了,難度不是很大。

原生IO流操作

有的時候,API不能滿足我們的需求,我們就需要使用原生IO流進行操作,自己寫了。

首先還是先定義一個獲取檔案系統的方法:

    private static FileSystem getFileSystem() throws Exception {
        Configuration conf = new Configuration();
        return FileSystem.get(new URI("hdfs://hadoop03:9000"), conf, "liuge");
    }

再定義一個通用的關閉方法,來關閉所有的流:

    private void closeStream(InputStream fis, OutputStream fos) throws Exception {
        IOUtils.closeStream(fos);
        IOUtils.closeStream(fis);
        fs.close();
    }

這裡使用了IOUtils,這是hadoop提供的工具。使用它我們可以很方便的關閉一些流。

檔案上傳與下載

    /**
     * 把檔案上傳到HDFS根目錄
     */
    @Test
    public void putFileToHDFS() throws Exception{
        // 1.獲取物件
        FileSystem fs = getFileSystem();
        // 2.獲取輸入流
        FileInputStream fis = new FileInputStream(new File("f:/update.txt"));
        // 3.獲取輸出流
        FSDataOutputStream fos = fs.create(new Path("/banzhang.txt"));
        // 4.流的對拷
        IOUtils.copyBytes(fis,fos, conf);
        // 5.關閉資源
        closeStream(fis, fos);
    }

    /**
     * 從HDFS拷貝檔案到本地
     * @throws Exception
     */
    @Test
    public void getFileFromHDFS() throws Exception{
        // 1.獲取物件
        FileSystem fs = getFileSystem();
        // 2.獲取輸入流
        FSDataInputStream fis = fs.open(new Path("/banzhang.txt"));
        // 3.獲取輸出流
        FileOutputStream fos = new FileOutputStream(new File("f:/banzhang.txt"));
        // 4.流的對拷
        IOUtils.copyBytes(fis,fos,conf);
        // 5.關閉資源
        closeStream(fis,fos);
    }

可以看到,整個操作也是比較套路化的。我們先獲取到檔案系統物件,然後建立輸入流和輸出流,最後對拷就可以了。

分塊拷貝

假如我現在有這樣的一個需求,我只想要一個大檔案的第二塊(block),不想要前面的塊。要實現這樣的需求,我們就可以使用原生IO流:

    /**
     * 下載第二塊
     * @throws Exception
     */
    @Test
    public void readFileSeek2() throws Exception{
        // 1.獲取物件
        fs = getFileSystem();
        // 2.獲取輸入流
        FSDataInputStream fis = fs.open(new Path("/hadoop-2.7.2.tar.gz"));
        // 3.設定指定讀取的起點 單位為B
        fis.seek(1024 * 1024 * 128);
        // 4.獲取輸出流
        FileOutputStream fos = new FileOutputStream(new File("f:/hadoop-2.7.2.tar.gz.part2"));
        // 5.流的對拷
        IOUtils.copyBytes(fis,fos,conf);
        // 6.關閉資源
        closeStream(fis,fos);
    }

這裡我們傳的大檔案大小大概是188MB。按照hadoop的分片規則,會分為兩塊。

可以看到,因為HDFS預設的塊大小為128MB,我們這裡就按照128MB對輸入流進行了定位,然後獲取到了128MB以後的塊,即第二塊。

總結

總的來說,對HDFS的操作還是比較簡單的。只需要多加練習就好。