MapReduce

Hadoop-MapReduce

MapReduce主要包括:map(映射)负责处理原始数据生成中间结果,reduce(归约)处理map输出中中间结果生成最终结果。

主要处理map与reduce的业务代码,map与reduce之间的shuffle(核心)过程,尤其关注key-value的设置

优势:MapReduce,多个节点同时运行效率高,数据处理时间较为均衡。

Map过程

  • block

    • 物理存储128M(除了最后一块)

  • split

    • 切片

    • 定义每个map任务的数据处理量

    • 默认切片大小与block大小相同

      • 切片过小,mapstak开启过多;切片过大,节点空暇,利用率低下

  • maptask

    • 一个split(切片)对应一个maptask,maptask只处理切片的原始数据

    • maptask根据用户的代码执行作业

    • maptask默认每次读取并处理一行数据,得到临时处理结果value

      • 可手动定义key-value读取器

    • 临时结果存在内存中,通过kvbuffer存入,同时还提交了比较器

  • kvbuffer

    • maptask的结果输出目标

    • 内存中的环形空间,默认100M

    • 通过溢写实现循环的数据写入(maptask的行处理结果)和数据写出(将临时结果存入硬盘)

    • 溢写的默认阈值为80%

  • spill

    • 溢写:kvbuffer默认设置80%的阈值,达到阈值时将kvbuffer中旧数据写入硬盘,同时写入新数据将已溢写出的内存区域覆盖,实现kvbuffer循环数据写入。

    • 调整80%的阈值,实现新数据存入速度与溢写数据到硬盘的均衡,否则新数据必须等待旧数据溢写完成

    • 每次溢写输出80M数据,数据大小与阈值关联

    • 溢写时根据map输入的比较器或默认比较器生成输出的每个数据的key

  • partation

    • 将溢写的数据进行分区,分区的数量与reduce数量相同

    • 根据每条数据的key对数据分区(分区比较器)

  • sort

    • 在溢写和分区全部完成后,对分区内的数据基于key进行排序(getOutputKeyComparator)

    • 默认使用快速排序法,排序基于key的内置比较器

  • merge

    • 使用归并算法,合并执行了分区排序的数据,默认根据分区的先后排列

    • 归并算法还是基于key的内置比较器进行排序

Reduce过程

  • fetch

    • 拉取数据,从每个mapertask节点拉取reduce所需的数据

  • merge

    • 将所有fetch的数据,根据key进行归并排序,生成一个文件。

    • 归并排序规则是基于key对象内置比较器(getOutputKeyComparator)

  • reducetask

    • 分组比较器将相同的key的数据(所有临时处理数据)拉取到一个reducetask中处理

    • 分组比较器将相同key(这是比较器认为的相同)的数据排到一起

    • 每个reducetask生成最终的key与value

  • output

    • reducetask将结果输出到hdfs,避免结果文件过大问题

源码分析

map源码

 

reduce源码

 

MR的1.0与2.0

1.0过程

  • Client

    • 客户端执行提交命令

      • hadoop jar wordcount.jar com.shsxt.ly.WordCountJob

    • 命令提交到JobTacker中

  • JobTacker

    • JobTacker与每个节点的taskTrcker保持心跳

    • 监控各节点的资源状态(CPU,IO)

    • jobTracker接收到任务后,将任务分配给(就近,空闲)的节点执行Task

    • 任务开始后,JobTacker监控TaskTracker的任务进度,在其他节点任务完成后,若还存在执行缓慢的TaskTracker,将task任务拷贝到其他节点执行,两个节点同时执行,采纳第一个完成的结果数据。

  • TaskTracker

    • 各节点各具备一个TaskTracker,监控所在节点资源(CPU IO 硬盘)

    • 与JobTacker保持心跳

    • 接收JobTacker的Task,TaskTracker分配节点资源给Task

    • 上述资源的大小单位为slot槽,slot的大小是固定的

  • Task

    • 分为maptask与reducetask

关于1.0的缺陷

  • 单JobTacker,任务量过大容易宕机,易产生单点故障

  • JobTacker内存无法扩充

  • MR耦合度过高,只服务MR,对其他计算框架支持差

2.0过程

