一文詳解大規模數據計算處理原理及操作重點

摘要: 大數據技術主要針對的是大規模數據的計算處理問題,那麼要想解決的這一問題,首先要解決的就是大規模數據的存儲問題。

一、RAID技術

大數據技術主要針對的是大規模數據的計算處理問題,那麼要想解決的這一問題,首先要解決的就是大規模數據的存儲問題。大規模數據存儲要解決的核心問題有三個方面:

數據存儲容量的問題,既然大數據要解決的是數以PB計的數據計算問題,而一般的服務器磁盤容量通常1-2TB,那麼如何存儲這麼大規模的數據?
數據讀寫速度的問題,一般磁盤的連續讀寫速度爲幾十MB,以這樣的速度,幾十PB的數據恐怕要讀寫到天荒地老……
數據可靠性的問題,磁盤大約是計算機設備中最易損壞的硬件了,在網站一塊磁盤使用壽命大概是一年,如果磁盤損壞了,數據怎麼辦?

在大數據技術出現之前,人們就需要面對這些關於存儲的問題,對應的解決方案就是RAID技術。

RAID(獨立磁盤冗餘陣列)技術主要是爲了改善磁盤的存儲容量、讀寫速度,增強磁盤的可用性和容錯能力。目前服務器級別的計算機都支持插入多塊磁盤(8塊或者更多),通過使用RAID技術,實現數據在多塊磁盤上的併發讀寫和數據備份。

常用RAID技術有以下幾種,如圖所示:

image.png

常用RAID技術原理圖

假設服務器有N塊磁盤:

RAID0
數據在從內存緩衝區寫入磁盤時,根據磁盤數量將數據分成N份,這些數據同時併發寫入N塊磁盤,使得數據整體寫入速度是一塊磁盤的N倍,讀取的時候也一樣,因此RAID0具有極快的數據讀寫速度。但是RAID0不做數據備份,N塊磁盤中只要有一塊損壞,數據完整性就被破壞,所有磁盤的數據都會損壞。

RAID1
數據在寫入磁盤時,將一份數據同時寫入兩塊磁盤,這樣任何一塊磁盤損壞都不會導致數據丟失,插入一塊新磁盤就可以通過複製數據的方式自動修復,具有極高的可靠性。

RAID10
結合RAID0和RAID1兩種方案,將所有磁盤平均分成兩份,數據同時在兩份磁盤寫入,相當於RAID1,但是在每一份磁盤裏面的N/2塊磁盤上,利用RAID0技術併發讀寫,既提高可靠性又改善性能,不過RAID10的磁盤利用率較低,有一半的磁盤用來寫備份數據。

RAID3
一般情況下,一臺服務器上不會出現同時損壞兩塊磁盤的情況,在只損壞一塊磁盤的情況下,如果能利用其它磁盤的數據恢復損壞磁盤的數據,就能在保證可靠性和性能的同時,大幅提升磁盤利用率。

在數據寫入磁盤的時候,將數據分成N-1份,併發寫入N-1塊磁盤,並在第N塊磁盤記錄校驗數據,任何一塊磁盤損壞(包括校驗數據磁盤),都可以利用其它N-1塊磁盤的數據修復。

但是在數據修改較多的場景中,任何磁盤修改數據都會導致第N塊磁盤重寫校驗數據,頻繁寫入的後果是第N塊磁盤比其它磁盤容易損壞,需要頻繁更換,所以RAID3很少在實踐中使用。

RAID5

相比RAID3,更多被使用的方案是RAID5。

RAID5和RAID3很相似,但是校驗數據不是寫入第N塊磁盤,而是螺旋式地寫入所有磁盤中。這樣校驗數據的修改也被平均到所有磁盤上,避免RAID3頻繁寫壞一塊磁盤的情況。

RAID6

