兩萬字長文向你解密大數據組件 Hadoop

大數據介紹

大數據這個概念一直都是如火如荼,那什麼是大數據呢?首先從名字來看,我們可以簡單地認爲數據量大,而數據量大也就意味着計算量大。這樣理解本身是沒有任何問題的,只不過這並不能很好地定義大數據。

而業界的一家權威機構,針對大數據做了描述,認爲大數據應該具備如下特徵:

1)數據量(Volume):數據量大,可以達到 TB、PB 甚至更高。而這種規模的數據,傳統的數據庫已經不好處理了,所以纔有了現在各種各樣的大數據框架。

2)多樣性(Variety):數據的類型繁多,比如簡單的數值、文本,地理位置、圖片、音頻、視頻等等,數據存在多樣性。而處理多樣性的數據所帶來的挑戰,也就比以前更高;

3)價值(Value):原始數據都是雜亂無章的,我們無法直接得到有效信息。而對數據進行清洗、分類、彙總等各種處理之後,我們能夠從中找到一些規律,將其變成商業價值,這一步也就是數據分析師要做的事情。所以分析數據的目的是從中找到一些規律、信息,將其變成價值。而數據的價值密度和數據的總量通常是不成正比的,你的數據量增加一倍,但是價值未必增加一倍;

4)速度(Velocity):對於大數據而言,我們不僅追求大量數據的處理,還希望它能有讓人滿意的速度。在早期的大數據處理框架中,它是做不到實時的,只能進行離線批處理。但是很多場景下,我們是需要立刻就能計算出結果的,所以大數據領域也就慢慢誕生了更多的實時性框架。

以上便是大數據的 4V 特徵。

而大數據的出現,也必然會帶來一些技術性的革新。

比如數據存儲,大數據可以到 PB、EB、ZB 級別,這種數據量單臺機器已經存儲不下了,所以數據存儲方面必然要發生改變。由原來的文件存儲變成了分佈式文件存儲,也就是將一個大文件切分成多個小塊存儲在多臺機器上,這樣就解決了數據存儲的問題。

不僅如此,由於散落在多個機器上,如果某一臺機器掛掉了,那麼就導致整個文件都無法讀取了。因此爲了容錯,在切分成多個小塊的時候,還會將每一個塊多拷貝兩份,散落在不同的機器上。這樣一臺機器掛掉了,還可以從其它機器上讀,這一點在後續介紹 HDFS 的時候會詳細說。

除了數據存儲,還有數據計算。因爲數據量大,必然也伴隨着計算量大,那該怎麼辦呢?類比數據存儲,存儲的時候可以存在多臺機器上,那麼計算的時候是不是也可以讓多臺機器一塊計算呢。所以將一個作業進行拆分,交給不同的機器進行計算,然後再將結果做一個歸併,這就是所謂的分佈式計算。

而大數據生態圈中最著名的 Hadoop,便是由分佈式文件系統 HDFS 和分佈式計算框架 MapReduce 組成的,這個我們後面會說。

除了存儲和計算,還有網絡問題。以前單機的時候,數據就在你的本地,計算也在本地,所以沒什麼好說的,直接讀取數據、計算就是了。但分佈式文件存儲和分佈式計算就不一樣了,因爲數據被切分成了多塊,有可能某臺機器上的計算任務所需要的數據在其它的機器上,這是很正常的。因此很多時候數據之間的傳輸是不可避免的,這對網絡也是一個挑戰,所以至少是萬兆網,千兆已經捉襟見肘了。特別是跨數據中心、跨地區,要求會更高。

大數據處理都分爲哪幾步

大數據在技術架構上所帶來的挑戰

  1. 對現有數據庫管理技術的挑戰:對於 PB、EB 級別的大數據而言,使用目前的關係型數據庫存儲是不現實的,儘管數據庫也可以部署集羣,但規模非常有限。而且由於數據量的原因,也很難使用現有的結構化查詢語言來分析現有的大數據;

  2. 經典數據庫技術並沒有考慮數據的多類別:大數據的 4V 特徵中有一個 V 是多類別,現在的數據庫沒有辦法很好地存儲一些特殊類型的數據;

  3. 實時性技術的挑戰:想從大量數據中提取相應的價值,花費的時間是不短的,如果使用現有的技術很難做到實時性;

  4. 網絡架構、數據中心、運維的挑戰:數據一直在高速增長,當涉及到大量數據的傳輸時,對數據中心、運維會是一個不小的挑戰;

如何對大數據進行存儲和分析呢

這是最直觀的一個問題,如果你都不能對大數據進行存儲、分析,那麼也就談不上所謂的商業價值了。而數據的存儲和分析,自然需要由專業的框架來完成,當然你也可以自己開發一個框架,但這顯然是非常困難的,我們也不會這麼做。因爲在大數據的存儲和計算中,存儲容量、讀寫速度、計算效率等等,這些都是需要考慮的。

而幸運的是,Google 的三駕馬車:GFS, MapReduce, BigTable 解決了這一點,但 Google 並沒有將它們開源,只是發表了相應的技術論文。而 Hadoop 便是 Hadoop 的作者基於 Google 的論文、使用 Java 語言開發的。我們來介紹一下:

Hadoop 概述

下面我們就來認識一下 Hadoop,看看它的概念是什麼?核心組件有哪些?具有哪些優勢?發展史等等。

提到 Hadoop,有狹義上的 Hadoop,還有廣義上的 Hadoop。

下面我們說的 Hadoop,指的是狹義上的 Hadoop,也就是 Hadoop 本身。

先說一下 Hadoop 這個名字的由來,這個名字沒有任何的意義,它是作者的兒子給一個玩具大象起的名字,所以 Hadoop 官網的 logo 也是一個大象。另外這些大數據框架基本上都是 Apache 的頂級項目,它們的官網都是項目名. apache.org,比如 Hadoop 的官網就是 hadoop.apache.org。

