一个例子带你了解MapReduce

写在前面:博主是一只经过实战开发历练后投身培训事业的“小山猪”,昵称取自动画片《狮子王》中的“彭彭”,总是以乐观、积极的心态对待周边的事物。本人的技术路线从Java全栈工程师一路奔向大数据开发、数据挖掘领域,如今终有小成,愿将昔日所获与大家交流一二,希望对学习路上的你有所助益。同时,博主也想通过此次尝试打造一个完善的技术图书馆,任何与文章技术点有关的异常、错误、注意事项均会在末尾列出,欢迎大家通过各种方式提供素材。

  • 对于文章中出现的任何错误请大家批评指出,一定及时修改。
  • 有任何想要讨论和学习的问题可联系我:zhuyc@vip.163.com。
  • 发布文章的风格因专栏而异,均自成体系,不足之处请大家指正。

一个例子带你了解MapReduce

本文关键字:大数据、Hadoop、MapReduce、WordCount

文章目录

  • 一个例子带你了解MapReduce
    • 一、前期准备
      • 1. 运行环境
      • 2. 项目新建
    • 二、从WordCount开始
      • 1. 基本流程梳理
      • 2. 常规思路实现
      • 3. MR思想实现
    • 三、MapReduce
      • 1. Mapper
      • 2. Reducer
      • 3. Executor
      • 4. 运行结果

一、前期准备

1. 运行环境

想要运行WordCount程序,其实可以不需要安装任何的Hadoop软件环境,因为实际上执行计算任务的是Hadoop框架集成的各种jar包。Hadoop启动后的各项进程主要用于支持HDFS的使用,各个节点间的通讯,任务调度等等。所以如果我们只是想测试程序的可用性的话可以只新建一个Java项目,然后集成Hadoop相关的jar包,直接运行程序即可。
这种方式只限于代码测试,因为可以随时修改代码并且执行,结果也可以很方便查看。本文主要讲解MapReduce的运行流程,因此不需要搭建任何Hadoop环境,关于Hadoop任务的提交方式将在其它文章中详细说明。

2. 项目新建

  • 首先在IDEA中新建一个Maven项目:

  • 修改pom.xml,添加Hadoop相关的依赖:
    <dependencies>
        <dependency>
            <groupId>org.apache.hadoop</groupId>
            <artifactId>hadoop-client</artifactId>
            <version>3.3.5</version>
        </dependency>
    </dependencies>

二、从WordCount开始

对于Hadoop来说,它的Hello World经典案例当属WordCount了,给出一段文本,我们统计出其中一共包含多少单词。我们可以使用MapReduce的思想来将任务分步执行,这样的好处是更利于任务的分割与合并。现在描述可能没有多大的感觉,我们直接来看下面两个对比。

1. 基本流程梳理

按照常规思路,我们希望最终的结果是以Map形式存储,每个key存储单词,对应的value存储统计数量。于是,我们定义一个Map<String, Integer>类型用来存储最终的结果。数据集先使用一个String[]来代替,在最后的MR完整实现中,会从文件中进行读取。

    static String[] text = {
            "what day is today",
            "today is a good day",
            "good good study",
            "day day up"
    };

2. 常规思路实现

如果只是单个的Java程序,我们可以这样做:

    public static void main(String[] args) {
        // 定义用于存放统计结果的Map结构
        Map<String, Integer> map = new HashMap<>();
        // 读取数组中的每个元素,模拟一次读取一行
        for (String line : text){
            // 将每个单词以空格分割
            String[] words = line.split(" ");
            // 读取每一个单词
            for (String word : words){
                // 每次将单词的统计结果取出,加1后放回
                if (map.containsKey(word)){
                    map.put(word, map.get(word) + 1);
                }else {
                    // 如果是第一次遇到这个单词,则存放1
                    map.put(word, 1);
                }
            }
        }
        // 输出结果
        System.out.println(map);
    }

由于是简单的Java程序,这里就不过多说明了,大家可以自己看一下注释。

3. MR思想实现

从上面的程序可以看到,我们使用循环结构,逐行逐个的处理每行字符串中的每个单词,然后将结果不断的更新到Map结构中。在这种情况下,如果我们让不同的线程【相当于不同的Hadoop节点】去处理不同行的数据,再放到Map中时,为了考虑线程安全问题,其实是无法发挥最大作用的,很多时候要等待锁的释放。如果我们用MapReduce的思想来将程序改写一些就会不同了。

  • 定义一个K-V键值对结构
    static class KeyValuePair<K,V>{
        K key;
        V value;

        public KeyValuePair(K key, V value){
            this.key = key;
            this.value = value;
        }

        @Override
        public String toString() {
            return "{" +
                    "key=" + key +
                    ", value=" + value +
                    '}';
        }
    }

以下程序的编写可以帮助大家理解MR过程中最为重要的3个核心步骤:Map、Shuffling、Reduce。这三个阶段会完成许许多多的工作,对于开发者来说我们最关心的是数据结构上的变化,因此,其中涉及到的排序等相关操作并没有去实现,想要深挖的小伙伴可以去看源码。

  • Map阶段

在这一阶段,会对数据逐行处理,key为偏移量,value则是这一行出现的数据键值对列表。

    static Map<Integer, List<KeyValuePair<String, Integer>>> doMapper(){
        Map<Integer, List<KeyValuePair<String, Integer>>> mapper = new HashMap<>();
        // 定义偏移量指标,作为key
        int offset = 0;
        for (String line : text){
            String[] words = line.split(" ");
            List<KeyValuePair<String, Integer>> list = new ArrayList<>();
            for (String word : words){
                // 将出现的单词作为键值对的key,将出现次数作为键值对的value
                KeyValuePair<String, Integer> keyValuePair = new KeyValuePair<>(word, 1);
                list.add(keyValuePair);
            }
            // 每次处理一行的数据,生成对应的键值对列表
            mapper.put(offset, list);
            // 调整偏移量,总字符加一个换行符
            offset += line.length() + 1;
        }
        return mapper;
    }

结果如下所示:

{0=[{key=what, value=1}, {key=day, value=1}, {key=is, value=1}, {key=today, value=1}], 18=[{key=today, value=1}, {key=is, value=1}, {key=a, value=1}, {key=good, value=1}, {key=day, value=1}], 38=[{key=good, value=1}, {key=good, value=1}, {key=study, value=1}], 54=[{key=day, value=1}, {key=day, value=1}, {key=up, value=1}]}
  • Shuffling阶段

在这一阶段,将会把所有的key进行排序,并把相同的value放在同一个列表中。

    static Map<String, List<Integer>> doShuffle(Map<Integer, List<KeyValuePair<String, Integer>>> mapper){
        Map<String, List<Integer>> shuffle = new HashMap<>();
        for (Integer key : mapper.keySet()){
            List<KeyValuePair<String, Integer>> keyValuePairs = mapper.get(key);
            for (KeyValuePair<String, Integer> keyValuePair : keyValuePairs){
                // 将出现过的相同单词放在同一个列表中
                if (shuffle.containsKey(keyValuePair.key)){
                    shuffle.get(keyValuePair.key).add(keyValuePair.value);
                } else {
                    // 如果是第一次记录,则创建一个列表
                    List<Integer> list = new ArrayList<>();
                    list.add(keyValuePair.value);
                    shuffle.put(keyValuePair.key, list);
                }
            }
        }
        return shuffle;
    }

此时,依然不涉及计算逻辑,结果如图所示:

{a=[1], study=[1], what=[1], today=[1, 1], is=[1, 1], up=[1], day=[1, 1, 1, 1], good=[1, 1, 1]}
  • Reduce阶段

在这一阶段,会在每个key对应的value列表中执行我们需要的计算逻辑。

    static Map<String, Integer> doReducer(Map<String, List<Integer>> shuffle){
        Map<String, Integer> reducer = new HashMap<>();
        for (String key : shuffle.keySet()){
            List<Integer> values = shuffle.get(key);
            Integer result = 0;
            // 此处对value进行处理,执行累加
            for (Integer value : values){
                result += value;
            }
            reducer.put(key, result);
        }
        return reducer;
    }

得到最终结果,执行结果如下:

{a=1, study=1, what=1, today=2, is=2, up=1, day=4, good=3}
  • 程序运行结果

三、MapReduce

上面的例子帮大家简单的梳理了一下整体流程,这样我们就不需要debug去看每一步的执行效果了,因为只是模拟实现,所以省略了一些步骤。上面定义的KeyValuePair中出现的泛型也是整个流程的重要组成部分,实际执行计算任务时经常要根据需要合理的去定义Key与Value的类型。

1. Mapper

新建一个Class,继承Mapper,重写其中的map方法。可以先定义好泛型,然后再自动生成map方法。

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