如果數據需要很高的可靠性,在出現同時損壞兩塊磁盤的情況下(或者運維管理水平比較落後,壞了一塊磁盤但是遲遲沒有更換,導致又壞了一塊磁盤),仍然需要修復數據,這時候可以使用RAID6。

RAID6和RAID5類似,但是數據只寫入N-2塊磁盤,並螺旋式地在兩塊磁盤中寫入校驗信息(使用不同算法生成)。

在相同磁盤數目(N)的情況下,各種RAID技術的比較如下表所示:

image.png

幾種RAID技術比較

RAID技術有硬件實現,比如專用的RAID卡或者主板直接支持,也可以通過軟件實現,在操作系統層面將多塊磁盤組成RAID,在邏輯視作一個訪問目錄。RAID技術在傳統關係數據庫及文件系統中應用比較廣泛,是改善計算機存儲特性的重要手段。

RAID技術只是在單臺服務器的多塊磁盤上組成陣列,大數據需要更大規模的存儲空間和訪問速度。將RAID技術原理應用到分佈式服務器集羣上,就形成了Hadoop分佈式文件系統HDFS的架構思想。

二、HDFS架構思想

1、HDFS架構原理

和RAID在多個磁盤上進行文件存儲及並行讀寫一樣思路,HDFS在一個大規模分佈式服務器集羣上,對數據進行並行讀寫及冗餘存儲。因爲HDFS可以部署在一個比較大的服務器集羣上,集羣中所有服務器的磁盤都可以供HDFS使用,所以整個HDFS的存儲空間可以達到PB級容量。HDFS架構如圖:

image.png

HDFS架構

HDFS中關鍵組件有兩個,一個是NameNode,一個是DataNode。

DataNode負責文件數據的存儲和讀寫操作,HDFS將文件數據分割成若干塊(block),每個DataNode存儲一部分block,這樣文件就分佈存儲在整個HDFS服務器集羣中。應用程序客戶端(Client)可以並行對這些數據塊進行訪問,從而使得HDFS可以在服務器集羣規模上實現數據並行訪問,極大地提高訪問速度。實踐中HDFS集羣的DataNode服務器會有很多臺,一般在幾百臺到幾千臺這樣的規模,每臺服務器配有數塊磁盤,整個集羣的存儲容量大概在幾PB到數百PB。

NameNode負責整個分佈式文件系統的元數據(MetaData)管理,也就是文件路徑名,數據block的ID以及存儲位置等信息,承擔着操作系統中文件分配表(FAT)的角色。HDFS爲了保證數據的高可用,會將一個block複製爲多份(缺省情況爲3份),並將三份相同的block存儲在不同的服務器上。這樣當有磁盤損壞或者某個DataNode服務器宕機導致其存儲的block不能訪問的時候,Client會查找其備份的block進行訪問。

block多份複製存儲如下圖所示:

image.png

HDFS的block複製備份策略

對於文件/users/sameerp/data/part-0,其複製備份數設置爲2,存儲的block ID爲1,3,block1的兩個備份存儲在DataNode0和DataNode2兩個服務器上,block3的兩個備份存儲DataNode4和DataNode6兩個服務器上,上述任何一臺服務器宕機後,每個block都至少還有一個備份存在,不會影響對文件/users/sameerp/data/part-0的訪問。

事實上,DataNode會通過心跳和NameNode保持通信,如果DataNode超時未發送心跳,NameNode就會認爲這個DataNode已經失效,立即查找這個DataNode上存儲的block有哪些,以及這些block還存儲在哪些服務器上,隨後通知這些服務器再複製一份block到其它服務器上,保證HDFS存儲的block備份數符合用戶設置的數目,即使再有服務器宕機,也不會丟失數據。

2、HDFS應用

Hadoop分佈式文件系統可以像一般的文件系統那樣進行訪問:使用命令行或者編程語言API進行文件讀寫操作。我們以HDFS寫文件爲例看HDFS處理過程,如下圖:

image.png

HDFS寫文件操作

