傳送門:
大數據學習系列:Hadoop3.0苦命學習(一)
大數據學習系列:Hadoop3.0苦命學習(二)
大數據學習系列:Hadoop3.0苦命學習(三)
大數據學習系列:Hadoop3.0苦命學習(四)
本節內容包括:MapReduce詳解及兩個實驗
目錄
1 MapReduce 介紹
MapReduce思想在生活中處處可見。或多或少都曾接觸過這種思想。MapReduce的思
想核心是“分而治之”,適用於大量複雜的任務處理場景(大規模數據處理場景)。
-
Map 負責“分”,即把複雜的任務分解爲若干個“簡單的任務”來並行處理。可以進行拆
分的前提是這些小任務可以並行計算,彼此間幾乎沒有依賴關係。 -
Reduce 負責“合”,即對map階段的結果進行全局彙總。
-
MapReduce 運行在yarn集羣
- ResourceManager
- NodeManager
這兩個階段合起來正是MapReduce思想的體現。
還有一個比較形象的語言解釋 MapReduce:
我們要數圖書館中的所有書。你數1號書架,我數2號書架。這就是“Map”。我們人越多,
數書就更快。
現在我們到一起,把所有人的統計數加在一起。這就是“Reduce”。
1.1 MapReduce 設計構思和框架結構
MapReduce 是一個分佈式運算程序的編程框架,核心功能是將用戶編寫的業務邏輯代碼和自帶默認組件整合成一個完整的分佈式運算程序,併發運行在Hadoop集羣上。
既然是做計算的框架,那麼表現形式就是有個輸入(input),MapReduce操作這個輸
入(input),通過本身定義好的計算模型,得到一個輸出(output)。
Hadoop MapReduce構思:
- 分而治之
- 對相互間不具有計算依賴關係的大數據,實現並行最自然的辦法就是採取分而治之的策略。並行計算的第一個重要問題是如何劃分計算任務或者計算數據以便對
劃分的子任務或數據塊同時進行計算。不可分拆的計算任務或相互間有依賴關係
的數據無法進行並行計算! - 統一構架,隱藏系統層細節
- 如何提供統一的計算框架,如果沒有統一封裝底層細節,那麼程序員則需要
考慮諸如數據存儲、劃分、分發、結果收集、錯誤恢復等諸多細節;爲此,MapReduce設計並提供了統一的計算框架,爲程序員隱藏了絕大多數系統
層面的處理細節。 - MapReduce 最大的亮點在於通過抽象模型和計算框架把需要做什麼(what
need to do)與具體怎麼做(how to do)分開了,爲程序員提供一個抽象和高層的編程接口和框架。程序員僅需要關心其應用層的具體計算問題,僅需編寫少量的處理應用本身計算問題的程序代碼。如何具體完成這個並行計算任務所相關的諸多系統層細節被隱藏起來,交給計算框架去處理:從分佈代碼的執行,到大到數千小到單個節點集羣的自動調度使用。
- 如何提供統一的計算框架,如果沒有統一封裝底層細節,那麼程序員則需要
- 對相互間不具有計算依賴關係的大數據,實現並行最自然的辦法就是採取分而治之的策略。並行計算的第一個重要問題是如何劃分計算任務或者計算數據以便對
- 構建抽象模型: Map和Reduce
- MapReduce 借鑑了函數式語言中的思想,用Map和Reduce兩個函數提供了高層
的並行編程抽象模型- Map: 對一組數據元素進行某種重複式的處理;
- Reduce: 對Map的中間結果進行某種進一步的結果整理。
- Map 和Reduce爲程序員提供了一個清晰的操作接口抽象描述。MapReduce
處理的數據類型是鍵值對。
- MapReduce 中定義瞭如下的Map和Reduce兩個抽象的編程接口,由用戶去編程
實現:- Map: (k1; v1) → [(k2; v2)]
- Reduce: (k2; [v2]) → [(k3; v3)]
- MapReduce 借鑑了函數式語言中的思想,用Map和Reduce兩個函數提供了高層
MapReduce 框架結構
一個完整的mapreduce程序在分佈式運行時有三類實例進程:
MRAppMaster
負責整個程序的過程調度及狀態協調MapTask
負責map階段的整個數據處理流程ReduceTask
負責reduce階段的整個數據處理流程
- 一種分佈式計算模型,解決海量數據的計算問題
- MapReduce 將整個並行計算過程抽象到兩個函數
- Map(映射):對一些獨立元素組成的列表的每一個元素進行指定的操作,可以高度並行。
- Reduce(化簡):對一個列表的元素進行合併。
- 一個簡單的 MapReduce 程序只需要指定map()、reduce()、input 和 output,剩下的事由框架完成。
2 MapReduce 編程規範
MapReduce 的開發一共有八個步驟, 其中 Map 階段分爲 2 個步驟,Shuffle 階段 4 個步驟,Reduce 階段分爲 2 個步驟
Map 階段 2 個步驟
- 設置 InputFormat 類, 將數據切分爲 Key-Value (K1和V1) 對, 輸入到第二步
- 自定義 Map 邏輯, 將第一步的結果轉換成另外的 Key-Value (K2和V2) 對, 輸出結果
Shuffle 階段 4 個步驟
- 對輸出的 Key-Value 對進行分區
- 對不同分區的數據按照相同的 Key 排序
- (可選) 對分組過的數據初步規約, 降低數據的網絡拷貝
- 對數據進行分組, 相同 Key 的 Value 放入一個集合中
Reduce 階段 2 個步驟
- 對多個 Map 任務的結果進行排序以及合併, 編寫 Reduce 函數實現自己的邏輯, 對輸
入的 Key-Value 進行處理, 轉爲新的 Key-Value (K3和V3) 輸出 - 設置 OutputFormat 處理並保存 Reduce 輸出的 Key-Value 數據
3 WordCount
需求: 在一堆給定的文本文件中統計輸出每一個單詞出現的總次數
大致流程:
Step 1 數據格式準備
- 創建一個新的文件
cd /export/services
vim wordcount.txt
- 向其中放入以下內容並保存
hello,world,hadoop
hive,sqoop,flume,hello
kitty,tom,jerry,world
hadoop
- 上傳到 HDFS
hdfs dfs -mkdir /wordcount/
hdfs dfs -put wordcount.txt /wordcount/
查看結果:
上傳成功。
下面開始寫代碼,整體代碼如下:
Step 2. Mapper
package cn.itcast.mapreduce;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/*
Mapper的泛型:
KEYIN:k1的類型 行偏移量 LongWritable
VALUEIN:v1的類型 一行的文本數據 Text
KEYOUT:k2的類型 每個單詞 Text
VALUEOUT:v2的類型 固定值1 LongWritable
*/
public class WordCountMapper extends Mapper<LongWritable, Text, Text, LongWritable> {
// key:是k1 value:是v1 context:表示MapReduce上下文對象
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1. 對每一行字符串進行拆分
String line = value.toString();
String[] strings = line.split(",");
// 2. 遍歷,獲取每一個單詞
for (String word: strings) {
context.write(new Text(word), new LongWritable(1));
}
}
}
Step 3. Reducer
package cn.itcast.mapreduce;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/*
KEYIN:k2 Text 每個單詞
VALUEIN:v2 LongWritable 集合中泛型的類型
KEYOUT:k3 Text 每個單詞
VALUEOUT:k3 LongWritable 每個單詞出現的次數
*/
public class WordCountReducer extends Reducer<Text, LongWritable, Text, LongWritable> {
/*
reduce方法的作用是將K2和V2轉爲K3和V3
key:K2
values:集合
context:MapReduce的上下文對象
*/
@Override
protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
long count = 0;
// 1.遍歷values集合
for (LongWritable value : values) {
// 2.將集合中的值相加
count += value.get();
}
// 3.將k3和v3寫入上下文中
context.write(key, new LongWritable(count));
}
}
Step 4 定義主類, 描述 Job 並提交 Job
package cn.itcast.mapreduce;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class JobMain extends Configured implements Tool {
@Override
public int run(String[] strings) throws Exception {
// 創建一個任務對象
Job job = Job.getInstance(super.getConf(), "mapreduce_wordcount");
// 打包放在集羣運行時,需要做一個配置
job.setJarByClass(JobMain.class);
// 第一步:設置讀取文件的類:K1和V1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job, new Path("hdfs://node01:8020/wordcount"));
// 第二步:設置Mapper類
job.setMapperClass(WordCountMapper.class);
// 設置Map階段的輸出類型:k2和v2的類型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
// 第三、四、五、六步採用默認方式(分區,排序,規約,分組)
// 第七步:設置Reducer類
job.setReducerClass(WordCountReducer.class);
// 設置Reduce階段的輸出類型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
// 第八步:設置輸出類
job.setOutputFormatClass(TextOutputFormat.class);
// 設置輸出路徑
TextOutputFormat.setOutputPath(job, new Path("hdfs://node01:8020/wordcount_out"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
// 啓動一個任務
ToolRunner.run(configuration, new JobMain(), args);
}
}
運行實驗
- 打包
- 在
pom.xml
中加上<packaging>jar</packaging>
- 點擊
package
按鈕進行打包
- 在
- 上傳打包後的文件
- 通過
hadoop jar day03_mapreduce_wordcount-1.0-SNAPSHOT.jar cn.itcast.mapreduce.JobMain
啓動程序
- 查看控制檯是否有文件
- 下載該文件,查看其內容
成功!撒花!重大時刻~~~
4 MapReduce 分區
在 MapReduce 中, 通過我們指定分區, 會將同一個分區的數據發送到同一個 Reduce 當
中進行處理
例如: 爲了數據的統計, 可以把一批類似的數據發送到同一個 Reduce 當中, 在同一個
Reduce 當中統計相同類型的數據, 就可以實現類似的數據分區和統計等
其實就是相同類型的數據, 有共性的數據, 送到一起去處理Reduce 當中默認的分區只有一個
4.1 修改代碼
下面我們將在第4節的基礎上進行修改
- 創建一個自定義的
Partitioner
package cn.itcast.mapreduce;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Partitioner;
public class PartitionerOwn extends Partitioner<Text, LongWritable> {
// text: 表示K2 longWritable: 代表V2 i: reduce個數
@Override
public int getPartition(Text text, LongWritable longWritable, int i) {
// 如果單詞的長度 >= 5 進入第一個分區
if (text.toString().length() >= 5) {
return 0;
}else{
return 1;
}
}
}
- 修改
JobMain
類中的代碼,共改動兩處- 設置分區的代碼,
job.setPartitionerClass(PartitionerOwn.class);
- 設置Reduce的個數,
job.setNumReduceTasks(2);
- 設置分區的代碼,
完整 JobMain
代碼如下:
public class JobMain extends Configured implements Tool {
@Override
public int run(String[] strings) throws Exception {
// 創建一個任務對象
Job job = Job.getInstance(super.getConf(), "mapreduce_wordcount");
// 打包放在集羣運行時,需要做一個配置
job.setJarByClass(JobMain.class);
// 第一步:設置讀取文件的類:K1和V1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job, new Path("hdfs://node01:8020/wordcount"));
// 第二步:設置Mapper類
job.setMapperClass(WordCountMapper.class);
// 設置Map階段的輸出類型:k2和v2的類型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
// 第三、四、五、六步採用默認方式(分區,排序,規約,分組)
job.setPartitionerClass(PartitionerOwn.class);
// 第七步:設置Reducer類
job.setReducerClass(WordCountReducer.class);
// 設置Reduce階段的輸出類型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
// 設置Reduce的個數
job.setNumReduceTasks(2);
// 第八步:設置輸出類
job.setOutputFormatClass(TextOutputFormat.class);
// 設置輸出路徑
TextOutputFormat.setOutputPath(job, new Path("hdfs://node01:8020/wordcount_out"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
// 啓動一個任務
ToolRunner.run(configuration, new JobMain(), args);
}
}
4.2 WordCount分區實驗
- 打包,上傳
- 通過
hadoop jar day03_mapreduce_wordcount-1.0-SNAPSHOT.jar cn.itcast.mapreduce.JobMain
運行
執行後結果:
查看控制檯:
可以看到有兩部分文件,先打開上面的文件:
都是單詞長度大於等於5的單詞統計
再打開下面的文件:
成功!都是單詞長度小於5的單詞統計
5 MapReduce 排序和序列化
- 序列化 (Serialization) 是指把結構化對象轉化爲字節流
- 反序列化 (Deserialization) 是序列化的逆過程. 把字節流轉爲結構化對象. 當要在進程間傳遞對象或持久化對象的時候 , 就需要序列化對象成字節流, 反之當要將接收到或從磁盤讀取的字節流轉換爲對象, 就要進行反序列化
- Java 的序列化 (Serializable) 是一個重量級序列化框架, 一個對象被序列化後, 會附帶很多額外的信息 (各種校驗信息, header, 繼承體系等), 不便於在網絡中高效傳輸. 所以, Hadoop 自己開發了一套序列化機制(Writable), 精簡高效. 不用像 Java 對象類一樣傳輸多層的父子關係, 需要哪個屬性就傳輸哪個屬性值, 大大的減少網絡傳輸的開銷
- Writable 是 Hadoop 的序列化格式, Hadoop 定義了這樣一個 Writable 接口. 一個類
要支持可序列化只需實現這個接口即可 - 另外 Writable 有一個子接口是 WritableComparable, WritableComparable 是既可
實現序列化, 也可以對key進行比較, 我們這裏可以通過自定義 Key 實現WritableComparable 來實現我們的排序功能
數據格式如下:
a 1
a 9
b 3
a 7
b 8
b 10
a 5
要求:
- 第一列按照字典順序進行排列
- 第一列相同的時候 , 第二列按照升序進行排列
解決思路:
- 將 Map 端輸出的 <key,value> 中的 key 和 value 組合成一個新的 key (newKey), value值不變
- 這裏就變成 <(key,value),value> , 在針對 newKey 排序的時候, 如果 key 相同, 就再對value進行排序
實驗總結構如下:
Step 1 自定義類型和比較器
package cn.itcast.mapreduce_sort;
import org.apache.hadoop.io.WritableComparable;
import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;
public class PairWritable implements WritableComparable<PairWritable> {
private String first;
private int second;
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
public int getSecond() {
return second;
}
public void setSecond(int second) {
this.second = second;
}
// 實習排序規則
@Override
public int compareTo(PairWritable other) {
// 先比較first,如果first相同則比較second
int result = this.first.compareTo(other.first);
if (result == 0) {
return this.second - other.second;
}
return result;
}
@Override
public void write(DataOutput dataOutput) throws IOException {
dataOutput.writeUTF(first);
dataOutput.writeInt(second);
}
@Override
public void readFields(DataInput dataInput) throws IOException {
this.first = dataInput.readUTF();
this.second = dataInput.readInt();
}
@Override
public String toString() {
return first + '\t' + second ;
}
}
Step 2 Mapper
package cn.itcast.mapreduce_sort;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
public class SortMapper extends Mapper<LongWritable, Text, PairWritable, Text> {
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// 1. 對每一行數據進行拆分,然後封裝到PairWritable對象中,作爲K2
String[] split = value.toString().split("\t");
PairWritable pairWritable = new PairWritable();
pairWritable.setFirst(split[0]);
pairWritable.setSecond(Integer.parseInt(split[1]));
// 2. 將K2和V2寫入上下文中
context.write(pairWritable, value);
}
}
Step 3 Reducer
package cn.itcast.mapreduce_sort;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
public class SortReducer extends Reducer<PairWritable, Text, PairWritable, NullWritable> {
@Override
protected void reduce(PairWritable key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
for (Text text: values) {
context.write(key, NullWritable.get());
}
}
}
Step 4 Main 入口
package cn.itcast.mapreduce_sort;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.NullWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.TextInputFormat;
import org.apache.hadoop.mapreduce.lib.output.TextOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;
public class JobMain extends Configured implements Tool {
@Override
public int run(String[] strings) throws Exception {
Job job = Job.getInstance(super.getConf(), "mapreduce_sort");
// 打包放在集羣運行時,需要做一個配置
job.setJarByClass(JobMain.class);
// 第一步:設置讀取文件的類:K1和V1
job.setInputFormatClass(TextInputFormat.class);
TextInputFormat.addInputPath(job, new Path("hdfs://node01:8020/input/sort"));
// 第二步:設置Mapper類
job.setMapperClass(SortMapper.class);
// 設置Map階段的輸出類型:k2和v2的類型
job.setMapOutputKeyClass(PairWritable.class);
job.setMapOutputValueClass(Text.class);
// 第三、四、五、六步採用默認方式(分區,排序,規約,分組)
// 第七步:設置Reducer類
job.setReducerClass(SortReducer.class);
// 設置Reduce階段的輸出類型
job.setOutputKeyClass(PairWritable.class);
job.setOutputValueClass(NullWritable.class);
// 第八步:設置輸出類
job.setOutputFormatClass(TextOutputFormat.class);
// 設置輸出路徑
TextOutputFormat.setOutputPath(job, new Path("hdfs://node01:8020/out/sort_out"));
boolean b = job.waitForCompletion(true);
return b?0:1;
}
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration();
int run = ToolRunner.run(configuration, new JobMain(), args);
System.exit(run);
}
}
運行實驗
- 準備數據
- 打包jar包,上傳
- 執行jar包,
hadoop jar day03_mapreduce_wordcount-1.0-SNAPSHOT.jar cn.itcast.mapreduce_sort.JobMain
查看結果: