【关于YARN与MapReduce的一些思考以及使用mapreduce对图书销售量进行统计】

关于YARN与MapReduce的一些思考以及使用mapreduce对图书销售量进行统计

【问1】什么是Map计算?什么是Reduce计算?为何说Map+Redcue的计算组合非常适合拆解计算任务,并套用分布式计算?

答:Map计算是指将输入数据分解为一组键值对,然后将每个键值对映射为另一组键值对的过程。在Map阶段,数据被划分为多个块,每个块被一个Map任务处理。Map任务将其输入的每个键值对转换为一组中间键值对,并将这些中间结果按键分组,以便后续的Reduce任务进行处理。
Reduce计算是指将一组中间键值对聚合为一组输出键值对的过程。在Reduce阶段,Map任务输出的中间键值对被分组,并按键进行排序。然后,每个Reduce任务将其输入的键值对列表合并为一组输出键值对。
Map计算和Reduce计算的组合非常适合拆解计算任务,并套用分布式计算,因为它们可以充分利用Hadoop分布式计算框架的优势。
首先,Map计算可以将输入数据分成多个块,并在不同的计算节点上并行处理,从而实现并行计算。每个Map任务都只处理一小部分数据,因此可以避免单节点计算任务的瓶颈问题,提高计算效率。
其次,Reduce计算可以将多个Map任务输出的中间结果进行聚合,并生成最终的计算结果。Reduce任务可以在不同的计算节点上并行处理,从而实现更高效的数据处理和计算。
因此,MapReduce模型将大规模数据处理任务分解为多个小任务,并在分布式计算集群上进行并行计算,从而实现更高效的数据处理和计算。这种计算模式非常适合处理大规模数据集,并且具有良好的可扩展性和容错性,可应用于多种不同的数据处理和计算场景,例如数据挖掘、机器学习、日志分析、搜索引擎等。

【问题2】为何会出现YARN这个开源项目?

答:YARN(Yet Another Resource Negotiator)是Apache Hadoop生态系统中的一个开源项目,用于管理和分配集群资源,支持多种数据处理应用程序的运行。在之前的Hadoop版本中,MapReduce是唯一的计算框架,所有的计算任务都必须在MapReduce框架下运行。但是,随着Hadoop生态系统的发展,越来越多的计算框架被引入到Hadoop中,这就需要一个更加通用的资源管理系统来支持这些计算框架的运行。
因此,YARN项目应运而生,它提供了一个通用的资源管理框架,可以支持多种计算框架的运行。YARN将Hadoop的资源管理和计算分离开来,使得资源管理和计算框架可以独立扩展和升级。它将集群资源分配给不同的应用程序,以便这些应用程序可以并行地执行任务。YARN的核心是Resource Manager和Node Manager。
Resource Manager是YARN的主要组件之一,它是集群资源的中央管理器,负责为不同的应用程序分配资源。Resource Manager维护了整个集群的资源信息,并根据应用程序的需求来分配资源。它还负责监控集群中的节点和任务状态,并根据需要重新分配资源。
Node Manager是YARN的另一个组件,它运行在每个计算节点上,负责管理该节点上的资源。Node Manager监控节点资源的使用情况,并向Resource Manager报告节点的可用资源。它还负责启动和停止计算容器(Containers),并为容器提供所需的资源。
YARN的优点在于它可以支持多个计算框架的运行,例如Apache Spark、Apache Flink、Apache Storm等,而不仅仅局限于MapReduce。这使得Hadoop生态系统更加灵活,可以适应更多的计算需求。另外,YARN还提供了更细粒度的资源管理和调度,可以更好地满足不同应用程序的需求。此外,YARN还支持多租户和安全性等功能,可以保证不同用户之间的资源隔离和安全性。
总之,YARN的出现是为了支持Hadoop生态系统中的多个计算框架的运行,并提供更灵活、更可扩展、更安全、更高效的资源管理和计算框架的支持。它的出现使得Hadoop生态系统更加完整和成熟,也为大数据处理提供了更多的选择和可能性。

