大数据学习之路(10):MapReduce 编程入门 实操wordcount

539 阅读5分钟

前提

本文主要介绍Hadoop的数据类型,然后写一个入门案例来统计wordcount的单词数量。通过本文的学习,可以了解到hadoop 与java不同的数据类型,然后入门MapReduce程序。

一、内置数据类型介绍

Java 类型Hadoop Writable类型
BooleanBooleanWritable
ByteByteWritable
IntIntWritable
FloatFloatWritable
LongLongWritable
DoubleDoubleWritable
StringText (使用UTF8格式存储的文本)
MapMapWritable
ArrayArrayWritable
NullNullWritable (当[key,value]中的key或value为空使用)

数据类型的使用:

package xl;

import org.apache.hadoop.io.*;
import org.junit.Test;

public class HadoopDataTypeTest {

    @Test
    public  void testText(){
        System.out.println("==== text ===");
        Text text=new Text("lei study hadoop");
        System.out.println(text.getLength());
        System.out.println(text.find("o"));
        System.out.println(text.toString());

        System.out.println("=======ArrayWritable======");
        ArrayWritable arrayWritable = new ArrayWritable(IntWritable.class);
        IntWritable year = new IntWritable(2021);
        IntWritable mouth = new IntWritable(4);
        IntWritable date = new IntWritable(24);
        arrayWritable.set(new IntWritable[]{year,mouth,date});
        System.out.println(arrayWritable.get()[0]);
        System.out.println(arrayWritable.get()[1]);
        System.out.println(arrayWritable.get()[2]);

        System.out.println("========== MapWritable =======");
        MapWritable mapWritable = new MapWritable();
        Text k1 = new Text("name");
        Text k2 = new Text("passwd");
        mapWritable.put(k1,new Text("lei"));
        mapWritable.put(k2, NullWritable.get());
        System.out.println(mapWritable.get(k1));
        System.out.println(mapWritable.get(k2));
    }

}

输出结果:

16
13
lei study hadoop
=======ArrayWritable======
2021
4
24
========== MapWritable =======
lei
(null)

二、wordcount 入门案例

2.1 MapReduce的编程规范养成

我们编写的程序分成三个部分内容:Mapper、Reduce和Drive

1、Mapper阶段

  • 用户自定义的Mapper要继承自己的父类
  • Mapper的输出数据是key-value的形式
  • Mapper中的业务逻辑写在map方法中
  • Mapper的输出数据是KV对的形式
  • map方法对每个k-v调用一次

2、Reduce阶段

  • 用户自定义的Reduce要继承自己的父类
  • Reducer的输入数据类型要对应Mapper的输出数据类型,也是k-v
  • Reduce的业务逻辑写在reduce方法中
  • ReduceTask进程 对每一组相同的k-v调用一次reduce方法

3、Driver阶段

相当于YARN集群的客户端,用于提交我们整个程序到YARN集群,提交的是封装了MapReduce程序相关运行参数的job对象。

2.2 wordcount思路讲解

这边可以准备两个文件:file1.txt 和file2.txt 两个文本文件,需要统计文本中每类单词的出现次数。 文本内容如下:

image.png

将文件拆分成split分片,所以每个文件为一个split,并将文件按行分割形成<key,value>对,这里的key是偏移量,由MapReduce自动计算出来的,包含回车所占的字符数。例如“hello lei”的偏移量是0,“bye lei”的偏移量是“12”。然后每行分割,map输出的形式为<单词,1>,键值对发到reduce,reduce将相同的单词的value进行累加后得到最终的键值对输出到文件。

2.3 环境准备

1、创建maven工程

2、在pom.xml文件中添加如下依赖

<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
    <dependency>
        <groupId>org.apache.logging.log4j</groupId>
        <artifactId>log4j-slf4j-impl</artifactId>
        <version>2.12.0</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>3.1.3</version>
    </dependency>
</dependencies>

3、在项目的resources目录下,新建一个文件,命名“log4j2.xml”,在文件中填入。

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="error" strict="true" name="XMLConfig">
    <Appenders>
        <!-- 类型名为Console,名称为必须属性 -->
        <Appender type="Console" name="STDOUT">
            <!-- 布局为PatternLayout的方式,
            输出样式为[INFO] [2018-01-22 17:34:01][org.test.Console]I'm here -->
            <Layout type="PatternLayout"
                    pattern="[%p] [%d{yyyy-MM-dd HH:mm:ss}][%c{10}]%m%n" />
        </Appender>
 
    </Appenders>
 
    <Loggers>
        <!-- 可加性为false -->
        <Logger name="test" level="info" additivity="false">
            <AppenderRef ref="STDOUT" />
        </Logger>
 
        <!-- root loggerConfig设置 -->
        <Root level="info">
            <AppenderRef ref="STDOUT" />
        </Root>
    </Loggers>
    
