Hadoop第二部分:MapReudce(一)

目录
Hadoop第0部分:分布式存储计算平台及Hadoop入门
Hadoop第一部分:HDFS的构架和使用
Hadoop第二部分:MapReudce(一)
Hadoop第二部分:MapReudce(二)
Hadoop第二部分:MapReudce(三)

本文项目地址:https://github.com/KingBobTitan/hadoop.git

MapReudce(一)

一、回顾hdfs

  • 应用场景
    • 一次写入,多次读取,不适于修改的业务场景
  • 特点
    • 分块机制:用于实现HDFS的分布式
    • 副本机制:用于实现HDFS数据的安全性保障机制
      • 默认会将每个块存储3份
      • HDFS上的文件是逻辑的概念,真正的物理存储是块
      • 当数据写入HDFS时,由DataNode 来实现副本的同步
  • HDFS读写流程
      • 客户端提交写请求给Namenode,Namenode验证请求,接受请求
      • 客户端分块,将 第一个块请求给Namenode写入
      • Namenode返回这个块要写入的三个地址
      • 客户端通过机架感知选择最近的DataNode,提交第一个块的写入
      • 最近的DataNode要将 数据同步给其他的DataNode
      • 直到整个文件写入完成
      • Namenode记录元数据,元数据的更改写入edits
      • 客户端提交读请求给Namenode,Namenode根据元数据返回该文件所有块的地址
      • 客户端会根据机架感知从每个块的 列表中获取每个块最近的机器上获取每个块的数据
      • 将所有块进行合并
  • HDFS客户端操作
    • 数据
      • hdfs dfs
      • -ls
      • -get
      • -put
      • -rm -r
      • -mkdir -p
    • 管理
      • dfsadmin:集群管理操作
        • report
        • refreshNodes
      • haadmin
      • fsck:文件系统检查
      • balance:负载均衡
  • 数据安全性
    • 数据安全
      • HDFS的数据安全:副本
        • 数据存储目录单独挂载在数据盘
      • Linux的操作系统安全
        • Raid:磁盘冗余阵列
        • Raid0:插入两块硬盘,每个1TB,做了raid0,在操作系统中只能看到1块2TB,负载均衡
        • Raid1:插入两块硬盘,每个1TB,做了raid1,在操作系统中只能看到1块1TB,高可用
          • 空间减少1半
        • Raid5:插入三块硬盘,每个1TB,做了raid1,在操作系统中只能看到1块2TB
      • Safe mode:
        • 当HDFS启动时,会自动进入安全模式
          • 等待所有DataNode进行汇报,与元数据进行比较,来保证数据是否丢失恢复
          • 当发现数据丢失,当前所有存在的块/元数据中记录的块 <= 99.9%,会停留在安全模式,实现数据恢复,直到大于99.9%,会自动退出
        • 安全模式下不允许写
          • 会报错:safe mode is on
          • 手动管理命令:hdfs dfsadmin
            hdfs dfsadmin -safemode <enter | leave | get | wait>
  • HDFS JAVA API
    • 首先要构建hdfs对象
      • 所有的Hadoop程序必须要有一个Configuration对象
      • 用于管理整个程序所有的配置
        • 第一步先从环境变量中加载所有的*-default.xml
        • 第二步先从环境变量中加载所有的*-site.xml
        • 自定义属性配置:conf.set(key,value)
    • 调用对应的方法即可
    • 所有分布式存储框架的读写一般都是通过分布式计算框架来读写
    • 几乎所有的分布式计算框架都封装了读写HDFS的接口
      • MapReduce:FileInputFormat:默认会从HDFS读取数据
        • setInputPath(new Path(“hdfs_path”))
      • Spark:调用了Hadoop的类
        • sc.textInput(“hdfs_path”)
    • 二次开发:基于HDFS API做产品

二、课程目标

  1. Hadoop的HA及Federation
    • HA:高可用机制,避免单点问题
    • Federation:负载均衡
  2. MapReudce编程模型
    • input map shuffle reduce output
    • Map Reduce
  3. MapReduce编程规则
    • 开发接口
  4. MapReduce实现WordCount
    • 自己开发
  5. MapReduce实现各地区二手房个数统计
    • 类似于统计类业务
  6. MapReduce编程模板
  7. 分区与自定义分区
    • shuffle的初探

三、Hadoop的HA及Federation

1、故障转移以及负载均衡

  • 故障转移:failover
    • 某个节点发生故障,这个节点的功能由别的节点实现
    • 两个或以上节点,功能都一模一样,但是只有一个是工作的
  • 负载均衡:load_balance
    • 两个或以上节点,功能都一模一样,但是所有节点一起工作

2、HA:高可用

  • HDFS
    • NameNode:NameNode只有一个,如果宕机,整个HDFS不可用,存在单点故障
      • 接客
      • 管理
      • 元数据
    • DataNode:如果datanode某台宕机,不影响业务,其他datanode继续工作,并且数据会被恢复到别的机器
  • YARN
    • ResourceManager:存在单点故障
      • 接客
      • 管理
      • 资源管理和任务调度
    • NodeManger:如果NM宕机,不影响业务,程序会在别的NM中启动并执行
  • HA:实现两个NameNode以及两个ResourceManager
    • Hadoop0.x:没有HA架构,只有单节点
    • Hadoop2.2+:支持HA,但只支持两个
    • Hadoop3.x:支持多个
  • 两个节点的状态
    • Active:正在工作
    • StandBy:备份【不工作】
      • 如果Active故障,StandBy会转换为Active状态,接替原来的节点

3、Federation

  • Hadoop2.x:增加的一个特性,HDFS特有的联盟机制
  • 本质:实现NameNode负载均衡
    • 两个NameNode,一起工作
  • 应用场景:非常大的公司,想将所有数据放在一个大的集群,但是需要按照业务拆分元数据
    • 每个NameNode专门负责一部分业务
    • 联盟机制不能与HA机制共存,如果非要共存,构建多个nameservice

4、Hadoop HA的实现

  • NameNode HA实现的原理【YARN的HA实现原理类似】

    • 问题:
      • 有两个NameNode的情况下,DataNode向谁注册?
        • 监听每个DataNode的状态,NameNode会返回给客户端
        • 都注册,因为任何一个NameNode都可能成为Active,每个NN都要监听DN
      • 有两个NameNode的情况下,Client向哪个NN提交请求?
        • 向ActiveNameNode请求
        • 如何知道谁是Active
        • HDFS将所有的NameNode进行封装成一个整体NameSpace:mycluster
        • 所有客户端启动时会读取配置文件,将请求发送给NameSpace,由NameSpace负责转发给Active
      • 有两个NameNode的情况下,如何实现主备的切换?
        • zookeeper:存储所有主备的信息,并监听active主节点
        • zkfc:每个zkfc负责监听一个NameNode
          • 注册:用于NameNode在zookeeper中进行注册
          • 监听状态:实现切换
      • 如何保证两个NameNode在切换以后元数据保持一致?
        • journalnode:统一的edits日志存储服务
        • active的负责请求journalnode将自己的元数据变化写入journalnode
        • standby的负责请求journalnode从journalnode中读取edits数据,并对自己的元数据进行操作,保证与active是一致的
  • 实现的架构
    在这里插入图片描述

  • Hadoop HA的搭建配置

    • HDFS:https://hadoop.apache.org/docs/r2.6.5/hadoop-project-dist/hadoop-hdfs/HDFSHighAvailabilityWithQJM.html#Configuration_overview
    • YARN:https://hadoop.apache.org/docs/r2.6.5/hadoop-yarn/hadoop-yarn-site/ResourceManagerHA.html
    • 节点的划分
      进程node01node02node03
      namenodenamenode【active】namenode【standby】
      datanode***
      journalnode***
      zkfc**
      zookeeper***
      resourcemanagerrm[standby]rm[ative]
      nodemanager***
    • 具体配置实现:参考其他
    • HA集群管理命令
      • 获取每个NameNode的状态
      hdfs haadmin -getServiceState nn1
      hdfs haadmin -getServiceState nn2
      
      • 如果配置了自动切换的属性,不允许使用手动切换的命令

        • 手动的切换命令
          hdfs haadmin -transitionToActive nn1 --forceactive
          hdfs haadmin -transitionToStandby nn2
          
        • 配置
          <!--开启自动切换-->
          <property>
          	<name>dfs.ha.automatic-failover.enabled</name>
          	<value>true</value>
          </property>
          
          #该配置开启,手动命令无法执行
          
      • 如果要在自动模式下进行切换

        hdfs haadmin -failover nn2 nn1
        
        nn2:当前是active
        nn1:要将nn1转成active
        
      • YARN的HA的管理命令

        yarn rmadmin
           -transitionToActive <serviceId> [--forceactive]
           -transitionToStandby <serviceId>
           -failover [--forcefence] [--forceactive] <serviceId> <serviceId>
           -getServiceState <serviceId>
        

问题:

  • 当前配置了HA以后
    • 以后任何的HDFS的客户端,如果想要访问hdfs,都要加载core-site和hdfs-site中所有的配置
    • 以后如果涉及到其他框架要访问HDFS的HA,将core-site.xml和hdfs-site.xml,拷贝到对应的配置文件目录下即可
      • idea:resource
      • flume:conf

MapReudce编程模型

1、MapReduce设计思想

  • 最初作为一个分布式计算框架,负责实现分布式的计算
    • 主:JobTracker
    • 从:TaskTracker
    • 资源管理和任务调度
    • 启动所有进程,以及运行所有的Task进程
  • Hadoop2:引进了YARN:负责资源管理和任务调度
    • MapReduce:整个计算模型的开发
  • 思想:分而治之
    • 将一个大的计算任务,进行拆分,交给每个节点来处理一个小的任务
    • 每个小的任务负责计算得到一个结果
    • 最后所有的小结果会合并返回一个结果
    • 例子:1+……+9
      • maptask1:1+2+3 6
      • maptask2:4+5+6 15
      • maptask3:7+8+9 24
      • reduceTask:6+15+24 = 45
    • 任务类型:
      • MapTask:大任务拆分以后的每个小的任务
      • ReduceTask:最后合并的任务

2、MapReduce五大阶段

  • 逻辑过程:MapReduce中所有的数据都是以keyvalue形式存在

    • Input:负责整个程序的输入

      • 默认读取HDFS上的文件

        由一个属性从哪里读取数据:mapreduce.job.inputformat.class
        job.setInputFormat():Java中设置用于指定从哪里读取数据
        	默认该属性的值:TextInputFormat.class
        		功能:从hdfs上读取数据,并且将HDFS上读取到的内容变成keyvalue格式
        		文件中一行会变成一个 keyvalue
        		key:行的偏移量
        		value:行的内容
        	DBInputFormat:用于读取数据库
        	如果没有提供需要的接口,怎么办?
        		提供了抽象类接口,允许自己实现
        
        • 例如:读取wordcount的文件

          hadoop  hive spark
          hue hadoop hive
          
          ||
          
          TextInputFormat加载这个数据,返回KeyValue格式
          ||
          
          key					value
          0					hadoop  hive spark
          18					hue hadoop hive
          
      • 将返回的KeyValue传递给下一个阶段

    • Map:分,启动多个MapTask,每个MapTask处理一部分数据

      • 每个MapTask处理的数据不同,但处理逻辑是一模一样

      • 处理逻辑:自定义一个方法:map

        #每个Maptask对自己负责的每一条数据就会调用一次map方法
        public void map (key,value,上下文){
        	#数据的处理逻辑,自定义
        	#输出
        }
        
      • Map阶段结束的输出的数据还是KeyValue

    • Shuffle:洗牌,默认实现的功能

      • 输入:

        key value

      • 分区:有几个reduce就有几个分区

        • 分区决定了当前这条数据会进入哪个Reduce,被哪个ReduceTask进行处理
      • 排序

        • 默认按照key进行字典顺序排序
      • 分组

        • 按照key进行分组,每一种key只有一条,相同key的value会放入同一个迭代器
      • 输出:

        key value[Itertor]

    • Reduce:合,启动ReduceTask对每一条/每一个key的数据进行处理,默认情况下,reduce只有1个

      • 每一种key或者每一条keyvalue会调用一次reduce方法

      • 处理逻辑:reduce

        public void reduce(key,Itertor[value],上下文){
        	#处理逻辑
        	#输出
        }
        
      • 输出

        key value

    • Output:负责将上一步的结果进行输出

      • 默认输出规则:将结果保存到HDFS上变成文件,并且key和value之间以制表符分隔
  • 物理程序

    • MapTask:input+map+map端的shuffle
    • ReduceTask:reduce端的shuffle+reduce+output

3、WordCount整个过程

Input
  输入:/wordcount/input   
  	【可以指定目录或者直接指定文件,如果指定目录,目录中不能包含目录】
  	hadoop hive hbase
      spark hive
      hadoop hive
  ||
  处理:InputFormat => FileInputForamt => TextInputFormat
  	key:行在文件中的偏移量
  	value:行的内容
  ||
  输出:key					value
  	0						hadoop hive hbase
  	10						spark hive
  	20						hadoop hive
  	
Map
  处理:map方法决定了每条数据的处理逻辑
  	以每个单词作为key
  	value全部为1
  	伪代码:key  value
  		String[] words = value.split(" ")
  		for(word : words){
  			key = word
  			value = 1
  		}
  		
  ||
  输出:key					value
  	hadoop					1
  	hive					1
  	hbase					1
  	spark					1
  	hive					1
  	hadoop					1
  	hive					1
Shuffle
  处理:分区、排序、分组
  排序:
  	hadoop					1
  	hadoop					1
  	hbase					1
  	hive					1
  	hive					1
  	hive					1
  	spark					1
  	
  分组:
  	hadoop					1,1
  	hbase					1
  	hive					1,1,1
  	spark					1

  	
  ||
  输出:key					value
  	hadoop					1,1
  	hbase					1
  	hive					1,1,1
  	spark					1