那麼 Hadoop 是什麼呢?

Hadoop 是一個可靠的、可擴展的、分佈式的計算框架,它可以對大量的數據集進行並行處理。我們知道單臺機器的能力是不夠的,所以可以將多臺機器組成一個集羣,而集羣中每一臺機器都叫做一個節點,Hadoop 可以從單機(單節點)擴展到上千個節點,來對數據進行存儲和計算。如果存儲不夠了,怎麼辦?直接加機器就好了,節點的擴展非常容易。

另外最重要的是,Hadoop 可以部署在廉價的機器上,而不需要使用昂貴的小型機、或者刀片機等等。而且還提供了故障恢復、容錯等機制。

Hadoop 主要由以下幾個部分組成。

HDFS 和 MapReduce 我們一開始說過了,但是還有一個 YARN,它是用來對集羣資源進行管理、以及作業的調度的。還是舉之前的例子,如果一個作業所需要的數據不在當前節點上該怎麼辦?顯然有兩種做法:

而在大數據領域中是有說法的,移動數據不如移動計算,因爲數據的移動成本要比計算的移動成本高很多。所以這個時候就需要 YARN 了,當然 YARN 還用於資源的管理,給每個作業分配相應的資源等等。

Hadoop 的優勢

那麼 Hadoop 的優勢都有哪些呢?

Hadoop 生態圈

我們說 Hadoop 分爲狹義 Hadoop 和廣義 Hadoop。

狹義 Hadoop 指的是:一個適合大數據的分佈式存儲(HDFS)、分佈式計算(MapReduce)和資源調度(YARN)平臺,所以狹義 Hadoop 指的就是 Hadoop 框架本身。

廣義 Hadoop 指的是:Hadoop 生態系統,這是一個很龐大的概念,Hadoop 框架是其中最重要、也是最基礎的一個部分;生態系統中的每一個子系統只解決某一個特定的問題域(甚至可能很窄),不搞統一型的一個全能系統,而是小而精的多個小系統。

Hadoop 生態圈裏面的東西還是非常非常多的,囊括了大數據處理的方方面面,並且這是一個成熟的生態圈。像 Flume 做日誌採集、Sqoop 做數據的導入導出,還有調度系統以及分佈式協調服務等等。我們這裏學習的是 Hadoop,並且是狹義上的 Hadoop。

Hadoop 常用的發行版

首先是社區版,也就是 Apache 版本,直接去官網就可以下載。它的特點是純開源,可以直接在源碼的基礎上進行二次開發;但是不同版本 / 框架之間的整合有時會比較麻煩,比如 jar 包的衝突等等。

然後是 CDH 版本,它是 cloudera 公司開發的 Hadoop,並且提供了 cloudera manager,可以通過頁面進行可視化操作,比如一鍵安裝各種框架、對框架升級等等,並且還幫你屏蔽了不同框架之間的 jar 包衝突。但是 CDH 不開源,並且與社區版本有點出入。但凡是使用 Hadoop 的公司,有百分之 60~70 使用的都是 CDH,包括筆者以前所在的公司,其信息中心就是採購的 CDH。

最後是 HDP 版本,由 Hortonworkds 公司提供,原裝 Hadoop、支持 tez;但是企業級安全不開源。

如果是在學習的時候,使用哪種發行版都無所謂,但是在生產環境中最好使用 CDH 或者 HDP,這裏我們就使用社區版了。

安裝 Hadoop

下面我們就開始安裝 Hadoop 了,你可以使用虛擬機,或者雲服務器等等。我這裏使用的騰訊雲服務器,操作系統是 CentOS7。

另外 Hadoop 的運行模式有三種:

我們後續使用的是僞分佈式,但是在學習的時候,和使用真正意義上的分佈式之間是沒有太大區別的。

在學習的時候,不建議上來就搭建完全分佈式的 Hadoop 集羣,Hadoop 還沒搞明白就開始搭建分佈式集羣的話,真的是很容易造成從入門到放棄。一開始完全可以搭建一個單機版的僞集羣,因爲在學習的時候和真正的集羣沒有太大區別。

下面就開始安裝 Hadoop 了,不過在安裝它之前,我們還需要安裝 jdk。因爲 Hadoop 是 Java 語言編寫的,所以我們需要安裝 jdk,至於版本請保證在 1.8 以上。

這裏我的軟件都安裝在 /opt 目錄下,其中 jdk 已經安裝好了,至於它的安裝也很簡單,這裏就不介紹了。

下面安裝 Hadoop,我們直接去官網下載即可,這裏我下載的版本是 2.8.4。下載成功之後,直接解壓即可。

解壓之後的文件目錄如上圖所示,我們來介紹幾個重要的目錄。

目錄裏面的文件我們在用到的時候會說,下面我們來介紹 Hadoop 最重要的三大組件,首先是 HDFS。

分佈式文件系統 HDFS

什麼是 HDFS,它和普通的文件系統之間有什麼區別呢?

HDFS(Hadoop Distributed File System)是一個分佈式文件系統,用於存儲文件,通過目錄樹來定位文件;其次它是分佈式的,可以橫跨 N 個機器,由多個節點聯合起來實現其功能,集羣中的節點有各自的角色。

HDFS 產生背景

隨着數據量越來越大,在一臺機器上無法存下所有的數據,那麼就分配到更多的機器上,但是這樣不方便管理和維護。因此迫切需要一種系統來管理多臺機器上的文件,這就是分佈式文件管理系統,HDFS 只是其中的一種。

HDFS 使用場景

適合一次寫入,多次讀出的場景,且不支持文件的修改。適合用來做數據分析,並不適合做網盤應用。很好理解,HDFS 的定位就決定了它不適合像關係型數據庫那樣,可以任意修改數據。而且寫入數據,一定是大批量一次性寫,至於原因後面會解釋。

HDFS 的設計目標

1)解決硬件故障

