hadoop3.x mapreduce案例:实现词统计wordcount(本机运行)

介绍

统计下图文件中,各个词(人名)出现的次数:

创建maven工程

略

idea创建maven工程必须修改idea配置

修改pom.xml

添加依赖,由于本文使用 hadoop 3.0.3,所以依赖包版本也是 3.0.3

<properties>
    <hadoop-version>3.0.3</hadoop-version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-common</artifactId>
        <version>${hadoop-version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-client</artifactId>
        <version>${hadoop-version}</version>
    </dependency>
    <dependency>
        <groupId>org.apache.hadoop</groupId>
        <artifactId>hadoop-hdfs</artifactId>
        <version>${hadoop-version}</version>
    </dependency>
</dependencies>

log4j.properties

打印 hadoop debug 信息

在 resources 文件夹下创建 log4j.properties 文件,内容如下:

log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n

java

mapper类

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;

import java.io.IOException;

/**
 * 继承自 Mapper 类
 * 泛型解释:
 * LongWritable:每一行开始位置的偏移量,对应 map() 方法第1个形成类型
 * Text:输入数据的值,通常是一行文本数据,对应 map() 方法第2个形成类型
 * Text:context写入的key的数据类型
 * IntWritable:context写入的value的数据类型
 */
public class WordcountMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
    //可以复用,所以定义为成员变量,例子就是这么写的
    private Text k = new Text();
    private final static IntWritable v = new IntWritable(1);

    /**
     *
     * @param key 
     * @param value MapReduce有默认的读取数据组件(TextInputFormat),一行一行的读取数据。key是每一行开始位置的偏移量,value是该行的内容
     * @param context 通过context写入数据
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void map(LongWritable key, Text value, Context context)    throws IOException, InterruptedException {

        //获取一行
        String line = new String(value.toString().getBytes(),"UTF-8");

        //根据空格拆分字符串
        String[] words = line.split(" ");

        //遍历数组,输出新的key、value
        for (String word : words) {

            k.set(word);
            context.write(k, v);
        }
    }
}

reducer类

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;

import java.io.IOException;

public class WordcountReducer extends Reducer<Text, IntWritable, Text, IntWritable> {

    IntWritable v = new IntWritable();

    /**
     * reduce默认行为:
     *    1. 根据 map输出结果的 key 进行字典排序
     *    2. 然后根据 key 进行分组
     *    3. 分组后,同一组数据组成一个新的 key/value,key还是原来的key;value是由原value组成的Iterable迭代器
     * @param key 单词
     * @param values 单词出现的次数,迭代器。如果出现2次,那么迭代器有2个1
     * @param context 通过context写入数据
     * @throws IOException
     * @throws InterruptedException
     */
    @Override
    protected void reduce(Text key, Iterable<IntWritable> values,Context context) throws IOException, InterruptedException {

        //累加求和
        int count = 0;
        for (IntWritable item : values) {
            count += item.get();
        }

        //输出
        v.set(count);
        context.write(key,v);
    }
}

主启动类-driver

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;

import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
        // 检查命令行参数,确保提供了输入和输出路径
        if (args.length != 2 || args == null){ // 检查命令行参数是否正确(需要两个参数)
            System.err.println("please input current Path"); // 提示用户输入路径
            System.exit(1); // 参数不正确时退出程序
        }
        //创建 config 对象
        Configuration config = new Configuration();
        //构建 Job 实例,设置job名字,在yarn 8088可看到
        Job job = Job.getInstance(config,"mywordcount");

        //设置jar加载路径,不加此代码有时可能会报错
        job.setJarByClass(Main.class);

        //设置map和reduce类
        job.setMapperClass(WordcountMapper.class);
        job.setReducerClass(WordcountReducer.class);

        // 设置 Mapper 输出key-value对的数据类型,必须与 Mapper 泛型一致
        // 与 job.setOutputKeyClass()、job.setOutputValueClass() 一致时可略
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(IntWritable.class);

        //设置最终输出 key/value 类型(关键:必须与 reducer 输出的泛型一致)
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(IntWritable.class);

        //根据输入参数 设置输入和输出路径
        FileInputFormat.setInputPaths(job, new Path(args[0]));
        FileOutputFormat.setOutputPath(job, new Path(args[1]));

        //job.submit();//这种提交较少用,原因在下面
        //一般采用这种方式提交,等待结束,客户端可实时看到进度
        boolean result = job.waitForCompletion(true);
        System.exit(result ? 0 : 1);
    }
}