Reduce
  处理:reduce方法决定每条数据的处理逻辑
  	伪代码:key,itertor<values>
  	for(value : values)
  		sum + = value
  	
  		
  ||
  输出:key					value
      hadoop					2
      hbase					1
      hive					3
      spark					1
Output:
  输出:保存到hdfs上

MapReduce编程规则

1、Driver类

  • 驱动类:包含main方法
  • 推荐:继承Configured ,实现接口Tool

2、Input类

  • 默认的Input类:TextInputFormat.class
  • 所有的输入类:都要继承自InputFormat

3、Mapper类

  • 负责Map阶段的任务,包含map方法
  • 必须继承Mapper

4、Reducer类

  • 负责Reduce阶段的任务逻辑,包含reduce方法
  • 必须继承Reducer类

5、Output类

  • 默认的Output类:TextOutputFormat.class -> FileOutputFormat
  • 所有的输出类必须继承OutputFormat类

6、数据结构

  • 整个MapReduce中所有的数据都以KeyValue的形式存在

7、数据类型

  • Java中的类型:int / long /double/boolean/null /String
  • Hadoop中所有类型必须为序列化类型
    • IntWritable
    • LongWritable
    • DoubleWritable
    • BooleanWritable
    • NullWritable
    • Text

MapReduce实现WordCount

1、需求分析以及五大阶段过程

Input
	输入:/wordcount/input   
		【可以指定目录或者直接指定文件,如果指定目录,目录中不能包含目录】
		hadoop hive hbase
        spark hive
        hadoop hive
	||
	处理:InputFormat => FileInputForamt => TextInputFormat
		key:行在文件中的偏移量
		value:行的内容
	||
	输出:key					value
		0						hadoop hive hbase
		10						spark hive
		20						hadoop hive
		
Map
	处理:map方法决定了每条数据的处理逻辑
		以每个单词作为key
		value全部为1
		伪代码:key  value
			String[] words = value.split(" ")
			for(word : words){
				key = word
				value = 1
			}
			
	||
	输出:key					value
		hadoop					1
		hive					1
		hbase					1
		spark					1
		hive					1
		hadoop					1
		hive					1
Shuffle
	处理:分区、排序、分组
	排序:
		hadoop					1
		hadoop					1
		hbase					1
		hive					1
		hive					1
		hive					1
		spark					1
		
	分组:
		hadoop					1,1
		hbase					1
		hive					1,1,1
		spark					1

		
	||
	输出:key					value
		hadoop					1,1
		hbase					1
		hive					1,1,1
		spark					1
Reduce
	处理:reduce方法决定每条数据的处理逻辑
		伪代码:key,itertor<values>
		for(value : values)
			sum + = value
		
			
	||
	输出:key					value
        hadoop					2
        hbase					1
        hive					3
        spark					1
Output:
	输出:保存到hdfs上

2、代码实现

public class WordCount extends Configured implements Tool {

    /**
     * 构建一个MapReduce程序,配置程序,提交程序
     * @param args
     * @return
     * @throws Exception
     */
    @Override
    public int run(String[] args) throws Exception {
        /**
         * 第一:构造一个MapReduce Job
         */
        //构造一个job对象
        Job job = Job.getInstance(this.getConf(),"mrword");
        //设置job运行的类
        job.setJarByClass(WordCount.class);
        /**
         * 第二:配置job
         */
        //input:设置输入的类以及输入路径
//        job.setInputFormatClass(TextInputFormat.class); 这是默认的
        Path inputPath = new Path(args[0]);//以程序的第一个参数作为输入路径
        TextInputFormat.setInputPaths(job,inputPath);
        //map
        job.setMapperClass(WordCountMapper.class);//指定Mapper的类
        job.setMapOutputKeyClass(Text.class);//指定map输出的key的类型
        job.setMapOutputValueClass(IntWritable.class);//指定map输出的value的类型
        //shuffle
        //reduce
        job.setReducerClass(WordCountReduce.class);//指定reduce的类
        job.setOutputKeyClass(Text.class);//指定reduce输出的 key类型
        job.setOutputValueClass(IntWritable.class);//指定reduce输出的value类型
//        job.setNumReduceTasks(1);//这是默认的
        //output
//        job.setOutputFormatClass(TextOutputFormat.class);//这是默认的输出类
        Path outputPath = new Path(args[1]);//用程序的第二个参数作为输出路径
        //如果输出目录已存在,就删除
        FileSystem hdfs = FileSystem.get(this.getConf());
        if(hdfs.exists(outputPath)){
            hdfs.delete(outputPath,true);
        }
        //设置输出的地址
        TextOutputFormat.setOutputPath(job,outputPath);

        /**
         * 第三:提交job
         */
        //提交job运行,并返回boolean值,成功返回true,失败返回false
        return job.waitForCompletion(true) ? 0 : -1;
    }