HDFS 設計出來就是爲了存儲大量數據,因爲 HDFS 可以跨上千個節點,每個節點只存儲數據的一部分。但是這樣的話,其中任何一個節點故障,都會導致無法讀取整個文件,所以 HDFS 還要具備相應的容錯性。

首先 HDFS 在接收到一個大文件之後,會將其切分成塊(block),每一個 block 的大小默認是 128M。然後將每一個塊再拷貝兩份(所以總共默認是 3 份,也就是三副本),分別散落在不同的節點上,這樣一臺節點掛了(或者磁盤壞掉了),還可以從其它節點上找。我們畫一張圖:

這裏我們以 500M 的文件爲例,顯然它頂多是個小型數據,這裏只是舉個例子。

因此以上便屬於硬件故障,HDFS 要具備快速檢測錯誤的能力,並且能從錯誤中自動恢復,這纔是 HDFS 設計架構的核心目標。不能說一臺機器癱了或者存儲壞掉了,整個 HDFS 集羣就不能工作了。

2)流式數據訪問

運行在 HDFS 上的應用程序會以流的方式訪問數據集,這和普通的文件系統不同,因爲 HDFS 更多地被應用於批處理,而不是和用戶之間進行交互式訪問。所以 HDFS 關注的是高吞吐量,而不是數據訪問的低延遲,從這一點我們也能看出 HDFS 不適合做實時處理。

3)大型數據集

HDFS 可以管理大型數據集,TB、PB 甚至更高的級別。而 HDFS 不怕文件大,就怕文件小,這是面試的時候經常被問到的點,這背後更深層次的含義我們後面會說。總之一個 HDFS 集羣可以有幾千個節點,可以管理非常大的文件。

4)移動計算比移動數據更划算

這一點我們之前說過了,假設計算任務在 A 節點上,但是數據在 B 節點上。這個時候要麼把計算調度到 B 機器上,要麼把數據傳輸到 A 機器上。而移動計算的成本比移動數據的成本低很多,所以我們應該將計算調度到 B 機器上,HDFS 也支持這一點。

HDFS 的架構

HDFS 有兩個核心概念:NameNode、DataNodes,並且是一個主從架構。而 NameNode 就是 master,DataNodes 是 slave。注意:DataNodes 的結尾有一個 s,意味着會有多個 DataNode;但是 NameNode 後面沒有 s,因此它只有一個,事實上也確實如此。

HDFS 集羣由一個 NameNode 和多個 DataNode 組成,NameNode 負責管理文件系統的 namespace(名字空間,比如文件的目錄結構便是 namespace 的一部分),並提供給客戶端固定的訪問途徑。因爲客戶端需要讀寫數據,首先經過的就是 NameNode。

除了 NameNode,還有多個 DataNode,DataNode 就是用來存儲數據的一個進程,通常一個節點對應一個 DataNode。所以一個 HDFS 集羣可以由多個節點組成,其中一個節點負責啓動 NameNode 進程,剩餘的節點負責啓動 DataNode 進程。在內部,一個文件會被拆分多個塊,並默認以三副本存儲,然後默認存儲在多個 DataNode 對應的節點上。

注意:我們說客戶端創建、刪除文件,都是通過 NameNode,它負責執行文件系統的類似 CRUD 的操作。並且最重要的是,它還決定了 block 和 DataNode 之間的映射關係。

假設由一個 150M 的文件,存儲的時候會被切分成兩個塊,那麼問題來了:block1 存在哪個 DataNode 中呢,block2 又存在哪個 DataNode 中呢?其實這一點不用擔心,因爲我們說 NameNode 會記錄每個 block 和 DataNode 的映射關係,這些便是數據的元信息。比如拆分成幾個塊,每個塊都散落在哪些 DataNode 上。

所以客戶端獲取數據的時候,一定要經過 NameNode,不然這些元信息你拿不到。因此在存儲的時候,NameNode 會記錄這些元信息,當我們獲取的時候 NameNode 會根據元信息找到對應的 DataNode,而這個過程對於用戶來說是不可見的。

所以你可以簡單地理解爲:HDFS 就是一個拆文件、合文件的一個過程。存儲的時候拆開,獲取的時候合併。

至於數據本身的讀寫,則是通過 DataNode 來完成的,因爲數據存在 DataNode 對應的節點上。

所以我們可以再來總結一遍。

NameNode:就是 master,它是一個主管、管理者

DataNode:就是 slave,NameNode 下達命令,DataNode 執行實際的操作

client:就是客戶端

還有一個 Secondary NameNode:它不是 NameNode 的替補,當 NameNode 掛掉時,並不能馬上替換 NameNode 並提供服務,它的作用如下

我們來看幾幅漫畫,寫得非常好,個人給翻譯了一遍。

從這裏我們便了解了 HDFS 的整體架構,用一張圖總結就是:

那麼下面再來總結一下 HDFS 的優缺點。

優點:

1)高容錯性:數據自動保存多個副本,它通過增加副本的形式,提高容錯性;即使某一個副本丟失,也可以自動恢復;

2)適合處理大數據:數據規模大,能夠處理 TB、甚至 PB 級別的數據;文件規模大,能夠處理百萬規模以上的文件,數量相當之大;

3)可構建在廉價的機器之上,通過多副本機制,提高可靠性;

缺點

1)不適合低延時數據訪問,如果你想做到毫秒級存儲,別想了,做不到的;

2)無法高效地對大量小文件進行存儲,存一個 1G 的數據比存 10 個 100MB 的數據要高效很多;至於原因和我們之前說的 HDFS 不怕文件大,就怕文件小是類似的。

NameNode 一般是唯一的,這就意味着空間是有限的。而 NameNode 要記錄文件的元數據,不管是 1KB,還是 1GB,都需要 150 字節的空間進行記錄。如果全是小文件的話,那是不是很耗費 NameNode 所在機器的空間呢?

