每日最新頭條.有趣資訊

用大白話告訴你小白都能看懂的Hadoop架構原理

可以啦,讓我們繼續相互陪伴。

Hadoop 是目前大數據領域最主流的一套技術體系,包含了多種技術,例如 HDFS(分布式檔案系統),YARN(分布式資源調度系統),MapReduce(分布式計算系統)等等。

有些朋友可能聽說過 Hadoop,但是卻不太清楚它到底是個什麽東西,這篇文章就用大白話給各位闡述一下。

假如你現在公司裡的數據都是放在 MySQL 裡的,那麽就全部放在一台數據庫伺服器上,我們就假設這台伺服器的磁盤太空有 2T 吧,大家先看下面這張圖。

現在問題來了,你不停的往這台伺服器的 MySQL 裡放數據,結果數據量越來越大了,超過了 2T 的大小了,現在怎辦?

你說,我可以搞多台 MySQL 數據庫伺服器,分庫分表啊!每台伺服器放一部分數據不就得了。如上圖所示!

好,沒問題,那咱們搞 3 台數據庫伺服器,3 個 MySQL 實例,然後每台伺服器都可以 2T 的數據。

現在我問你一個問題,所謂的大數據是在幹什麽?我們來說一下大數據最初級的一個使用場景。

假設你有一個電商網站,要把這個電商網站裡所有的用戶在頁面和 App 上的點擊、購買、瀏覽的行為日誌都存放起來分析。

你現在把這些數據全都放在了 3 台 MySQL 伺服器上,數據量很大,但還是勉強可以放的下。

某天早上,你的 Boss 來了。要看一張報表,比如要看每天網站的 X 指標、Y 指標、Z 指標,等等,二三十個數據指標。

好了,兄弟,現在你嘗試去從那些點擊、購買、瀏覽的日誌裡,通過寫一個 SQL 來分析出那二三十個指標試試看?

我跟你打賭,你絕對會寫出來一個幾百行起步,甚至上千行的超級複雜大 SQL。這個 SQL,你覺得他能運行在分庫分表後的 3 台 MySQL 伺服器上麽?

如果你覺得可以的話,那你一定是不太了解 MySQL 分庫分表後有多坑,幾百行的大 SQL 跨庫 Join,各種複雜的計算,根本不現實。

所以說,大數據的存儲和計算壓根兒不是靠 MySQL 來搞的,因此 Hadoop、Spark 等大數據技術體系才應運而生。

本質上,Hadoop、Spark 等大數據技術,其實就是一系列的分布式系統。比如 Hadoop 中的 HDFS,就是大數據技術體系中的核心基石,負責分布式存儲數據,這是啥意思?別急,繼續往下看。

HDFS 全稱是 Hadoop Distributed File System,是 Hadoop 的分布式檔案系統。

它由很多機器組成,每台機器上運行一個 DataNode 進程,負責管理一部分數據。

然後有一台機器上運行了 NameNode 進程,NameNode 大致可以認為是負責管理整個 HDFS 集群的這麽一個進程,它裡面存儲了 HDFS 集群的所有元數據。

然後有很多台機器,每台機器存儲一部分數據!好,HDFS 現在可以很好的存儲和管理大量的數據了。

這時候你肯定會有疑問:MySQL 伺服器不也是這樣的嗎?你要是這樣想,那就大錯特錯了。

這個事情不是你想的那麽簡單的,HDFS 天然就是分布式的技術,所以你上傳大量數據,存儲數據,管理數據,天然就可以用 HDFS 來做。

如果你硬要基於 MySQL 分庫分表這個事兒,會痛苦很多倍,因為 MySQL 並不是設計為分布式系統架構的,它在分布式數據存儲這塊缺乏很多數據保障的機制。

好,你現在用 HDFS 分布式存儲了數據,接著不就是要分布式來計算這些數據了嗎?

對於分布式計算:

很多公司用 Hive 寫幾百行的大 SQL(底層基於 MapReduce)。

也有很多公司開始慢慢的用 Spark 寫幾百行的大 SQL(底層是 Spark Core 引擎)。

總之就是寫一個大 SQL,然後拆分為很多的計算任務,放到各個機器上去,每個計算任務就負責計算一小部分數據,這就是所謂的分布式計算。

這個,絕對比你針對分庫分表的 MySQL 來跑幾百行大 SQL 要靠譜的多。

對於上述所說的分布式存儲與分布式計算,老規矩,同樣給大家來一張圖,大夥兒跟著圖來仔細捋一下整個過程。

HDFS 的 NameNode 架構原理

好了,前奏鋪墊完之後,進入正題。本文主要就是討論一下 HDFS 集群中的 NameNode 的核心架構原理。

NameNode 有一個很核心的功能:管理整個 HDFS 集群的元數據,比如說檔案目錄樹、權限的設定、副本數的設定,等等。

下面就用最典型的檔案目錄樹的維護,來給大家舉例說明,我們看看下面的圖。現在有一個客戶端系統要上傳一個 1TB 的大檔案到 HDFS 集群裡。

此時它會先跟 NameNode 通信,說:大哥,我想創建一個新的檔案,它的名字叫“/usr/hive/warehouse/access_20180101.log”,大小是 1TB,你看行不?

然後 NameNode 就會在自己記憶體的檔案目錄樹裡,在指定的目錄下搞一個新的檔案對象,名字就是“access_20180101.log”。

這個檔案目錄樹不就是 HDFS 非常核心的一塊元數據,維護了 HDFS 這個分布式檔案系統中,有哪些目錄,有哪些檔案,對不對?

但是有個問題,這個檔案目錄樹是在 NameNode 的記憶體裡的啊!這可坑爹了,你把重要的元數據都放在記憶體裡,萬一 NameNode 不小心宕機了可怎整?元數據不就全部丟失了?

可你要是每次都頻繁的修改磁盤檔案裡的元數據,性能肯定是極低的啊!畢竟這是大量的磁盤隨機讀寫!

沒關係,我們來看看 HDFS 優雅的解決方案。每次記憶體裡改完了,寫一條 edits log,元數據修改的操作日誌存到磁盤檔案裡,不修改磁盤檔案內容,就是順序追加,這個性能就高多了。

每次 NameNode 重啟的時候,把 edits log 裡的操作日誌讀到記憶體裡回放一下,不就可以恢復元數據了?

大家順著上面的文字,把整個過程,用下面這張圖跟著走一遍:

但是問題又來了,那 edits log 如果越來越大的話,豈不是每次重啟都會很慢?因為要讀取大量的 edits log 回放恢復元數據!

所以 HDFS 說,我可以這樣子啊,我引入一個新的磁盤檔案叫做 fsimage,然後呢,再引入一個 JournalNodes 集群,以及一個 Standby NameNode(備節點)。

每次 Active NameNode(主節點)修改一次元數據都會生成一條 edits log,除了寫入本地磁盤檔案,還會寫入 JournalNodes 集群。

然後 Standby NameNode 就可以從 JournalNodes 集群拉取 edits log,應用到自己記憶體的檔案目錄樹裡,跟 Active NameNode 保持一致。

然後每隔一段時間,Standby NameNode 都把自己記憶體裡的檔案目錄樹寫一份到磁盤上的 fsimage,這可不是日誌,這是完整的一份元數據。這個操作就是所謂的 checkpoint 檢查點操作。

然後把這個 fsimage 上傳到 Active NameNode,接著清空掉 Active NameNode 的舊的 edits log 檔案,這裡可能都有 100 萬行修改日誌了!

然後 Active NameNode 繼續接收修改元數據的請求,再寫入 edits log,寫了一小會兒,這裡可能就幾十行修改日誌而已!

如果說此時,Active NameNode 重啟了,Bingo!沒關係,只要把 Standby NameNode 傳過來的 fsimage 直接讀到記憶體裡,這個 fsimage 直接就是元數據,不需要做任何額外操作,純讀取,效率很高!

然後把新的 edits log 裡少量的幾十行的修改日誌回放到記憶體裡就 OK 了!

這個過程的啟動速度就快的多了!因為不需要回放大量上百萬行的 edits log 來恢復元數據了!如下圖所示。

此外,大家看看上面這張圖,現在咱們有倆 NameNode:

一個是主節點對外提供服務接收請求。

另外一個純就是接收和同步主節點的 edits log 以及執行定期 checkpoint 的備節點。

大家有沒有發現!他們倆記憶體裡的元數據幾乎是一模一樣的啊!所以呢,如果 Active NameNode 掛了,是不是可以立馬切換成 Standby NameNode 對外提供服務?

這不就是所謂的 NameNode 主備高可用故障轉移機制麽!接下來大家再想想,HDFS 客戶端在 NameNode 記憶體裡的檔案目錄樹,新加了一個檔案。

但是這個時候,人家要把數據上傳到多台 DataNode 機器上去啊,這可是一個 1TB 的大檔案!怎傳呢?

很簡單,把 1TB 的大檔案拆成 N 個 block,每個 block 是 128MB。1TB = 1024GB = 1048576MB,一個 block 是 128MB,那麽就是對應著 8192 個 block。

這些 block 會分布在不同的機器上管理著,比如說一共有 100 台機器組成的集群,那麽每台機器上放 80 個左右的 block 就 OK 了。