引入了yarn技术也就是ResourceManager与ApplicationMaster分别执行资源监控以及任务执行。

  • Client

  • ResourceManager

    • 资源管理集群

      可以使用主备节点架构

    • 可将Container与ApplicationMaster视为由ResourceManager生成

  • NodeManager

    • 负责本节点的资源管理

    • 实时与ResourceManager心跳汇报

    • 使得ResourceManger能够知道各节点的资源状态

  • Container

    • 从ResourceManager申请资源

    • 可以实现资源动态调整,任务查出申请资源额度则kill该task

  • ApplicationMaster

    • 一个MR任务对应的主节点

    • ResourceManager将ApplicationMaster随机分配到任意节点上

    • 负责应用程序相关的事务:任务调度、任务监控、容错

    • 将任务生成Task,分配到各节点上

    • task执行完毕,向ResourceManager发送信号

      • ResourceManger去kill当前对象

      • ResourceManger回收Container

  • Task

    • 实际任务执行者

    • 每个task中存有对应ApplicationTask的地址

    • 分为maptask与reducetask

    • maptask的数量与split数量一致

    • reduce需要手动指定,数量要少于节点数

    • 结果存放在hdfs上,避免容量不可控

MR集群搭建

基于hadoop2.x的HA集群搭建,需要hadoop与zookeeper关闭。

配置文件

目录/opt/sxt/hadoop-2.6.5/etc/hadoop

  1. 配置mapred-site.xml

    • cp mapred-site.xml.template mapred-site.xml

    • vim mapred-site.xml

      配置yarn框架

      <property>
      <name>mapreduce.framework.name</name>
      <value>yarn</value>
      </property>
  2. 配置yarn-site.xml

    • vim yarn-site.xml

    • yarn.resourcemanager.cluster-id配置yurn的集群名

    • yarn.resourcemanager.ha.rm-ids配置Reduce的主备Reduce(yurn)节点

    • yarn.resourcemanager.hostname.xxx配置Reduce各节点

    • yarn.resourcemanager.zk-address配置Map节点,与DN对应一致

      <property>
      <name>yarn.nodemanager.aux-services</name>
      <value>mapreduce_shuffle</value>
      </property>
      <property>
      <name>yarn.resourcemanager.ha.enabled</name>
      <value>true</value>
      </property>
      <property>
      <name>yarn.resourcemanager.cluster-id</name>
      <value>mr_shsxt</value>
      </property>
      <property>
      <name>yarn.resourcemanager.ha.rm-ids</name>
      <value>rm1,rm2</value>
      </property>
      <property>
      <name>yarn.resourcemanager.hostname.rm1</name>
      <value>node3</value>
      </property>
      <property>
      <name>yarn.resourcemanager.hostname.rm2</name>
      <value>node1</value>
      </property>
      <property>
      <name>yarn.resourcemanager.zk-address</name>
      <value>node1:2181,node2:2181,node3:2181</value>
      </property>
  3. 将配置文件拷贝到其他节点

    scp mapred-site.xml yarn-site.xml  root@node3:`pwd`

节点启动

各节点 zkServer.sh start 启动Zookeeper

主节点 start-all.sh 启动DFS与主yarn节点

备用yarn节点 yarn-daemon.sh start resourcemanager

jsp查看节点启动状态

DFSZKFailoverController
NameNode
JournalNode
DataNode
ResourceManager             RS节点(reduce)主备
NodeManager                 NM节点(map)对应DN
QuorumPeerMain              
Jps

访问节点是:RS节点主机:8088

 

 

MR计算实现

1 依赖导入

导入hadoop121个相关jar

导入hadoop配置文件core-site.xml,hdfs-site.xml,mapred-site.xml,yarn-site.xml到conf资源文件夹中

2 Job类

在main方法中写入配置信息

  • job名称,主类,Mapper类,Reduce类,Reduce数量,数据源路径,结果存放路径

  • mapper临时结果的key与value类型

  • 分区比较器,分组比较器

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;
public class WeatherJob {
public static void main(String[] args) throws Exception {
//获取配置文件
Configuration configuration = new Configuration(true);
//本地模式运行
configuration.set("mapreduce.framework.name", "local");
//创建JOB
Job job = Job.getInstance(configuration);
//设置主Class
job.setJarByClass(WeatherJob.class);
//设置Job的名称
job.setJobName("xxx");
//设置JOB的Reduce数量
job.setNumReduceTasks(2);
//设置JOB的输入路径
FileInputFormat.setInputPaths(job, new Path("/shsxt/java/weather"));
//设置Job的输出路径
FileOutputFormat.setOutputPath(job, new Path("/shsxt/java/weather_result_" + System.currentTimeMillis()));
//设置Job-Mapper的输出key类型
job.setMapOutputKeyClass(Weather.class);
//设置Job-Mapper的输出value类型
job.setMapOutputValueClass(IntWritable.class);
//设置分区器
job.setPartitionerClass(WeatherPartitioner.class);
//设置分组比较器
job.setGroupingComparatorClass(WeatherGroupingComparator.class);
//设置Job的Mapper
job.setMapperClass(WeatherMapper.class);
//设置Job的Reduce
job.setReducerClass(WeatherReducer.class);
//等待Job的完成
job.waitForCompletion(true);
}
}