【问题3】对照下面这张图,用自己的语言描述MR执行过程。

答:开始的时候先对数据进行预处理,以指定字符分隔开,然后再传入map中去,map再一行一行读取,将数据转化位键值对。然后再存入内存里面去,形成缓冲区,然后经过shuffle对数据进行排序,分组,分区,,然后写入磁盘里面去,然后再将多个已经排序好了的数据合并成为数据集,因为这是处理大数据要合并的数据集可能太大而无法全部加载到内存中,所以需要使用硬盘作为临时存储空间,然后各个map再从内存里面读取排序好的键值对数据传入到reduce中去,然后相同的键的数据到一个reduce里面去,在这里shuffle还会对数据再进行排序,将reduce中的数据再一次排序。然后reduce在开始计算,最后输出结果。

【问题4】尝试讨论shuffle为何是MR的灵魂?能否从源码角度介绍shuffle的排序机制?

答:shuffle是MapReduce中实现数据分布和数据排序的关键步骤。Shuffle的核心任务是将Map任务的输出按照key进行分组,排序等,并将分组后的数据传递给Reduce然后在对reduce进行排序,最后进行聚合和计算。Shuffle的实现涉及到多个组件和模块,包括Partitioner、RawComparator、MergeSort和IFile等。
1.在Map阶段,每个Map任务会将输出数据按照key值进行排序,保证同一个partition中的key-value对是有序的。在Hadoop中,Map任务的输出数据会被写入到环形缓冲区中,当缓冲区达到一定大小时,Hadoop会将缓冲区中的数据写入到磁盘上的临时文件中。在写入临时文件时,Hadoop会将数据按照partition编号和key值进行排序,具体实现参考MapOutputBuffer类。MapOutputBuffer是Map任务输出数据的缓冲区,它维护了多个Partition的输出数据,每个Partition对应一个RecordWriter。MapOutputBuffer中的数据是按照partition编号和key值排序的,Map任务的输出数据会被写入到对应的Partition中。在写入Partition时,Hadoop会调用Partitioner的getPartition()方法,根据key值的hash值计算数据应该写入哪个Partition中。
2.在Shuffle阶段,Reducer任务会向对应的Map任务请求数据,并将请求到的数据按照key值进行排序和合并。Hadoop会将Map任务的输出数据分成若干个partition,并将每个partition写入磁盘上的一个临时文件中。在Shuffle阶段,Reducer任务会打开这些临时文件,并按照partition编号和key值进行排序和合并,生成最终的结果。具体来说,在Shuffle阶段,Reducer任务会打开对应的Map任务的临时文件,读取其中的数据,并按照key值进行排序和合并。Hadoop使用MergeSort对所有partition的数据进行排序和合并,具体实现参考MergeSort类。MergeSort是一个通用的排序和合并工具,它可以对多个输入文件进行排序和合并,生成一个有序的输出文件。在Shuffle阶段,MergeSort会将所有partition的数据按照key值进行合并和排序,生成一个有序的输出文件。在合并的过程中,Hadoop会使用RawComparator来比较key值的大小,将相同key值的数据进行合并。具体实现参考MergeSort类中的merge()方法。
3.在Reduce阶段,所有Reducer的输出会被合并和排序,生成最终的结果。在Hadoop中,Reduce任务会使用MergeSort对所有Reducer的输出进行排序和合并。具体实现参考ReduceTask类中的mergeParts()方法。在Reduce任务的run()方法中,每个Reducer会将输出数据写入到磁盘上的一个临时文件中。在写入临时文件时,Hadoop会将数据按照key值进行排序,具体实现参考ReduceTask类中的Sorter类。Sorter是一个通用的排序工具,它可以对多个输入文件进行排序,生成一个有序的输出文件。在Reduce阶段,Sorter会将所有Reducer的输出数据按照key值进行排序和合并,生成一个有序的输出文件。在合并的过程中,Hadoop会使用RawComparator来比较key值的大小,并将相同key值的数据进行合并。具体实现参考Sorter类中的merge()方法。

