hadoop(二MapReduce)

不羁的心 提交于 2019-12-31 01:11:13

hadoop(二MapReduce)


介绍

MapReduce:其实就是把数据分开处理后再将数据合在一起.

  • Map负责“分”,即把复杂的任务分解为若干个“简单的任务”来并行处理。可以进行拆分的前提是这些小任务可以并行计算,彼此间几乎没有依赖关系。
  • Reduce负责“合”,即对map阶段的结果进行全局汇总。
  • MapReduce运行在yarn集群

MapReduce中定义了如下的MapReduce两个抽象的编程接口,由用户去编程实现.Map和Reduce,

MapReduce处理的数据类型是键值对


代码处理

MapReduce 的开发一共有八个步骤, 其中 Map 阶段分为 2 个步骤,Shuwle 阶段 4 个步 
骤,Reduce 阶段分为 2 个步骤

​ Map 阶段 2 个步骤

  1. 设置 InputFormat 类, 将数据切分为 Key-Value(K1和V1) 对, 输入到第二步
  2. 自定义 Map 逻辑, 将第一步的结果转换成另外的 Key-Value(K2和V2) 对, 输出结果 
    Shuwle 阶段 4 个步骤
  3. 对输出的 Key-Value 对进行分区
  4. 对不同分区的数据按照相同的 Key 排序
  5. (可选) 对分组过的数据初步规约, 降低数据的网络拷贝
  6. 对数据进行分组, 相同 Key 的 Value 放入一个集合中 
    Reduce 阶段 2 个步骤
  7. 对多个 Map 任务的结果进行排序以及合并, 编写 Reduce 函数实现自己的逻辑, 对输入的 
    Key-Value 进行处理, 转为新的 Key-Value(K3和V3)输出
  8. 设置 OutputFormat 处理并保存 Reduce 输出的 Key-Value 数据

常用Maven依赖

<packaging>jar</packaging> <dependencies> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.7.5</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>2.7.5</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> <version>2.7.5</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-mapreduce-client-core</artifactId> <version>2.7.5</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> <!--    <verbal>true</verbal>--> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>2.4.3</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <minimizeJar>true</minimizeJar> </configuration> </execution> </executions> </plugin> </plugins> </build>

入门---统计

结构