job说明

  1. job可以通过内部类直接包含map与reduce

    map与reduce均为静态内部类

  2. 定义K-V行读取方式

    job.setInputFormatClass(KeyValueTextInputFormat.class)

    KeyValueTextInputFormat是基于制表符(/t)分隔行数据,第一个 /t 之前的数据为key,之后为value;若行数据中无/t,默认行数据为key,value为空。

    若不设置默认与普通行读取器

  3. 基于配置文件的全局数据存取

    存:conf.set("key","value") 或setInt(key,2)...

    取:context.getConfiguration().getInt("key", 1) 后一个参数为空值时的默认值

    将数据存到conf配置中,可以存入多种类型的数据,使用对应的get,set方法进行存取

  4. 全局累加

    • 设置job的enum内部类作为累加计数器

      //枚举元素为my
      public static enum Mycounter { my }
    • 存值(在map与reduce)

      • context.getCounter(Mycounter.my).increment(xxx);

      • xxx就是累加到原先my中的值

      • 累加时需要取整

    • 对于循环job,每次job完成,累加计数器会重置

    • 取值

      • job.getCounters().findCounter(Mycounter.my).getValue()

      • 结果返回了一个long数值

job循环

  • 循环在main方法中,可设置在while(true)死循环内

  • 循环跳出方式:

    • 在每次job执行后,等待waitForCompletion返回的执行结果true

    • 执行比较判断,跳出值与计算中产生的特定值比较,满足条件break

  • 循环外的配置项

    • 获取配置文件Configuration conf = new Configuration(true);

    • 项目执行方式:本地和跨平台

    • 设置循环结束的指标,也就是上述的跳出值

  • 循环内的配置项

    • 迭代的次数计数器 i++(在循环外设置int i=0)

    • 基础配置:当前job,主类,mapper,reduce,map输出的中间K-V的类型

    • 迭代的任务名与数据输入输出路径

      • 迭代的任务名job.setJobName("pagerank" + i)

      • 迭代的结果输出路径

        FileOutputFormat.setOutputPath(job, new Path("/output/xxx" + (i - 1)))

      • 迭代的数据输入路径

        配置第一迭代的数据输入路径(原始文件)

        FileInputFormat.addInputPath(job, new Path("/input"))

        配置第二次以后迭代输入路径,前一次计算结果作为后一次计算的输入

        FileInputFormat.addInputPath(job, new Path("/output/xxx" + i))

      • 最终效果为:第i次循环,数据来源路径为/output/xxx(i-1),计算结果存放路径为/output/xxx(i-1)

    • 等待单次job结束,并执行业务判断是否结束迭代循环

      boolean flag = job.waitForCompletion(true);

      //等待当前循环结束
      boolean flag = job.waitForCompletion(true);
      if (flag) {
         //执行业务判断(例:获取枚举计数器my的值)
         long sum = job.getCounters().findCounter(Mycounter.my).getValue();
         if (sum < xxx) {
             //满足条件跳出while循环
             break;
        }
      }

3 Mapper类

  • XxxMapper的传入参数为:

    LongWriter行偏移量,Text行数据类型,临时结果key类型,临时结果value类型

  • map方法的参数为:

    key行偏移量,vlaue行数据,context上下文容器