而且文件在讀取之前需要先確定它位於哪些 DataNode 上,相當於尋址,而文件過小的話,那麼尋址時間反而會超過讀取時間,這違反了 HDFS 的設計目標。

3)不支持併發寫入和文件的隨機修改,一個文件只能有一個寫,不允許多個線程同時寫。僅支持數據的 append,不支持文件的隨機修改。

HDFS 塊大小的設置

HDFS 中的文件在物理上是分塊存儲(block),塊的大小可以通過配置參數(df.blocksize)指定,默認大小是 128M,老版本是 64M。

那麼這個塊大小是怎麼來的呢?首先我們說讀取一個塊的時候,需要先經過 NameNode 確定該塊在哪些節點上,而這是需要時間的,一般稱爲尋址時間。而當尋址時間爲塊讀取時間的 1% 時,是最佳狀態。

一般尋址時間爲 10ms,那麼塊讀取時間爲 1s,而目前磁盤的傳輸速率普遍爲 100M/s,因此塊大小大概 100M 左右。根據實際情況可能略有不同,但大致是 100 多 M 左右。

思考:爲什麼塊不能設置太小,也不能設置太大?

總結:HDFS 塊的大小設置主要取決於磁盤的傳輸速率。

修改配置文件,啓動僞集羣

下面來啓動集羣,但需要先修改幾個配置文件。我們在介紹 Hadoop 目錄結構的時候說過,配置文件都在安裝目錄的 etc/hadoop 目錄中。

修改 hadoop-env.sh

裏面配置好 JAVA 的安裝路徑。

修改 core-site.xml

修改 hdfs-site.xml

修改 slaves

將集羣當中的 DataNode 節點都寫在裏面,而當前只有一個節點,它既是 NameNode 又是 DataNode,所以寫一個 localhost 進去就行。但該文件裏面默認就是 localhost,因此這個文件當前不需要修改。之所以提一嘴,是想提示在搭建集羣的時候,別忘記將 DataNode 所在節點都寫在裏面。

目前只需要修改以上幾個文件,然後啓動集羣(僞)。但在此之前需要先格式化 NameNode,注意:只需要在第一次啓動時格式化。

# 如果你配置了環境變量,可以直接輸入 hdfs
bin/hdfs namenode -format

如果格式化成功,那麼 hadoop.tmp.dir 參數指定的目錄會自動創建。

然後啓動 Hadoop 集羣:

# 如果把 sbin 目錄也配置了環境變量
# 那麼 sbin/ 也不需要加,關閉集羣則是 stop-dfs.sh
sbin/start-dfs.sh

執行該命令的時候會依次啓動 NameNode, DataNode, SecondaryNameNode,但會要求你三次輸入當前用戶的密碼,並且每次啓動、關閉的時候都是如此。在多節點通信的時候,顯然不能這樣,雖然目前是單節點,但每次輸入密碼也很麻煩,因此建議配置免密碼登錄。

啓動完畢,並且啓動時的日誌信息也記錄在了文件裏,但如果你查看的話會發現並沒有什麼信息。這是因爲顯示的文件不對,你只需要把結尾的 out 改成 log 就可以查看日誌信息了,這個應該是 Hadoop 內部的問題,不過無關緊要。

那麼到底有沒有啓動成功呢?我們輸入 jps 查看一下。

如果出現了黃色方框裏的內容,那麼就證明啓動成功了。另外這是三個獨立的進程,可能出現有的啓動成功,有的啓動失敗,比如你發現顯示的進程中沒有 NameNode,那就證明 NameNode 啓動失敗了,你就需要去對應的日誌文件中查看原因。

PS:如果你發現 NameNode 真的啓動失敗了,那麼很有可能是 9000 端口衝突了。

只有當上面三個進程同時出現,纔算啓動成功。然後我們在瀏覽器輸入:ip:50070,可以查看 webUI 界面。

頁面信息大致如上,在 Summary 中有很多關於節點的信息,可以看一下。另外導航欄中的最後一個 utilities,點擊的話會出現一個下拉菜單,裏面有一個 Browse the file system。點擊的話,可以查看整個文件的目錄結構,後面會說。

最後補充一點,我們不可以重複格式化 NameNode。因爲該操作會產生新的集羣 id,導致 NameNode 和 DataNode 的集羣 id 不一致,集羣找不到以往的數據。所以格式化 NameNode 的時候,務必要先刪除 data 數據和 log 日誌,然後再格式化。因爲兩者需要有一個共同的 id,這樣才能交互。

hdfs shell 命令

HDFS 的 shell 命令和 Linux 是非常類似的,比如查看某個目錄下的文件,Linux 是 ls,那麼 hdfs shell 就是 hdfs dfs -ls,再比如查看文件內容是 hdfs dfs -cat filename。兩者非常相似,只不過在 hdfs shell 中需要加上一個橫槓。

另外 hdfs dfs 還可以寫成 hadoop fs,對於 shell 操作來說兩者區別不大,下面就來介紹一些常用的命令。

hdfs dfs -help 某個命令

顯然這是查看某個命令使用方法的命令,比如查看 cat 的使用方法。

hdfs dfs -ls 目錄路徑

查看某個目錄有哪些文件,加上 -R 表示遞歸顯示。由於當前沒有文件,所以不演示了。

hdfs dfs -mkdir 目錄

在 hdfs 上面創建目錄,加上 -p 表示遞歸創建,和 Linux 是一樣的。

hdfs dfs -moveFromLocal **本地路徑 **hdfs 路徑

將本地文件或目錄移動到 hdfs 上面,注意是移動,移完之後本地就沒了。

hdfs dfs -cat 文件

查看一個文件的內容。

hdfs dfs -appendToFile **追加的文件 **追加到哪個文件

將一個文件的內容追加到另一個文件裏面去。

我們將本地 other.py 裏的內容追加到了 HDFS 上的 /code.py 文件中。

