零基礎入門Hadoop:IntelliJ IDEA遠端連線伺服器中Hadoop執行WordCount

努力的小雨發表於2024-11-16

今天我們來聊一聊大資料,作為一個Hadoop的新手,我也並不敢深入探討複雜的底層原理。因此,這篇文章的重點更多是從實際操作和入門實踐的角度出發,帶領大家一起了解大資料應用的基本過程。我們將透過一個經典的案例——WordCounter,來幫助大家入門。簡單來說,這個案例的目標是從一個文字檔案中讀取每一行,統計其中單詞出現的頻率,最後生成一個統計結果。表面上看,這個任務似乎不難,畢竟我們在本地用Java程式就可以很輕鬆地實現。

然而,實際情況並非如此簡單。雖然我們能夠在一臺計算機上透過簡單的Java程式完成類似的任務,但在大資料的場景下,資料量遠遠超過一臺機器能夠處理的能力。此時,單純依賴一臺機器的計算資源就無法應對龐大的資料量,這正是分散式計算和儲存技術的重要性所在。分散式計算將任務拆分為多個子任務,並利用多臺機器協同工作,從而實現高效處理海量資料,而分散式儲存則可以將資料切分並儲存在多個節點上,解決資料儲存和訪問的瓶頸。

image

因此,透過今天的介紹,我希望能夠帶大家從一個簡單的例子出發,逐步理解大資料處理中如何藉助Hadoop這樣的分散式框架,來高效地進行資料計算和儲存。

環境準備

Hadoop安裝

這裡我不太喜歡在本地 Windows 系統上進行安裝,因為本地環境中通常會積累很多不必要的檔案和配置,可能會影響系統的乾淨與流暢度。因此,演示的重點將放在以 Linux 伺服器為主的環境上,透過 Docker 實現快速部署。

我們將利用寶塔皮膚進行一鍵式安裝,只需透過簡單的操作即可完成整個部署過程,免去手動敲命令的麻煩,讓安裝變得更加便捷和快速。

image

開放埠

這裡,系統本身已經對外開放了部分埠,例如 9870 用於訪問 Web UI 介面,但有一個重要的埠 8020 並沒有開放。這個埠是我們需要透過本地的 IntelliJ IDEA 進行連線和使用的,因此必須手動進行額外的配置,確保該埠能夠正常訪問。具體操作可以參考以下示意圖進行設定,以便順利完成連線。

image

如果你已經成功啟動並完成配置,那麼此時你應該能夠順利訪問並檢視 Web 頁面。如圖所示:

image

專案開發

建立專案

我們可以直接建立一個新的專案,並根據專案需求手動配置相關的專案資訊,例如 groupIdartifactIdversion 等基本配置。為了確保相容性和穩定性,我們選擇使用 JDK 8 作為開發環境版本。

image

首先,讓我們先來檢視一下專案的檔案目錄結構,以便對整個專案的組織形式和檔案分佈有一個清晰的瞭解。

tree /f 可以直接生成

├─input
│      test.txt
├─output
├─src
│  ├─main
│  │  ├─java
│  │  │  └─org
│  │  │      └─xiaoyu
│  │  │              InputCountMapper.java
│  │  │              Main.java
│  │  │              WordsCounterReducer.java
│  │  │
│  │  └─resources
│  │          core-site.xml
│  │          log4j.xml

接下來,我們將實現大資料中的經典示例——"Hello, World!" 程式,也就是我們通常所說的 WordCounter。為了實現這個功能,首先,我們需要編寫 MapReduce 程式。在 Map 階段,主要的任務是將輸入的檔案進行解析,將資料分解並轉化成有規律的格式(例如,單詞和其出現次數的鍵值對)。接著,在 Reduce 階段,我們會對 Map 階段輸出的資料進行彙總和統計,最終得到我們想要的統計結果,比如每個單詞的出現次數。

此外,我們還需要編寫一個啟動類——Job 類,用來配置和啟動 MapReduce 任務,確保 Map 和 Reduce 階段的流程能夠順利進行。透過這整套流程的實現,我們就完成了一個基本的 WordCounter 程式,從而理解了 MapReduce 的核心思想與應用。

pom依賴

這裡沒有什麼好說的,直接新增相關依賴即可:

<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-common</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-hdfs</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-client</artifactId>
    <version>3.2.0</version>
</dependency>

<!--mapreduce-->
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-core</artifactId>
    <version>3.2.0</version>
</dependency>
<dependency>
    <groupId>org.apache.hadoop</groupId>
    <artifactId>hadoop-mapreduce-client-common</artifactId>
    <version>3.2.0</version>