/*  四个泛型解释:    KEYIN :K1的类型    VALUEIN: V1的类型    KEYOUT: K2的类型    VALUEOUT: V2的类型*/public class WordCountMapper extends Mapper<LongWritable,Text, Text , LongWritable> { //map方法就是将K1和V1 转为 K2和V2 /*      参数:         key    : K1   行偏移量(默认几乎一直固定为LongWritable)         value  : V1   每一行的文本数据         context :表示上下文对象     */ /*      如何将K1和V1 转为 K2和V2        K1         V1        0   hello,world,hadoop        15  hdfs,hive,hello       ---------------------------        K2            V2        hello         1        world         1        hdfs          1        hadoop        1        hello         1     */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { Text text = new Text(); LongWritable longWritable = new LongWritable(); //1:将一行的文本数据进行拆分 String[] split = value.toString().split(","); //2:遍历数组,组装 K2 和 V2 for (String word : split) { //3:将K2和V2写入上下文            text.set(word);            longWritable.set(1);            context.write(text, longWritable); } }}


/*  四个泛型解释:    KEYIN:  K2类型    VALULEIN: V2类型    KEYOUT: K3类型    VALUEOUT:V3类型*/public class WordCountReducer extends Reducer<Text,LongWritable,Text,LongWritable> { //reduce方法作用: 将新的K2和V2转为 K3和V3 ,将K3和V3写入上下文中 /*      参数:        key : 新K2        values: 集合 新 V2        context :表示上下文对象        ----------------------        如何将新的K2和V2转为 K3和V3        新  K2         V2            hello      <1,1,1>            world      <1,1>            hadoop     <1>        ------------------------           K3        V3           hello     3           world     2           hadoop    1     */ @Override protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException { long count = 0; //1:遍历集合,将集合中的数字相加,得到 V3 for (LongWritable value : values) {             count += value.get(); } //2:将K3和V3写入上下文中        context.write(key, new LongWritable(count)); }}

public class JobMain extends Configured implements Tool { //该方法用于指定一个job任务 @Override public int run(String[] args) throws Exception { //1:创建一个job任务对象 Job job = Job.getInstance(super.getConf(), "wordcount"); //如果打包运行出错,则需要加该配置        job.setJarByClass(JobMain.class); //2:配置job任务对象(八个步骤) //第一步:指定文件的读取方式和读取路径        job.setInputFormatClass(TextInputFormat.class); TextInputFormat.addInputPath(job, new Path("hdfs://node01:8020/wordcount")); //TextInputFormat.addInputPath(job, new Path("file:///D:\\mapreduce\\input")); //第二步:指定Map阶段的处理方式和数据类型         job.setMapperClass(WordCountMapper.class); //设置Map阶段K2的类型          job.setMapOutputKeyClass(Text.class); //设置Map阶段V2的类型          job.setMapOutputValueClass(LongWritable.class); //第三,四,五,六 采用默认的方式 //第七步:指定Reduce阶段的处理方式和数据类型          job.setReducerClass(WordCountReducer.class); //设置K3的类型           job.setOutputKeyClass(Text.class); //设置V3的类型           job.setOutputValueClass(LongWritable.class); //第八步: 设置输出类型           job.setOutputFormatClass(TextOutputFormat.class); //设置输出的路径 Path path = new Path("hdfs://node01:8020/wordcount_out"); TextOutputFormat.setOutputPath(job, path); //TextOutputFormat.setOutputPath(job, new Path("file:///D:\\mapreduce\\output")); //获取FileSystem FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration()); //判断目录是否存在 boolean bl2 = fileSystem.exists(path); if(bl2){ //删除目标目录                 fileSystem.delete(path, true); } //等待任务结束 boolean bl = job.waitForCompletion(true); return bl ? 0:1; } public static void main(String[] args) throws Exception { Configuration configuration = new Configuration(); //启动job任务 int run = ToolRunner.run(configuration, new JobMain(), args); System.exit(run); }}

shuwle阶段

分区

分区实则目的是按照我们的需求,将不同类型的数据分开处理,最终分开获取

代码实现

结构

public class MyPartitioner extends Partitioner<Text,NullWritable> { /*      1:定义分区规则      2:返回对应的分区编号     */ @Override public int getPartition(Text text, NullWritable nullWritable, int i) { //1:拆分行文本数据(K2),获取中奖字段的值 String[] split = text.toString().split("\t"); String numStr = split[5]; //2:判断中奖字段的值和15的关系,然后返回对应的分区编号 if(Integer.parseInt(numStr) > 15){ return 1; }else{ return 0; } }}

 //第三步,指定分区类            job.setPartitionerClass(MyPartitioner.class); //第四, 五,六步 //设置ReduceTask的个数            job.setNumReduceTasks(2);

MapReduce 中的计数器

计数器是收集作业统计信息的有效手段之一,用于质量控制或应用级统计

可辅助诊断系统故障

看能否用一个计数器值来记录某一特定事件的发生 ,比分析一堆日志文件容易

通过enum枚举类型来定义计数器 统计reduce端数据的输入的key有多少个

public class PartitionerReducer extends Reducer<Text,NullWritable,Text,NullWritable> { public static enum Counter{            MY_INPUT_RECOREDS,MY_INPUT_BYTES } @Override protected void reduce(Text key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException { //方式2:使用枚枚举来定义计数器        context.getCounter(Counter.MY_INPUT_RECOREDS).increment(1L);       context.write(key, NullWritable.get()); }}

排序(包含序列化)

  • 序列化 (Serialization) 是指把结构化对象转化为字节流
  • 反序列化 (Deserialization) 是序列化的逆过程. 把字节流转为结构化对象. 当要在进程间传 
    递对象或持久化对象的时候, 就需要序列化对象成字节流, 反之当要将接收到或从磁盘读取 
    的字节流转换为对象, 就要进行反序列化
  • Java 的序列化 (Serializable) 是一个重量级序列化框架, 一个对象被序列化后, 会附带很多额 
    外的信息 (各种校验信息, header, 继承体系等), 不便于在网络中高效传输. 所以, Hadoop 
    自己开发了一套序列化机制(Writable), 精简高效. 不用像 Java 对象类一样传输多层的父子 
    关系, 需要哪个属性就传输哪个属性值, 大大的减少网络传输的开销
  • Writable 是 Hadoop 的序列化格式, Hadoop 定义了这样一个 Writable 接口. 一个类要支持可 
    序列化只需实现这个接口即可
  • 另外 Writable 有一个子接口是 WritableComparable, WritableComparable 是既可实现序列 
    化, 也可以对key进行比较, 我们这里可以通过自定义 Key 实现 WritableComparable 来实现 
    我们的排序功能

public class SortBean implements WritableComparable<SortBean>{ private String word; private int  num; public String getWord() { return word; } public void setWord(String word) { this.word = word; } public int getNum() { return num; } public void setNum(int num) { this.num = num; } @Override public String toString() { return   word + "\t"+ num ; } //实现比较器,指定排序的规则 /*      规则:        第一列(word)按照字典顺序进行排列    //  aac   aad        第一列相同的时候, 第二列(num)按照升序进行排列     */ @Override public int compareTo(SortBean sortBean) { //先对第一列排序: Word排序 int result = this.word.compareTo(sortBean.word); //如果第一列相同,则按照第二列进行排序 if(result == 0){ return this.num - sortBean.num; } return result; } //实现序列化 @Override public void write(DataOutput out) throws IOException {        out.writeUTF(word);        out.writeInt(num); } //实现反序列 @Override public void readFields(DataInput in) throws IOException { this.word = in.readUTF(); this.num = in.readInt(); }}
public class SortMapper extends Mapper<LongWritable,Text,SortBean,NullWritable> { /*      map方法将K1和V1转为K2和V2:      K1            V1      0            a  3      5            b  7      ----------------------      K2                         V2      SortBean(a  3)         NullWritable      SortBean(b  7)         NullWritable     */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //1:将行文本数据(V1)拆分,并将数据封装到SortBean对象,就可以得到K2 String[] split = value.toString().split("\t"); SortBean sortBean = new SortBean();        sortBean.setWord(split[0]);        sortBean.setNum(Integer.parseInt(split[1])); //2:将K2和V2写入上下文中        context.write(sortBean, NullWritable.get()); }}
public class SortReducer extends Reducer<SortBean,NullWritable,SortBean,NullWritable> { //reduce方法将新的K2和V2转为K3和V3 @Override protected void reduce(SortBean key, Iterable<NullWritable> values, Context context) throws IOException, InterruptedException {       context.write(key, NullWritable.get()); }}

job略


规约Combiner

在三大阶段的第一阶段map处理完后,可能数据过多,利用分布式思想,抢在reduce前先做一次合并,后再由reduce合并,目的是:提高网络IO 性能

实现步骤

 //第三(分区),四 (排序) //第五步: 规约(Combiner)      job.setCombinerClass(MyCombiner.class); //第六步 分布


案例:流量统计(key相同则++++++++)

public class FlowBean implements Writable { private Integer upFlow; //上行数据包数 private Integer downFlow; //下行数据包数 private Integer upCountFlow; //上行流量总和 private Integer downCountFlow;//下行流量总和 //下略get   set   序列化  反序列化
public class FlowCountMapper extends Mapper<LongWritable,Text,Text,FlowBean> { /*      将K1和V1转为K2和V2:      K1              V1      0               1363157985059     13600217502    00-1F-64-E2-E8-B1:CMCC    120.196.100.55    www.baidu.com    综合门户    19    128    1177    16852    200     ------------------------------      K2              V2      13600217502     FlowBean(19    128    1177    16852)     */ @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { //1:拆分行文本数据,得到手机号--->K2 String[] split = value.toString().split("\t"); String phoneNum = split[1]; //2:创建FlowBean对象,并从行文本数据拆分出流量的四个四段,并将四个流量字段的值赋给FlowBean对象 FlowBean flowBean = new FlowBean();        flowBean.setUpFlow(Integer.parseInt(split[6]));        flowBean.setDownFlow(Integer.parseInt(split[7]));        flowBean.setUpCountFlow(Integer.parseInt(split[8]));        flowBean.setDownCountFlow(Integer.parseInt(split[9])); //3:将K2和V2写入上下文中        context.write(new Text(phoneNum), flowBean); }}
public class FlowCountReducer extends Reducer<Text,FlowBean,Text,FlowBean> { @Override protected void reduce(Text key, Iterable<FlowBean> values, Context context) throws IOException, InterruptedException { //1:遍历集合,并将集合中的对应的四个字段累计 Integer upFlow = 0; //上行数据包数 Integer downFlow = 0; //下行数据包数 Integer upCountFlow = 0; //上行流量总和 Integer downCountFlow = 0;//下行流量总和 for (FlowBean value : values) {            upFlow += value.getUpFlow();            downFlow += value.getDownFlow();            upCountFlow += value.getUpCountFlow();            downCountFlow += value.getDownCountFlow(); } //2:创建FlowBean对象,并给对象赋值  V3 FlowBean flowBean = new FlowBean();        flowBean.setUpFlow(upFlow);        flowBean.setDownFlow(downFlow);        flowBean.setUpCountFlow(upCountFlow);        flowBean.setDownCountFlow(downCountFlow); //3:将K3和V3下入上下文中        context.write(key, flowBean); }}
public class JobMain extends Configured implements Tool { //该方法用于指定一个job任务 @Override public int run(String[] args) throws Exception { //1:创建一个job任务对象 Job job = Job.getInstance(super.getConf(), "mapreduce_flowcount"); //如果打包运行出错,则需要加该配置        job.setJarByClass(JobMain.class); //2:配置job任务对象(八个步骤) //第一步:指定文件的读取方式和读取路径        job.setInputFormatClass(TextInputFormat.class); //TextInputFormat.addInputPath(job, new Path("hdfs://node01:8020/wordcount")); TextInputFormat.addInputPath(job, new Path("file:///D:\\input\\flowcount_input")); //第二步:指定Map阶段的处理方式和数据类型         job.setMapperClass(FlowCountMapper.class); //设置Map阶段K2的类型          job.setMapOutputKeyClass(Text.class); //设置Map阶段V2的类型          job.setMapOutputValueClass(FlowBean.class); //第三(分区),四 (排序) //第五步: 规约(Combiner) //第六步 分组 //第七步:指定Reduce阶段的处理方式和数据类型          job.setReducerClass(FlowCountReducer.class); //设置K3的类型           job.setOutputKeyClass(Text.class); //设置V3的类型           job.setOutputValueClass(FlowBean.class); //第八步: 设置输出类型           job.setOutputFormatClass(TextOutputFormat.class); //设置输出的路径 TextOutputFormat.setOutputPath(job, new Path("file:///D:\\out\\flowcount_out")); //等待任务结束 boolean bl = job.waitForCompletion(true); return bl ? 0:1; } public static void main(String[] args) throws Exception { Configuration configuration = new Configuration(); //启动job任务 int run = ToolRunner.run(configuration, new JobMain(), args); System.exit(run); }}

如增加需求:

上行流量倒序排序

public class FlowBean implements WritableComparable<FlowBean> { //指定排序的规则 @Override public int compareTo(FlowBean flowBean) { // return this.upFlow.compareTo(flowBean.getUpFlow()) * -1; return flowBean.upFlow - this.upFlow ; }}

需求:手机号码分区

public class FlowCountPartition extends Partitioner<Text,FlowBean> { /*      该方法用来指定分区的规则:        135 开头数据到一个分区文件        136 开头数据到一个分区文件        137 开头数据到一个分区文件        其他分区       参数:         text : K2   手机号         flowBean: V2         i   : ReduceTask的个数     */ @Override public int getPartition(Text text, FlowBean flowBean, int i) { //1:获取手机号 String phoneNum = text.toString(); //2:判断手机号以什么开头,返回对应的分区编号(0-3) if(phoneNum.startsWith("135")){ return 0; }else if(phoneNum.startsWith("136")){ return 1; }else if(phoneNum.startsWith("137")){ return 2; }else{ return 3; } }}
 //第三(分区),四 (排序)            job.setPartitionerClass(FlowCountPartition.class); //第五步: 规约(Combiner) //第六步 分组 //设置reduce个数            job.setNumReduceTasks(4);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!