但是問題又來了,那如果這個時候 1 台機器宕機了,不就導致 80 個 block 丟失了?

也就是說上傳上去的 1TB 的大檔案,會丟失一小部分數據啊。沒關係!HDFS 都考慮好了!

它會默認給每個 block 搞 3 個副本,一模一樣的副本,分放在不同的機器上,如果一台機器宕機了,同一個 block 還有另外兩個副本在其他機器上呢!

大夥兒看看下面這張圖。每個 block 都在不同的機器上有 3 個副本,任何一台機器宕機都沒事!還可以從其他的機器上拿到那個 block。

這下子,你往 HDFS 上傳一個 1TB 的大檔案,可以高枕無憂了吧!

OK,上面就是大白話加上一系列手繪圖,給大家先聊聊小白都能聽懂的 Hadoop 的基本架構原理。

大規模集群下 Hadoop NameNode 如何承載每秒上千次的高並發訪問

上面我們已經初步給大家解釋了 Hadoop HDFS 的整體架構原理,相信大家都有了一定的認識和了解。

下面我們來看看,如果大量客戶端對 NameNode 發起高並發(比如每秒上千次)訪問來修改元數據,此時 NameNode 該如何抗住?

問題源起

我們先來分析一下,高並發請求 NameNode 會遇到什麽樣的問題。

大家現在都知道了,每次請求 NameNode 修改一條元數據(比如說申請上傳一個檔案,那麽就需要在記憶體目錄樹中加入一個檔案),都要寫一條 edits log。

包括如下兩個步驟:

寫入本地磁盤。

通過網絡傳輸給 JournalNodes 集群。

但是如果對 Java 有一定了解的同學都該知道多線程並發安全問題吧?

NameNode 在寫 edits log 時的第一條原則:必須保證每條 edits log 都有一個全局順序遞增的 transactionId(簡稱為 txid),這樣才可以標識出來一條一條的 edits log 的先後順序。

那麽如果要保證每條 edits log 的 txid 都是遞增的,就必須得加鎖。

每個線程修改了元數據,要寫一條 edits log 的時候,都必須按順序排隊獲取鎖後,才能生成一個遞增的 txid,代表這次要寫的 edits log 的序號。

好了,那麽問題來了,大家看看下面的圖。如果每次都是在一個加鎖的代碼塊裡,生成 txid,然後寫磁盤檔案 edits log,網絡請求寫入 JournalNodes 一條 edits log,會怎樣?

不用說,這個絕對完蛋了!NameNode 本身用多線程接收多個客戶端發送過來的並發的請求,結果多個線程居然修改完記憶體中的元數據之後,排著隊寫 edits log!

而且你要知道,寫本地磁盤 + 網絡傳輸給 JournalNodes,都是很耗時的啊!性能兩大殺手:磁盤寫 + 網絡寫!

如果 HDFS 的架構真要是這麽設計的話,基本上 NameNode 能承載的每秒的並發數量就很少了,可能就每秒處理幾十個並發請求處理撐死了!

HDFS 優雅的解決方案

所以說,針對這個問題,人家 HDFS 是做了不少的優化的!

首先大家想一下,既然咱們不希望每個線程寫 edits log 的時候,串行化排隊生成 txid + 寫磁盤 + 寫 JournalNode,那麽是不是可以搞一個記憶體緩衝?

也就是說,多個線程可以快速的獲取鎖,生成 txid,然後快速的將 edits log 寫入記憶體緩衝。

接著就快速的釋放鎖,讓下一個線程繼續獲取鎖後,生成 id + 寫 edits log 進入記憶體緩衝。

然後接下來有一個線程可以將記憶體中的 edits log 刷入磁盤,但是在這個過程中,還是繼續允許其他線程將 edits log 寫入記憶體緩衝中。

但是這裡又有一個問題了,如果針對同一塊記憶體緩衝,同時有人寫入,還同時有人讀取後寫磁盤,那也有問題,因為不能並發讀寫一塊共享記憶體數據!

所以 HDFS 在這裡采取了 double-buffer 雙緩衝機制來處理!將一塊記憶體緩衝分成兩個部分:

其中一個部分可以寫入。

另外一個部分用於讀取後寫入磁盤和 JournalNodes。

大家可能感覺文字敘述不太直觀,老規矩,咱們來一張圖,按順序給大家闡述一下。

分段加鎖機制 + 記憶體雙緩衝機制

首先各個線程依次第一次獲取鎖,生成順序遞增的 txid,然後將 edits log 寫入記憶體雙緩衝的區域 1,接著就立馬第一次釋放鎖了。

