Hadoop
爲什麼要有Hadoop?
從計算機誕生到現今,積累了海量的數據,這些海量的數據有結構化、半結構化、非
結構的數據,並且這些海量的數據存儲和檢索就成爲了一大問題。
我們都知道大數據技術難題在於一個數據複雜性、數據量、大規模的數據計算。
Hadoop就是爲了解決這些問題而出現的。
Hadoop的誕生
Doug Cutting是Lucene的作者,當時Lucene面臨和谷歌同樣的問題,就是海量的數據存儲和檢索,於是就誕生了Nutch。
在這之後,谷歌的大牛就爲解決這個問題發了三篇論文(GFS、Map-Reduce、BigTable),這三篇論文總體表達的意思就是部署多臺廉價的服務器集羣,通過分佈式的方式將海量數據存儲在這個集羣上,然後利用集羣上的所有機器進行數據計算,這樣谷歌就不用買很多很貴的服務器,只需要把普通的機器組合在一起。
Doug Cutting等人就去研究這三篇論文,發現價值巨大,於是Doug Cutting等人在Nutch上實現了GFS和Map-Reduce,使得Nutch的性能飆升。
於是Doug Cutting等人就把這兩部分納入到Hadoop項目中,主要還是爲了將Hadoop項目作爲一個大數據整體化的解決方案。
所以爲什麼後面就出現了Hadoop而不是在Nutch上去做整體化大數據解決方案。
這三篇論文對應Hadoop的組件:
GFS -> HDFS 文件系統
Map-Reduce -> MR 計算框架
BigTable -> Hbase 數據庫系統
什麼是Hadoop?
Hadoop是Apache下的一個分佈式系統基礎架構,主要是爲了解決海量數據存儲和海量的數據計算問題。
在這個基礎之上發展出了的更多的技術,使得Hadoop稱爲大數據技術生態圈之一。
Hadoop發行版本
1、Apache版本最原始的版本
2、Clodera版本,在大型互聯網企業中用的比較多,軟件免費,通過服務收費。
3、Hortonworks文檔比較好
特點
高可靠:維護多個副本,假設計算元素和存儲出現故障時,可以對失敗節點重新分佈處理
高擴展:在集羣間分配任務數據,可方便的擴展數以千計的節點
高效性:並行工作
高容錯:自動保存多個副本,並且能夠對失敗任務重新分配
Hadoop組成
HDFS:一個高可靠高吞吐量的分佈式文件系統
NameNode(nn):存儲文件的元數據,如:文件名、文件目錄結構等信息
DataNode(dn):在文件系統存儲文件塊數據,以及數據的校驗和,也就是真正存儲文件內容的,只是文件大的時候會切割成一小塊一小塊的。
SecondayNameNode(2nn):用於監控HDFS狀態的輔助後臺程序,每隔一段時間就獲取HDFS的快照,就是備份和監控狀態
Yarn:作業調度與集羣資源管理框架。(Hadoop2.0加入)
ResourceManager(rm):處理客戶端請求、啓動和監控MRAppMaster、監控NodeManager,以及資源分配和調度。
NodeManager(nn):單個節點上的資源管理、處理來自ResourceManager的命令,處理來自MRAppMaster的命令。
MRAppMaster:數據切分、爲應用程序申請資源,並分配內部任務、任務監控和容錯。
Container:對任務運行環境的抽象,封裝了CPU、內存等多維資源以及環境變量、啓動命令等任務運行相關信息(hadoop內部文件操作命令和Liunx差不多)
MapReduce:分佈式離線並行計算框架。
Map階段:並行處理數據
Reduce階段:對Map階段處理的結果數據進行彙總
Common:支持其他模塊的工具模塊。
理解Hadoop組成
有一個建築工地的建造時間很緊急,設立了一個支持小組,支援各個小分隊(Common),首先1000包水泥,這些水泥要進行存儲(HDFS),假設這些水泥有防水的和不防水的,防水的水泥存到倉庫1(HDFS-dn),不防水的存儲到倉庫2(HDFS-dn),那麼就要進行記錄,哪些水泥存放到哪裏了(HDFS-nn),因爲趕工期擔心水泥可能會因爲潮溼那些問題,出現不可用,所以又準備了1000包水泥,並且每天都要對這些水泥進行檢查(HDFS-2nn)。
如果一個小分隊要領取水泥就要和工地倉儲管理人員申請,倉儲管理人員同意了,就要向公司申請人員來搬水泥(Yarn-MRAppMaster),開始調動這些人員搬運水泥(Yarn-rm),小分隊領取到了水泥之後,開始決定給修外牆的多少包水泥(Yarn-nm)。
修外牆小組就開始拿着水泥幹活了(MapReduce-Map),直到整棟樓的外牆修好了(MapReduce-Reduce),第N棟也是如此(MapReduce-Map)。
Hadoop內爲什麼要如此劃分?
數據存放在Hadoop,那麼Hadoop必然需要對數據進行管理,如果沒有一個專門管理數據存儲的組件或數據運算的組件,全部都融合在一個東西里面就會顯得很臃腫,並且組件之間只需要通過接口進行溝通,那麼各自的組件就可以僅僅自身的需求做優化等,那麼就不會影響到其他的組件。
各自的組件只需要做好自己的事情,對外提供接口接收相應的數據及返回數據,只要符合我組件規範的就運行,不符合就不運行,而不需要關心其他,專心做自己的事情,也可以使得組件之間可以單獨的運行。
Hadoop目錄
bin:程序級命令(hdfs、Yarn等)
etc:配置文件
include:類庫等文件
lib:類庫等文件
libexec:類庫等文件
sbin:hadoop系統命令(關閉、啓動等)
share:官方提供的案例等
Hadoop運行模式
本地模式:不需要啓動單獨進程,直接運行,一般測試和開發使用,一臺機器就可以運行,如果是在Liunx,跑的是本地,可以直接通過命令運行相應的jar包。
僞分佈式模式:等同於分佈式,但只有一個節點,具有集羣的配置信息和運行,由於僞分佈式只有一臺機器,可以不啓動Yarn,那麼也就算是Hadoop的HDFS啓動了,直接運行MapReduce程序的話,結果都在HDFS上,不在是在本地,如果需要交由YARN上進行資源調度和分配任務,則需要配置Yarn地址,以及指定數據獲取方式。
完全分佈式模式:多個節點一起運行,可以指定不同節點幹不同的活,比如機器1幹NameNode的活,機器2幹ResourceManger的活。
注意:啓動NameNode時,DataNode會記錄NameNode信息(id),當緩存的NameNode記錄刪除了,這個時候啓動就會報錯,這個時候就需要將NameNode格式化(刪除所有數據),之後在重新啓動。
HDFS
HDFS是什麼?
HDFS就是一個分佈式文件存儲系統,通過目錄樹來定位文件,由於分佈式特點那麼集羣中的服務器就有各自的角色。
特點
低成本:由於是衆多服務器組成的,那麼在某服務器掛了,只需要付出一臺廉價的服務器。
高容錯性:HDFS是由衆多服務器實現的分佈式存儲,每個文件都會有冗餘備份,那麼如果存儲數據的某個服務器掛了,那麼還有備份的數據,允許服務器出現故障。
高吞吐量:HDFS是一次寫多次讀的訪問模型,不允許修改文件,並簡化了數據的一致性問題。
就近原則:在數據附近執行程序,也體現出來移動計算比移動數據效率高。
可移植性:HDFS可以實現不同平臺之間的移植。
應用場景
一次寫入,多次讀取,且不支持文件的修改。
適合數據分析場景,不適合網盤應用。
HDFS數據塊
HDFS的文件在物理上是分塊存儲的,1.x版本的數據塊默認大小是64MB,2.x版本的數據塊默認塊大小是128MB,這個值是可以通過配置參數(dfs.blocksize)進行調整的。
HDFS的塊比磁盤的塊大,目的就在於要減少尋址的開銷(標準:尋址時間只佔傳輸時間的1%),如果塊設置的夠大,從磁盤傳輸數據的時間明顯就大於定位這個塊開始位置所需要的文件,因此傳輸一個由多個塊組成的文件的時間取決於磁盤傳輸速率。
HDFS常用命令(和Liunx差不多)
基本命令:hadoop fs
查看幫助:hadoop fs 或 hadoop fs -help(詳情)
創建目錄:hadoop fs -mkdir /usr
查看目錄信息:hadoop fs -ls /usr
本地剪切,粘貼到集羣:hadoop fs -moveFromLocal test.txt /usr/
追加一個文件到已存在文件的末尾:hadoop fs -appendToFile test2.txt /usr/test.txt
顯示文件內容:hadoop fs -cat /usr/test.txt
顯示一個文件末尾:hadoop fs -tail /usr/ test.txt
以字符形式打印一個文件內容:hadoop fs -text /usr/test.txt
修改文件所屬權限(-chgrp、-chomd、chown)(liunx一樣用法): hadoop fs -chmod 777 /usr/test.txt
從本地複製到hdfs:hadoop fs -copyFormLocal text.txt /usr/test
hdfs複製到本地:hadoop fs -copyToLocal /usr/ text.txt ./
從hdfs路徑拷貝到hdfs另一個路徑:hadoop fs -cp /usr/dir1 /usr/dir2
在hdfs目錄中移動文件:hadoop fs -mv /usr/test.txt /usr/dir
從hdfs下載文件到本地:hadoop fs -get /usr/test.txt ./
合併下載多個文件:hadoop fs -getmerge /usr /*.txt ./result.txt
上傳文件等於copyFormLocal:hadoop fs -put test.txt /usr
刪除文件或文件夾:hadoop fs -rmr /usr/test.txt
刪除空目錄:hadoop fs -rmdir /usr/test3
統計文件系統可用空間信息(-h格式化信息):hadoop fs -df -h
統計文件夾大小信息:hadoop fs -du -h /
統計制定目錄下的文件節點數據量(嵌套級,當前文件個數,大小):hadoop fs -count -h /usr
設置文件的副本數:hadoop fs -setrep 3 /usr/test.txt
NameNode
NameNode和SecondaryNameNode工作機制
第一階段:NameNode的工作
1、第一次啓動namenode格式化後,創建fsimage和edits文件,如果不是第一次啓動,直接加載編輯日誌和鏡像文件到內存。
2、客戶端對元數據進行操作請求
3、NameNode記錄操作日誌,更新滾動日誌。
4、NameNode在內存中對數據進行操作
第二階段:Secondary NameNode的工作
1、Secondary NameNode詢問NameNode是否需要checkpoint,直接帶回NameNode檢查結果。
2、Secondary NameNode請求執行checkpoint
3、NameNode滾動正在寫的eits日誌
4、將滾動前的編輯日誌和鏡像文件拷貝到Secondary NameNode
5、Secondary NameNode加載編輯日誌和鏡像文件到內存並且合併
6、生成新的鏡像文件fsimage.chkpoint
7、拷貝fsimage.chkpoint到NameNode
8、NameNode將fsimage.chkpoint重命名爲fsimage
說明
Fsimage文件:HDFS文件系統元數據的一個永久檢查點,其中包含HDFS文件系統所有目錄和文件,以及node序列化信息。
Edits文件:存放HDFS文件系統的所有更新操作,文件系統客戶端執行的所有寫操作日誌都會記錄到edits文件。
Secondary NameNode:在主NameNode掛了,可以從Secondary NameNode中恢復數據,但是由於同步的條件限制,會出現數據不一致。
DataNode
工作機制
集羣安全模式
NameNode啓動時,受限將鏡像文件加載進去內存,並編輯日誌文件中的各項操作,一旦內存中成功建立文件系統元數據鏡像,則創建一個新的fsimage文件和一個空的編輯日誌。
此時的NameNode開始監聽DataNode請求,但此刻,NameNode是運行在安全模式,則此時NameNode文件系統對於客戶端來說是只可讀。
系統中數據塊文件並不是由NameNode維護的,而是以塊列表的形式存儲在DataNode,在系統正常操作期間,NameNode會在內存中保留所有塊位置影像信息。
在安全模式下,各個DataNode會向NameNode發送最新的塊列表信息,NameNode瞭解到足夠多的塊信息之後,即可高效運行文件系統。
如果滿足最小複本條件,NameNode會在30秒後就退出安全模式,最小複本條件指的是整個文件系統中99%的塊都滿足最小複本級別,在啓動一個剛剛格式化的HDFS集羣時,因爲系統中還沒有塊,所以NameNode不會進入安全模式。
集羣啓動完成後自動退出安全模式。
安全模式的應用場景
銀行對賬、維護。
Java操作HDFS
Demo
public static void main(String[] args) throws IllegalArgumentException, IOException, InterruptedException, URISyntaxException {
//配置信息 Configuration configuration = new Configuration();
//獲取文件系統 FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration, "levi");
//拷貝本地文件到集羣 fileSystem.copyFromLocalFile(new Path("e:/hdfs/test.txt"), new Path("/usr/hdfs/test.txt"));
//關閉 fileSystem.close(); }
|
HDFS數據流
IO流寫流程
IO流方式上傳文件 (Java)
public void fileUpload() throws IOException, InterruptedException, URISyntaxException { //配置 Configuration configuration = new Configuration();
//文件系統 FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop102:8020"),configuration,"levi");
//獲取輸出流(上傳到服務器) - 服務器 FSDataOutputStream fsDataOutputStream = fileSystem.create(new Path("/usr/hdfs/test03.txt"));
//文件輸入流(本地上傳) FileInputStream fileInputStream = new java.io.FileInputStream(new File("E:/hdfs/test03.txt"));
//流對接 IOUtils.copyBytes(fileInputStream, fsDataOutputStream, configuration);
fsDataOutputStream.hflush(); IOUtils.closeStream(fileInputStream); IOUtils.closeStream(fsDataOutputStream);
//關閉 fileSystem.close();
} |
IO流讀流程
IO流方式下載文件 (Java)
public void readFile() throws IOException, InterruptedException, URISyntaxException { //配置 Configuration configuration = new Configuration();
//文件系統 FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration, "levi");
//輸入流(下載) 服務器 FSDataInputStream fsDataInputStream = fileSystem.open(new Path("/usr/hdfs/hadoop-2.7.2.tar.gz"));
//輸出(本地) FileOutputStream fileOutputStream = new FileOutputStream(new File("E:/hdfs/block.txt"));
//流對接 //--- 第一塊 /*byte[] buf = new byte[1024]; for (int i = 0; i < 1024*128; i++) { fsDataInputStream.read(buf); fileOutputStream.write(buf); } //關閉 fsDataInputStream.close(); fileOutputStream.close();*/
//--- 第二塊 fsDataInputStream.seek(1024 * 1024 * 128);//定位這個位置開始讀 IOUtils.copyBytes(fsDataInputStream, fileOutputStream, 1024); IOUtils.closeStream(fileOutputStream); IOUtils.closeStream(fsDataInputStream);
fileSystem.close(); } |
副本節點選擇
在海量數據的處理中,節點之間的數據傳輸速率是很重要,特別是在帶寬很稀缺的情況下,而節點和節點之間的距離越遠,那麼必然會影響數據的傳輸。
在成千的服務器集羣中,Hadoop是怎麼選擇副本節點呢?
低版本Hadoop
第一個副本在客戶端所處的節點上,但是如果客戶端是在集羣外,隨機選取一個節點
第二個副本和第一個副本位於不同機架的隨機節點上,也就是不和第一個副本在相同機架。
第三個副本和第二個副本位於相同機架,節點隨機
Hadoop2.5版本以上
第一個副本在客戶端所處節點上。如果客戶端在集羣外,隨機選一個
第二個副本和第一個副本位於相同機架,隨機節點
第三個副本位於不同機架,節點隨機
HDFS誤區
小文件存儲
每個文件均按照塊存儲,每個塊的元數據存儲在NamNode的內存中(一個文件/目錄/文件塊一般佔有150字節的元數據內存空間),因此Hadoop存儲小文件會非常低效,因爲大量小文件會耗盡NameNode中大部分內存,但存儲小文件所需要的磁盤容量和存儲這些文件原始內容所需要的磁盤空間相比也不會增多。
例如:上傳一個文件1MB,那麼這個文件會在HDFS中的一個塊存儲着,這個塊默認是128MB,那麼是不是佔用了128MB的磁盤空間呢?
每一個塊128MB只是HDFS的邏輯上的劃分,所以在磁盤佔用空間還是1MB,只有當一個或多個文件在一個塊內超過128MB,之後將這個文件進行切割。
副節點處理
HDFS是先把當前這個節點處理完,在去處理副本節點的。
回收站
回收站默認是不啓用的,在core-site.xml文件中的配置fs.trash.interval默認是爲0.
HDFS全過程
MapReduce
MapReduce是什麼?
MapReduce是一個分佈式運算程序的編程框架,是用戶開發基於Hadoop的數據分析應用的核心框架。
MapReduce核心功能是將用戶編寫的業務邏輯代碼和自帶默認組件整合成一個完整的分佈式運算程序,併發的運行在一個Hadoop集羣上。
作用
由於硬件資源限制,海量數據無法在單機上處理,單機版程序擴展到集羣進行分佈式運算,增加程序的複雜度和開發難度。
MapReduce框架就是要使得開發人員開源將絕大部分工作集中在業務邏輯的開發上,而分佈式運算的複雜性交由MapReduce來處理。
特點
適合數據複雜度運算
不適合算法複雜度運算
不適合實時計算、流式計算
核心思想
分佈式的運算程序最少需要分成兩個階段:
第一個階段:MapTask併發實例,完全並行運行,互不相干
第二個階段:ReduceTask併發實例,互不相干,但是他們的數據依賴於上一個階段的所有MapTask併發實例的輸出
MapReduce編程模型只能包含一個Map階段和Reduce階段,如果用戶的業務邏輯非常複雜,那就只能多個MapReduce程序,串行運行。
總結
Map:並行處理任務(運算)。
Reduce:等待相關的所有Map處理完任務,在將任務數據彙總輸出。
MRAppMaster:負責整個程序的過程調度和狀態協調。
MapReduce進程
一個完整的MapReduce程序在分佈式允許時有三類實例進程:
MRAppMaster:負責整個程序的過程調度和狀態協調。
MapTask:負責Map階段的整個數據處理流程。
ReduceTask:負責Reduce階段的整個數據處理流程。
序列化
序列化就是把內存中的對象轉換成字節序列(或其他數據傳輸協議),以便於存儲(持久化)和網絡傳輸。
而序列化就是Map到Reducer的橋樑。
Java序列化是一個重量級的序列化框架(Serializable),使用這個框架進行序列化後會附帶很多額外信息(各種校驗信息、header等),不便於網絡傳輸,所以Hadoop自己開發了一套序列化機制(Writable),精確、高效。
Java類型 |
Hadoop Writable類型 |
boolean |
BooleanWritable |
byte |
ByteWritable |
int |
IntWritable |
float |
FloatWritable |
long |
LongWritable |
double |
DoubleWritable |
string |
Text |
map |
MapWritable |
array |
ArrayWritable |
備註:自定義的反序列類中的write方法和read方法中DataOutput和DataInput這兩個類所提供的方法中,對應Java類型String的方法,分別是writeUTF()和readUTF()。
實例(統計單詞)
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private Text key = new Text();
@Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //讀取每一行 String line = value.toString();
//切割出每一個單詞 String [] words = line.split("\t");
//將讀取到的每一個單詞都寫出,並且值都爲1,因爲是在map計算完後到reduce進行彙總,形成Key 多個Value for (String word : words) { //每次文件內的讀取一行都調用一次map,那樣就形成了調用多次map,那樣的話就不用創建多個key對象了 this.key.set(word); context.write(this.key, new IntWritable(1)); } } } |
public class WorkCountReducer extends Reducer<Text, IntWritable, Text, IntWritable>{
@Override protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException { //這裏就形成 多個值的彙總結果,那麼將這個值多個進行彙總後,統一歸併到一個key,就形成了一個key對應多個value int count = 0; for (IntWritable value : values) { count += value.get(); } context.write(key, new IntWritable(count));
} }
|
public class WordCountDriver { public static void main(String[] args) throws Exception { //配置 Configuration configuration = new Configuration();
//任務運行 Job job = Job.getInstance(configuration); job.setJarByClass(WordCountDriver.class);
//運算類和彙總類 job.setMapperClass(WordCountMapper.class); job.setReducerClass(WorkCountReducer.class);
//運算和彙總輸入和輸出 job.setMapOutputKeyClass(Text.class); job.setMapOutputValueClass(IntWritable.class);
//最終輸出 job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class);
//運算文件的輸入和結果輸出 FileInputFormat.setInputPaths(job, new Path("E:/hadooptest/mapreduce/input")); FileOutputFormat.setOutputPath(job, new Path("E:/hadooptest/mapreduce/output"));
//提交 job.submit();
//等待 boolean result = job.waitForCompletion(true); System.exit(result ? 0 : 1); } } |
程序流程分析
1、MapReduce程序讀取輸入目錄存放的相應文件。
2、客戶端在submit方法執行之前,獲取到待處理的數據信息,讓後根據急羣衆參數配置形成一個任務分配規劃。
1、建立連接
2、創建提交任務的代理(本地:LocalRunner、遠程:YarnRunner)
3、創建給集羣提交數據的stag路徑
4、獲取到任務id,並創建任務路徑
5、獲取到任務jar包,拷貝jar包到集羣(這個jar就是程序運行的業務代碼)
6、計算切片,生成切片規劃文件
computeSliteSize(Math.max(minSize,Math.max(maxSize,blocksize)))=blocksize=128MB
7、提交任務,返回提交狀態
3、客戶端提交job.split、jar包、job.xml等文件給Yarn,Yarn中的resourcemanager啓動MRAppMater。
4、MRAppMater啓動後根據job的描述信息,計算出需要的MapTask實例數量,然後向集羣申請機器,啓動相應數量的Map Task進程。
5、MapTask利用客戶指定的InputFormat來讀取數據,形成KV對。
6、MapTask將輸入KV對傳遞給客戶定義的map()方法,做邏輯運算。
7、map()運算完畢後將運算結果的KV對,手機到MapTask緩存。
8、MapTask緩存中的KV對按照K分區排序後不斷寫到磁盤文件。
9、MRAppMaster監控到所有MapTask進程任務完成後,會根據用戶指定的參數啓動相應數量的ReduceTask進程,並告知ReduceTask進程要處理的數據分區。
10、ReduceTask進程啓動後,根據MRAppMaster告知待處理數據所在位置,從N臺MapTask運行所在的機器上獲取到N個MapTask輸出結果文件,並在本地運行重新歸併排序,按照相同Key的KV爲一個組,調用客戶定義的reduce()方法進行邏輯運算。
11、ReduceTask運算完畢後,調用客戶指定的OuputFormat將結果數據輸出(文件)到外部存儲。
說明:
切片是邏輯上的切片
規劃文件就是裏面描述了切多少個片,每個片是怎麼樣的。
數據切片
MapTask的並行任務是否越多越好?並行度是如何決定的?MapTask到底開多少個合適?
1、一個job的map()階段並行度(MapTask開幾個),由客戶端在提交job時決定。
2、每一個Split切片分配一個MapTask並行實例處理。
3、默認情況下切片大小=塊大小(blocksize)
4、切片時不考慮數據集整體,而是針對每一個文件單獨切片(這個是邏輯上的劃分)
切片流程
1、獲取到數據存儲目錄
2、找到要便利處理目錄下的每一個文件
3、讀取第一個文件test.txt(257MB)
1、獲取文件大小
2、計算分片大小,每次切片時,都要判斷剩下的部分是否大於塊大小的1.1倍,大於就在劃分一個塊切片
切片:
第一塊:128MB
第二塊:129MB / 128MB = 1.0078125
1.0078125 < 1.1 = 不在切片,反之繼續切
源碼:computeSliteSize(Math.max(minSize,Math.max(naxSize,blocksize)));
3、將切片信息寫到一個切片規劃文件(說明文件)中
4、整個切片的核心過程在於getSplit()方法(看submit()源碼)中完成,數據切片只是邏輯上對輸入數據進行切片,並不會在磁盤上,將文件切分進行存儲。
InputSplit只是記錄了分片的元數據信息。比如:起始位置、長度、所在的節點列表等。
注意:塊是HDFS上物理存儲的數據,切片只是邏輯上的劃分。
5、提交切片規劃文件(說明文件)到Yarn上,Yarn上的MrAppMaster就根據切片規劃文件(說明文件)計算開啓的MapTask個數(多少個切片就多少個MapTask)。
FileInputFormat中默認的切片機制
1、簡單按照文件內容長度切片
2、切片大小,默認是塊大小
3、切片時不考慮數據集整體性,而是逐個文件的單獨切片,循環遍歷每一個文件。
MaxSize(切片最大值):如果比塊大小還小,則會讓切片變小。
MinSize(切片最小值):如果比塊大小還大,則會讓切片變得比塊還大。
假設:塊大小128MB
MaxSize設爲100MB
切片後的存儲佔塊大小100MB
小文件切片處理
如果有大量的小文件,而每一個文件都是一個單獨的切片,都會各自交給一個MapTask處理,那麼需要開啓大量的MapTask,則會產生大量的MapTask,導致處理效率低下。
解決方案
1、在數據處理前端,先把小文件合併成大文件,在上傳到HDFS做後續分析
2、如果已經有大量的小文件存在HDFS,使用CombineFileInputFormat進行處理,CombineFileInputFormat的切片邏輯跟TextFileInputFormat不同,他可以將多個小文件邏輯上規劃到一個切片中,這樣多個小文件就可以交給一個MapTask。
3、優先滿足最小切片大小,不超過最大切片大小的前提下。
文件合併
//-------- 使用提供的自定義類,指定切片大小 job.setInputFormatClass(CombineTextInputFormat.class); //最大輸入切片大小,一個文件的大小是4M就開始切,算法是1.1倍 CombineTextInputFormat.setMaxInputSplitSize(job, 4194304); //最小輸入切片大小,多個文件合併到了一起,到了2M就切,算法是1.1倍,優先滿足最小切片大小 CombineTextInputFormat.setMinInputSplitSize(job, 2097152); |
備註:在運行日誌中查找number of就可以看到了
Shuffle機制
1、在MapReduce中,Map階段處理的數據如何傳遞給Reduce階段額,是MapReduce框架中關機的一個流程,這個流程就叫Shuffle。
2、Shuffle(洗牌、發牌):核心就是數據分區、排序、緩存
3、MapTaks輸出處理結果數據,分發給ReduceTask並在分發過程中對數據按照Key進行分區和排序。
Shuffle機制緩存流程圖
Shuffle是MapReduce處理流程中一個過程,每一個步驟都是分散在各個MapTask和ReduceTask節點上。
MapReduce詳細運行流程
總結
MapReduce詳細運行流程圖就是一個流水線一般的作業,從左向右過去,而在開發的過程中,需要使用到什麼組件,這些組件會起到什麼作用,在哪一個時間起作用,都可以在這個圖中詳細的描述
分區
自定義分區
public class MyPartitioner extends Partitioner<Text, FlowBean>{ @Override public int getPartition(Text key, FlowBean value, int numPartitions) { //拿到手機號碼前三位 String phoneNum = key.toString().substring(0, 3);
//建立5個分區,從0開始 int partition = 4;
//判斷 if("135".equals(phoneNum)) { partition = 0;
}else if("136".equals(phoneNum)) { partition = 1;
}else if("137".equals(phoneNum)) { partition = 2;
}else if("138".equals(phoneNum)) { partition = 3; }
return partition; } } |
//設置分區類 //如果沒有設置分區,那麼則會按照塊大小去計算什麼時候進行分區 job.setPartitionerClass(MyPartitioner.class);
//設置ReduceTask數量 job.setNumReduceTasks(5); |
總結
reduce數量小於分區數量就會報錯。
reduce數量是1,那麼則所有結果輸出到一個文件內,即便配置了分區也不會去跑分區的代碼(執行分區)
reduce數量大於分區數量,輸出的其他文件爲空
分區數量 = reduce數量,按照分區數量輸出結果文件數量
分區就是對map的結果數據進行二次處理,從而再去決定是否影響輸出的reduce結果輸出。
排序
MapTask和ReduceTask均會對數據(按Key排序)進行排序,這個操作屬於Hadoop默認行爲,任何應用程序中的數據均會被排序,而不管邏輯上是否需要。
對於MapTask,他會把處理的結果暫時放到一個緩衝區,當緩衝區使用率達到了閾值就對緩衝區的數據進行排序,並將這些有序的數據寫到磁盤上,而當數據處理完後,他會對磁盤上所有文件進行一次合併,將這些文件合併成一個有序的文件。
對於ReduceTask,他從每個MapTask遠程拷貝相應的數據文件,如果文件大小超過一定閾值,則放到磁盤上,否則放到內存中,如果磁盤上文件數目達到一定閾值,則進行一次合併,生成一個更大的文件,如果內存文件大小或數目超過閾值,則進行合併後將數據寫出到磁盤上,當所有的數據拷貝完畢後,再統一的對內存核磁盤上的所有文件進行一次合併。
自定義排序
public class FlowBean implements WritableComparable<FlowBean> {
private Long sum;
public FlowBean() { super(); }
@Override public void write(DataOutput dataOutput) throws IOException { dataOutput.writeLong(sum); }
@Override public void readFields(DataInput dataInput) throws IOException { sum = dataInput.readLong(); }
public Long getSum() { return sum; }
public void setSum(Long sum) { this.sum = sum; }
@Override public String toString() { return sum.toString(); }
@Override public int compareTo(FlowBean o) { return this.sum > o.getSum() ? -1 : 1; } } |
總結
Shullt規定Key是要進行排序的,如果作爲Key是必須要實現WritableComparable接口的。
Combiner合併
ReducrTask是接收總的MapTask結果,Combiner在每一個MapTask運行的,對每每個MapTask的結果彙總(局部彙總),將MapTask彙總後之後進行壓縮傳輸,可以減少網絡傳輸量。
但是Combiner的前提是不能影響到最終的業務邏輯,如果是累加求和是沒有問題的,如果是求平均值就有問題的。
如:
1、在每一個MapTask進行求平均值之後在ReduceTask再求一次平均值,結果是不一樣的。
2、將MapTask的數據全部彙總到ReduceTask之後再求平均值。
這兩種結果是不一樣的。
自定義Combiner合併
public class WordCountCombiner extends Reducer<Text, IntWritable, Text, IntWritable>{ @Override protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException { int count = 0; for (IntWritable intWritable : values) { count += intWritable.get(); } context.write(key, new IntWritable(count)); } } |
//reduce是接收總的MapTask彙總,combiner在每一個maptask運行的,對每一個maptask彙總 //如:每一個maptask都進行彙總,之後進行壓縮傳輸 job.setCombinerClass(WordCountCombiner.class); |
分組
就是對分區排序好的數據,在進行一次合併分類開來,再一次合併的話,就有個比較標識,如果兩個數據標識是一樣的,就認爲是一組數據,最後過濾去重,最終得到有哪些組。
自定義分組
public class OrderGoupingComparator extends WritableComparator {
protected OrderGoupingComparator() { super(OrderBean.class, true); } @Override public int compare(WritableComparable a, WritableComparable b) { OrderBean abean = (OrderBean) a; OrderBean bbean = (OrderBean) b; // 將orderId相同的bean都視爲一組 return abean.getOrderId().compareTo(bbean.getOrderId());
} } |
//設置Reduce端分組 job.setGroupingComparatorClass(OrderGoupingComparator.class);
//分區 job.setPartitionerClass(OrderPartition.class); job.setNumReduceTasks(3); |
自定義InputFormat
對小文件的輸入進行合併處理。
1、設置文件不可切割
2、讀取到整個文件,並且整個文件的數據作爲value輸出給MapTask(將分片傳進去去讀取後,將讀取到的所有分片數據合併給到MapTask)
3、MapTask在對合並後的數據做操作
public class DistriutedCacheMapper extends Mapper<LongWritable, Text, Text, NullWritable>{
private Map<String, String> map = new HashMap<>();
private Text key = new Text();
@Override protected void setup(Mapper<LongWritable, Text, Text, NullWritable>.Context context) throws IOException, InterruptedException { //獲取緩存文件,這個文件給加載進了hadoop系統了,在緩存中的根,可以直接通過名字調用 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("pd.txt")))); String line; while(StringUtils.isNotEmpty(line = bufferedReader.readLine())) { //數據處理 String [] strings = line.split("\t");
//將數據放到緩存集合中 map.put(strings[0], strings[1]); } bufferedReader.close(); }
@Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //讀取到order的每一行 String[] strings = value.toString().split("\t");
String orderId = strings[0];
String name = map.get(orderId);
this.key.set(value.toString() + "\t" + name); context.write(this.key, NullWritable.get()); } }
|
自定義OutputFormat
獲取到ReduceTask的運行結果,自定義要輸出的結果數據和文件
public class FilterRecordWriter extends RecordWriter<Text, NullWritable>{
private FileSystem fileSystem; private FSDataOutputStream aCreate; private FSDataOutputStream oCreate;
public FilterRecordWriter(TaskAttemptContext job) { try { fileSystem = FileSystem.get(job.getConfiguration()); //創建輸出文件路徑 Path aPath = new Path("E:\\hadooptest\\mapreduce\\a.log"); Path oPath = new Path("E:\\hadooptest\\mapreduce\\o.log");
//創建輸出流 aCreate = fileSystem.create(aPath); oCreate = fileSystem.create(oPath); } catch (IOException e) { e.printStackTrace(); } }
@Override public void write(Text key, NullWritable value) throws IOException, InterruptedException { if(key.toString().contains("levi")) { aCreate.write(key.toString().getBytes()); }else { oCreate.write(key.toString().getBytes()); } }
@Override public void close(TaskAttemptContext context) throws IOException, InterruptedException { if(null != aCreate) { aCreate.close(); } if(null != oCreate) { oCreate.close(); } } } |
//設置輸出類爲自定義輸出類 job.setOutputFormatClass(FliterOutputFormat.class); //雖然自定義了一個輸出,但是還是要輸出,因爲有個成功狀態標識文件要輸出,不然會報錯 FileOutputFormat.setOutputPath(job, new Path("E:\\hadooptest\\mapreduce\\output")); |
計數器
Hadoop爲每一個作業維護了若干個內置計算器,以描述多項指標,例如:某些計數器記錄已處理的字節數等。
計數器的使用
context.getCounter("counterGroup組名","countera變量"),increment(1); |
說明:計數器的結果在程序運行後的控制檯日誌中可查看
總結
HDFS根據配置在各個節點存儲數據,並且存儲相應的副本數據。
MapReduce就是在需要執行無論是MapTask或ReduceTask的時候,會先去ResouceManager去詢問,任務要在哪裏運行,其實ResourceManger就是看要運行這個任務的輸入數據在哪個節點,從而去告知這個節點執行任務,那麼就形成了直接移動計算,而不是移動數據的方式。
因爲數據可能存儲在服務器1或服務器2…服務器,那麼不需要移動數據,負責執行任務的服務器,到指定的路徑,下載要運算的任務jar包,直接在本地運行,那麼當數據非常大的時候就不用去移動數據。
YARN
Yarn是什麼?
Yarn是一個資源調度平臺,負責爲運算程序提供服務器運算資源,相當於一個分佈式的操作系統平臺,在之前說了,可以配置MapReduce在Yarn之上運行,所以MapReduce等運算程序則相當於運行於操作系統之上的應用程序。
Yarn機制
1、不需要知道用戶提交的程序運行機制,只要符合Yarn規範的資源請求機制即可使用Yarn,Spark、Storm等運算框架都可以整合在Yarn上運行,意味着與用戶程序完全解耦。
2、只提供運算資源的調度,程序向Yarn申請資源,Yarn負責分配資源
3、Yarn總的資源調度是ResourceManager,提供運算資源的角色叫NodeManager。
4、Yarn作爲一個通用的資源調度平臺,企業以前存在的各種運算集羣都可以整合在一個物理集羣上,提高資源利用率,方便數據共享。
Yarn作業流程
1、客戶端將MapReduce程序提交到客戶端所在的節點。
2、YarnRunner就向RsourceManager申請一個Application。
3、RsourceManager內部運行一下,看看哪個節點離提交申請節點近,以及系統資源等,內部運行完了,就將應用程序資源路徑返回給YarnRunner。
4、程序就將運行程序所需要的資源提交到HDFS上。
5、程序資源提交完後,申請運行MRAppMaster。
6、RsourceManager將用戶請求轉化爲一個task(任務),並尋找最適合的NodeManager,並將任務分配給這個NodeManager。
7、NodeManager領取到任務後,創建容器(Container),併產生MRAppMaster。
8、MRAppMaster向RsourceManager申請運行N個MapTask容器(切片文件中有說明)。
9、RsourceManager又尋找了一下,將MapTask分配給另外兩個NodeManager,這兩個NodeManager領取到任務,並且創建容器(Container)。
10、RsourceManager告知申請運行MapTask容器的NodeManger,向那兩個接受到任務的NodeManager發送程序啓動腳本,這兩個NodeManger就分別啓動MapTask,MapTask對數據進行分區排序。
11、MRAppMaster看程序都跑完了,趕緊申請2個容器,運行ReduceTask。
12、ReduceTask的容器向MapTask容器獲取相應分區的數據,並執行任務。
13、程序運行完畢後,MapResource會向RsourceManager註銷自己。
Hadoop – HelloWorld
準備
1、三臺機器
2、ssh
3、防火牆
配置
JAVA_HOME
hadoop-env.sh
yarn-env.sh
mapred-env.sh
core-site.xml
<!-- 指定HDFS中NameNode的地址 -->
<property>
<name>fs.defaultFS</name>
<value>hdfs://hadoop-senior00-levi.com:8082</value>
</property>
<!-- 指定hadoop運行時產生文件的存儲目錄 -->
<property>
<name>hadoop.tmp.dir</name>
<value>/opt/module/hadoop-2.5.0-cdh5.3.6/data/tmp</value>
</property>
yarn-site.xml
<!-- Site specific YARN configuration properties -->
<!-- reducer獲取數據的方式 -->
<property>
<name>yarn.nodemanager.aux-services</name>
<value>mapreduce_shuffle</value>
</property>
<!-- 指定YARN的ResourceManager的地址 -->
<property>
<name>yarn.resourcemanager.hostname</name>
<value>hadoop-senior01-levi.com</value>
</property>
<!-- 任務歷史服務 -->
<property>
<name>yarn.log.server.url</name>
<value>http://hadoop-senior00-levi.com:19888/jobhistory/logs/</value>
</property>
hdfs-site.xml
<!-- 指定seconddaryNameNode地址,主要這個是避免NameNode掛了 -->
<property>
<name>dfs.namenode.secondary.http-address</name>
<value>hadoop-senior02-levi.com:50090</value>
</property>
<!-- 指定name.dir,默認就是,但是避免未啓用,設置一下 -->
<property>
<name>dfs.namenode.name.dir</name>
<value>/opt/module/hadoop-2.5.0-cdh5.3.6/data/tmp/name</value>
</property>
mapred-site.xml
<!-- 指定mr運行在yarn上 -->
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
<!-- 配置 MapReduce JobHistory Server 地址 ,默認端口10020 -->
<property>
<name>mapreduce.jobhistory.address</name>
<value>hadoop-senior00-levi.com:10020</value>
</property>
<!-- 配置 MapReduce JobHistory Server web ui 地址, 默認端口19888 -->
<property>
<name>mapreduce.jobhistory.webapp.address</name>
<value>hadoop-senior00-levi.com:19888</value>
</property>
slaves
hadoop-senior00-levi.com
hadoop-senior01-levi.com
hadoop-senior02-levi.com
Hadoop-HA
Hadoop爲什麼要有HA?
我們都知道NameNode是存儲了所有數據的路徑,在Hadoop第一個版本是沒有HA,單臺的NameNode節點掛了,那麼整個數據就沒辦法訪問了,
那個的工程師就自己寫一個腳本去解決這個問題,定時的拷貝NameNode的fsimage和edits到別的服務器,但是數據量大的時候,拷貝就很慢了,而且工程師半夜正在和周公下棋的時候,NameNode掛了,那就很尷尬了。
雖然可以到第二天早上來恢復,但是數據量那麼大的時候,太慢了,滿足不了需求。
所以Hadoop爲了解決這個問題,在後面的版本繼承了HA(高可用)。
Hadoop-HA是什麼?
Hadoop-HA(高可用)就是在一臺服務器掛了,第二臺服務器可以馬上頂上去。
兩個基本問題:
1、第一臺服務器和第二臺服務器的數據必須要同步。
Hadoop-HA通過edits-log的變化,來將數據寫入到JournalNode節點裏面去,以分享給其他的NameNode。
2、要解決第一臺和第二臺服務器同時啓用的情況,在這種情況下,子節點怎麼提交數據,會提交到兩臺服務器,但是又會出現搶佔資源的情況,(給一個人送東西和給兩個人送東西所耗費的體力是不言而喻的),
這個問題在Hadoop-HA中稱爲腦裂,藉助第三方框架(Zookeeper)實現隔離機制來解決腦裂這個問題。
Hadoop–HA 實現
NameNode
hdfs-site.xml
<configuration> <!-- 指定HDFS副本的數量 --> <property> <name>dfs.replication</name> <value>3</value> </property>
<!-- 指定seconddaryNameNode地址,主要這個是避免NameNode掛了 --> <property> <name>dfs.namenode.secondary.http-address</name> <value>hadoop104:50090</value> </property>
<!-- 檢查節點 --> <property> <name>dfs.namenode.checkpoint.period</name> <value>120</value> </property>
<!-- 指定name.dir,默認就是,但是避免未啓用,設置一下 --> <property> <name>dfs.namenode.name.dir</name> <value>/opt/module/hadoop-2.7.2/data/tmp/dfs/name</value> </property>
<!-- 服役新節點 --> <property> <name>dfs.hosts</name> <value>/opt/module/hadoop-2.7.2/etc/hadoop/dfs.hosts</value> </property>
<!-- 退役配置 --> <property> <name>dfs.hosts.exclude</name> <value>/opt/module/hadoop-2.7.2/etc/hadoop/dfs.hosts.exclude</value> </property>
<!-- 完全分佈式集羣名稱 --> <property> <name>dfs.nameservices</name> <value>mycluster</value> </property> <!-- 集羣中NameNode節點都有哪些 --> <property> <name>dfs.ha.namenodes.mycluster</name> <value>nn1,nn2</value> </property> <!-- nn1的RPC通信地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn1</name> <value>hadoop102:8020</value> </property> <!-- nn2的RPC通信地址 --> <property> <name>dfs.namenode.rpc-address.mycluster.nn2</name> <value>hadoop103:8020</value> </property> <!-- nn1的http通信地址 --> <property> <name>dfs.namenode.http-address.mycluster.nn1</name> <value>hadoop102:50070</value> </property> <!-- nn2的http通信地址 --> <property> <name>dfs.namenode.http-address.mycluster.nn2</name> <value>hadoop103:50070</value> </property> <!-- 指定NameNode元數據在JournalNode上的存放位置 --> <property> <name>dfs.namenode.shared.edits.dir</name> <value>qjournal://hadoop102:8485;hadoop103:8485;hadoop104:8485/mycluster</value> </property> <!-- 配置隔離機制,即同一時刻只能有一臺服務器對外響應 --> <property> <name>dfs.ha.fencing.methods</name> <value>sshfence</value> </property> <!-- 使用隔離機制時需要ssh無祕鑰登錄--> <property> <name>dfs.ha.fencing.ssh.private-key-files</name> <value>/home/levi/.ssh/id_rsa</value> </property> <!-- 聲明journalnode服務器存儲目錄--> <property> <name>dfs.journalnode.edits.dir</name> <value>/opt/module/hadoop/data/jn</value> </property> <!-- 關閉權限檢查--> <property> <name>dfs.permissions.enable</name> <value>false</value> </property>
<!-- 訪問代理類:client,mycluster,active配置失敗自動切換實現方式--> <property> <name>dfs.client.failover.proxy.provider.mycluster</name> <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value> </property> <!-- 開啓NameNode自動故障轉移 --> <property> <name>dfs.ha.automatic-failover.enabled</name> <value>true</value> </property>
</configuration> |
core-site.xml
<configuration> <!-- 指定HDFS中NameNode的地址 --> <!-- <property> <name>fs.defaultFS</name> <value>hdfs://hadoop102:8020</value> </property> --> <!-- NameNode地址改爲集羣的地址 -->
<property> <name>fs.defaultFS</name> <value>hdfs://mycluster</value> </property>
<!-- 指定hadoop運行時產生文件的存儲目錄 --> <property> <name>hadoop.tmp.dir</name> <value>/opt/module/hadoop-2.7.2/data/tmp</value> </property>
<!-- 配置回收時間,1分鐘 --> <property> <name>fs.trash.interval</name> <value>1</value> </property> <!-- 設置回收站,回收用戶 --> <property> <name>hadoop.http.staticuser.user</name> <value>levi</value> </property>
<!-- ZK集羣 --> <property> <name>ha.zookeeper.quorum</name> <value>hadoop102:2181,hadoop103:2181,hadoop104:2181</value> </property> </configuration> |
ResourceManager
hdfs-site.xml
<configuration> <!-- 指定YARN的ResourceManager的地址 --> <!-- <property> <name>yarn.resourcemanager.hostname</name> <value>hadoop103</value> </property> -->
<!-- reducer獲取數據的方式 --> <property> <name>yarn.nodemanager.aux-services</name> <value>mapreduce_shuffle</value> </property>
<!-- 日誌 --> <property> <name>yarn.log-aggregation-enable</name> <value>true</value> </property>
<property> <name>yarn.log.server.url</name> <value>http://hadoop103:19888/jobhistory/logs/</value> </property>
<property> <name>yarn.log-aggregation.retain-seconds</name> <value>86400</value> </property>
<!--啓用resourcemanager ha--> <property> <name>yarn.resourcemanager.ha.enabled</name> <value>true</value> </property>
<!--聲明兩臺resourcemanager的地址--> <property> <name>yarn.resourcemanager.cluster-id</name> <value>cluster-yarn1</value> </property>
<property> <name>yarn.resourcemanager.ha.rm-ids</name> <value>rm1,rm2</value> </property>
<!-- 配置地址1 --> <property> <name>yarn.resourcemanager.hostname.rm1</name> <value>hadoop103</value> </property>
<!-- 配置地址2 --> <property> <name>yarn.resourcemanager.hostname.rm2</name> <value>hadoop104</value> </property>
<!--指定zookeeper集羣的地址--> <property> <name>yarn.resourcemanager.zk-address</name> <value>hadoop102:2181,hadoop103:2181,hadoop104:2181</value> </property>
<!--啓用自動恢復--> <property> <name>yarn.resourcemanager.recovery.enabled</name> <value>true</value> </property>
<!--指定resourcemanager的狀態信息存儲在zookeeper集羣--> <property> <name>yarn.resourcemanager.store.class</name> <value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value> </property>
</configuration> |