mapreduce概述
mr是负责计算的。
定义: mapreduce是一个分布式运算程序的编程框架;是给予hadoop的数据分析应用的核心框架。
分布式是多个节点干同一件事情。
优点:
- 易于编程;简单的实现一些接口就能完成一个分布式的任务。
- 良好的扩展性;通过增加机器就能提高性能。
- 高容错性;其中一台挂了, 会自动的转移到另一个节点上运行,不需要人为操作。
- 适合pb级别的海量离线数据处理。
缺点: - 不是实时计算;不能像mysql一样在毫秒或者秒内返回结果。
- 不擅长流式计算。
流式计算输入的数据是动态的;mr输入的数据是静态的; - 不擅长有向图(DGA)计算
有向图指的是多个程序存在依赖关系;后一个程序输入是前一个程序的输出;mr其实也能做,但是输出结果会造成大量磁盘io,导致性能下降;
map阶段:分
reduce:合
物理分区 逻辑分区
mr的核心编程思想
需求: 统计其中每一个单词传的总次数,查询结果a-p写到一个文件,q-z写到一个文件;
mapreduce运算程序一般分为2个阶段:map阶段和reduce阶段。map阶段是分;reduce阶段是合。
map阶段:
map阶段默认是按照块的大小,对数据进行分割,默认128M一个文件。假如是一个200M的文件, 会分成一个128M的和72M的文件。
处理一个数据块的时候会启动一个进程, 即MapTask任务。
MapTask任务是完全并行的。
处理数据的时候是启动MapTask任务,MapTask是并行运行,互不相干。
MapTask任务所做的工作:
- 读数据,按行处理
- 按空格切分每行的单词
- 切出来的结果是按照键值对的形式存储;比如<hadoop, 2> <spark,1> 前面的事键, 后面的是出现的次数 ,方便后面reduce阶段的统计。
- 将所有的键值对中的单词,按照单词首字母,分成2个分区溢写到磁盘
MapTask是按照实际的需求,进行任务操作。 MapTask任务完成之后,进入reduce阶段。
reduce阶段并发执行ReduceTask,但是数据依赖于上一个阶段的MapTask并行实际的输出结果。
ReduceTask任务所在的工作:
- 把MapTask任务的输出拿过来合并。
- 合并之后再进行输出;
mr编程模型只能包含一个map阶段和一个reduce阶段;如果用户业务逻辑复杂, 那只能是多个mr任务串行,第一个mr任务结束的输出作为下一个mr任务的输入。
一个MR任务在分布式运行的时候需要三个实例进程:
- MrAppMaster: 负责整个程序的资源调度;比如内存, cpu, 如何开启maptask和 reducetask ,是一个job的老大。
- Maptask: 负责map阶段的数据处理,就是分;
- Reducetask: 负责reduce阶段的数据处理,就是合;
hadoop数据类型和java数据类型
java | hadoop |
---|---|
int | IntWirteble |
long | LongWirteble |
float | FloatWritable |
String | Text |
double | DoubleWritable |
byte | ByteWritable |
boolean | BooleanWritable |
null | NullWirteble |
MR任务编程规范
map阶段:
- 用户自定义的mapper要继承自己的父类
- mapper的输入数据是键值对的形式,键值对的类型 可自定义
- mapper的业务逻辑写在map方法中
- mapper的输出是键值对形式
- map方法(maptask进程)对每一个键值对(<K, V>)调用一次
reduce阶段:
- 用户自定义的reducer要继承自己的父类
- reduce的输入数据是mapper的输出数据类型,也是KV,键值对
- reduce的业务逻辑写在reduce方法中
- reducetask进程对每一组相同的k的(<K, V>)组调用一次reduce方法
driver阶段:
相当于yarn集群的客户端,会将整个程序提交到yarn集群。提交的是封装了mapreduce程序相关运行参数的job对象。
junit 单元测试包
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.apache.hadoop.mapreduce;
import java.io.IOException;
import org.apache.hadoop.classification.InterfaceAudience.Public;
import org.apache.hadoop.classification.InterfaceStability.Stable;
@Public
@Stable
public class Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
public Mapper() {
}
// 在开始的时候调用一次 初始化
protected void setup(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
}
//每个键值对都会被调用一次,大多数的任务都要重写这方法。 必须重写的方法。
protected void map(KEYIN key, VALUEIN value, Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
context.write(key, value);
}
// 任务结束的时候调用一次
protected void cleanup(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
}
public void run(Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>.Context context) throws IOException, InterruptedException {
this.setup(context);
try {
while(context.nextKeyValue()) {
this.map(context.getCurrentKey(), context.getCurrentValue(), context);
}
} finally {
this.cleanup(context);
}
}
// 上下文方法
public abstract class Context implements MapContext<KEYIN, VALUEIN, KEYOUT, VALUEOUT> {
public Context() {
}
}
}
reduce的源码和map的差不多。
代码示例:
package demo1;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
//map阶段
//自己创建类,并且继承Mapper类,
// Mapper类有4个参数, 形式是这样的 Mapper<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
//四个参数的含义
// KEYIN, 输入数据的key
// VALUEIN, 输入数据的value
// KEYOUT, 输出数据的key的类型,
// VALUEOUT 输出数据的value类型
public class demo_2 extends Mapper<LongWritable, Text, Text, IntWritable>{
// Text k = new Text();
// IntWritable v = new IntWritable(1); // m默认设置为 1
// // LongWritable 代表行的偏移量
// protected void map(LongWritable key, Text value, Context context)
// throws IOException, InterruptedException{
//
1. 获取一行, 将其转为字符串 哪怕拿到的可能已经是个字符串了
// String line = value.toString();
//
2. 切分单词 按空格
// String[] words = line.split(" ");
//
3. 循环写出
// for(String word: words){
创建对象 并将其设置
// k.set(word);
将value设置为1
// context.write(k, v);
// }
// }
Text k = new Text();
IntWritable v = new IntWritable(1);
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
String line = value.toString();
String[] words = line.split(" ");
for(String word:words){
k.set(word);
context.write(k, v);
}
}
}
package demo1;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
//继承Reducer父类 形式是 Reducer<KEYIN, VALUEIN, KEYOUT, VALUEOUT>
//KEYIN, reduce的输入 是map的输出的
// VALUEIN,
// KEYOUT,
// VALUEOUT
public class demo_3 extends Reducer<Text, IntWritable, Text, IntWritable>{
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context)
throws IOException, InterruptedException {
int sum = 0;
for(IntWritable value: values){
sum += value.get();
}
// sum是int类型, 通过创建IntWritable对象,并使用set方法,将sum转为IntWritable类型
IntWritable v = new IntWritable();
v.set(sum);
context.write(key, v);
}
}
package demo1;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Job;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import java.io.IOException;
public class demo_4 {
public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException{
// 1. 获取job对象
Configuration conf = new Configuration();
Job job = Job.getInstance(conf);
// 到这一步,下面都是对job的封装,提交的就是封装后的job。
// 2. 设置jar存储的位置
// 有2种方式设置
// 1.
// job.setJar(String jar); 这样写 就是将jar的位置写死, 不灵活
// 2.
// job.setJarByClass(demo_4.class); 传入的参数是当前的类名, 原理是通过反射找到包的位置。
job.setJarByClass(demo_4.class);
// 3. 关联map类和reduce类
job.setMapperClass(demo_2.class); // 调用的是 setMapperClass(Class<? extends Mapper> 类 reduce同理。
job.setReducerClass(demo_3.class);
// 4. 设置mapper阶段的输出数据的key 和value类型
job.setMapOutputKeyClass(Text.class); // setMapOutputKeyClass(Class<?> theClass)
job.setMapOutputValueClass(IntWritable.class); // setMapOutputValueClass(Class<?> theClass)
// 5. 设置最终数据输出的key 和value的类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 6. 设置输入的路径 和输出的路径
FileInputFormat.setInputPaths(job, new Path(args[0]));
// FileInputFormat.setOutputPaths(job, new Path(args[1])) 这里本该有setOutputPaths 方法, 但是没出来, 不确定是不是调用的包的问题。
// 7. 提交job
// job.submit(); 这样的也行
boolean result = job.waitForCompletion(true); // 使用这个方式, 如果为true会打印一些信息。
System.exit(result ? 0 : 1);
//
//
}
}