    /**
     * 整个程序的入口,负责调用当前类的run方法
     * @param args
     */
    public static void main(String[] args) {
        //构造一个conf对象,用于管理当前程序的所有配置
        Configuration conf = new Configuration();
        try {
            //调用当前类的run方法
            int status = ToolRunner.run(conf, new WordCount(), args);
            //根据程序运行的 结果退出
            System.exit(status);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * Mapper的类,实现四个泛型,inputkey,inputValue,outputKey,outputValue
     * 输入的泛型:由输入的类决定:TextInputFormat:Longwritable Text
     * 输出的泛型:由代码逻辑决定:Text,IntWritable
     * 重写map方法
     */
    public static class WordCountMapper extends Mapper<LongWritable, Text,Text, IntWritable>{
        //构造用于输出的key和value
        private Text outputKey = new Text();
        private IntWritable outputValue = new IntWritable(1);

        /**
         * map方法:Input传递过来的每一个keyvalue会调用一次map方法
         * @param key:当前的 key
         * @param value:当前的value
         * @param context:上下文,负责将新的keyvalue输出
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            //将每一行的内容转换为String
            String line = value.toString();
            //对每一行的内容分割
            String[] words = line.split(" ");
            //迭代取出每个单词
            for(String word:words){
                //将单词赋值给key
                this.outputKey.set(word);
                //输出
                context.write(this.outputKey,this.outputValue);
            }
        }
    }

    /**
     * 所有的Reduce都需要实现四个泛型
     * 输入的keyvalue:就是Map的 输出的keyvalue类型
     * 输出的keyvalue:由代码逻辑决定
     * 重写reduce方法
     */
    public static class WordCountReduce extends Reducer<Text, IntWritable,Text, IntWritable>{

        private IntWritable outputValue = new IntWritable();

        /**
         * reduce方法 ,每一个keyvalue,会调用一次reduce方法
         * @param key:传进来的key
         * @param values:迭代器,当前key的所有value
         * @param context
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            //取出迭代器的值进行累加
            int sum = 0;
            for (IntWritable value : values) {
                sum += value.get();
            }
            //封装成输出的value
            this.outputValue.set(sum);
            //输出每一个key的结果
            context.write(key,outputValue);
        }
    }


}

3、Windows上运行Hadoop程序

  • 第一步:在windows上创建一个不包含中文路径的目录
    • 例如:c:\\hadoop
  • 第二步:在windows上配置一个HADOOP_HOME
    • HADOOP_HOME=c:\\hadoop
  • 第三步:将提供的bin文件夹放入c:\\hadoop目录下
  • 第四步:在path环境变量中添加c:\\hadoop\bin
  • 第五步:测试,是否成功,进入cmd,输入hadoop
    • 只要提示hadoop的用法,就是成功的
  • 第六步:重启IDEA,如果不行,就重启电脑

MapReduce实现各地区二手房统计

1、需求分析

  • 统计各地区二手房的个数

  • 数据格式:

    小区名称
    户型
    面积
    地区
    楼层
    朝向
    总价
    单价
    建造年份
    
  • 过程

    • input:读取该文件
      • 总共有2万多个keyvalue
    • map
      • 调用2万多次map方法
      • key:地区
      • value:1
    • shuffle
      • 分组
    • reduce
      • 输入
        • key:地区
        • value:{1,1,1,1,1,……}
    • output
      • key:地区
      • value:个数
  • 自己测试新需求:统计各地区的平均单价、最高单价、最低单价

    • 结果:地区 平均单价 最高单价 最低单价

2、代码实现

/**
 TODO 用于实现Wordcount的MapReduce
 */
public class SecondHouseMR extends Configured implements Tool {

    /**
     * 构建一个MapReduce程序,配置程序,提交程序
     * @param args
     * @return
     * @throws Exception
     */
    @Override
    public int run(String[] args) throws Exception {
        /**
         * 第一:构造一个MapReduce Job
         */
        //构造一个job对象
        Job job = Job.getInstance(this.getConf(),"mrhouse");
        //设置job运行的类
        job.setJarByClass(SecondHouseMR.class);
        /**
         * 第二:配置job
         */
        //input:设置输入的类以及输入路径
//        job.setInputFormatClass(TextInputFormat.class); 这是默认的
        Path inputPath = new Path(args[0]);//以程序的第一个参数作为输入路径
        TextInputFormat.setInputPaths(job,inputPath);
        //map
        job.setMapperClass(HouseMapper.class);//指定Mapper的类
        job.setMapOutputKeyClass(Text.class);//指定map输出的key的类型
        job.setMapOutputValueClass(IntWritable.class);//指定map输出的value的类型
        //shuffle
        //reduce
        job.setReducerClass(HouseReduce.class);//指定reduce的类
        job.setOutputKeyClass(Text.class);//指定reduce输出的 key类型
        job.setOutputValueClass(IntWritable.class);//指定reduce输出的value类型
        job.setNumReduceTasks(2);//这是默认的
        //output
//        job.setOutputFormatClass(TextOutputFormat.class);//这是默认的输出类
        Path outputPath = new Path(args[1]);//用程序的第二个参数作为输出路径
        //如果输出目录已存在,就删除
        FileSystem hdfs = FileSystem.get(this.getConf());
        if(hdfs.exists(outputPath)){
            hdfs.delete(outputPath,true);
        }
        //设置输出的地址
        TextOutputFormat.setOutputPath(job,outputPath);

        /**
         * 第三:提交job
         */
        //提交job运行,并返回boolean值,成功返回true,失败返回false
        return job.waitForCompletion(true) ? 0 : -1;
    }

    /**
     * 整个程序的入口,负责调用当前类的run方法
     * @param args
     */
    public static void main(String[] args) {
        //构造一个conf对象,用于管理当前程序的所有配置
        Configuration conf = new Configuration();
        try {
            //调用当前类的run方法
            int status = ToolRunner.run(conf, new SecondHouseMR(), args);
            //根据程序运行的 结果退出
            System.exit(status);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * Mapper的类,实现四个泛型,inputkey,inputValue,outputKey,outputValue
     * 输入的泛型:由输入的类决定:TextInputFormat:Longwritable Text
     * 输出的泛型:由代码逻辑决定:Text,IntWritable
     * 重写map方法
     */
    public static class HouseMapper extends Mapper<LongWritable, Text,Text, IntWritable>{
        //构造用于输出的key和value
        private Text outputKey = new Text();
        private IntWritable outputValue = new IntWritable(1);

        /**
         * map方法:Input传递过来的每一个keyvalue会调用一次map方法
         * @param key:当前的 key
         * @param value:当前的value
         * @param context:上下文,负责将新的keyvalue输出
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            //将每个二手房的信息中的地区取出来
            String region = value.toString().split(",")[3];
            this.outputKey.set(region);
            //输出当前地区出现一套二手房
            context.write(this.outputKey,this.outputValue);
        }
    }

    /**
     * 所有的Reduce都需要实现四个泛型
     * 输入的keyvalue:就是Map的 输出的keyvalue类型
     * 输出的keyvalue:由代码逻辑决定
     * 重写reduce方法
     */
    public static class HouseReduce extends Reducer<Text, IntWritable,Text, IntWritable>{

        private IntWritable outputValue = new IntWritable();

        /**
         * reduce方法 ,每一个keyvalue,会调用一次reduce方法
         * @param key:传进来的key
         * @param values:迭代器,当前key的所有value
         * @param context
         * @throws IOException
         * @throws InterruptedException
         */
        @Override
        protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
            //取出迭代器的值进行累加
            int sum = 0;
            for (IntWritable value : values) {
                sum += value.get();
            }
            //封装成输出的value
            this.outputValue.set(sum);
            //输出每一个key的结果
            context.write(key,outputValue);
        }
    }


}

MapReduce编程模板

1、Driver类

public class MRModel extends Configured implements Tool {

	//负责整个MapReduce任务的初始化
    @Override
    public int run(String[] args) throws Exception {
        /**
         * 构建一个job
         */
        Job job = Job.getInstance(this.getConf(),"mrjob");
        job.setJarByClass(MRModel.class);

        /**
         * 配置job
         */
        //input
        TextInputFormat.setInputPaths(job,new Path(args[0]));
        //map
        job.setMapperClass(MRModelMapper.class);
        job.setMapOutputKeyClass(null);
        job.setMapOutputValueClass(null);
        //shuffle
        //reduce
        job.setReducerClass(MRModelReduce.class);
        job.setOutputKeyClass(null);
        job.setOutputValueClass(null);
        job.setNumReduceTasks(1);
        //output
        TextOutputFormat.setOutputPath(job,new Path(args[1]));

        /**
         * 提交job
         */

        return job.waitForCompletion(true) ? 0:-1;
    }

	//负责程序的入口,以及run方法的调用
    public static void main(String[] args) throws Exception {
        Configuration conf = new Configuration();
        int status = ToolRunner.run(conf, new MRModel(), args);
        System.exit(status);
    }
    
}

2、Mapper类

//用于Map阶段的逻辑处理,包含map方法    
public static class MRModelMapper extends Mapper<LongWritable,Text,Text, Text>{
        @Override
        protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
            super.map(key, value, context);
        }
    }

3、Reduce类

    //用于reduce阶段的处理逻辑,包含reduce方法
    public static class MRModelReduce extends Reducer<Text,Text,Text,Text>{
        @Override
        protected void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
            super.reduce(key, values, context);
        }
    }

附录一:Hadoop HA配置文件修改及启动流程

1、拍摄三台虚拟机的快照

  • 关闭所有进程

    sbin/stop-dfs.sh
    sbin/stop-yarn.sh
    
  • 关机

    • 不关机拍摄的快照比较大,占磁盘空间
  • 拍摄快照【用于恢复当前状态,后续学习过程中不用HA】

  • 启动三台机器

2、修改配置文件

  • 删除三台机器原来的临时目录,重新创建,三台机器都要执行

    cd /export/servers/hadoop-2.6.0-cdh5.14.0/
    rm -rf hadoopDatas/
    #创建hadoop的临时存储目录:存储数据块、fsimage等
    mkdir datas
    #用于journal进程存储edits文件的目录
    mkdir journalnode
    
  • 修改第一台机器的配置

  • core-site.xml

    <!--配置HDFS的入口地址,即NameNode的地址-->
    <property>
    	<name>fs.defaultFS</name>
    	<value>hdfs://mycluster</value>
    </property>
    <!--配置Hadoop的本地存储位置-->
    <property>
    	<name>hadoop.tmp.dir</name>
    	<value>/export/servers/hadoop-2.6.0-cdh5.14.0/datas</value>
    </property>
    <!--开启hdfs垃圾箱机制,指定垃圾箱中的文件七天之后就彻底删掉单位为分钟-->
    <property>
    	<name>fs.trash.interval</name>
    	<value>10080</value>
    </property>
    <!--指定zookeeper的地址-->
    <property>
       <name>ha.zookeeper.quorum</name>
       <value>node-01:2181,node-02:2181,node-03:2181</value>
    </property>
    
  • hdfs-site.xml

    <!--关闭hdfs的访问权限-->
    <property>
    	<name>dfs.permissions.enabled</name>
    	<value>false</value>
    </property>
    <!--指定一个HDFS入口的逻辑名称-->
    <property>
      <name>dfs.nameservices</name>
      <value>mycluster</value>
    </property>
    <!--指定HDFS中每个NameNode的逻辑名称-->
    <property>
      <name>dfs.ha.namenodes.mycluster</name>
      <value>nn1,nn2</value>
    </property>
    <!--指定的两个NameNode具体的rpc地址-->
    <property>
      <name>dfs.namenode.rpc-address.mycluster.nn1</name>
      <value>node-01:8020</value>
    </property>
    <property>
      <name>dfs.namenode.rpc-address.mycluster.nn2</name>
      <value>node-02:8020</value>
    </property>
    <!--指定的两个NameNode具体的http的地址-->
    <property>
      <name>dfs.namenode.http-address.mycluster.nn1</name>
      <value>node-01:50070</value>
    </property>
    <property>
      <name>dfs.namenode.http-address.mycluster.nn2</name>
      <value>node-02:50070</value>
    </property>
    <!--指定三台journalnode的地址-->
    <property>
      <name>dfs.namenode.shared.edits.dir</name>
      <value>qjournal://node-01:8485;node-02:8485;node-03:8485/mycluster</value>
    </property>
    <!--指定edits文件具体存在journal机器的什么位置-->
    <property>
      <name>dfs.journalnode.edits.dir</name>
      <value>/export/servers/hadoop-2.6.0-cdh5.14.0/journalnode</value>
    </property>
    <!--指定客户端实现HA切换的代理类-->
    <property>
      <name>dfs.client.failover.proxy.provider.mycluster</name>
      <value>org.apache.hadoop.hdfs.server.namenode.ha.ConfiguredFailoverProxyProvider</value>
    </property>
    <!--配置隔离机制-->
    <property>
      <name>dfs.ha.fencing.methods</name>
      <value>sshfence</value>
    </property>
    <property>
      <name>dfs.ha.fencing.ssh.private-key-files</name>
      <value>/root/.ssh/id_rsa</value>
    </property>
    <!--开启自动切换-->
    <property>
    	<name>dfs.ha.automatic-failover.enabled</name>
    	<value>true</value>
    </property>
    
    
  • yarn-site.xml

    <!--配置YARN的HA:开启HA-->
    <property>
    	<name>yarn.resourcemanager.ha.enabled</name>
    	<value>true</value>
    </property>
    <!--配置YARN的HA:配置统一RM服务名-->
    <property>
      	<name>yarn.resourcemanager.cluster-id</name>
      	<value>cluster1</value>
    </property>
    <!--配置YARN的HA:配置两台RM的id-->
    <property>
      	<name>yarn.resourcemanager.ha.rm-ids</name>
      	<value>rm1,rm2</value>
    </property>
    <!--在node3上配置rm1,在node2上配置rm2-->
    <!--注意:这个在YARN的另一个机器上一定要修改,非RM的其他机器上不配置此项-->
    <property>
      	<name>yarn.resourcemanager.ha.id</name>
    	<value>rm1</value>
    </property>
    <!--配置YARN的HA:配置两台RM的具体地址-->
    <property>
      	<name>yarn.resourcemanager.hostname.rm1</name>
      	<value>node-03</value>
    </property>
    <property>
      	<name>yarn.resourcemanager.hostname.rm2</name>
      	<value>node-02</value>
    </property>
    <property>
      	<name>yarn.resourcemanager.webapp.address.rm1</name>
      	<value>node-03:8088</value>
    </property>
    <property>
      	<name>yarn.resourcemanager.webapp.address.rm2</name>
      	<value>node-02:8088</value>
    </property>
    <!--配置YARN的HA:配置Zookeeper的地址-->
    <property>
      	<name>yarn.resourcemanager.zk-address</name>
      	<value>node-01:2181,node-02:2181,node-03:2181</value>
    </property>
    <!--配置YARN的HA:启动自动故障恢复-->
    <property>
      	<name>yarn.resourcemanager.recovery.enabled</name>
      	<value>true</value>
    </property>
    <!--配置YARN的HA:配置容错存储为Zookeeper,默认为文件-->
    <property>
      	<name>yarn.resourcemanager.store.class</name>
      	<value>org.apache.hadoop.yarn.server.resourcemanager.recovery.ZKRMStateStore</value>
    </property>
    <!--Yarn上运行程序的类型为MapReduce-->
    <property>
        <name>yarn.nodemanager.aux-services</name>
        <value>mapreduce_shuffle</value>
    </property>
    

3、分发并修改

  • 分发给第二台和第三台机器

    cd /export/servers/hadoop-2.6.0-cdh5.14.0/etc/hadoop/
    scp core-site.xml hdfs-site.xml yarn-site.xml node-02:$PWD
    scp core-site.xml hdfs-site.xml yarn-site.xml node-03:$PWD
    
  • 修改第一台机器的yarn-site.xml

    • 编辑yarn-site.xml

      vim yarn-site.xml
      
    • 删除以下内容

      <!--在node3上配置rm1,在node2上配置rm2-->
      <!--注意:这个在YARN的另一个机器上一定要修改,非RM的其他机器上不配置此项-->
      <property>
      	<name>yarn.resourcemanager.ha.id</name>
      	<value>rm1</value>
      </property>
      
  • 修改第二台机器的yarn-site.xml

    • 编辑yarn-site.xml

      vim yarn-site.xml
      
    • 更改以下内容:将rm1修改为rm2

      <!--在node3上配置rm1,在node2上配置rm2-->
      <!--注意:这个在YARN的另一个机器上一定要修改,非RM的其他机器上不配置此项-->
      <property>
      	<name>yarn.resourcemanager.ha.id</name>
      	<value>rm2</value>
      </property>
      

4、启动测试

  • 启动zookeeper

    /export/servers/zookeeper-3.4.5-cdh5.14.0/bin/start-zk-all.sh 
    
  • 第一次启动需要进行格式化:以后不需要执行

    • 启动三台机器的journalnode

      cd /export/servers/hadoop-2.6.0-cdh5.14.0/
      sbin/hadoop-daemon.sh start journalnode
      
    • 第一台机器的NameNode进行格式化

      bin/hdfs namenode -format
      
    • 第一台机器同步元数据到第二台机器

      scp -r datas node-02:$PWD
      
    • 关联zookeeper,进行初始化

      bin/hdfs zkfc -formatZK
      
    • 三台机器关闭journalnode

      sbin/hadoop-daemon.sh stop journalnode
      
  • 第一台机器启动HDFS

    sbin/start-dfs.sh
    #会自动启动所有HDFS的进程
    
  • 第三台机器启动YARN

    sbin/start-yarn.sh
    #只启动当前机器的RM和所有的NM
    
  • 第二台机器启动ResourceManager

    sbin/yarn-daemon.sh start resourcemanager
    

附录二:MapReduce编程依赖

 <!-- 指定仓库位置,依次为aliyun、cloudera和jboss仓库 -->
    <repositories>
        <repository>
            <id>aliyun</id>
            <url>http://maven.aliyun.com/nexus/content/groups/public/</url>
        </repository>
        <repository>
            <id>cloudera</id>
            <url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
        </repository>
        <repository>
            <id>jboss</id>
            <url>http://repository.jboss.com/nexus/content/groups/public</url>
        </repository>
    </repositories>

    <properties>
        <hadoop.version>2.6.0-cdh5.14.0</hadoop.version>
    </properties>

    <dependencies>
        <!--引入单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <!-- Hadoop Client 依赖 -->
        <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>
 	<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>
        </plugins>
    </build>
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值