MR的wordcount的练习就相当于java的helloworld一样

学习前提:

  • JAVA基础:数据类型、方法、面向对象、反射等等(看懂语法)

  • maven(处理依赖、打包)

  • Hadoop-HDFS的存储原理(看懂集群架构、block等)

  • Hadoop-MapReduce的执行原理(看懂流程)

  • Linux (shell操作)


一共要定义3个类,

  • Map类(定义Map阶段怎么处理)
  • Reduce类(定义Reduce阶段怎么处理)
  • JobMain主类(定义整个MapReduce处理流程,见下)

准备数据

hello,world,hadoop
hive,sqoop,flume,hello
kitty,tom,jerry,world
hadoop

保存成.txt文件

MapReduce 处理数据流程:

Map阶段:

  1. 输入,读取源数据( setInputFormatClass方法 / 得出K1,V1)
  2. 设置Mapper类( 继承Mapper类 / K1,V1 转换 K2,V2)

shuffle阶段(直接默认,跳过)

  1. 分区
  2. 排序
  3. 规约
  4. 分组

Reduce阶段

  1. 设置Reduce类( 继承Reduce类 / 新K2,V2 转换 K3,V3)
  2. 输出,保存结果( setOutputFormatClass方法 / 输出K3,V3)

代码:

pom.xml 设置远程仓库、依赖、脚本

    <!--指定仓库-->
    <repositories>
        <repository>
            <id>cloudera</id>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
        </repository>
    </repositories>
    <!--打包方式-->
    <packaging>jar</packaging>
    <!--包-->
    <dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>2.6.0-mr1-cdh5.14.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>2.6.0-cdh5.14.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>2.6.0-cdh5.14.0</version>
        </dependency>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-core</artifactId>
            <version>2.6.0-cdh5.14.0</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.testng</groupId>
            <artifactId>testng</artifactId>
            <version>RELEASE</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <encoding>UTF-8</encoding>
                </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>

Mapper类

1.必须继承Mapper类并且指定好K1,V1,K2,V2对应的hadoop数据类型

2.必须重写map方法

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/*
 KEYIN:K1的类型
 VALUEIN:V1的类型
 KEYOUT:K2的类型
 VALUEOUT:V2的类型
 注意:
 Mapper泛型里面要用hadoop自定义的类型(其实就是Hadoop将原本的类型加上序列化操作再封装) 
 即org.apache.hadoop包下的数据类型
 如:long -> LongWritable;
    String-> Text
    ...
*/
public class WordCountMapper extends Mapper<LongWritable, Text, Text, LongWritable> {
    //目的:将K1,V1 转换 K2,V2
    /*
    参数:
    key    :K1  行偏移量
    value  : V1  每一行的文本数据
    context:上下文对象,桥梁,连接shuffle阶段
     */
    @Override
    protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
        //创建对象保存数据
        LongWritable ValueOut = new LongWritable();
        Text KeyOut = new Text();
        //1:拆分文本数据
        String[] split = value.toString().split(",");
        //2:遍历数据,拆分,重组装K2 ,V2
        for (String word : split) {
            KeyOut.set(word);
            ValueOut.set(1);
            //3:将K2,V2写入上下文对象当中
            context.write(KeyOut, ValueOut);
        }
    }
}
//完毕

Reduce

(同Map类似)

1.必须继承Reducer并指定K2,V2,K3,V3的类型

2.重写reduce方法

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
//虽然第二个参数即V2是集合,但是仍然使用集合里面的元素类型作为泛型参数
public class WordCountReduce extends Reducer<Text, LongWritable, Text, LongWritable> {
    //目的:新K2,V2 转成K3,V3,将K3,V3写入上下文
    /*
    该类方法实现的结果如下:
    要处理的数据:
     新 K2     V2
       Hello   <1,1,1>
     -----------------  
    最终输出结果:	
        K3     V3
        hello  3
     */
    @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));
    }
}

主类JobMain

(依照8个流程步骤编写代码)

1.必须继承Configured类、实现Tool接口

2.必须重写run方法

3.main方法来启动程序

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.conf.Configured;
import org.apache.hadoop.fs.FileSystem;
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;

import java.net.URI;

//MapReduce需要定义主类来描述job并提交job,用来启动MR-Job
//关键点1:必须要继承 Configured 配置类,和实现 Tool 接口,注意是hadoop包下的
public class JobMain extends Configured implements Tool {

    //关键点2:必须重写一个run方法来调用
    @Override
    public int run(String[] strings) throws Exception {
        Job job = Job.getInstance(super.getConf(), JobMain.class.getSimpleName());
        
        //打包到集群上面运行时候,必须要添加以下配置,指定程序的main函数
        //如果打包出错就需要加上该配置
        job.setJarByClass(JobMain.class);

        //第一步:设置输入类型,读取路径,读取输入文件解析成键值对K1,V1
        job.setInputFormatClass(TextInputFormat.class);
        
        //集群做好hosts地址映射,不用直接些IP,写node01即可
        TextInputFormat.addInputPath(job, new Path("hdfs://node01:8020/wordcount"));//自动读取文件夹所有的文件

        //本地运行(必须配置本地hadoop环境)
        /*TextInputFormat.addInputPath(job,
        			new Path("file:///F:\\mapreduce\\mrinput\\wordcount.txt"));*/

        //第二步:设置Mapper类,并设置Map阶段完成之后的输出类型(K2,V2)
        job.setMapperClass(WordCountMapper.class);//class是反射的知识
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);

        //第三、四、五、六步,默认,暂时不用写

        //第七步:设置Reduce类,并设置Reduce阶段完成之后的输出类型(K3,V3)
        job.setReducerClass(WordCountReduce.class);
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);

        //第八步:设置输出类型和输出路径
        job.setOutputFormatClass(TextOutputFormat.class);
        Path path = new Path("hdfs://node01:8020/wordcount_out");

        //本地运行(必须配置本地hadoop环境)
  		/*TextOutputFormat.setOutputPath(job, 
 							 new Path("file:///F:\\mapreduce\\mroutput"));*/

        TextOutputFormat.setOutputPath(job, path);//如果目录已存在会报错

        //改良:避免目录已存在,先判断是否存在,存在就删除
        //连接HDFS文件系统
        //获取FileSystem
        FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), 
                                               new Configuration());
        //判断目录是否存在
        boolean hbl = fileSystem.exists(path);
        if (hbl) {
            //删除目录   第一个参数是删除哪个目录   第二个参数是否递归删除
            fileSystem.delete(path, true);
        }
        //等待MR程序完成..
        boolean mrb = job.waitForCompletion(true);
        
        //返回run主类的执行结果
        return mrb ? 0 : 1;//三元运算符
    }

    //关键点3:由于是主启动函数,需要创建main主函数
    public static void main(String[] args) throws Exception {
        Configuration configuration = new Configuration();
        //本地执行,可以加入配置参数
        //configuration.set("mapreduce.framework.name", "local");
        //configuration.set("yarn.resourcemanager.hostname", "local");
        Tool tool = new JobMain();
        int run = ToolRunner.run(configuration, tool, args);
        System.exit(run);
    }
}

最后,打包,上次,执行

集群运行:

#hadoop jar  编写好的MR程序Jar包    主方法所在的类
hadoop jar original-mapreducedemo-1.0-SNAPSHOT.jar com.yh.mapreduce.JobMain

本地运行需要的环境:

  • 下载解压windows版Hadoop
  • 环境变量HADOOP_HOME
  • Path:%HADOOP_HOME%\bin
  • 复制bin目录下的hadoop.dll到c:\system32目录下
  • 重启

End!~