hdfs dfs [-chgrp、-chmod、-chown]

更改組、更改權限、更改所有者,這個和 Linux 中用法一樣。

hdfs dfs -copyFromLocal 本地路徑 hdfs 路徑

將文件從本地拷貝到 HDFS 上面去,這個和剛纔的 moveFromLocal 就類似於 Linux 中的 cp 和 mv。

**hdfs dfs -copyToLocal **hdfs 路徑 本地路徑

將 HDFS 上的文件拷貝到本地,此時是 HDFS 路徑在前、本地路徑在後。

hdfs dfs -cp 源 hdfs 路徑 目的 hdfs 路徑

上面的拷貝都是針對本地路徑和 HDFS 路徑,而 -cp 則是在兩個 HDFS 路徑之間拷貝。

hdfs dfs -mv源 hdfs 路徑** 目的 hdfs 路徑**

和 -cp 用法一樣,不過 -cp 是拷貝,-mv 是移動。

**hdfs dfs -get **hdfs 路徑 本地路徑

等同於 copyToLocal。

hdfs dfs -put 本地路徑 hdfs 路徑

等同於 copyFromLocal。

**hdfs dfs -getmerge **hdfs 路徑 (通配符) 本地路徑

將 hdfs 上面的多個文件合併下載到本地。

hdfs dfs -tail 文件名

顯示文件的結尾,類似於 Linux 的 tail。

hdfs dfs -rm 文件

刪除文件,如果是文件夾需要加上 -r。

hdfs dfs -rmdir 空目錄

刪除一個空目錄,不常用,一般使用 -rm。

hdfs dfs -du 目錄

統計目錄的大小信息:

hdfs dfs -setrep 數值 文件

設置文件的副本數量,比如 hdfs dfs -setrep 5 /file.txt,表示將 file.txt 的副本設置成 5。

使用 webUI 觀察 HDFS 存儲

我們上傳一個稍微大一點的文件進去吧,就把 jdk 壓縮包上傳到 HDFS 上。

這裏我們上傳到了 HDFS 的根目錄,通過 webUI 來查看一下。

我們發現文件已經在裏面了,並且這個文件的大小是 180.06M,顯然會被切成兩個塊,每個塊的副本系數是 1,因爲我們設置的是 1。然後點擊一下該文件:

可以看到拆分之後的兩個塊的信息,分別是 block0 和 block1,而且每個塊都有一個 ID,是依次增大的,並且兩個 Size 加起來也一定是 jdk 安裝包的大小。然後它存在什麼地方呢?還記得之前配置的 hadoop.tmp.dir 嗎?

最終我們進入到 subdir0 這個目錄,層級非常的多,然後我們看看裏面都包含了哪些內容。

箭頭所指的就是 jdk 壓縮包的兩個塊,並且文件名的結尾就是對應的塊 id,而且大小也和 webUI 上顯示的一樣。然後 jdk 壓縮包被切分成了兩個塊,而現在這兩個塊我們都找到了,如果將它們合併在一起話,那麼 tar 命令能不能正常解壓呢?我們來試試。

現在你還覺得 HDFS 神奇嗎?所以就是之前說的,只是將文件切分成塊,然後散落在不同節點的本地存儲中。查找的時候,會去 NameNode 獲取元信息,找到相應的塊再組合起來,就這麼簡單。因此 HDFS 還是需要依賴本地進行存儲的,只不過內部的 NameNode 會幫助我們對塊進行管理,但本質上就是文件的拆分與合併的過程。

Python 連接 HDFS

使用 HDFS SHELL 只是用來臨時做測試用,工作中肯定是通過代碼來操作的,那麼下面來看看如何使用 Python 連接 HDFS,並進行相關操作。

這裏爲什麼要用 Python 呢?因爲筆者是 Python 方向的,Java、Scala 一概不懂。

首先 Python 若想操作 HDFS,需要下載一個第三方庫,也叫 hdfs,直接 pip install hdfs 即可。

from pprint import pprint
import hdfs

# 導入相關模塊,輸入 http://ip:50070,創建客戶端
client = hdfs.Client("http://82.157.146.194:50070")

client.list:查看指定目錄的內容

pprint(client.list("/"))
"""
['code.py', 'code1.py', 'girls', 
 'jdk-8u221-linux-x64.tar.gz']
"""

# status 表示是否顯示文件的相關屬性, 默認爲 False
# 指定爲 True 的話,會同時返回文件相關屬性
# 返回的數據格式爲:[("文件名", {相關屬性}),  ...]
pprint(client.list("/"status=True))
"""
[('code.py',
  {'accessTime': 1667875155444,
   'blockSize': 134217728,
   'childrenNum': 0,
   'fileId': 16397,
   'group': 'supergroup',
   'length': 32,
   'modificationTime': 1667875310739,
   'owner': 'root',
   'pathSuffix': 'code.py',
   'permission': '644',
   'replication': 1,
   'storagePolicy': 0,
   'type': 'FILE'}),
   
  ...,
]  
"""

client.status:獲取指定路徑的狀態信息

pprint(client.status("/"))
"""
{'accessTime': 0,
 'blockSize': 0,
 'childrenNum': 3,
 'fileId': 16385,
 'group': 'supergroup',
 'length': 0,
 'modificationTime': 1607194594505,
 'owner': 'root',
 'pathSuffix': '',
 'permission': '755',
 'replication': 0,
 'storagePolicy': 0,
 'type': 'DIRECTORY'}
"""
# 裏面還有一個 strict=True,表示嚴格模式
# 如果改爲 False,那麼如果輸入的路徑不存在就返回 None
# 爲 True 的話,路徑不存在,報錯

client.makedirs:創建目錄

# 會自動遞歸創建,如果想創建的時候給目錄賦予權限
# 可以使用 permission 參數,默認爲 None
client.makedirs("/a/b/c"permission=777)
pprint(client.list("/a"))  # ['b']
pprint(client.list("/a/b"))  # ['c']