如果调用 job.submit() 提交,在 yarn 上运行时,只能看到提交信息,看不到执行进度,如下图:

本地调试

  1. 在开发机器上运行,如:windows
  2. 读取的数据 和 输出结果 在 本地 上

注意:需要搭建开发环境 hadoop3.x HDFS JAVA开发介绍

本地调试 - idea 操作

准备数据

在 D:\hadoop_std\data 目录下创建文件 1.txt,内容如下:

李雷 韩梅梅
韩梅梅 lucy
lili 李雷 李四
杜子腾 李雷 张三
李雷 lucy lili

设置启动参数

在主启动类点右键,如下操作:

红框处是:D:\hadoop_std\data D:\hadoop_std\result

解释: 中间有空格,这是2个参数

  • D:\hadoop_std\data:读取数据所在的目录

  • D:\hadoop_std\result:生成结果的目录,执行前,result 目录必须删除,否则报错

运行

然后 run 或 debug 运行

执行结果

内容如下,每个词(人名)出现的次数:

本地调试 - eclipse 操作

在主启动类点右键,如下操作:

传入参数:D:\bigdata\wordcount\data C:\Users\mym\Desktop\res

解释: 中间有空格,这是2个参数

  • D:\bigdata\wordcount\data:读取数据所在的目录

  • C:\Users\mym\Desktop\res:生成结果的目录,执行前,result 目录必须删除,否则报错

准备数据

在 D:\bigdata\wordcount\data 目录下创建文件 1.txt,内容如下:

李雷 韩梅梅
韩梅梅 lucy
lili 李雷 李四
杜子腾 李雷 张三
李雷 lucy lili

运行

然后 run 或 debug 运行

执行时可能提示下面错误

不影响运行

NativeIO.createDirectoryWithMode error, path = \tmp\hadoop-mym\mapred\local\localRunner\mym\jobcache\job_local582540742_0001\attempt_local582540742_0001_m_000000_0\output, mode = 755
183: 当文件已存在时,无法创建该文件。

    at org.apache.hadoop.io.nativeio.NativeIO$Windows.createDirectoryWithMode0(Native Method)
    at org.apache.hadoop.io.nativeio.NativeIO$Windows.createDirectoryWithMode(NativeIO.java:521)
    at org.apache.hadoop.fs.RawLocalFileSystem.mkOneDirWithMode(RawLocalFileSystem.java:509)
    at org.apache.hadoop.fs.RawLocalFileSystem.mkdirsWithOptionalPermission(RawLocalFileSystem.java:562)
    at org.apache.hadoop.fs.RawLocalFileSystem.mkdirs(RawLocalFileSystem.java:534)
    at org.apache.hadoop.fs.RawLocalFileSystem.create(RawLocalFileSystem.java:303)
    at org.apache.hadoop.fs.RawLocalFileSystem.create(RawLocalFileSystem.java:292)
    at org.apache.hadoop.fs.FileSystem.create(FileSystem.java:1083)
    at org.apache.hadoop.fs.FileSystem.create(FileSystem.java:972)
    at org.apache.hadoop.fs.FileSystem.create(FileSystem.java:960)
    at org.apache.hadoop.mapred.MapTask$MapOutputBuffer.sortAndSpill(MapTask.java:1617)
    at org.apache.hadoop.mapred.MapTask$MapOutputBuffer.flush(MapTask.java:1505)
    at org.apache.hadoop.mapred.MapTask$NewOutputCollector.close(MapTask.java:735)
    at org.apache.hadoop.mapred.MapTask.runNewMapper(MapTask.java:805)
    at org.apache.hadoop.mapred.MapTask.run(MapTask.java:347)
    at org.apache.hadoop.mapred.LocalJobRunner$Job$MapTaskRunnable.run(LocalJobRunner.java:271)
    at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    at java.lang.Thread.run(Thread.java:748)

原文出处:/show_1IXbjGltYlE.html