趁著這個空隙,後面的線程就可以再次立馬第一次獲取鎖,然後立即寫自己的 edits log 到記憶體緩衝。

寫記憶體那麽快,可能才耗時幾十微妙,接著就立馬第一次釋放鎖了。所以這個並發優化絕對是有效果的,大家有沒有感受到?

接著各個線程競爭第二次獲取鎖,有線程獲取到鎖之後,就看看,有沒有誰在寫磁盤和網絡?

如果沒有,好,那麽這個線程是個幸運兒!直接交換雙緩衝的區域 1 和區域 2,接著第二次釋放鎖。這個過程相當快速,記憶體裡判斷幾個條件,耗時不了幾微秒。

好,到這一步為止,記憶體緩衝已經被交換了,後面的線程可以立馬快速的依次獲取鎖,然後將 edits log 寫入記憶體緩衝的區域 2,區域 1 中的數據被鎖定了,不能寫。

怎麽樣,是不是又感受到了一點點多線程並發的優化?

多線程並發吞吐量的百倍優化

接著,之前那個幸運兒線程,將記憶體緩衝的區域 1 中的數據讀取出來(此時沒人寫區域 1 了,都在寫區域 2),將裡面的 edtis log 都寫入磁盤檔案,以及通過網絡寫入 JournalNodes 集群。

這個過程可是很耗時的!但是沒關係啊,人家做過優化了,在寫磁盤和網絡的過程中,是不持有鎖的!

因此後面的線程可以劈裡啪啦的快速的第一次獲取鎖後,立馬寫入記憶體緩衝的區域 2,然後釋放鎖。

這個時候大量的線程都可以快速的寫入記憶體,沒有阻塞和卡頓!怎麽樣?並發優化的感覺感受到了沒有!

緩衝數據批量刷磁盤 + 網絡的優化

那麽在幸運兒線程吭哧吭哧把數據寫磁盤和網絡的過程中,排在後面的大量線程,快速的第一次獲取鎖,寫記憶體緩衝區域 2,釋放鎖,之後,這些線程第二次獲取到鎖後會幹嘛?

他們會發現有人在寫磁盤啊,兄弟們!所以會立即休眠 1 秒,釋放鎖。

此時大量的線程並發過來的話,都會在這裡快速的第二次獲取鎖,然後發現有人在寫磁盤和網絡,快速的釋放鎖,休眠。

怎麽樣,這個過程沒有人長時間的阻塞其他人吧!因為都會快速的釋放鎖,所以後面的線程還是可以迅速的第一次獲取鎖後寫記憶體緩衝!

Again!並發優化的感覺感受到了沒有?

而且這時,一定會有很多線程發現,好像之前那個幸運兒線程的 txid 是排在自己之後的,那麽肯定就把自己的 edits log 從緩衝裡寫入磁盤和網絡了。

這些線程甚至都不會休眠等待,直接就會返回後去幹別的事情了,壓根兒不會卡在這裡。這裡又感受到並發的優化沒有?

然後那個幸運兒線程寫完磁盤和網絡之後,就會喚醒之前休眠的那些線程。

那些線程會依次排隊再第二次獲取鎖後進入判斷,咦!發現沒有人在寫磁盤和網絡了!

然後就會再判斷,有沒有排在自己之後的線程已經將自己的 edtis log 寫入磁盤和網絡了:

如果有的話,就直接返回了。

沒有的話,那麽就成為第二個幸運兒線程,交換兩塊緩衝區,區域 1 和區域 2 交換一下。

然後釋放鎖,自己開始吭哧吭哧的將區域 2 的數據寫入磁盤和網絡。

但是這個時候沒有關係啊,後面的線程如果要寫 edits log 的,還是可以第一次獲取鎖後立馬寫記憶體緩衝再釋放鎖。以此類推。

總結

這套機制還是挺複雜的,涉及到了分段加鎖以及記憶體雙緩衝兩個機制。

通過這套機制,NameNode 保證了多個線程在高並發的修改元數據之後寫 edits log 的時候,不會說一個線程一個線程的寫磁盤和網絡,那樣性能實在太差,並發能力太弱了!

所以通過上述那套複雜的機制,盡最大的努力保證,一個線程可以批量的將一個緩衝中的多條 edits log 刷入磁盤和網絡。

在這個漫長的吭哧吭哧的過程中,其他的線程可以快速的高並發寫入 edits log 到記憶體緩衝裡,不會阻塞其他的線程寫 edits log。

所以,正是依靠以上機制,最大限度優化了 NameNode 處理高並發訪問修改元數據的能力!

本文轉載自【51CTO技術棧】

獲得更多的PTT最新消息
按讚加入粉絲團