如果不出意外的話,你在執行的時候應該會報錯:hdfs.util.HdfsError: Permission denied:。因爲默認情況下我們只能查看 HDFS 上的數據,但是不能寫入、修改、刪除。

如果你創建目錄的話,就會拋出這個異常。至於解決辦法也很簡單,我們修改 hdfs-site.xml,在裏面添加如下內容,然後重啓 Hadoop 集羣。

<property>
  <name>dfs.permissions</name>
  <value>false</value>
</property>

但如果你重啓集羣之後立即執行的話,那麼還是會報錯,提示:Name node is in safe mode。因爲 Hadoop 集羣啓動之後會進入短暫的安全模式,你需要等待一會纔可以創建,關於安全模式一會單獨說。

**client.write、**client.read:往文件裏面寫內容、讀內容

with client.write("/這是一個不存在的文件.txt") as writer:
    # 需要傳入字節
    writer.write(
        bytes("this file not exists"encoding="utf-8")
    )

with client.read("/這是一個不存在的文件.txt") as reader:
    # 讀取出來也是字節類型
    print(reader.read())  # b'this file not exists'

如果你在執行的時候出現了連接錯誤,那麼就在 hosts 文件中增加服務器 IP 到主機名的映射。

然後注意這裏的 write 方法,如果不指定額外的參數,那麼要求文件不能存在,否則會報錯,提示文件已經存在。如果要對已存在的文件進行操作,那麼需要顯式的指定參數:overwrite 或者 append,也就是重寫或追加,相當於 Python 文件操作的 w 模式和 r 模式。

注意:overwrite 和 append 不能同時出現,否則報錯。

client.content:查看目錄的彙總情況

比如:當前目錄下有多少個子目錄、多少文件等等。

pprint(client.content("/"strict=True))
"""
{'directoryCount': 7,
 'fileCount': 3,
 'length': 195094805,
 'quota': 9223372036854775807,
 'spaceConsumed': 195094805,
 'spaceQuota': -1,
 'typeQuota': {}}
"""

client.set_owner:設置所有者

client.set_permission:設置權限

client.set_replication:設置副本系數

client.set_times:設置時間

"""
def set_owner(self, hdfs_path, owner=None, group=None):
def set_permission(self, hdfs_path, permission):
def set_replication(self, hdfs_path, replication):
def set_times(self, hdfs_path, access_time=None, modification_time=None):
"""

client.resolve: 將帶有符號的路徑,轉換成絕對、規範化路徑

# 當然並不要求路徑真實存在
print(
    client.resolve("/古明地覺/古明地覺/..")
)  # /古明地覺

client.walk:遞歸遍歷目錄

# 遞歸遍歷文件,類似於 os.walk,會返回一個生成器,可以進行迭代
# 每一次迭代的內容都是一個三元組,內容如下:
# ("路徑", ["目錄文件", ...], ["文本文件", ...])
for file in client.walk("/"):
    print(file)
"""
('/', ['a', 'girls'], ['code.py', 'code1.py', 'jdk-8u221-li...'])
('/a', ['b'], [])
('/a/b', ['c'], [])
('/a/b/c', [], [])
('/girls', ['koishi', 'satori'], [])
('/girls/koishi', [], [])
('/girls/satori', [], [])
"""

client.upload:上傳文件

print("1.c" in client.list("/"))  # False
client.upload(hdfs_path="/"local_path="1.c")
print("1.c" in client.list("/"))  # True

client.download:下載文件

client.download(hdfs_path="/code.py"local_path="code.py")
print(
    open("code.py""r"encoding="utf-8").read()
)

client.checksum:獲取文件的校驗和

# 獲取文件的校驗和
pprint(client.checksum("/code.py"))

client.delete: 刪除文件或目錄

# recursive 表示是否遞歸刪除,默認爲 False
try:
    client.delete("/girls")
except Exception as e:
    print(e)  # `/girls is non empty': Directory is not empty

client.delete("/girls"recursive=True)

當然還有一些其它操作,比如重命名:client.rename 等等,這裏就不說了。

HDFS 元數據管理

再來聊聊 HDFS 的元數據管理,首先元數據包含:文件名、副本系數(或者說副本因子)、塊 id、以及散落在哪個 DataNode 上。

因此 HDFS 的元數據也就是整個 HDFS 文件系統的層級結構,以及每個文件的 block 信息。

而這些元信息存在於 hadoop.tmp.dir 中,我們來看一下。

然後 current 目錄裏面存放了很多的文件:

首先 NameNode 啓動之後,這些元數據會在內存中,因此爲了防止重啓之後丟失,肯定要定期寫入磁盤。圖中的 fs_image 文件便是寫入之後的結果,但寫入是定期寫入的,假設每隔半小時寫入一次。

如果 NameNode 宕掉,那麼還是會丟失半小時的數據,這也是我們所無法忍受的。因此就像 Redis 一樣,在緩存數據的同時還將執行的命令操作緩存起來,記錄在 edits 文件中。edits 文件是時刻記錄的,因爲記錄的只是命令而已。

然後根據 edits 中的命令,和 fsimage 綜合起來,生成一個新的 fsimage,再把老的 fsimage 給替換掉,這樣就確保了元數據的不丟失。但要注意,這一步並不是交給 NameNode 來做的,因爲它還要處理來自客戶端的請求,如果合併的工作再交給它,那麼 NameNode 的壓力就太大了。

那麼給誰做呢?沒錯,正是 SecondaryNameNode。所以 NameNode 和它並不是所謂的主備關係,後者相當於前者的小弟,主要是幫大哥減輕壓力的。

以上這個過程,叫做 HDFS 的 CheckPoint。

HDFS 的安全模式