在map处理行数据,生成key与value的结果,并将行数据写出

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
public class XxxMapper extends Mapper<LongWriter,Text,Xxx,IntWritable>{
@Overrider
protected void map(LongWritable key,Text vlaue,Mapper<LongWritable, Text, Weather, IntWritable>.Context context ){
       //根据需求对value执行分析处理,生成key与value
       //可以将key或value数据存入自定义对象中
       //示例代码将vlaue中数据转为自定义Weather对象和hadoop的IntWritable对象
       String[] ss = value.toString().split("\t");
       Date date = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(ss[0]);
       Calendar calendar = Calendar.getInstance();
       calendar.setTime(date);
       IntWritable outputValue = new IntWritable(Integer.parseInt(ss[1].replace("c", "")));
       Weather weather = new Weather();
       weather.setYear(calendar.get(Calendar.YEAR));
       weather.setMonth(calendar.get(Calendar.MONTH) + 1);
       weather.setDay(calendar.get(Calendar.DAY_OF_MONTH));
       weather.setTemperature(outputValue.get());
       //写出result结果context.write(key,value),注意数据类型
       context.writer(weather,outputValue);
  }
}

map端合并

在job中设置:job.setCombinerClass(xxxReduce.class),该类与reudce基本相同

处理指定的切片

//获取切片
FileSplit fs = (FileSplit) context.getInputSplit();
//判断切片所在的文件,是否是包含xxx路径文件的数据
//若包含则处理切片
//若是取反,则表示不处理包含某个文件数据的切片
if (fs.getPath().getName().contains("xxx")) {
//这里执行map代码
}

4 Reducer类

  • XxxReducer的传入参数依次为:

    • Xxx临时结果的key类型

    • IntWritable临时结果的value类型

    • Text最终结果的key类型

    • IntWritable最终结果的value类型

  • reduce方法的参数依次为:临时结果的key,key所对应的所有临时结果values,上下文context

在map方法中

  • 通过迭代器获取每个临时结果的value与对应的key

    • Iterator<IntWritable> iterator = values.iterator();

    • while (iterator.hasNext()) { iterator.next() } 中, iterator.next()为临时value值,key为临时key值

    • key具体值可能不相同,只是分组比较器将其识别为形同的key分到一个reduce中处理

  • 通过上下文context输出

    • context.writer(最终key,最终value)

    • 在一个reduce中context输出多次

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapred.ReduceTask;
import org.apache.hadoop.mapreduce.Reducer;
public class WeatherReducer extends Reducer<Weather, IntWritable, Text, IntWritable> {
@Override
protected void reduce(Weather key, Iterable<IntWritable> values, Reducer<Weather, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
//设置计数器
int count = 0;
//获取临时结果的迭代器
Iterator<IntWritable> iterator = values.iterator();
//遍历迭代器中的数据,
while (iterator.hasNext()) {
Text outputKey = new Text(key.getYear() + "-" + key.getMonth() + "-" + key.getDay());
IntWritable outputValue = iterator.next();
//判断是否需要退出
count++;
if (count <= 2)
            //将数据写出
context.write(outputKey, outputValue);
}
}
}

5 hadoop数据包装类型

内部自带比较器

  • Text文本类型

    toString()转为字符串

  • LongWritable和IntWritable表示整形数字,DoubleWritable表示浮点小数

    上述包装类型的get方法返回对应的类型的数值,get()-->int/long/double

java数值或String通过带参构造转为包装类:new Text("参数")

6 关于key自定类

  • 实现WritableComparable<>接口,范型传入自身

  • 内部写入若干自定义字段

  • 实现接口的三个方法

    • write(定义序列化的数据)

      out.writeInt(this.year); 定义字段以何种数据类型写入硬盘

    • readFields(定义反序列化的数据)

      this.year=in.readInt(); 定义字段以何种格式从硬盘输出到内存

    • 注:write与readFields的字段的顺序和类型必须一致

    • compareTo(定义比较方法,实现基于key的排序比较)

      使用字段进行多重比较,借助字段本身的compareTo方法实现比较

  • set,get,toString,hashCode方法

import org.apache.hadoop.io.WritableComparable;
public class Weather implements WritableComparable<Weather>{
private Integer year;
private Integer month;
private Integer day;
private Integer temperature;

//写出数据,序列化
@Override
public void write(DataOutput out) throws IOException {
out.writeInt(this.year);
out.writeInt(this.month);
out.writeInt(this.day);
out.writeInt(this.temperature);
}
//读入数据,反序列化
@Override
public void readFields(DataInput in) throws IOException {
this.year=in.readInt();
this.month=in.readInt();
this.day=in.readInt();
this.temperature=in.readInt();
}

//比较方法
@Override
public int compareTo(Weather o) {
int result=this.year.compareTo(o.getYear());
if(result==0){
result=this.month.compareTo(o.getMonth());
if(result==0){
result=this.temperature.compareTo(o.getTemperature());
}
}
return result;
}
//set,get,toString,hashCode省略
}