【问题5】已知HDFS+MR形成了block+split的双层机制去控制数据处理粒度,请问:照这个策略去切分数据,会不会出现默认128MB切分导致某一行数据被切坏(即分割到两个数据微粒)的情况?Hadoop又是怎么避免or解决这个问题的?能否从源码角度解释?

答:不会,在HDFS中,文件会被分割成一个个的数据块(Block),每个数据块通常为128MB。在MapReduce作业中,每个数据块会被切分成一个个的数据片段(Split),每个数据片段通常为默认128MB或自定义大小。每个Mapper任务会处理一个或多个数据片段,将输入数据映射成一组中间结果,然后将中间结果传递给Reducer任务进行合并和排序。在默认情况下,Hadoop会根据输入文件的大小自动切分数据块,以确保每个数据片段的大小大致相同。但是,如果某一行数据的长度超过了数据片段的大小,就会出现该行数据被切坏的情况。Hadoop提供了两个解决方案来避免或解决这个问题:
1.自定义InputFormat和RecordReader
Hadoop允许用户自定义InputFormat和RecordReader,以控制读取输入数据的方式和粒度。如果用户知道输入数据的格式和大小,可以自定义RecordReader来确保每个数据片段的大小适合输入数据的格式和大小。例如,如果输入数据是文本文件,并且每行数据的长度不确定,可以自定义一个RecordReader,将输入数据按行读取,并根据行的长度切分数据片段。这样就可以避免某一行数据被切坏的情况。具体实现可以参考TextInputFormat和LineRecordReader类。
2.使用CombineFileInputFormat
Hadoop提供了一个CombineFileInputFormat类,它可以将多个小文件或数据块合并成一个数据片段,以减少数据片段的数量和Mapper任务的启动时间。使用CombineFileInputFormat可以避免某一行数据被切坏的情况,因为多个小文件或数据块被合并成一个数据片段后,每个数据片段的大小就比较稳定了。具体实现可以参考CombineFileInputFormat和CombineFileRecordReader类。

【问题6】[编程题]关于图书销售排行前三统计案例

某市连锁书店共有4个门店,每个门店的每一笔图书销售记录都会自动汇总到该市分公司的销售系统后台,销售记录数据如下(约100万数量级):

BTW-08001 2011 年 1 月 2 日 2011年1月2日 201112鼎盛书店$BK-83021$12

BTW-08002 2011 年 1 月 4 日 2011年1月4日 201114博达书店$BK-83033$5

BTW-08003 2011 年 1 月 4 日 2011年1月4日 201114博达书店$BK-83034$41

其中记录样例说明如下:

//[流水单号] [ 交易时间 ] [交易时间] [交易时间][书店名称] [ 图书编号 ] [图书编号] [图书编号][售出数量]

BTW-08001 2011 年 1 月 2 日 2011年1月2日 201112鼎盛书店$BK-83021$12

年底,公司市场部需要统计本年度各门店图书热销Top3,并做成4张报表上报公司经理;请在MR框架下编程实现这个需求,对应数据文件已经附上。

解题思路:

1.首先看我们要分析计算的数据:
从左往右依次是:

流水单号、出售日期、书店名称、图书编号、出售数量
在这里插入图片描述

在根据题目进行分析

它需要按照每一年统计不同书店的图书销售量排行前三的图书。就有问题了:
1.原数据的日期是string类型,不是data类型,所以需要转化。
2.要按照每一年统计的话,还需要提取日期中的年份。
3.相同书店的相同图书的销售量要按照年份来累加。
4.最后取出前三就行。

分析之后就开始编写程序了。

(1).编写Book类将一行数据转化为一个book对象,并且实现序列化和反序列化。

package com.Book;

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

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

public class Book implements Writable {

	// 数据:BTW-08003$2011年1月4日$博达书店$BK-83034$41
	// [流水单号]$[交易时间]$[书店名称]$[图书编号]$[售出数量]
	private String BId;// 流水单号
	private String booktime;// 交易时间
	private String shopname;// 书店名称
	private String bookname;// 图书编号
	private int Num;// 售出数量