import java.io.IOException;

/**
 * 以下泛型声明的是map阶段输入和输出数据的对应类型
 * KEYIN: 偏移量,为整数类型
 * VALUEIN: 每一行的字符串,为文本类型
 * KEYOUT: 单词,为文本类型
 * VALUEOUT: 出现次数1,为整数类型
 */
public class WordCountMapper extends Mapper<LongWritable, Text, Text, LongWritable> {

    /**
     * map阶段将字符的偏移量作为key,每次得到的value为一行的数据
     * @param key 字符偏移量,包含换行符
     * @param value 整行的数据
     * @param context 将结果输出到下一阶段的对象
     */
    @Override
    protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, LongWritable>.Context context) throws IOException, InterruptedException {
        if (value != null){
            // 获取该行的数据
            String line = value.toString();
            // 根据空格分离出每个单词
            String[] words = StringUtils.split(line, ' ');
            // 将每个单词以键值对输出
            for(String word : words){
                context.write(new Text(word), new LongWritable(1));
            }

        }
    }

}

2. Reducer

新建一个Class,继承Reducer,重写其中的reduce方法。可以先定义好泛型,然后再自动生成reduce方法。

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

import java.io.IOException;

/**
 * 以下泛型声明的是reduce阶段输入和输出数据的对应类型,输入类型对应的是Map阶段的输出
 * KEYIN: 单词,为文本类型
 * VALUEIN: 出现次数1,为整数类型
 * KEYOUT: 单词,为文本类型
 * VALUEOUT: 统计次数,为整数类型
 */
public class WordCountReducer extends Reducer<Text, LongWritable, Text, LongWritable> {

    /**
     * 本例中省略了对shuffle的自定义,获取到的是默认处理后的数据
     * @param key 单词
     * @param values 出现1次的数据列表[1,1,...]
     * @param context 将结果最终输出的对象
     */
    @Override
    protected void reduce(Text key, Iterable<LongWritable> values, Reducer<Text, LongWritable, Text, LongWritable>.Context context) throws IOException, InterruptedException {
        // 定义用于记录累加结果的变量
        long sum = 0;
        // 遍历列表,执行累加操作
        for (LongWritable value : values){
            sum += value.get();
        }
        // 输出最后的统计结果
        context.write(key, new LongWritable(sum));
    }
}

3. Executor

新建一个Class,继承Configured,并实现Tool接口,完整代码如下:

import edu.sand.mapper.WordCountMapper;
import edu.sand.reducer.WordCountReducer;
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.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
import org.apache.hadoop.util.Tool;
import org.apache.hadoop.util.ToolRunner;


public class WordCountExecutor extends Configured implements Tool {

    @Override
    public int run(String[] strings) throws Exception {
        // 初始化配置,可以通过这个对象设置各种参数
        Configuration conf = new Configuration();
        // 完成Job初始化,设置任务名称
        Job job = Job.getInstance(conf, "wordCount");
        // 设置Job的运行主类
        job.setJarByClass(WordCountExecutor.class);
        // 设置Map阶段的执行类
        job.setMapperClass(WordCountMapper.class);
        // 设置Map阶段的数据输出类型
        job.setMapOutputKeyClass(Text.class);
        job.setMapOutputValueClass(LongWritable.class);
        // 设置Reduce阶段的执行类
        job.setReducerClass(WordCountReducer.class);
        // 设置Reduce阶段的数据输出类型
        job.setOutputKeyClass(Text.class);
        job.setOutputValueClass(LongWritable.class);
        // 指定数据输入文件路径,如果指定的是文件夹,将读取目录下所有文件
        FileInputFormat.setInputPaths(job, new Path("input/"));
        // 指定结果输出文件路径,最后一级路径会自动创建,每次重新执行时需要删除或修改名称
        FileOutputFormat.setOutputPath(job, new Path("output/wordCount"));
        // 使用job调用执行,true代表显示详细信息,成功时返回0
        return job.waitForCompletion(true) ? 0 : -1;
    }

    public static void main(String[] args) throws Exception {
        // 调用执行
        ToolRunner.run(new Configuration(), new WordCountExecutor(), args);
    }
}

4. 运行结果

  • 项目结构说明

由于是本地代码运行,所以数据输入和结果输出都保存在本地磁盘上,可以在src同级创建两个文件夹inputoutput

  • 日志配置