</dependency>

core-site.xml

這裡配置的我們遠端Hadoop連線配置資訊:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
    <property>
        <name>fs.defaultFS</name>
        <value>hdfs://你自己的ip:8020</value>
    </property> 
</configuration>

test.txt

我們此次主要以演示為主,因此並不需要處理非常大的檔案。為了簡化演示過程,我在此僅提供了一部分資料。

xiaoyu xiaoyu
cuicui ntfgh
hanhan dfb
yy yy
asd dfg
123 43g
nmao awriojd

InputCountMapper

先來構建一下InputCountMapper類。程式碼如下:

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

public class InputCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();

    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        String line = value.toString().trim();
        for (int i = 0; i < line.split(" ").length; i++) {
            word.set(line.split(" ")[i]);
            context.write(word, one);
        }
    }
}

在Hadoop的MapReduce程式設計中,寫法其實是相對簡單的,關鍵在於正確理解和定義泛型。你需要整合一個Mapper類,並根據任務的需求為其定義四個泛型型別。在這個過程中,每兩個泛型組成一對,形成K-V(鍵值對)結構。以上面的例子來說,輸入資料的K-V型別是LongWritable-Text,輸出資料的K-V型別定義為Text-IntWritable。這裡的LongWritableTextIntWritable等都是Hadoop自定義的資料型別,它們代表了不同的資料格式和型別。除了String在Hadoop中被替換成Text,其他的資料型別通常是在後面加上Writable字尾。

接下來,對於Mapper類的輸出格式,我們已經在程式碼中定義了格式型別。然而,需要注意的是,我們重寫的map方法並沒有直接返回值。相反,Mapper類會透過Context上下文物件來傳遞最終結果。

因此,我們只需要確保在map方法中將格式化後的資料存入Context,然後交給Reducer處理即可。

WordsCounterReducer

這一步的程式碼如下:

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class WordsCounterReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
        int sum = 0;
        for (IntWritable val : values) {
            sum += val.get();
        }
        context.write(key, new IntWritable(sum));
    }
}

在Hadoop的MapReduce程式設計中,Reduce階段的寫法也遵循固定模式。首先,我們需要整合Reducer類,並定義好四個泛型引數,類似於Mapper階段。這四個泛型包括輸入鍵值對型別、輸入值型別、輸出鍵值對型別、以及輸出值型別。

Reduce階段,輸入資料的格式會有所變化,尤其是在值的部分,通常會變成Iterable型別的集合。這個變化的原因是,Mapper階段處理時,我們通常將每個單詞的出現次數(或其他統計資訊)作為1存入Context。比如,假設在Mapper階段遇到單詞“xiaoyu”時,我們每次都會輸出一個(xiaoyu, 1)的鍵值對。結果,如果單詞“xiaoyu”在輸入資料中出現多次,Context會把這些鍵值對合併成一個Iterable集合,像是(xiaoyu, [1, 1]),表示該單詞出現了兩次。

在這個例子中,Reduce階段的操作非常簡單,只需要對每個Iterable集合中的值進行累加即可。比如,對於xiaoyu的輸入集合(xiaoyu, [1, 1]),我們只需要將其所有的1值累加起來,得出最終的結果2。

Main

最後我們需要生成一個Job,程式碼如下:

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class Main {
    static {
        try {
            System.load("E:\\hadoop.dll");//建議採用絕對地址,bin目錄下的hadoop.dll檔案路徑
        } catch (UnsatisfiedLinkError e) {
            System.err.println("Native code library failed to load.\n" + e);
            System.exit(1);
        }
    }


    public static void main(String[] args) throws Exception{
        Configuration conf = new Configuration(); 
        Job job = Job.getInstance(conf, "wordCounter"); 
        job.setJarByClass(Main.class);
        job.setMapperClass(InputCountMapper.class);
        job.setReducerClass(WordsCounterReducer.class);

        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        FileInputFormat.addInputPath(job, new Path("file:///E:/hadoop/test/input"));
        FileOutputFormat.setOutputPath(job, new Path("file:///E:/hadoop/test/output"));

        System.exit(job.waitForCompletion(true) ? 0 : 1);
    }
}

好的,這裡所展示的是一種完全固定的寫法,但在實際操作過程中,需要特別注意的是,我們必須透過 Windows 環境來連線遠端的 Hadoop 叢集進行相關操作。這個過程中會遇到很多潛在的問題和坑,尤其是在配置、連線、許可權等方面。

接下來,我將逐一解析並解決這些常見的難題,希望能為大家提供一些實際的參考和指導,幫助大家更順利地完成操作。

疑難解答

目錄不存在

