古代,人們用牛來拉重物,當一頭牛拉不動一根圓木時,他們不曾想過培育更大更壯的牛,而是使用多頭牛。同樣,我們也不需要嘗試開發超級計算機,而應試着結合使用更多計算機系統。
一、什麼是Hadoop
大數據目前很火!是的,當今我們正處於大數據時代,但是我們應如何存儲和分析這些數據,如何從海量數據中取出有價值的信息,例如搜索引擎中如何快速查找出有用信息,淘寶網如何快速根據你的之前瀏覽頁面給出你可能需要的信息?Hadoop是一個能夠對大量數據進行分佈式處理的一個軟件框架。
讀取一個磁盤中所有的數據需要很長的時間,寫甚至更慢。一個很簡單的減少讀取時間的辦法是同時從多個磁盤上讀取數據。試想,如果我們擁有100個磁盤,每個磁盤存儲1%的數據,並行讀取,那麼不到兩分鐘就可以讀取所有數據。Hadoop運用這種思想,讓多臺機器分佈並行處理數據,從而減少讀取時間,大大提高效率。Hadoop的文件系統HDFS存儲需要處理的數據,Hadoop的MapReduce提出了一個編程模型,該模型將磁盤讀寫問題進行抽象,並轉換爲對一個數據集(由鍵/值對組成)的計算,該計算由map和reduce兩部分組成。
簡而言之,對於大量數據的處理,Hadoop提供了一個可靠的共享存儲和分析系統。Hadoop中的HDFS實現存儲,而MapReduce實現分析處理,提取出我們需要的數據。縱然Hadoop還有其他功能,但這兩部分是它的核心。
二、Hadoop與傳統關係型數據庫的區別
傳統型關係型數據庫也能實現數據存儲和數據分析功能,Hadoop的優勢或不同在哪?
首先,磁盤一個發展趨勢是尋址時間的提高遠遠慢於傳輸速率的提高,Hadoop將數據傳輸到其它機器上處理,進行高速的流式讀寫操作,而關係型數據庫是不斷尋址訪問磁盤處理;
第二,關係型數據庫使用B樹作爲數據結構,當數據庫系統更新大部分數據時,B樹需要不斷地使用“排序/合併”(sort/merge)來重建數據庫。相比之下,Hadoop的MapReduce的效率更高。
第三,MapReduce和關係型數據庫所操作的數據集也是不同的。關係型數據庫操作的是結構化數據(有既定格式的實體化數據,如關係表、XML文檔);MapReduce操作的是非結構化(沒有什麼特別的內部結構,就只是一些數據而已,數據之間一般也沒什麼關係,如純文本、圖像數據)或半結構化數據(如電子表格)。
三、HDFS
當數據集的大小超過一臺獨立物理計算機的存儲能力時,就有必要對它進行分區(partition)並存儲到若干臺單獨的計算機上。Hadoop的分佈式文件系統稱爲HDFS,該系統架構於網絡之上,勢必會引入網絡編程的複雜性,因此分佈式文件系統比普通磁盤文件系統更爲複雜。HDFS以流式數據訪問模式來存儲超大文件,運行於硬件集羣上。
HDFS的構建思路是這樣的:一次寫入、多次讀取是最高效的訪問模式。數據集通常由數據源生成或從數據源複製而來,接着長時間在此數據集上進行各類分析。每次分析都將涉及該數據集的大部分數據甚至全部,因此讀取整個數據集的時間延遲比讀取第一條記錄的時間延遲更重要。
HDFS同樣也有塊(block)的概念,但是大得多,默認爲64 MB。與單一磁盤上的文件系統相似,HDFS上的文件也被劃分爲塊大小的多個分塊(chunk),作爲獨立的存儲單元。但與其他文件系統不同的是,HDFS中小於一個塊大小的文件不會佔據整個塊的空間。
在一個全配置的集羣上,“運行Hadoop”意味着在網絡分佈的不同服務器上運行一組守護進程( daemons )。這些守護進程有特殊的角色,一些僅存在於單個服務器上,一些則運行在多個服務器上。它們包括:
NameNode(名字節點);
DataNode(數據節點);
SecondaryNameNode(次名字節點);
JobTracker(作業跟蹤節點);
TaskTracker(任務跟蹤節點);
1、NameNode
Hadoop在分佈式計算與分佈式存儲中都採用了主/從(masterlslave)結構。NameNode是HDFS的書記員,它跟蹤文件如何被分割成文件塊,而這些塊又被哪些節點存儲,以及分佈式文件系統的整體運行狀態是否正常。NameNode一般放在主節點機器上,看作是任務的管理者。
2、DataNode
每一個集羣上的從節點都會駐留一個DataNode守護進程,來執行分佈式文件系統的繁重工作-------將HDFS數據塊讀取或者寫入到本地文件系統的實際文件中。當希望對HDFS文件進行讀寫時,文件被分割爲多個塊,由NameNode告知客戶端每個數據塊駐留在哪個DataNode。客戶端直接與DataNode守護進程通信,來處理與數據塊相對應的本地文件。而後,DataNode會與其他DataNode進行通信,複製這些數據塊以實現冗餘。
初始化時,每個DataNode將當前存儲的數據塊告知NameNode,在這個初始映射完成後,DataNode仍會不斷地更新NameNade,爲之提供本地修改的相關信息,同時接收指令創建、移動或刪除本地磁盤上的數據塊。
3、Secondary NameNode
Secondary NameNode ( SNN)是一個用於監測HDFS集羣狀態的輔助守護進程。像NameNode一樣,每個集羣有一個SNN。SNN與NameNode的不同在於它不接收或記錄HDFS的任何實時變化。相反,它與NameNode通信,根據集羣所配置的時間間隔獲取HDFS元數據的快照。
4、JobTracker
JobTracker守護進程是應用程序和Hadoop之間的紐帶。一旦提交代碼到集羣上,JobTracker就會確定執行計劃,包括決定處理哪些文件、爲不同的任務分配節點以及監控所有任務的運行。如果任務失敗,JobTracker將自動重啓任務,但所分配的節點可能會不同,同時受到預定義的重試次數限制。
5、TaskTracker
與存儲的守護進程(NameNode和DataNode)一樣,計算的守護進程也遵循主/從架構:JobTracker作爲主節點,監測MapReduce作業的整個執行過程,同時,TaskTracker管理各個任務在每個從節點上的執行情況。每個TaskTracker負責執行由JobTracker分配的單項任務。雖然每個從節點上僅有一個TaskTracker,但每個TaskTracker可以生成多個JVM( Java虛擬機)來並行地處理許多map或reduce任務。TaskTraeker的一個職責是持續不斷地與JobTracker通信。如果JobTracker在指定的時間內沒有收到來自TaskTracker的“心跳”,它會假定TaskTracker已經崩潰了,進而重新提交相應的任務到集羣中的其他節點中。
如下,是一個Hadoop集羣的拓撲圖:
四、MapReduce
MapReduce是一種可用於數據處理的編程模型。MapReduce任務過程被分爲兩個處理階段:map階段和reduce階段。每個階段都以鍵/值對作爲輸入和輸出,並由程序員選擇它們的類型。程序員需要具體定義兩個函數:map函數和reduce函數。將查詢操作和數據集分解爲組件---這就是map(映射);在查詢中被映射的組件可以被同時處理,從而可以快速返回結果---這就是reduce(歸約)。
MapReduce邏輯數據流:
Map: (K1,V 1)——>(K2,list(V2))
reduce: (K2,list( V2))——>list(K3,V3)
1、輸入數據爲一些文本,多個文本文件被分佈在不同的節點上,各節點對文本處理,表示成鍵/值對形式(K1,V1),k1是該行記錄相對於文件的偏移量(這個值我們在此用不到),V1就是文本值;
2、各節點(k1,v1)經過map函數處理,轉化成list(k2,v2),k2指代是哪一個單詞,V2表示該節點上單詞k2的個數;
3、各節點交換數據,相同key的鍵值對list(k2,v2)被髮送到同一節點上處理,稱爲是一個洗牌的過程,洗牌後產生鍵值對(k2,list(V2));
4、(k2,list(v2))經過Reduce函數處理,統計所有機器上k2的個數,輸出鍵值對(K3,V3);
5、結果顯示輸出。
輸入3段text字符,首先轉換成鍵值對形式(0,“Hello world Bye World”)、(4,“hello Hadoop.....”)、(8,“ByeHadoop.....”),將這些鍵值對分佈在三臺機器上,並輸入到map函數處理,每臺機器的map函數產生一系列鍵值對輸出,如上圖。
各機器(從結點)交換鍵值對(洗牌),相同key的鍵值對發到同一節點上,如key=”bye”的鍵值對都發送到1號從節點,key=”hello”的鍵值對都發往2號從節點。相同key的鍵值對必須發往同一節點,但同一節點上不一定只有一種key。經過洗牌之後的鍵值對,在各自節點上被reduce函數處理,統計單詞總數,輸出結果。
下面我們以Hadoop自帶的WordCount程序,具體講解MapReduce是怎樣編程的。
五、 MapReduce編程應用開發
1、導入Hadoop開發所需的包
package WordCount;
import java.io.IOException;//異常包
import java.util.StringTokenizer;//分隔字符串,默認用空格來分隔
importorg.apache.hadoop.conf.Configuration;//任務配置包,配置任務所需的map等函數
import org.apache.hadoop.fs.Path;//設置文件路徑
importorg.apache.hadoop.io.IntWritable;//Hadoop自定義的數據局類型,類似Java中int
import org.apache.hadoop.io.Text;//Hadoop自定義的數據局類型,類似Java中String
import org.apache.hadoop.mapreduce.Job;//控制整個作業運行
importorg.apache.hadoop.mapreduce.Mapper;//map接口
importorg.apache.hadoop.mapreduce.Reducer;//reduce接口
importorg.apache.hadoop.mapreduce.lib.input.FileInputFormat;//定義輸入路徑
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;//定義輸出路徑
importorg.apache.hadoop.util.GenericOptionsParser;//解析用戶指定的參數,獲取基本選 //項以及根據需要修改配置
2、MapReduce中具體實現Map函數:功能是把輸入的字符串分隔成key,並統計自己機器節點上的key的個數Value,輸出(key,value)
public class WordCount {
public static class TokenizerMapper extendsMapper<Object, Text, Text, IntWritable>{
//TokenizerMapper類繼承與Mapper類
private final static IntWritable one = new IntWritable(1);//初始化輸入的key,對應 //mapper中的Object
private Text word = new Text();//初始化輸入值value,對應mapper中的Text
public void map(Object key, Text value, Context context//實現Map函數
) throws IOException,InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());//以空格爲分隔符,分割出 //各個key
while (itr.hasMoreTokens()) {//在while循環中,統計每個key的個數
word.set(itr.nextToken());
context.write(word, one);//輸出的(key,value)
}
}
}
3、MapReduce中具體實現Reducer函數:功能是統計所有機器上的(key,value)對中相同key的個數。
public static class IntSumReducer extendsReducer<Text,IntWritable,Text,IntWritable> {
//IntSumReducer類繼承與Reducer類
private IntWritable result = new IntWritable();//初始化輸入的vlaue,對應Reducer中的 //IntWritable
public void reduce(Text key, Iterable<IntWritable> values, Contextcontext
) throws IOException,InterruptedException {//實現Reducer函數
int sum = 0;
for (IntWritable val : values) {//統計所有機器上相同key的個數
sum += val.get();
}
result.set(sum);//將結果賦值給result
context.write(key, result);//輸出的(key,value)或者output.collect(key, new //IntWritable(sum));OutputCollector接口收集Mapper //和Reducer輸出的<k,v>對
}
}
4、負責運行MapReduce作業的代碼
public static void main(String[] args)throws Exception {
Configuration conf = new Configuration();//讀取Hadoop配置到conf中
String[] otherArgs = new GenericOptionsParser(conf,args).getRemainingArgs();//解析 //用戶指定的參數,獲取基本選項以及根據需要修改配置
if(otherArgs.length != 2) {//檢查輸入參數
System.err.println("Usage: wordcount <in> <out>");
System.exit(2);
}
Jobjob = new Job(conf, "word count");//實例化一道作業
job.setJarByClass(WordCount.class);//設置該作業的運行接口類
job.setMapperClass(TokenizerMapper.class);//設置該作業的map類
job.setCombinerClass(IntSumReducer.class);//設置該作業組合時的類
job.setReducerClass(IntSumReducer.class);//設置該作業的reduce類
job.setOutputKeyClass(Text.class);//設置該作業的輸出key
job.setOutputValueClass(IntWritable.class);//設置該作業的輸出值
FileInputFormat.addInputPath(job, new Path(otherArgs[0]));//設置該作業的輸入路徑
FileOutputFormat.setOutputPath(job, new Path(otherArgs[1]));//設置該作業的輸出路徑
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}