在 HDFS 剛啓動的時候,會進入到一種特殊的模式:安全模式,這是 Hadoop 的一種自我保護機制,用於保證集羣中數據塊的安全性。比如:副本系數是 3,但是某個塊的數量是 2,這個時候就會再拷貝一份,滿足副本系數。

如果 HDFS 是在安全模式下的話,那麼客戶端不能進行任何文件修改的操作,比如:上傳文件,刪除文件,重命名,創建目錄等操作。當然正常情況下,安全模式會運行一段時間自動退出的,只需要我們稍等一會就行了,到底等多長時間呢,我們可以通過 50070 端口查看安全模式退出的剩餘時間。

我這裏的安全模式是關閉的,如果你當前處於安全模式的話,那麼頁面信息會提示你還需多長時間才能結束安全模式。在安全模式下,雖然不能進行修改文件的操作,但是可以瀏覽目錄結構、查看文件內容。

此外我們可以通過命令行,顯式地控制安全模式的進入、查看、以及退出等等。

安全模式是 Hadoop 的一種保護機制,在啓動時,最好是等待集羣自動退出該模式,然後進行文件操作。有的小夥伴在和 Hive, Spark 整合的時候,剛一啓動 HDFS,就開始啓動 Hive, Spark 寫數據,結果發現寫的時候報錯了,此時應該先等待集羣退出安全模式。

分佈式計算框架 MapReduce

下面來介紹一下 MapReduce,源自於 Google 在 2004 年 12 月發表的 MapReduce 技術論文,它的思想邏輯很簡單,完全可以看成是 Python 中 map 和 reduce 的組合。

另外 B 站上有一位 MIT 大佬的公開課,介紹分佈式系統的,裏面對 Google 設計 HDFS 和 MapReduce 的流程進行了闡述。強烈推薦去看一下,或者也可以讀一讀原版論文。

MapReduce 實際上就是 map 和 reduce 兩者的組合:

比如我們要做一個詞頻統計:

MapReduce 就是將一個作業拆分成多份,在多個機器上並行處理,然後再將處理之後的結果彙總在一起。因此它非常適合海量數據的離線處理,不怕你的數據量大,PB, EB 都無所謂,只要你的節點數量足夠即可。並且內部的細節我們不需要關心,只需按照它提供的 API 進行編程,我們便能得到結果。

並且當你的計算資源不足時,你可以通過簡單的增加機器來擴展計算能力。並且一個節點掛了,它可以把上面的計算任務轉移到另一個節點上運行,不至於這個任務完全失敗。而且這個過程不需要人工參與,是由 Hadoop 內部完成的。

但是對比 Spark, Flink 等框架,MapReduce 其實是比較雞肋的。官方說使用 MapReduce 易開發、易運行,只是相對於我們自己實現而言,而使用 Spark, Flink 進行處理要比使用 MapReduce 清蒸的多。

因爲 MapReduce 有以下幾個缺點:

1)不擅長實時計算,無法像傳統的關係型數據庫那樣,可以在毫秒級或者秒級內返回結果。

2)不擅長流式計算,流式計算輸入的數據是動態的,而 MapReduce 的數據必須是靜態的,不能動態變化。這是因爲 MR 自身的設計特點決定了數據源必須是靜態的。

3)不擅長 DAG(有向無環圖)計算,多個程序之間存在依賴,後一個應用程序的輸入依賴於上一個程序的輸出。在這種情況,MR 不是不能做,而是使用後,每個 MR 作業的輸出結果都會寫入到磁盤,然後再從磁盤中讀取,進行下一個操作。顯然這樣做會造成大量的磁盤 IO,導致性能非常的低下。

關於 MR 的實際操作就不說了,事實上現在基本都不用 MR 編程了,我們有 Spark,它的出現解決了 MR 的效率低下問題。而且還有 Hive,Hive 也是我們後面要學習的一個重要的大數據組件,很多公司都在用,它是將 MR 進行了一個封裝,可以讓我們通過寫 SQL 的方式來操作 HDFS 上的數據,這樣就方便多了。

但既然是寫 SQL,那麼肯定要像傳統關係型數據庫一樣,有表名、字段名、字段信息等等。沒錯,這些信息在 Hive 中也叫作元信息、或者元數據,它一般存在 MySQL 等關係型數據庫中,實際的數據依舊是存儲在 HDFS 上,相當於幫你做了一層映射關係。

關於 Hive 我們介紹的時候再說,總之它也是一個非常重要的大數據組件,畢竟使用 SQL 進行編程肯定要比 MR 簡單的多,而 Hive 也可以使用 Python 進行連接、執行操作。

資源調度框架 YARN

下面我們來介紹一下資源調度框架 YARN,但是在介紹它之前我們需要先了解爲什麼會有 YARN,一項技術的誕生必然是有其原因的。

首先我們用的都是 Hadoop 2.x 或 3.x,但在 1.x 的時候 MR 的架構是怎樣的呢?當然不管是哪個版本,MR 都是 master/slave 架構。

HDFS 是一個 NameNode 帶多個 DataNode,形成主從架構,在 1.x 的 MR 中也是如此,一個 JobTracker 帶多個 TaskTracker。JobTracker 用來跟蹤一個作業,而我們說一個作業可以被拆分成多個任務,每個任務對應一個 TaskTracker。

當然這裏的拆分並不是將計算本身拆分,而是將文件拆分,舉個例子:我們有 100G 的文件,分別散落在 10 個節點上。我們要對這 100G 的文件執行兩次 Map、一次 Reduce,那麼結果就是每個節點分別對 10G 的數據執行兩次 Map、一次 Reduce。

另外 TaskTracker 可以和 JobTracker 進行通信,並需要告訴 JobTracker 自己是否存活。所以客戶端 Client 提交作業是先提交到 JobTracker 上面,然後再由 TaskScheduler(任務調度器)將任務調度到 TaskTracker 上運行,比如:MapTask, ReduceTask。

此外 TaskTracker 會定期向 JobTracker 會報節點的健康狀況、任務的執行狀況,以及資源的使用情況等等。