如果希望看到更详细的日志输出,可以在resources文件夹下创建一个log4j.properties,内容如下:

log4j.rootLogger=INFO,stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%p\t%d{ISO8601}\t%r\t%c\t[%t]\t%m%n

第一行的日志级别可以设置为INOF或者DEBUG

  • 执行结果

运行后会在对应路径下自动生成一个文件夹,其中主要包含3类文件:任务执行标志文件、结果输出文件、校验文件。以crc结尾的文件为校验类文件,当任务成功执行时,会产生一个**_SUCCESS文件,具体的运行结果会存放在part-r-xxxxx**文件中,part文件的名称和个数取决于Reduce的数量以及开发者的需要。

扫描下方二维码,加入CSDN官方粉丝微信群,可以与我直接交流,还有更多福利哦~
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/30945.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

RT-DETR论文解读与代码

1.概述 目前以大名鼎鼎的YOLO为代表的基于CNN的实时监测网络需要NMS进行后处理&#xff0c;导致不能很好的优化网络&#xff0c;并且网络不够健壮&#xff0c;从而导致检测器的推理速度出现延迟。研究者也分析了Anchor-based和Anchor-free的YOLO的性能&#xff0c;发现Anchor并…

Java多线程与并发-原理

1、synchronized 线程安全问题的主要诱因 存在共享数据&#xff08;也称临界资源&#xff09;。存在多条线程共同操作这些共享数据。 解决问题的根本方法&#xff1a; 同一时刻有且只有一个线程在操作共享数据&#xff0c;其他线程必须等到该线程处理完数据后再对共享数据进…

Django之模板层

一、模板简介 在刚刚介绍完的视图层中我们提到&#xff0c;浏览器发送的请求信息会转发给视图进行处理&#xff0c;而视图在经过一系列处理后必须要有返回信息给浏览器。如果我们要返回html标签、css等数据给浏览器进行渲染&#xff0c;我们可以在视图中这么做 from django.s…

【Spring】核心与设计思想

哈喽&#xff0c;哈喽&#xff0c;大家好~ 我是你们的老朋友&#xff1a;保护小周ღ 谈起Java 圈子里的框架&#xff0c;最年长最耀眼的莫过于 Spring 框架啦&#xff0c;如今已成为最流行、最广泛使用的Java开发框架之一。不知道大家有没有在使用 Spring 框架的时候思考过这…

20230622作业:字符设备驱动内部实现原理及流程