應用程序Client調用HDFS API,請求創建文件,HDFS API包含在Client進程中;
HDFS API將請求參數發送給NameNode服務器,NameNode在meta信息中創建文件路徑,並查找DataNode中空閒的block,然後將空閒block的id、對應的DataNode服務器信息返回給Client。因爲數據塊需要多個備份,所以即使Client只需要一個block的數據量,NameNode也會返回多個NameNode信息;
Client調用HDFS API,請求將數據流寫出;
HDFS API連接第一個DataNode服務器,將Client數據流發送給DataNode,該DataNode一邊將數據寫入本地磁盤,一邊發送給第二個DataNode,同理第二個DataNode記錄數據併發送給第三個DataNode;
Client通知NameNode文件寫入完成,NameNode將文件標記爲正常,可以進行讀操作了。

HDFS雖然提供了API,但是在實踐中,我們很少自己編程直接去讀取HDFS中的數據,原因正如開篇提到,在大數據場景下,移動計算比移動數據更划算。

與其寫程序去讀取分佈在這麼多DataNode上的數據,不如將程序分發到DataNode上去訪問其上的block數據。但是如何對程序進行分發?分發出去的程序又如何訪問HDFS上的數據?計算的結果如何處理,如果結果需要合併,該如何合併?

Hadoop提供了對存儲在HDFS上的大規模數據進行並行計算的框架,就是MapReduce。

三、MapReduce

Hadoop解決大規模數據分佈式計算的方案是MapReduce。MapReduce既是一個編程模型,又是一個計算框架。也就是說,開發人員必須基於MapReduce編程模型進行編程開發,然後將程序通過MapReduce計算框架分發到Hadoop集羣中運行。我們先看一下作爲編程模型的MapReduce。

1、MapReduce編程模型

MapReduce是一種非常簡單又非常強大的編程模型。

簡單在於其編程模型只包含map和reduce兩個過程,map的主要輸入是一對值,經過map計算後輸出一對值;然後將相同key合併,形成;再將這個輸入reduce,經過計算輸出零個或多個對。

但是MapReduce同時又是非常強大的,不管是關係代數運算(SQL計算),還是矩陣運算(圖計算),大數據領域幾乎所有的計算需求都可以通過MapReduce編程來實現。

我們以WordCount程序爲例。WordCount主要解決文本處理中的詞頻統計問題,就是統計文本中每一個單詞出現的次數。如果只是統計一篇文章的詞頻,幾十K到幾M的數據,那麼寫一個程序,將數據讀入內存,建一個Hash表記錄每個詞出現的次數就可以了,如下圖:


小數據量的詞頻統計

但是如果想統計全世界互聯網所有網頁(數萬億計)的詞頻數(這正是google這樣的搜索引擎典型需求),你不可能寫一個程序把全世界的網頁都讀入內存,這時候就需要用MapReduce編程來解決。

WordCount的MapReduce程序如下:

public class WordCount {
  public static class TokenizerMapper
       extends Mapper<Object, Text, Text, IntWritable>{
    private final static IntWritable one = new IntWritable(1);
    private Text word = new Text();
    public void map(Object key, Text value, Context context
                    ) throws IOException, InterruptedException {
      StringTokenizer itr = new StringTokenizer(value.toString());
      while (itr.hasMoreTokens()) {
        word.set(itr.nextToken());
        context.write(word, one);
      }
    }
  }
  public static class IntSumReducer
       extends Reducer<Text,IntWritable,Text,IntWritable> {
    private IntWritable result = new IntWritable();
    public void reduce(Text key, Iterable<IntWritable> values,
                       Context context
                       ) throws IOException, InterruptedException {
      int sum = 0;
      for (IntWritable val : values) {
        sum += val.get();
      }
      result.set(sum);
      context.write(key, result);
    }
  }}


其核心是一個map函數,一個reduce函數。

map函數的輸入主要是一個對,在這個例子裏,value是要統計的所有文本中的一行數據,key在這裏不重要,我們忽略。

