Hadoop---MapReduce

MapReduce

1. 概述

     (1):MapReduce是一种分布式计算模型

     (2):有谷歌提出来的,基于GFS进行设计,主要用于搜索领域中解决海量数据的计算问题

     (3):MapReduce是由两个阶段组成:Map和Reduce,用户只需要实现map以及reduce两个函数,,既可以实现分布式计                     算,这样做的目的是简化分布式程序的开发和试用周期

 2. 组成

     (1):JobTracker/ResourceManager:任务调度者,管理多个TaskTracker。ResourceManager是Hadoop2.0版本之后引入Yarn之后用于替代JobTracke部分功能的机制

     (2):TaskTracker/NodeManager:任务执行者

3. 结构图

              

    (1):从HDFS中获取数据

    (2):MapReduce首先会将输入的数据进行逻辑切片,每一个切片是一个IInputSpit对象

    (3):每一个InputSpit对象会交给一个MapTask来执行

    (4):切片中的每一行数据都会触发一次map方法

    (5):map方法的输入的默认值默认为数据偏移量,输入这一行的数据;输出的键以及值的类型根据业务来确定

    (6):在Barrier阶段,会将所有相同的键所对应的值放入一个ArrayList中,然后产生一个迭代器交给ReduceTask来执行

    (7):在ReduceTask中,每一个键都会触发一次reduce方法

    (8):将结果写在HDFS中 

 4. 入门案例(给单词计数)

     WorldCountMapper

     读取文件

hello tom hello bob
hello joy
hello rose
hello joy
hello jerry
hello tom
hello rose
hello joy
package cn.tedu;

import java.io.IOException;

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 WorldCountMapper extends Mapper<LongWritable,Text,Text, IntWritable>{
	/*
	 * MapReduce中要求传输的数据必须能够被序列化
	 * KEYIN - 输入的键的类型。默认是行的偏移量,实际上就是记录这一行在这个文件中的开始位置
	 * VALUEIN - 输入的值的类型。默认是这一行数据
	 * KEYOUT - 输出的键的类型 - 当前案例中键就是单词
	 * VALUEOUT - 输出的值的类型 - 当前案例中值是次数
	 */
	@Override
	protected void map(LongWritable key, Text value, Mapper<LongWritable, Text, Text, IntWritable>.Context context)
			throws IOException, InterruptedException {
		/*
		 * key值---行偏移量
		 * value--行数据
		 * context---负责将结果写出到Reduce当中
		 */
		//hello tom hello bob
		String[] arr = value.toString().split(" ");
		for (String arri : arr) {
			context.write(new Text(arri), new IntWritable(1));
		}
	}

}

WorldCountReduce

package cn.tedu;

import java.io.IOException;

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

public class WorldCountReduce extends Reducer<Text, IntWritable, Text, IntWritable>{

	/*
	 * KEYIN - 输入的键的类型
	 * VALUEIN - 输入的值的类型
	 * KEYOUT - 输出的键的类型
	 * VALUEOUT - 输出的值的类型
	 */
	@Override
	protected void reduce(Text key, Iterable<IntWritable> values,
			Reducer<Text, IntWritable, Text, IntWritable>.Context context) throws IOException, InterruptedException {
		// 计算总次数
		/*
		 * key - 输入的键
		 * values - 存储值的迭代器
		 * context - 负责将结果写出
		 */
		int sum = 0;
	    for (IntWritable value : values) {
	    	sum+=value.get();
		}
	    context.write(key, new IntWritable(sum));
		
	}
}

      WorldCountDriver

package cn.tedu;

import java.io.IOException;

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 org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class WorldCountDriver{
	
	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		//在MapReduce上需要先给这个程序申请Job任务
		Configuration config = new Configuration();
		Job job = Job.getInstance(config);
		
		//设置入口类
		job.setJarByClass(WorldCountDriver.class);
		//设置mapper类
		job.setMapperClass(WorldCountMapper.class);
		//设置Reducer类
		job.setReducerClass(WorldCountReduce.class);
		
		//声明Mapper的数据结果类型
	
		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(IntWritable.class);
		
		//声明Reducer的数据类型结果
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.class);
		
		//设置要处理的文件的路径
		FileInputFormat.addInputPath(job, new Path("hdfs://10.42.162.10:9000/txt/words.txt"));
		
		//设置输出文件路径
		FileOutputFormat.setOutputPath(job, new Path("hdfs://10.42.162.10:9000/result/worldcount"));
		
		// 启动执行
		job.waitForCompletion(true);
	}

}

  结果