如果你並不是以本地 Windows 目錄為主,而是以遠端伺服器上的目錄為主進行操作,那麼你可能會採用類似以下的寫法:

FileInputFormat.addInputPath(job, new Path("/input"));
FileOutputFormat.setOutputPath(job, new Path("/output"));

那麼,在這種情況下,我們必須先建立與操作相關的輸入目錄(input),但需要特別注意的是,切勿提前建立輸出目錄(output),因為 Hadoop 在執行作業時會自動建立該目錄,如果該目錄已存在,會導致作業執行失敗。因此,只需要進入 Docker 環境並直接執行以下命令即可順利開始操作。

hdfs dfs -mkdir /input

image

當然,還有一種更簡單的方式,就是直接透過圖形介面在頁面上建立相關目錄或資源。具體操作可以參考以下步驟,如圖所示:

image

Permission denied

接下來,當你在執行 Job 任務時,系統會在最後一步嘗試建立輸出目錄(output)。然而,由於當前使用者並沒有足夠的許可權來進行此操作,因此會出現類似於以下的許可權錯誤提示:Permission denied: user=yu, access=WRITE, inode="/":root:supergroup:drwxr-xr-x。該錯誤意味著當前使用者(yu)試圖在根目錄下建立目錄或檔案,但由於該目錄的許可權設定為只有管理員(root)才能寫入,普通使用者無法進行寫操作,從而導致作業執行失敗。

所以你仍需要進入docker容器,執行以下命令:

hadoop fs -chmod 777 /

這樣基本上就可以順利完成任務了。接下來,你可以直接點選進入檢視 output 目錄下的檔案內容。不過需要注意的是,由於我們沒有配置具體的 IP 地址,因此在進行檔案下載時,你需要手動將檔案中的 IP 地址替換為你自己真實的 IP 地址,才能確保下載過程能夠順利進行併成功獲取所需的檔案。

image

報錯:org.apache.hadoop.io.nativeio.NativeIO$Windows

這種問題通常是由於缺少 hadoop.dll 檔案導致的。在 Windows 系統上執行 Hadoop 時,hadoop.dll 或者 winutils.exe 是必需的依賴檔案,因為它們提供了 Hadoop 在 Windows 上所需的原生代碼支援和執行環境。

為了確保順利執行,你需要下載對應版本的 hadoop.dll 或者 winutils.exe 檔案。已經為你準備好了多個 Hadoop 版本對應的這些檔案,所有的檔案都可以從以下連結下載:https://github.com/cdarlint/winutils

我們這裡只下載一個hadoop.dll,為了不重啟電腦,直接在程式碼裡面寫死:

static {
  try {
      System.load("E:\\hadoop.dll");//建議採用絕對地址,bin目錄下的hadoop.dll檔案路徑
  } catch (UnsatisfiedLinkError e) {
      System.err.println("Native code library failed to load.\n" + e);
      System.exit(1);
  }
}

如果仍然有問題,那就配置下windows下的wsl子系統:

使用Windows + R快捷鍵開啟「執行」對話方塊,執行OptionalFeatures開啟「Windows 功能」。

勾選「適用於 Linux 的 Windows 子系統」和「虛擬機器平臺」,然後點選「確定」。

image

最終效果

終於成功跑出結果了!在這個過程中,輸出的結果是按照預設的順序進行排序的,當然這個排序方式是可以根據需要進行自定義的。如果你對如何控制排序有興趣,實際上可以深入瞭解並調整排序機制。

image

總結

透過今天的分享,我們簡單地瞭解了大資料處理中一個經典的應用——WordCounter,並透過Hadoop框架的實踐,展示瞭如何使用MapReduce進行分散式計算。雖然表面上看,WordCounter是一個相對簡單的程式,但它卻揭示了大資料處理中的核心思想。

從安裝配置到編寫程式碼,我們一步步走過了Hadoop叢集的搭建過程,希望透過這篇文章,你能對大資料應用開發,特別是Hadoop框架下的MapReduce程式設計,獲得一些啟發和幫助。大資料的世界龐大而複雜,但每一次小小的實踐,都會帶你離真正掌握這門技術更近一步。


我是努力的小雨,一名 Java 服務端碼農,潛心研究著 AI 技術的奧秘。我熱愛技術交流與分享,對開源社群充滿熱情。同時也是一位騰訊雲創作之星、阿里雲專家博主、華為云云享專家、掘金優秀作者。

💡 我將不吝分享我在技術道路上的個人探索與經驗,希望能為你的學習與成長帶來一些啟發與幫助。

🌟 歡迎關注努力的小雨!🌟

相關文章