8.3 MapReduce 中的序列化(二)

任务目的

  • 理解流量统计项目案例的业务逻辑
  • 掌握流量统计项目案例 Map 端的自定义业务逻辑的编写
  • 掌握流量统计项目案例 Reduce 端的自定义业务逻辑的编写
  • 熟记 MapReduce Driver 端编程规范

任务清单

  • 任务1:流量统计项目案例
  • 任务2:Map 端程序编写
  • 任务3:Reduce 端程序编写
  • 任务4:Driver 端程序编写

详细任务步骤

任务1:流量统计项目案例

  (1)数据样例

13726238888 2481 24681 
13560436666 1116 954 
13726230503 2481 24681 
13826544101 264 0 
13926435656 132 1512 
13926251106 240 0 
18211575961 1527 2106

  (2)字段释义

字段中文释义 字段英文释义 数据类型
手机号 phone String
上行流量 upflow Long
下行流量 downflow Long

  (3)项目需求一

  统计每一个用户(手机号)所耗费的总上行流量、总下行流量、总流量。

  期望输出数据格式:

13480253104	2494800	2494800	4989600

  (4)项目解析

  其实此需求和之前我们写的WordCount有点类似,也是按照key进行求和,只不过此时的key不是每个单词,而是手机号,此时的value不是出现的次数1,而是手机号对应的**{上行流量,下行流量,上行流量+下行流量}**。

  根据之前的 WordCount 示例我们可以联想到,我们需要将**<手机号,{上行流量,下行流量,上行流量+下行流量}>**作为 Map 端的输出,传递给 Reduce 端,让Reduce端按照key,即手机号做汇总操作。

  所以此时的 Map 端的输出value不再是基础类型了,所以我们需要自定义 bean 类来封装流量信息,把 {上行流量,下行流量,上行流量+下行流量} 封装成一个对象。

  序列化输出类 FlowBean 上一节课我们已经定义完成,本节课我们主要实现的是,利用 MapReduce 完成按照手机号 对 value 进行汇总。

任务2:Map 端程序编写

  回顾 MapReduce Map 端编码规范:
  1. 用户自定义的 Mapper 需要继承父类 Mapper
  2. Mapper 的输入数据是 KV 对的形式(KV 的类型可自定义)
  3. Mapper 的输出数据是 KV 对的形式(KV 的类型可自定义)
  4. Mapper 中的业务逻辑写在 map() 方法
  5. MapTask 进程对每一个调用一次map()方法

  接下来进入 Map 端程序的编写,eclipse 成功连接到 Hadoop 集群后,选择“File”->“New”->“Project”->“Map/Reduce Project”创建名为 MyMR 的项目名,在此项目下创建名为 com.hongyaa.sum 的包名,在此包下创建名为 FlowSumMapper.java 的类,如下图所示:

8.3 MapReduce 中的序列化(二)_第1张图片

图1

 

  首先编写 Map 端编程框架,自定义的 FlowSumMapper 需要继承父类 Mapper,输入数据和输出数据都是KV 对的形式。具体框架代码如下:

public class FlowSumMapper extends Mapper{

}
  • KEYIN:是指框架读取到的数据的key的类型,在默认的InputFormat下,读到的key是一行文本的起始偏移量,所以key的类型是Long,对应 Hadoop 中的 LongWritable
  • VALUEIN:是指框架读取到的数据的value的类型,在默认的InputFormat下,读到的value是一行文本的内容,所以value的类型是String,对应 Hadoop 中的 Text
  • KEYOUT:用户自定义逻辑方法返回数据中key的类型,由用户业务逻辑决定,在此程序中,我们输出的key是11位的手机号,所以是String,对应 Hadoop 中的 Text
  • VALUEOUT:用户自定义逻辑方法返回数据中value的类型,由用户业务逻辑决定,在此程序中,我们输出的 value 是我们封装好的流量信息类 FlowBean

  已知 Mapper 中的业务逻辑写在 map() 方法中,在此 map()方法中我们需要实现的是将读取到的每一行文本拆分成3列,分别是手机号,上行流量和下行流量,对**{上行流量,下行流量}**进行封装,每遇到一个手机号就把其转换成一个 key-value 对,比如手机号 13726238888,就转换成<13726238888,{2481,24681,27162}>发送给 ReduceTask 去汇总。具体代码如下所示:

@Override
protected void map(LongWritable key, Text value, Mapper.Context context)
		throws IOException, InterruptedException {
	//(1)将maptask传给我们的每行文本内容先转换成String
	String line = value.toString(); 
	//(2)根据空格将这一行切分成单词
	String[] splits = line.split("\t");
	//(3)抽取业务所需的字段
	String telephone = splits[0]; // 手机号
	String upFlow = splits[1];  // 上行流量
	String downFlow = splits[2];  // 下行流量
	//(4)获取上行流量和下行流量,对其进行封装
	FlowBean fb = new FlowBean(Long.parseLong(upFlow), Long.parseLong(downFlow));
	//(5)将手机号作为key,将封装的流量信息类作为value
	context.write(new Text(telephone), fb);
}

  FlowSumMapper.java 的完整代码如下所示:

public class FlowSumMapper extends Mapper {
	@Override
	protected void map(LongWritable key, Text value, Mapper.Context context)
			throws IOException, InterruptedException {
		//(1)将maptask传给我们的每行文本内容先转换成String
		String line = value.toString(); 
		//(2)根据空格将这一行切分成单词
		String[] splits = line.split("\t");
		//(3)抽取业务所需的字段
		String telephone = splits[0]; // 手机号
		String upFlow = splits[1];  // 上行流量
		String downFlow = splits[2];  // 下行流量
		//(4)获取上行流量和下行流量,对其进行封装
		FlowBean fb = new FlowBean(Long.parseLong(upFlow), Long.parseLong(downFlow));
		//(5)将手机号作为key,将封装的流量信息类作为value
		context.write(new Text(telephone), fb);
	}
}

任务3:Reduce 端程序编写

  回顾 MapReduce Reduce 端编码规范:
  1. 用户自定义的 Reducer 需要继承父类 Reducer
  2. Reducer 的输入数据类型对应 Mapper 的输出数据类型,也是 KV
  3. Reducer 的输出数据是 KV 对的形式(KV 的类型可自定义)
  4. Reducer 的业务逻辑写在 reduce() 方法
  5. ReduceTask 进程对每一组相同 k 的组调用一次 reduce() 方法

  接下来进入 Reduce 端程序的编写,在 com.hongyaa.sum 包下创建名为 FlowSumReducer.java 的类,如下图所示:

8.3 MapReduce 中的序列化(二)_第2张图片

图2

 

  首先编写 Reduce 端编程框架,自定义的 FlowSumReducer 需要继承父类 Reducer,输入数据和输出数据都是KV 对的形式。具体框架代码如下:

public class FlowSumReducer extends Reducer {

}
  • KEYIN:对应 Mapper 端输出的 KEYOUT,即每个手机号,所以是 String,对应 Hadoop 中的 Text
  • VALUEIN:对应 Mapper 端输出的 VALUEOUT,即封装的流量信息类,所以是 FlowBean
  • KEYOUT:用户自定义逻辑方法返回数据中key的类型,由用户业务逻辑决定,在此程序中,我们输出的key是手机号,所以是String,对应 Hadoop 中的 Text
  • VALUEOUT:用户自定义逻辑方法返回数据中value的类型,由用户业务逻辑决定,在此程序中,我们输出的value是累加求和后封装的流量信息类,所以是 FlowBean

  已知 Reducer 中的业务逻辑写在 reduce() 方法中,在此 reduce()方法中我们需要接收 MapTask 的输出结果,然后按照 key(手机号) 对 value(上行流量、下行流量) 做汇总操作。具体代码如下所示:

@Override
protected void reduce(Text key, Iterable values,
		Reducer.Context context) throws IOException, InterruptedException {
	long sumUpFlow = 0L; // 总上行流量
	long sumDownFlow = 0L; // 总下行流量
	
	for (FlowBean fb : values) {
		sumUpFlow += fb.getUpFlow();
		sumDownFlow += fb.getDownFlow();
	}

	// 获取总上行流量和总下行流量,对其进行封装
	FlowBean resultsum = new FlowBean(sumUpFlow, sumDownFlow);
	// 将手机号作为key,将封装的流量信息类作为value,写出最终结果
	context.write(key, resultsum);
}

  FlowSumReducer.java 的完整代码如下所示:

public class FlowSumReducer extends Reducer {
	 /** 
	  * 框架每传递一组数据<13502468823,{FlowBean,FlowBean,FlowBean...}>调用一次reduce方法
	  * reduce中的业务逻辑是遍历values,然后进行累加求和后输出
	  */

	@Override
	protected void reduce(Text key, Iterable values,
			Reducer.Context context) throws IOException, InterruptedException {
		long sumUpFlow = 0L; // 总上行流量
		long sumDownFlow = 0L; // 总下行流量
		
		for (FlowBean fb : values) {
			sumUpFlow += fb.getUpFlow();
			sumDownFlow += fb.getDownFlow();
		}

		// 获取总上行流量和总下行流量,对其进行封装
		FlowBean resultsum = new FlowBean(sumUpFlow, sumDownFlow);
		// 将手机号作为key,将封装的流量信息类作为value,写出最终结果
		context.write(key, resultsum);
	}
}

任务4:Driver 端程序编写

  回顾 MapReduce Driver 端编码规范:整个程序需要一个 Drvier 来进行提交,提交的是一个描述了各种必要信息的 job 对象。

  接下来进入 Driver 端程序的编写,在 com.hongyaa.sum 包下创建名为 FlowSum.java 的类,如下图所示:

8.3 MapReduce 中的序列化(二)_第3张图片

图3

 

  Driver 端为该 FlowSum 程序运行的入口,相当于 YARN 集群(分配运算资源)的客户端,需要创建一个 Job 类对象来管理 MapReduce 程序运行时需要的相关运行参数,最后将该 Job 类对象提交给 YARN。

  Job对象指定作业执行规范,我们可以用它来控制整个作业的运行。接下来,我们来看一下作业从提交到执行的整个过程。

  FlowSum.java 的完整代码如下所示:

public class FlowSum {
	/**
	 * 项目需求:统计每一个用户(手机号)所耗费的总上行流量、总下行流量、总流量;
	 * 
	 */
	public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
		// (1)创建配置文件对象,指定mapreduce程序所需的 HDFS 相关参数
		Configuration conf = new Configuration();
		// 本地运行模式
		conf.set("fs.defaultFS", "hdfs://localhost:9000");

		// (2)新建一个 job 任务
		Job job = Job.getInstance(conf);

		// (3)将 job 所用到的那些类(class)文件,打成jar包 (打成jar包在集群运行必须写)
		job.setJarByClass(FlowSum.class);

		// (4)指定Mapper和Reducer组件
		job.setMapperClass(FlowSumMapper.class);
		job.setReducerClass(FlowSumReducer.class);

		// (5)指定MapTask和ReduceTask的输出key-value类型
		job.setOutputKeyClass(Text.class);
		job.setOutputValueClass(FlowBean.class);

		// (6)指定该MapReduce程序数据的输入输出路径
		Path inPath = new Path("/flow/input");
		Path outPath = new Path("/flow/output_sum");
		// 获取fs对象
		FileSystem fs = FileSystem.get(conf);
		// 判断输出路径是否存在
		if (fs.exists(outPath)) {
			// 输出路径存在则递归删除
			fs.delete(outPath, true);
		}

		FileInputFormat.addInputPath(job, inPath);
		FileOutputFormat.setOutputPath(job, outPath);

		// (7)最后提交任务给YARN来运行,等着集群运行完成返回反馈信息,客户端退出
		boolean waitForCompletion = job.waitForCompletion(true);
		System.exit(waitForCompletion ? 0 : 1);
	}
}

  执行结果如下所示:

8.3 MapReduce 中的序列化(二)_第4张图片

图4

你可能感兴趣的:(Hadoop技术)