public void map(Object key, Text value, Context context)


map函數的計算過程就是,將這行文本中的單詞提取出來,針對每個單詞輸出一個這樣的對。

MapReduce計算框架會將這些收集起來,將相同的word放在一起,形成>這樣的數據,然後將其輸入給reduce函數。

public void reduce(Text key, Iterable<IntWritable> values,Context context)


這裏的reduce的輸入參數values就是由很多個1組成的集合,而key就是具體的單詞word。

reduce函數的計算過程就是,將這個集合裏的1求和,再將單詞(word)和這個和(sum)組成一個()輸出。每一個輸出就是一個單詞和它的詞頻統計總和。

假設有兩個block的文本數據需要進行詞頻統計,MapReduce計算過程如下圖:

image.png

MapReduce計算過程

一個map函數可以針對一部分數據進行運算,這樣就可以將一個大數據切分成很多塊(這也正是HDFS所做的),MapReduce計算框架爲每個塊分配一個map函數去計算,從而實現大數據的分佈式計算。

2、MapReduce計算框架架構原理

前面提到MapReduce編程模型將大數據計算過程切分爲map和reduce兩個階段,在map階段爲每個數據塊分配一個map計算任務,然後將所有map輸出的key進行合併,相同的key及其對應的value發送給同一個reduce任務去處理。

這個過程有兩個關鍵問題需要處理:

如何爲每個數據塊分配一個map計算任務,代碼是如何發送數據塊所在服務器的,發送過去是如何啓動的,啓動以後又如何知道自己需要計算的數據在文件什麼位置(數據塊id是什麼)?
處於不同服務器的map輸出的 ,如何把相同的key聚合在一起發送給reduce任務?

這兩個關鍵問題正好對應前面文章中“MapReduce計算過程”一圖中兩處“MapReduce框架處理”:


image.png

MapReduce計算過程中兩處MapReduce框架處理

我們先看下MapReduce是如何啓動處理一個大數據計算應用作業的:

MapReduce作業啓動和運行機制

我們以Hadoop1爲例,MapReduce運行過程涉及以下幾類關鍵進程:

大數據應用進程:啓動用戶MapReduce程序的主入口,主要指定Map和Reduce類、輸入輸出文件路徑等,並提交作業給Hadoop集羣。
JobTracker進程:根據要處理的輸入數據量啓動相應數量的map和reduce進程任務,並管理整個作業生命週期的任務調度和監控。JobTracker進程在整個Hadoop集羣全局唯一。
TaskTracker進程:負責啓動和管理map進程以及reduce進程。因爲需要每個數據塊都有對應的map函數,TaskTracker進程通常和HDFS的DataNode進程啓動在同一個服務器,也就是說,Hadoop集羣中絕大多數服務器同時運行DataNode進程和TaskTacker進程。

如下圖所示:


image.png

MapReduce作業啓動和運行機制

具體作業啓動和計算過程如下:

應用進程將用戶作業jar包存儲在HDFS中,將來這些jar包會分發給Hadoop集羣中的服務器執行MapReduce計算;
應用程序提交job作業給JobTracker;
JobTacker根據作業調度策略創建JobInProcess樹,每個作業都會有一個自己的JobInProcess樹;
JobInProcess根據輸入數據分片數目(通常情況就是數據塊的數目)和設置的reduce數目創建相應數量的TaskInProcess;
TaskTracker進程和JobTracker進程進行定時通信;
如果TaskTracker有空閒的計算資源(空閒CPU核),JobTracker就會給它分配任務。分配任務的時候會根據TaskTracker的服務器名字匹配在同一臺機器上的數據塊計算任務給它,使啓動的計算任務正好處理本機上的數據,以實現我們一開始就提到的“移動計算比移動數據更划算”;
TaskRunner收到任務後根據任務類型(map還是reduce),任務參數(作業jar包路徑,輸入數據文件路徑,要處理的數據在文件中的起始位置和偏移量,數據塊多個備份的DataNode主機名等)啓動相應的map或者reduce進程;
map或者reduce程序啓動後,檢查本地是否有要執行任務的jar包文件,如果沒有,就去HDFS上下載,然後加載map或者reduce代碼開始執行;
如果是map進程,從HDFS讀取數據(通常要讀取的數據塊正好存儲在本機);如果是reduce進程,將結果數據寫出到HDFS。

