一、MapReduce介绍

MapReduce是一个分布式计算框架,可以部署在Hadoop、Spark等大数据平台上,实现海量数据的并行计算。它采用“分而治之”的思想,将一个计算任务交给集群中的多台机器共同完成,之后再汇总成最终结果。

一般来说读取一个TB,PB级的文件,普通计算机的速度是比较慢的,而要想提高速度就要提高计算机的硬件配置,这对于普通用户来说是很难做到的,也提高了这一领域的门槛。而采用廉价的机器组成分布式系统,只要集群的机器数量足够多,那么计算的速度就会足够快。

二、MapReduce特点

优点

(1)易于编程

完全独立完成一个MapReduce程序是一个很困难的事情,这需要很强的编程能力。好在MapReduce给我们提供了大量的方便开发的接口,我们只需要继承一些接口,实现一些特定的函数就能完成一个MapReduce程序。

(2)高拓展性

这是一个分布式计算框架,我们可以简单粗暴的,通过增加机器来提高计算性能。

(3)高容错性

由于MapReduce集群采用的大多是廉价的机器,宕机,BUG等都是家常便饭。但MapReduce框架提供多种有效的错误检测和恢复机制。如果一个结点出现了问题,其他结点会接替这个结点的工作,等结点恢复正常后,又可以继续工作,这些都由Hadoop内部完成。

(4)高吞吐量

MapReduce可以对PB级以上也就是1024TB的数据进行离线计算。

缺点

(1)难以实时计算

MapReduce处理的是磁盘上的数据。

(2)不能流式计算

MapReduce处理的是磁盘上的静态数据,而流式计算的输入数据的动态的。

(3)难以用于DAG计算

DAG(有向无环图)多个任务间存在依赖关系,后一个应用的输入可能是前一个应用的输出。而MapReduce的输出结果都会写在磁盘上,这会造成大量的磁盘IO,降低集群的性能。

三、MapReduce编程

以词频统计程序为例

pom依赖

https://mvnrepository.com/寻找以下几个依赖
hadoop-common、hadoop-hdfs、hadoop-mapreduce-client-core、junit
插件:maven-compiler-plugin、maven-shade-plugin
下面是我的pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>cn.pineapple</groupId>
    <artifactId>MapReduceTest</artifactId>
<!--    <packaging>pom</packaging>-->
    <version>1.0-SNAPSHOT</version>
    <properties>
        <hadoop.version>2.6.0</hadoop.version>
    </properties>
    <dependencies>
        <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-common</artifactId>
            <version>${hadoop.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-hdfs</artifactId>
            <version>${hadoop.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-mapreduce-client-core -->
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-mapreduce-client-core</artifactId>
            <version>${hadoop.version}</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>RELEASE</version>
<!--            <scope>test</scope>-->
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.apache.maven.plugins/maven-shade-plugin -->
<!--        <dependency>-->
<!--            <groupId>org.apache.maven.plugins</groupId>-->
<!--            <artifactId>maven-shade-plugin</artifactId>-->
<!--            <version>2.4.3</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>
                </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>
</project>

要做统计的文件内容为:

Hello,Hadoop,BigData
Hello,Hadoop,MapReduce
Hello,Hadoop,HDFS
BigData,Perfect

MapReduce由Map和Reduce两个阶段组成。

Map

在此之前还有一个读取文件的操作,这个只要在主类中指定一下就好,不需要写。

读取后文件的内容为:

//K1	V1
0 		Hello,Hadoop,BigData
21 		Hello,Hadoop,MapReduce
44 		Hello,Hadoop,HDFS
62 		BigData,Perfect

Map后的文件内容为:

//K2		V2
Hello 		1
Hadoop 		1
BigData 	1
Hello 		1
Hadoop 		1
MapReduce 	1
Hello 		1
Hadoop 		1
HDFS 		1
BigData 	1
Perfect 	1

Map表示“映射”,将文件拆分成多个块,然后发给集群上的机器统一计算
要继承Mapper类并重写map()函数,将K1,V1转换成下面的K2,V2

package cn.pineapple.day1;

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

import java.io.IOException;

/**
 * 四个泛型的解释:
 * <p>
 * KEYIN:K1的类型
 * <p>
 * VALUEIN:V1的类型
 * <p>
 * KEYOUT:K2的类型
 * <p>
 * VALUEOUT:V2的类型
 */

public class WordCountMapper extends Mapper<LongWritable, Text, Text, LongWritable> {

    /**
     * 将K1,V1转换成K2,V2
     *
     * @param key:     行偏移量
     * @param value:   一行文本内容
     * @param context: 上下文对象
     * @throws IOException:
     * @throws InterruptedException:
     */
    @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);
        }
    }
}