bob	1
hello	9
jerry	1
joy	3
rose	2
tom	2

  补充(eclipse打包)

  项目---->右键export---->JAR file

  hadoop中运行:hadoop jar jar包的名字

 5.序列化/反序列化机制

    (1):序列化/反序列化机制

           a:在Hadoop的集群工作过程中,一般是利用RPC来进行集群节点之间的通信和消息的传输,所以要求MapReduce处理                  的对象必须可以进行序列化/反序列操作

           b:Hadoop并没有使用Java原生的序列化,而是利用的是Avro实现的序列化和反序列,并且在此基础上进行了更好的封                     装,提供了便捷的API

           c:在Hadoop中要求被序列化的对象对应的类必须实现Writable接口,重写其中的write方法以及readFields方法

           d:序列化过程中要求属性值不能为null

    (2):案例

           流量统计:

           原始数据flow.txt

    13877779999 bj zs 2145
    13766668888 sh ls 1028
    13766668888 sh ls 9987
    13877779999 bj zs 5678
    13544445555 sz ww 10577
    13877779999 sh zs 2145
    13766668888 sh ls 9987

创建一个类实现Writeable接口,就可以实现序列化AVRO机制

package cn.tedu.serialflow;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.hadoop.io.Writable;

public class Flow implements Writable{
	
	private String ipone;
	private String city;
	private String name;
	private int Flow;
	public String getIpone() {
		return ipone;
	}
	public void setIpone(String ipone) {
		this.ipone = ipone;
	}
	public String getCity() {
		return city;
	}
	public void setCity(String city) {
		this.city = city;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getFlow() {
		return Flow;
	}
	public void setFlow(int flow) {
		Flow = flow;
	}
	@Override
	public void readFields(DataInput in) throws IOException {
		// TODO Auto-generated method stub
		this.ipone=in.readUTF();
		this.city=in.readUTF();
		this.name=in.readUTF();
		this.Flow=in.readInt();
		
	}
	@Override
	public void write(DataOutput out) throws IOException {
		// TODO Auto-generated method stub
		out.writeUTF(ipone);
		out.writeUTF(city);
		out.writeUTF(name);
		out.writeInt(Flow);
		
		
	}
	

}

Mapper类

package cn.tedu.serialflow;

import java.io.IOException;

import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import org.apache.htrace.fasterxml.jackson.databind.annotation.JsonPOJOBuilder.Value;

public class FlowMapper extends Mapper<LongWritable, Text, Text, Flow> {

	public void map(LongWritable ikey, Text ivalue, Context context) throws IOException, InterruptedException {
		
		String[] arr = ivalue.toString().split(" ");
		Flow f = new Flow();
		f.setIpone(arr[0]);
		f.setCity(arr[1]);
		f.setName(arr[2]);
		f.setFlow(Integer.parseInt(arr[3]));
		context.write(new Text(arr[2]), f);

	}

}

Reduce 类

package cn.tedu.serialflow;

import java.io.IOException;

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

public class FlowReduce extends Reducer<Text, Flow, Text, IntWritable> {

	public void reduce(Text _key, Iterable<Flow> values, Context context) throws IOException, InterruptedException {
		// process values
		int sum = 0;
		for (Flow val : values) {
			sum += val.getFlow();
		}
		context.write(_key, new IntWritable(sum));
	}

}

Driver 类

package cn.tedu.serialflow;

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.Mapper;
import org.apache.hadoop.mapreduce.Reducer;
import org.apache.hadoop.mapreduce.lib.input.FileInputFormat;
import org.apache.hadoop.mapreduce.lib.output.FileOutputFormat;

public class FlowDriver {

	public static void main(String[] args) throws Exception {
		Configuration conf = new Configuration();
		Job job = Job.getInstance(conf, "JobName");
		job.setJarByClass(cn.tedu.serialflow.FlowDriver.class);
		// TODO: specify a mapper
		job.setMapperClass(FlowMapper.class);
		// TODO: specify a reducer
		job.setReducerClass(FlowReduce.class);

		job.setMapOutputKeyClass(Text.class);
		job.setMapOutputValueClass(Flow.class);
		// TODO: specify output types
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(IntWritable.class);

		// TODO: specify input and output DIRECTORIES (not files)
		FileInputFormat.addInputPath(job, new Path("hdfs://10.42.162.10:9000/txt/flow.txt"));
		FileOutputFormat.setOutputPath(job, new Path("hdfs://10.42.162.10:9000/result/flowcount"));

		if (!job.waitForCompletion(true))
			return;
	}

}

数据处理结果

ls	21002
ww	10577
zs	9968

  6. 分区--Partitioner

      (1):分区的作用主要是对数据进行分类

      (2):默认只有一个分区,默认只有一个ReduceTask

      (3):每一个分区都必须对应一个ReduceTask,每一个ReduceTask都会产生一个结果文件

      (4):如果不指定,默认的类时HashPartitioner

      (5):分区号的默认值是从0开始的