但這個架構是存在問題的,因爲只有一個 JobTracker,它要跟蹤所有的作業。如果 JobTracker 掛掉了怎麼辦?要是掛掉了,那麼客戶端的所有作業都無法提交到集羣上運行了。

此外 JobTracker 要負責和 Client 進行通信,還要和 TaskTracker 進行通信,因此它的壓力會非常大。在後續集羣的擴展時,JobTracker 很容易成爲瓶頸。此外最關鍵的一點,在 Hadoop1.x 的時候,JobTracker 僅僅只能支持 MapReduce 作業,想提交 Spark 作業是不可能的。

並且這種集羣的資源利用率也很低,比如我們有 Hadoop 集羣,Saprk 集羣,不同的集羣進行不同的資源分配。有可能 Hadoop 集羣處於空閒狀態,Spark 集羣處於缺資源狀態,導致它們沒辦法充分利用集羣的資源。

而解決這一點的辦法就是,所有的計算框架都運行在一個集羣中,共享一個集羣的資源,做到按需分配。而想實現這一點,首先要滿足支持不同種類的作業,所以 YARN 便誕生了。

什麼是 YARN

YARN(Yet Another Resource Manager,另一種資源管理器),是一個通用的資源管理系統,爲上層應用提供統一的資源管理和調度,所以 YARN 支持不同種類作業的調度。在介紹 Hadoop 生態圈的時候,我們貼了一張圖,在 HDFS 之上的就是 YARN。而在 YARN 之上可以運行各種作業,像 MapReduce 作業、Spark 作業等等都可以,只需要提交到 YARN 上面就可以了

YARN 就類似於 1.x 裏面的 JobTracker,但是它內部包含了兩個部分:一個是資源管理,一個是作業調度或監控,這是兩個單獨的進程。而在 1.x 當中,都是通過 JobTracker 來完成的,所以它壓力大。

所以基於這種架構,會有一個全局的 Resource Manager,每一個應用程序都有一個 Application Master,比如:MapReduce 作業會有一個 MapReduce 對應的 AM,Spark 作業會有一個 Spark 對應的 AM。而一個應用程序可以是一個獨立 MapReduce 作業,也可以是多個作業組成的 DAG(有向無環圖,多個任務之間有依賴)。

我們說 Resource Manager 是全局的,但除了 RM 之外每個節點還有各自的 NodeManager。目前拋出的概念有點多,我們來慢慢介紹。

Resource Manager

Resource Manager

Application Master

Container

所以整體流程如下:

整個流程並沒有那麼複雜,並且在運行過程中 AM 是知道每一個 Task 的運行情況的,這便是 MR 在 YARN 上的執行流程。注意:不僅僅是 MapReduce,還有 Spark,它們除了 AM 不同之外,整體的執行流程是沒有任何區別的。

YARN 環境部署

Hadoop2.x 自帶 YARN,如果我們想啓動 YARN,還是要先修改配置文件。下面看看 YARN 的單節點部署。

修改 yarn-env.sh

和 hadoop-env.sh 一樣,配置 JAVA_HOME。

修改 yarn-site.xml

<!--reducer獲取數據的方式-->
<property>
 <name>yarn.nodemanager.aux-services</name>
 <value>mapreduce_shuffle</value>
</property>

<!--指定yarn的ResourceManager的地址-->
<property>
 <name>yarn.resourcemanager.hostname</name>
 <value>主機名</value>
</property>

修改 mapred-env.sh

老規矩,遇到 -env.sh 都是配 JAVA_HOME。

修改 mapred-site.xml

你會發現 hadoop 沒有提供這個文件,不過有一個 mapred-site.xml.template,我們可以拷貝一份。

<!--指定MR運行在yarn上-->
<property>
 <name>mapreduce.framework.name</name>
 <value>yarn</value>
</property>

修改完畢,下面啓動集羣。

我們看到多出了兩個進程,分別是 RM 和 AM,關於 YARN 我們還可以通過 webUI 的方式查看,端口是 8088。

裏面包含了很多關於節點的信息以及任務的信息,注意圖中的 Active Nodes,我們看到目前有一個節點存活。因此在運行任務的時候,可以多通過 webUI 的方式關注一下節點和任務的信息。

但如果你還想查看程序的歷史運行情況,那麼還需要額外配置一個參數,修改 mapred-site.xml。

<!--指定歷史服務端地址-->
<property>
 <name>mapreduce.jobhistory.address</name>
 <value>主機名:10020</value>
</property>

<!--歷史web端地址-->
<property>
 <name>mapreduce.jobhistory.webapp.address</name>
 <value>主機名:19888</value>
</property>

最後是提交作業到 YARN 上運行,但這裏我們就不說了,因爲還是那句話,現在基本上很少直接使用 MR 進行編程了。在後續學習 Spark 的時候,我們會學習如何將作業提交到 YARN 上運行,至於 MapReduce 我們瞭解一下它的概念即可。

而 YARN,它是一個資源管理器,我們需要掌握它的整體架構,在未來學習 Spark 的時候依舊需要了解 YARN。所以到時候我們再說如何將作業提交到 YARN 上執行吧。

小結

如果想走進大數據的大門,那麼 Hadoop 是必須要了解的。在瞭解完 Hadoop 之後,下一個目標就是 Hive,因爲使用 MapReduce 編程其實是很不方便的。而 Hive 則是可以讓我們像寫 SQL 一樣來進行 MapReduce 編程,並且很多公司都在使用 Hive 這個大數據組件,我們以後再介紹。

最後我這裏搭建的 Hadoop 集羣其實是個僞集羣,你也可以多找幾個節點搭建一個完全分佈式集羣。當然在工作中,這一塊應該是由專業的大數據團隊負責,我們只需要瞭解它的原理以及使用即可。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/IKgEaeNrDVMVdoqnSoWUiQ