</Configuration>

2.4 案例实操

1、编写Mapper类

/**
 * 自定义的xxxMapper类,需要继承Hadoop提供的Mapper类,并重写map方法
 * 四个泛型:两组KV对
 * 按照当前wordcount程序来分析:
 * KEYIN,   :LongWritable  表示文件读取数据的偏移量,简单理解为从哪个位置开始读取数据
 * VALUEIN, :Text  实际从文件中读取的数据
 *
 * KEYOUT   :Text,表示一个单词
 * VALUEOUT :IntWritable,表示这个单词出现了1次
 */
public class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable> {
    
    private Text k=new Text();
    private IntWritable v=new IntWritable(1);
    /**
     * @param key         KEYIN  输入数据的key
     * @param value       VALUEIN 输入数据的value
     * @param context     上下文对象,负责整个Mapper类中方法的调用
    **/
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //1、获取到输入的一行数据 把value转为java  2、切割  3、将每个单词拼成kv写出
        String value2=value.toString();
        String[] words = value2.split(" ");
        for (String word : words) {
            k.set(word);
            context.write(k,v);
        }
    }
}

2、编写Reducer类

/**
 * 自定义的xxxReducer类需要继承Hadoop提供的Reducer类
 * 根据当前的wordcount程序分析,4个泛型
 * 输入的kv类型
 *      KEYIN,    :Text    map端写出的一个单词
 *      VALUEIN,  :IntWritable  表示单词出现了一次
 * 输出的kv类型
 *      KEYOUT,   :表示一个单词
 *      VALUEOUT> :表示单词出现的次数
 */
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {

    int sum=0;
    IntWritable v=new IntWritable();
    /**
     * @param key     keyin ,表示一个单词
     * @param values  迭代器对象,表示一个相同的单词,出现次数的封装,能够表明当前的单词总共出现了多少次
     * @param context  上下文对象,负责reducer执行过程调度
    **/
 
@Override
protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {
// 1 累加求和
sum = 0;
for (IntWritable count : values) {
sum += count.get();
}
// 2 输出
       v.set(sum);
context.write(key,v);
}
}

3、编写Driver驱动类

package com.atguigu.mapreduce.wordcount;
import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
 
public class WordcountDriver {
 
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
 
// 1 获取配置信息以及封装任务
Configuration configuration = new Configuration();
Job job = Job.getInstance(configuration);
 
// 2 设置jar加载路径
job.setJarByClass(WordcountDriver.class);
 
// 3 设置map和reduce类
job.setMapperClass(WordcountMapper.class);
job.setReducerClass(WordcountReducer.class);
 
// 4 设置map输出
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
 
// 5 设置最终输出kv类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6 设置输入和输出路径
FileInputFormat.setInputPaths(job, new Path("D:/cs/input"));
FileOutputFormat.setOutputPath(job, new Path("D:/cs/output"));
 
// 7 提交
boolean result = job.waitForCompletion(true);
 
System.exit(result ? 0 : 1);
}
}

4、本地测试

image.png

本地的输出结果如图所示。

5、集群测试

将该程序打包成jar,提交到服务器上。 执行命令:

类名+ 输入+输出
hadoop jar wc.jar 
xl.WordCountDriver /wcinput  /wcoutput

三、一个小问题

执行MapReduce 程序的时候报错,如下

2019-07-12 00:51:53,868 INFO mapreduce.Job: Task Id : attempt_1562862697087_0005_m_000003_1001, Status : FAILED [2019-07-12 00:51:52.484]Exception from container-launch. Container id: container_1562862697087_0005_02_000011 Exit code: 127

[2019-07-12 00:51:52.490]Container exited with a non-zero exit code 127. Error file: prelaunch.err. Last 4096 bytes of prelaunch.err : Last 4096 bytes of stderr : /bin/bash: /bin/java: No such file or directory  

很好理解 /bin/java 无法执行

在shell 终端执行  /bin/java   无法执行

需要创建一个/bin/java 的软连接  实际指向  jdk 真实目录

我的jdk真实目录是 /opt/sofeware/java8/

所以在shell 终端执行 

ln -s  /opt/sofeware/java8/ /bin/java

再执行  /bin/java 

发现命令可以运行

如果hadoop是集群环境,需要在每台机器上创建软连接

再执行 MapReduce程序 就不会报错了