	public Book() {
		// 无构造函数
	}

	public Book(String BId, String booktime, String shopname, String bookname, int Num) {
		this.BId = BId;
		this.booktime = booktime;
		this.shopname = shopname;
		this.bookname = bookname;
		this.Num = Num;
	}

	public void write(DataOutput out) throws IOException {
		// TODO 自动生成的方法存根
		out.writeUTF(this.BId);
		out.writeUTF(this.booktime);
		out.writeUTF(this.shopname);
		out.writeUTF(this.bookname);
		out.writeInt(this.Num);
	}

	public void readFields(DataInput in) throws IOException {
		// TODO 自动生成的方法存根
		this.BId = in.readUTF();
		this.booktime = in.readUTF();
		this.shopname = in.readUTF();
		this.bookname = in.readUTF();
		this.Num = in.readInt();

	}

	@Override
	// 这是 toString() 方法的重写,将 SalesRecord 对象转换为字符串形式,方便输出。
	public String toString() {
		return BId + "\t" + booktime + "\t" + shopname + "\t" + bookname + "\t" + Num;
	}

	public String getBId() {
		return BId;
	}

	public void setBId(String bId) {
		BId = bId;
	}

	public String getBooktime() {
		return booktime;
	}

	public void setBooktime(String booktime) {
		this.booktime = booktime;
	}

	public String getShopname() {
		return shopname;
	}

	public void setShopname(String shopname) {
		this.shopname = shopname;
	}

	public String getBookname() {
		return bookname;
	}

	public void setBookname(String bookname) {
		this.bookname = bookname;
	}

	public int getNum() {
		return Num;
	}

	public void setNum(int num) {
		Num = num;
	}
}

(2).编写bookMapper类,对原始数据进行处理,转化为键值对的形式好为了之后的reduce做准备。转化的键值对,键是书店名称-年份,值是book类的对象,包含book的全部信息。

package com.Book;

import java.io.IOException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

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 org.apache.hadoop.mapreduce.Mapper.Context;

public class bookMapper extends Mapper<LongWritable, Text, Text, Book> {
	// 输出键

	private String delimiter = "\\$";

	@Override
	protected void map(LongWritable key1, Text value1, Context context) throws IOException, InterruptedException {
		// 数据:BTW-08003$2011年1月4日$博达书店$BK-83034$41
		// [流水单号]$[交易时间]$[书店名称]$[图书编号]$[售出数量]
		String data = value1.toString();

		// 分词:按空格来分词
		String[] booksParts = data.split(delimiter);

		// 判断输入的字段数量是否足够
		if (booksParts.length >= 5) {
			String BId = booksParts[0];
			String Booktime = booksParts[1];
			String Shopname = booksParts[2];
			String Bookname = booksParts[3];
			int Num = Integer.parseInt(booksParts[4]);// 将字符串转化位数字。

			// 解析日期字段获取年份信息
			LocalDate timeDate = parseDate(booksParts[1]);
			int year = timeDate.getYear();

			// 将书店名称和图书编号作为key,图书编号和售出数量作为value输出到
			Text outputKey = new Text(Shopname + "-" + year);
			Book outputValue = new Book();
			
			outputValue.setBId(BId);
			outputValue.setBookname(Bookname);
			outputValue.setBooktime(Booktime);
			outputValue.setNum(Num);
			outputValue.setShopname(Shopname);
			
			context.write(outputKey, outputValue);// 这个传给Ruducer是书店的所有信息。v2就代表所有信息
		} else {
			 // 处理字段数量不足的情况,可以输出日志或进行其他适当的处理
            System.err.println("Invalid input format: " + value1.toString());
		}
	}

	private LocalDate parseDate(String transactionDateStr) {
		//解析一个字符串类型的日期字段
		DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年M月d日");
		//将字符串日期和日期解析格式传进去。
		//返回的就是LocalDate 类型的日期对象。
		return LocalDate.parse(transactionDateStr, formatter);
	}
}