1.1字符设备驱动内部实现原理 1>用户打开设备open("~/dev/mycdev",O_RDWR);("路径"&#xff0c;打开方式)2>在内核的虚拟文件系统层会同步执行sys_open函数,实现如下操作3>根据open函数的路径&#xff0c;找到struct inode结构体4>在struct…

基于ASP.NET MVC的网络书店系统/书店商城

摘 要 随着书店规模的不断扩大&#xff0c;人流数量的急剧增加&#xff0c;有关书店的各种信息量也在不断成倍增长。面对庞大的信息量&#xff0c;就需要有网络书店来提高书店工作的效率。通过这样的系统&#xff0c;我们可以做到信息的规范管理和快速查询&#xff0c;从而减少…

【深度学习】5-3 与学习相关的技巧 - Batch Normalization

如果为了使各层拥有适当的广度&#xff0c;“强制性”地调整激活值的分布会怎样呢&#xff1f;实际上&#xff0c;Batch Normalization 方法就是基于这个想法而产生的 为什么Batch Norm这么惹人注目呢?因为Batch Norm有以下优点&#xff1a; 可以使学习快速进行(可以增大学习…

Web安全——HTML基础

HTML 一、对于前端以及后端的认识以及分析二、HTML认知1、网页的组成2、浏览器3、Web标准 三、简单的HTML页面架构四、HTML常见标签1、meta标签2、标题标签3、文本属性4、form表单5、a 标签6、锚文本7、img 标签8、table 表格9、列表标签9.1、无序列表9.2、有序列表 10、框架的…

Java性能权威指南-总结14

Java性能权威指南-总结14 堆内存最佳实践对象生命周期管理对象重用 堆内存最佳实践 对象生命周期管理 在很大程度上&#xff0c;Java会尽量减轻开发者投入到对象生命周期管理上的精力&#xff1a;开发者在需要的时候创建对象&#xff0c;当不再需要这些对象时&#xff0c;它们…

Java 被挤出前三。。

TIOBE 2023 年 06 月份的编程语言排行榜已经公布&#xff0c;官方的标题是&#xff1a;Python 还会保持第一吗&#xff1f;&#xff08;Will Python remain number 1?&#xff09; 在过去的 5 年里&#xff0c;Python 已经 3 次获得 TIOBE 指数年度大奖&#xff0c;这得益于…

浅谈C++|引用篇

目录 引入 一.引用的基本使用 (1)引用的概念&#xff1a; (2)引用的表示方法 (3)引用注意事项 (4)引用权限 二.引用的本质 三.引用与函数 (1)引用做函数参数 (2)引用做函数返回值 四.常量引用 五.引用与指针 引入 绰号&#xff0c;又称外号&#xff0c;是人的本名以外…

【k8s系列】一分钟搭建MicroK8s Dashboard

本文基于上一篇文章的内容进行Dashboard搭建&#xff0c;如果没有看过上一篇的同学请先查阅上一篇文章 k8s系列】使用MicroK8s 5分钟搭建k8s集群含踩坑经验 使用MicroK8s搭建Dashboard很简单&#xff0c;只需要在Master节点按照以下几步操作 1.启用Dashboard插件 microk8s en…

【软件工程】软件工程期末考试复习题

软件工程期末考试试题及参考答案 一、单向选择题 1、软件的发展经历了&#xff08;D&#xff09;个发展阶段。 一二三四 2、需求分析的任务不包括&#xff08;B&#xff09;。 问题分析系统设计需求描述需求评审。 3、一个软件的宽度是指其控制的&#xff08;C&#xff0…

[进阶]TCP通信综合案例:群聊

代码演示如下&#xff1a; 客户端&#xff1a; public class Client {public static void main(String[] args) throws Exception{System.out.println("客户端开启&#xff01;");//1.创建Socket对象&#xff0c;并同时请求与服务端程序的连接。Socket socket new…

新人拿到一个web项目如何使用idea发布运行

本文描述的是一个新手&#xff0c;拿到一个web项目&#xff0c;使用idea如何发布运行。项目中没有非常复杂的元素&#xff0c;只是试着描述应该如何配置相关内容。 内容描述前提&#xff0c;首先请您确认tomcat已经安装&#xff0c;其次确认jdk已经安装&#xff0c;并明确他们在…

STM32速成笔记—GPIO

文章目录 一、什么是GPIO二、GPIO的输入/输出模式三、GPIO初始化配置四、Boot引脚五、一些特殊的GPIO六、点亮LED1. 硬件电路2. 拉高/拉低GPIO3. 程序设计 七、GPIO的位带操作 一、什么是GPIO GPIO(英语:General-purpose input/output)&#xff0c;通用型之输入输出的简称&…

Java与SpringBoot对redis的使用方式

目录 1.Java连接redis 1.1 使用Jedis1.2 使用连接池连接redis1.3 java连接redis集群模式 2.SpringBoot整合redis 2.1 StringRedisTemplate2.2 RedisTemplate 1.Java连接redis redis支持哪些语言可以操作 &#xff08;去redis官网查询&#xff09; 1.1 使用Jedis (1)添加jedis…

【数字图像处理】2.几何变换

目录 什么是几何变换&#xff1f; 为什么要对图像进行几何变换&#xff1f; 2.1 仿射变换&#xff08;二维&#xff09; 2.2 投影变换&#xff08;三维&#xff09; 2.3 极坐标变换 2.3.1 将笛卡尔坐标转化为极坐标 2.3.2 将极坐标转换为笛卡尔坐标 2.3.3 利用极坐标变…

汇编学习教程:寻址大总结

前言 在上篇博文中&#xff0c;我们主要学习了一个全新的寄存器&#xff1a;bp。bp 寄存器在功能和使用上与 bx 有着异曲同工之妙&#xff0c;只不过两人绑定的服务对象不同&#xff1a;bx 默认绑定的是 DS 段寄存器&#xff0c;而 bp 默认绑定的是 SS 段寄存器。bx 和 bp 有着…

Unity之透明度混合与ps的透明度混合计算结果不一致

一、问题 前段时间学习shader时发现了一个问题&#xff0c;一张纯红色透明度为128的图片叠加在一张纯绿色的图片上得出的结果与ps中的结果不一致。网上查找了ps中的透明混合的公式为 color A.rgb*A.alpha B.rgb*(1-A.alpha)。自己使用代码在unity中计算了一下结果总是不对。…