通過以上過程,MapReduce可以將大數據作業計算任務分佈在整個Hadoop集羣中運行,每個map計算任務要處理的數據通常都能從本地磁盤上讀取到,而用戶要做的僅僅是編寫一個map函數和一個reduce函數就可以了,根本不用關心這兩個函數是如何被分佈啓動到集羣上的,數據塊又是如何分配給計算任務的。這一切都由MapReduce計算框架完成。

MapReduce數據合併與連接機制

在WordCount例子中,要統計相同單詞在所有輸入數據中出現的次數,而一個map只能處理一部分數據,一個熱門單詞幾乎會出現在所有的map中,這些單詞必須要合併到一起進行統計才能得到正確的結果。

事實上,幾乎所有的大數據計算場景都需要處理數據關聯的問題,簡單如WordCount只要對key進行合併就可以了,複雜如數據庫的join操作,需要對兩種類型(或者更多類型)的數據根據key進行連接。

MapReduce計算框架處理數據合併與連接的操作就在map輸出與reduce輸入之間,這個過程有個專門的詞彙來描述,叫做shuffle。


image.png

MapReduce shuffle過程

每個map任務的計算結果都會寫入到本地文件系統,等map任務快要計算完成的時候,MapReduce計算框架會啓動shuffle過程,在map端調用一個Partitioner接口,對map產生的每個進行reduce分區選擇,然後通過http通信發送給對應的reduce進程。這樣不管map位於哪個服務器節點,相同的key一定會被髮送給相同的reduce進程。reduce端對收到的進行排序和合並,相同的key放在一起,組成一個傳遞給reduce執行。

MapReduce框架缺省的Partitioner用key的哈希值對reduce任務數量取模,相同的key一定會落在相同的reduce任務id上,實現上,這樣的Partitioner代碼只需要一行,如下所示:

/** Use {@link Object#hashCode()} to partition. */
public int getPartition(K2 key, V2 value, int numReduceTasks) {
    return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
 }


shuffle是大數據計算過程中發生奇蹟的地方,不管是MapReduce還是Spark,只要是大數據批處理計算,一定會有shuffle過程,讓數據關聯起來,數據的內在關係和價值纔會呈現出來。不理解shuffle,就會在map和reduce編程中產生困惑,不知道該如何正確設計map的輸出和reduce的輸入。shuffle也是整個MapReduce過程中最難最消耗性能的地方,在MapReduce早期代碼中,一半代碼都是關於shuffle處理的。

3、工具——Hive

既然MapReduce計算模型可以解決絕大多數的數據分析與數據挖掘任務,那麼對於如下我們常見的一條SQL分析語句,MapReduce如何編程實現?

SELECT pageid, age, count(1) FROM pv_users GROUP BY pageid, age;


這是一條非常常見的SQL統計分析語句,統計不同年齡的用戶訪問不同網頁的興趣偏好,對於產品運營和設計很有價值。具體數據輸入和執行結果如下圖示例:

group by輸入輸出示例

左邊是要分析的數據表,右邊是分析結果。實際上把左邊表相同的行累計求和,就得到右邊的表了,看起來跟WordCount的計算很一樣。確實也是這樣,我們看下這條SQL語句的MapReduce的計算過程,map和reduce函數的輸入輸出以及函數處理過程分別是什麼樣。

首先,看下map函數的輸入key和value,key不重要,忽略掉,value就是左邊表中每一行的數據,<1, 25>這樣。map函數的輸出就是以輸入的value作爲key,value統一設爲1,<<1, 25>, 1>這樣。