      (6):举一个例子

             统计每个人,每一个月的利润----以人名分区

package cn.tedu.partprofit;

import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.mapreduce.Partitioner;

public class PartProfit extends Partitioner<IntWritable, Consumer>{

	@Override
	public int getPartition(IntWritable key, Consumer consumer, int numReduceTasks) {
		
		if(consumer.getName().equals("ls")){
			return 0;
		}else if(consumer.getName().equals("zs")){
			return 1;
		}else{
			return 2;
		}	
		
	}

}

   7.排序

     (1):在MapReduce中,默认是对键会进行(自然序)升序排列的

     (2):要求作为键的对象对应的类必须实现Comparable,又因为键需要被序列化,所以一般需要实现的是WritableComparable               接口

     (3):在排序的时候,如果所有比较的字段都一样,认为是同一个键,会就会把对应的值也会分到一组,这时候就需要做一些               处理

      (4):练习:先按照月份进行升序排序,如果月份一致,则按照业绩降序排列

package cn.tedu.sortprofit;

import java.io.DataInput;
import java.io.DataOutput;
import java.io.IOException;

import org.apache.hadoop.io.Writable;
import org.apache.hadoop.io.WritableComparable;

public class Consumer implements WritableComparable<Consumer>{
	
	private int month;
	private String name;
	private int profit;
	
	public int getMonth() {
		return month;
	}
	public void setMonth(int month) {
		this.month = month;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getProfit() {
		return profit;
	}
	public void setProfit(int profit) {
		this.profit = profit;
	}
	@Override
	public void readFields(DataInput in) throws IOException {
		// TODO Auto-generated method stub
		this.month=in.readInt();
		this.name=in.readUTF();
		this.profit=in.readInt();
		
	}
	@Override
	public void write(DataOutput out) throws IOException {
		// TODO Auto-generated method stub
		out.writeInt(month);
		out.writeUTF(name);
		out.writeInt(profit);
		
	}
	@Override
	public int compareTo(Consumer o) {
		// TODO Auto-generated method stub
		int i = this.month-o.month;
		if(i==0){
			int j = o.profit-this.profit;
			return j==0?-1:j;
		}
		return i;
	}
	@Override
	public String toString() {
		return "Consumer [month=" + month + ", name=" + name + ", profit=" + profit + "]";
	}
   
	

}

   8. 合并--Combiner

       (1):在MapReduce中由于MapTask数量多而ReduceTask数量少,那么ReduceTask的执行效率就成了整个MapReduce的瓶                 颈。所以可以将一部分的汇总计算任务前移到MapTask,这个时候ReduceTask的计算压力就会变小  - 这个过程称之为                 合并 - Combine
       (2):实际生产环境中,Combiner和Reducer的逻辑是一样的 - job.setCombinerClass(Reducet.class);
       (3):虽然Combiner可以提高效率,但是不是所有的场景都适合于用Combiner

     (MapReduce)举例:找出隐藏好友(A和B是好友,B和C是好友,那么A和C就是隐藏好友)

  tom rose
  tom jim
  tom smith
  tom lucy
  rose tom
  rose lucy
  rose smith
  jim tom
  jim lucy
  smith jim
  smith tom
  smith rose

          逻辑:Mapper:tom [rose,Jim,smith,lucy],如果是好友就把key:【自己--friend】,value:1

                                    假设rose和jim,Smith,Lucy是好友:key【自己-friend】,value:0

                                           假设Jim和Smith,Lucy是好友:key  【自己-friend】,value:0

                                                   。。。。。

                      以第一次的结果为源文件,只有当key对应的所有的value都是0的时候,那么自己和friend才是隐藏好友

package cn.tedu.friend;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

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

public class RelationReducer extends Reducer<Text, Text, Text, IntWritable> {

	// 在MapReduce中,这个迭代器只能遍历一次
	public void reduce(Text key, Iterable<Text> values, Context context) throws IOException, InterruptedException {
		List<String> fs = new ArrayList<>();
		// 记录真实好友关系
		String name = key.toString();
		for (Text val : values) {
			String f = val.toString();
			fs.add(f);
			if (name.compareTo(f) < 0)
				context.write(new Text(name + "-" + f), new IntWritable(1));
			else
				context.write(new Text(f + "-" + name), new IntWritable(1));
		}
		// 推测列表中好友可能认识
		for (int i = 0; i < fs.size() - 1; i++) {
			for (int j = i + 1; j < fs.size(); j++) {
				String f1 = fs.get(i);
				String f2 = fs.get(j);
				if (f1.compareTo(f2) < 0)
					context.write(new Text(f1 + "-" + f2), new IntWritable(0));
				else 
					context.write(new Text(f2 + "-" + f1), new IntWritable(0));
					
			}
		}
	}

}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值