(3).编写bookReducer类,对键值对数据进行计算,累加相同年份相同书店的书籍的销售量。返回前三名。

package com.Book;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;


public class bookReducer extends Reducer<Text, Book, Text, Text> {
	// 序列化:
	protected void reduce(Text k3, Iterable<Book> v3, // Iterable是可迭代的接口
			Context context) throws IOException, InterruptedException {
		
		// 用于存储每种书籍类型的销量总和
		//Integer 是 Java 中的一个内置类,它用于表示整数类型的数据。
		Map<String, Integer> bookTypeSales = new HashMap<String, Integer>();

		// 将所有销售记录加入到List中
		for (Book value : v3) {
			String bookname = value.getBookname();//图书的编号。
			int Num = value.getNum();
			// 累加相同的书籍的销售量。
			if (bookTypeSales.containsKey(bookname)) {// 是否存在相同的键
				int totalSales = bookTypeSales.get(bookname) + Num;// 就在这个键的基础上加Num就行。
				bookTypeSales.put(bookname, totalSales);// 最后将键值对加入HashMap相当于更新该条目的值
			} else {
				bookTypeSales.put(bookname, Num);
			}
		}

		// 将键值对集合转换为 List
		List<Map.Entry<String, Integer>> list = new ArrayList<Entry<String, Integer>>(bookTypeSales.entrySet());
		
		//使用Collections.sort方法对list进行按值排序
		Collections.sort(list,new Comparator<Map.Entry<String,Integer>>(){
			public int compare(Map.Entry<String, Integer> o1,Map.Entry<String, Integer> o2 ) {
				return o2.getValue().compareTo(o1.getValue());
			} 
		});
		
		//支持在字符串任意位置进行插入、删除、替换和拼接等操作,是一个可变的字符序列。
	    StringBuilder result = new StringBuilder();
		
		// 输出排序后的前三个键值对的键和值
		int count = 0; // 计数器
		for (Map.Entry<String, Integer> entry : list) {
		    if (count < 3) { // 只输出前三个键值对的键和值
		        System.out.println(entry.getKey() + ": " + entry.getValue());
		        result.append(entry.getKey()).append(" ").append(entry.getValue()).append("\t");
		        count++;
		    } else {
		        break; // 如果已经输出了前三个键值对,就退出循环
		    }
		}
		//写数据
		context.write(k3, new Text(result.toString()));
	}
}

(4).最后编写一个Main类就行。指定mapper、reducer的输入输出键值对类型,以及他们的位置,还有指定分区的类。

package com.Book;

import java.io.IOException;

import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.NullWritable;
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 Main {
	public static void main(String[] args) throws Exception {
		Job job = Job.getInstance();// 创建一个带有默认配置设置的新Job对象,这个就自动配置。
		job.setJarByClass(Main.class); // main方法所在的class
		
		//2.指定job的mapper和输出的类型<k2 v2>
		job.setMapperClass(bookMapper.class);//指定Mapper类
		job.setMapOutputKeyClass(Text.class);    //k2的类型
		job.setMapOutputValueClass(Book.class);  //v2的类型
		job.setPartitionerClass(bookPartition.class);//设置分区类。
		
		//3.指定job的reducer和输出的类型<k4  v4>
		job.setReducerClass(bookReducer.class);//指定Reducer类
		job.setOutputKeyClass(Text.class);  //k4的类型
		job.setOutputValueClass(Text.class);  //v4的类型
		
		//设置输入格式为自定义的输入格式CustomInputFormat,并指定分隔符为逗号;
		job.getConfiguration().set("delimiter", "$");
		//4.指定job的输入和输出的路径。
		FileInputFormat.setInputPaths(job, new Path(args[0]));
		FileOutputFormat.setOutputPath(job, new Path(args[1]));
		
        // 设置Reduce任务的数量为4
        job.setNumReduceTasks(4);
		
		//5.执行job并返回执行结果。
		job.waitForCompletion(true);	 
	}
}

运行结果:

在这里插入图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值