map函數的輸出經過shuffle以後,相同的key及其對應的value被放在一起組成一個,作爲輸入交給reduce函數處理。如<<2, 25>, 1>被map函數輸出兩次,那麼到了reduce這裏,就變成輸入<<2, 25>, <1, 1>>,key是<2, 25>, value集合是<1, 1>。在reduce函數內部,value集合裏所有的數字被相加,然後輸出。reduce的輸出就是<<2, 25>, 2>。

計算過程如下圖示例:

image.png

group by的MapReduce計算過程示例

這樣一條很有實用價值的SQL就這樣被很簡單的MapReduce計算過程處理好了。在數據倉庫中,SQL是最常用的分析工具,那麼有沒有能夠自動將SQL生成MapReduce代碼的工具呢?這個工具就是Hadoop大數據倉庫Hive。

自動將SQL生成MapReduce代碼的工具——Hive

Hive能夠直接處理用戶輸入的SQL語句(Hive的SQL語法和數據庫標準SQL略有不同),調用MapReduce計算框架完成數據分析操作。具體架構如下圖:

image.png

Hive架構

用戶通過Hive的Client(Hive的命令行工具,JDBC等)向Hive提交SQL命令。如果是創建數據表的DDL語句,Hive就會通過執行引擎Driver將數據表的信息記錄在Metastore組件中,這個組件通常用一個關係數據庫實現,記錄表名、字段名、字段類型、關聯HDFS文件路徑等這些數據庫的meta信息(元信息)。

如果用戶提交的是查詢分析數據的DQL語句,Driver就會將該語句提交給自己的編譯器Compiler進行語法分析、語法解析、語法優化等一系列操作,最後生成一個MapReduce執行計劃。然後根據該執行計劃生成一個MapReduce的作業,提交給Hadoop MapReduce計算框架處理。

對於一個較簡單的SQL命令,比如:

SELECT * FROM status_updates WHERE status LIKE ‘michael jackson’;

其對應的Hive執行計劃如下圖:

image.png

Hive執行計劃示例

Hive內部預置了很多函數,Hive的執行計劃就是根據SQL語句生成這些函數的DAG(有向無環圖),然後封裝進MapReduce的map和reduce函數中。這個例子中,map函數調用了三個Hive內置函數TableScanOpoerator、FilterOperator、FileOutputOperator,就完成了map計算,而且無需reduce函數。

除了上面這些簡單的聚合(group by)、過濾(where)操作,Hive還能執行連接(join on)操作。上面例子中,pv_users表的數據在實際中是無法直接得到的,因爲pageid數據來自用戶訪問日誌,每個用戶進行一次頁面瀏覽,就會生成一條訪問記錄,保存在page_view表中。而年齡age信息則記錄在用戶表user中。如下圖:

image.png

page_view表和user表示例

這兩張表都有一個相同的字段userid,根據這個字段可以將兩張表連接起來,生成前面的pv_users表,SQL命令如下:

SELECT pv.pageid, u.age FROM page_view pv JOIN user u ON (pv.userid = u.userid);


同樣,這個SQL命令也可以轉化爲MapReduce計算,如下圖:

image.png

join的MapReduce計算過程示例

join的MapReduce計算過程和前面的group by稍有不同,因爲join涉及兩張表,來自兩個文件(夾),所以需要在map輸出的時候進行標記,比如來自第一張表的輸出value就記錄爲<1, X>,這裏的1表示數據來自第一張表。這樣經過shuffle以後,相同的key被輸入到同一個reduce函數,就可以根據表的標記對value數據求笛卡爾積,輸出就join的結果。

在實踐中,工程師並不需要經常編寫MapReduce程序,因爲網站最主要的大數據處理就是SQL分析,在Facebook,據說90%以上的MapReduce任務都是Hive產生的。Hive在大數據應用中的作用非常重要。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章