7 分区比较器

控制自定义相同的key进入同一个分区(reduce节点)

对于自定类,将需要处于同一分组的各字段构建出数字,该数字对分区数(reduces数)取余。

  • 继承Partitioner<>传入需要比较的key与value类型

  • 重写getPartition方法,其中numPartitions就是分区数

import org.apache.hadoop.mapreduce.Partitioner;
public class WeatherPartitioner extends Partitioner<Weather, IntWritable>{
@Override
public int getPartition(Weather key, IntWritable value, int numPartitions) {
return (key.getYear() + key.getMonth())%numPartitions;
}
}

控制某些key独享一个分区reduce,使用如下代码

public class FirstPartition extends HashPartitioner<Text, IntWritable> {
public int getPartition(Text key, IntWritable value, int reduceCount) {
//if中执行逻辑判断,使得满足条件的key进入
       if (key.equals(new Text("xxx"))) {
           //指定分配,由于reduceCount个数从0计数,直接分配的最后一个reduce分区
return reduceCount - 1;
           //可以多个条件,reduce值从大到小分配对应的key
} else {
           //若并非指定的key,则使用父类的默认分区器,平分剩余的reduce分区
           //注意:reduceCount需要减去已分配的分区,避免冲突
return super.getPartition(key, value, reduceCount - 1);
}
}
}

8 分组比较器

控制自定义相同的key进入同一个分组(reducetask)

对于自定义类,将需要处于同一reducetask的字段进行比较

  • 继承WritableComparator类

  • 构造器中传入自定义类

  • 重写public int compare(WritableComparable a, WritableComparable b)方法

    其中a和b两个比较的对象,需要强转为自定义类

import org.apache.hadoop.io.WritableComparable;
import org.apache.hadoop.io.WritableComparator;
public class WeatherGroupingComparator extends WritableComparator{
   //构造器传入自定义类
public WeatherGroupingComparator() {
super(Weather.class ,true);
}
   //重写比较器
@Override
public int compare(WritableComparable a, WritableComparable b) {
       //强转为自定义类
Weather w1=(Weather) a;
Weather w2=(Weather) b;
       //将指定字段的值进行比较
       //本例中相同的year与month能够分在一个reducetask中
int result=w1.getYear().compareTo(w2.getYear());
if(result==0){
result=w1.getMonth().compareTo(w2.getMonth());
}
return super.compare(a, b);
}
}

9 运行及打包

  • 打包方式

    项目右键Export,选择JAR file ,选择打包项目,JAR file中选取导出路径。

    将jar文件放入linux节点中,运行命令:hadoop jar xxx.jar com.jay.XxxJob (指定jar文件与主类)

  • 本地运行

    job类中加入配置:configuration.set("mapreduce.framework.name", "local");

    右键-run-hadoop

  • 跨平台运行

    conf.set("mapreduce.app-submission.corss-paltform", "true");

    conf.set("mapreduce.framework.name", "local");

10 数据拉取到本地进行计算

  • job配置:

    job.addCacheFile(new Path("/xxx/xxx").toUri()); 将hdfs的数据载入缓存

  • map及reduce中的使用

    重写setup方法,该方法是另一个前置方法,且只执行一次

    //获取所有缓存文件的路径数组
    URI[] cacheFiles = context.getCacheFiles();
    //依次遍历缓存文件
    for (int i = 0; i < cacheFiles.length; i++) {
       //获取缓存文件的名字
       URI uri = cacheFiles[i];
       // 获取制定的文件
       if (uri.getPath().endsWith("xxx文件名")) {
           //定义文件路径
           Path path = new Path(uri.getPath());
           //获取字符流
           BufferedReader br = new BufferedReader(new FileReader(path.getName()));
           String line =null;
           String alldata=null;
           //循环一行一行读取,最后拼接起来
           while ((line = br.readLine()) != null) {
               String alldata=line+"";//可以使用其他方式
    }
           br.close();
      }
    }

     

 

 

posted @ 2019-10-19 09:50  小布大佬  阅读(196)  评论(0编辑  收藏  举报