map()函数内的代码,只需要针对文件一行考虑。所以首先要提取每个单词,用value.toString()将value也就是这一行的内容转换成String类型,再用split()方法进行拆分。K2的值默认为1。在写入的时候,还要将K2和V2转换成Text类型和LongWritable类型。

Reduce

在Map和Reduce中间还有一个shuffle,目前用不到这个shuffle,可以采取默认的方式,它会把K2,V2转换成:

//K2		V2
Hello 		<1,1,1>
Hadoop 		<1,1,1>
BigData 	<1,1>
HDFS 		<1>
MapReduce 	<1>
Perfect 	<1>

接着Reduce的最终结果是:

//K3 		V3
BigData		2
HDFS		1
Hadoop		3
Hello		3
MapReduce	1
Perfect		1

Reduce表示“归约”,将所有结果都统一起来,继承Reducer类并重写reduce()函数,将K2和V2转换为K3和V3,也就是最终结果。

package cn.pineapple.day1;

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

import java.io.IOException;


/**
 * 四个泛型的解释:
 * <p>
 * KEYIN:K2类型
 * <p>
 * VALUEIN:V2类型
 * <p>
 * KEYOUT:K3类型
 * <p>
 * VALUEOUT:V3类型
 */
public class WordCountReducer extends Reducer<Text, LongWritable, Text, LongWritable> {

    /**
     * 将K2和V2转换为K3和V3,将K3和V3写入上下文中
     *
     * @param key:     新K2
     * @param values:  新V2
     * @param context: 上下文对象
     * @throws IOException:
     * @throws InterruptedException:
     */
    @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:将K3V3写入上下文中
        context.write(key, new LongWritable(count));
    }
}

为了实现这个最终结果,K3和K2是一样的,我们只需要遍历这个集合,然后相加就能得到V3。value.get()可以将LongWritable类型转换成long类型,最后在写入的时候再进行一次转换。

Main方法

光有这两个类是不行的,我们还要写一个Main方法,详细的列一下任务流程,指定一下任务配置等。
要继承Configured类并实现Tool接口。

package cn.pineapple.day1;

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 {

    /**
     * 指定一个job任务
     *
     * @param strings:
     * @return :
     * @throws Exception:
     */
    public int run(String[] strings) throws Exception {
        //1:创建一个job任务对象
        Job wordCount = Job.getInstance(super.getConf(), "WordCount");
        //如果打包运行出错,则需要加改配置
        wordCount.setJarByClass(JobMain.class);

        //2:配置job任务对象(八个步骤)

        //第一步:指定文件的读取方式和
        wordCount.setInputFormatClass(TextInputFormat.class);
        //      读取路径
        TextInputFormat.addInputPath(wordCount, new Path("hdfs://nsv:8020/input/wordcount"));

        //第二步:指定Map阶段的处理方式和数据类型
        wordCount.setMapperClass(WordCountMapper.class);
        //      设置K2的类型
        wordCount.setMapOutputKeyClass(Text.class);
        //      设置V2的类型
        wordCount.setMapOutputValueClass(LongWritable.class);

        //第三、四、五、六shuffle阶段采用默认的方式

        //第七步:指定Reduce阶段的处理方式和数据类型
        wordCount.setReducerClass(WordCountReducer.class);
        //      设置K3的类型
        wordCount.setOutputKeyClass(Text.class);
        //      设置V3的类型
        wordCount.setOutputValueClass(LongWritable.class);

        //第八步:指定输出类型
        wordCount.setOutputFormatClass(TextOutputFormat.class);
        //      输出路径
        TextOutputFormat.setOutputPath(wordCount, new Path("hdfs://nsv:8020/output/wordcount"));

        //等待任务结束
        boolean bl = wordCount.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);
    }
}

run()方法里就是本次任务的详细流程和配置,然后main()方法里要调用上面写的run方法。它接受一个int类型作为退出代码,0或1。

四、打包运行

双击package打包,出现BUILD SUCCESS表示打包成功

mapreduce 只在一个节点上运行 mapreduce程序只能用java编写_hadoop


出现了两个jar包,origin包是因为用了maven-shade-plugin打包插件,不会包含依赖jar包,所有体积较小,可以选择这个jar包运行。

将要统计的文件放到HDFS上
hdfs dfs -mkdir -p /input/wordcounthdfs dfs -put wordcount.txt /input/wordcount

将jar包上传到集群上后,跑一下喽
hadoop jar original-MapReduceTest-1.0-SNAPSHOT.jar cn.pineapple.day1.JobMain,cn.pineapple.day1.JobMain是主类的全路径

mapreduce 只在一个节点上运行 mapreduce程序只能用java编写_Text_02


completed successfully看来是成功了,去Web上看一下实际结果

mapreduce 只在一个节点上运行 mapreduce程序只能用java编写_apache_03


点击Download可以查看下载结果

mapreduce 只在一个节点上运行 mapreduce程序只能用java编写_Text_04