定义
MapReduce最早是由谷歌公司研究提出的一种面向大规模数据处理的并行计算模型和方法。
特点:
MapReduce是一个基于集群的高性能并行计算平台。
MapReduce是一个并行计算与运行软件框架。
MapReduce是一个并行程序设计模型与方法。
易于编程,良好的扩展性,高容错性,适合PB级别以上的海量数据的离线处理
但是同时,不适合实时计算,不擅长流式计算,不擅长DAG计算(程序依赖)
概念
Job(任务): 一个MR程序
MRAppMaster(MR任务的主节点):一个Job在运行是,会先启动一个进程,负责Job执行过程的监控,容错,申请资源,提交task
Task(任务):计算
Map:切分。 将输入数据切分成若干小部分,每个部分为1片split,每片数据交给一个task进行计算(MapTask)
Reduce: MapTask的汇总
常用组件:
Mapper
Reducer
InputFormat 输入目录的文件格式。 普通文件:FileInputFormat , SequeceFileInput(hadoop格式),DBInputFormat(数据库的格式)
OutputFormat 类上
RecordWriter 记录写出其 结果以什么格式,写出到文件中
Partitioner 分区器
流程
MapReduce中,Map阶段处理的数据如何传递给Reduce阶段,是整个MapReduce框架中最关键的一个流程,这个流程就叫Shuffle。它的核心机制包括数据分区、排序和缓存等。
支持的数据类型
(1)BooleanWritable:标准布尔型数值。
(2)ByteWritable:单字节数值。
(3)DoubleWritable:双字节数。
(4)FloatWritable:浮点数。
(5)IntWritable:整型数。
(6)LongWritable:长整型数。
(7)Text:使用UTF8格式存储的文本。
(8)NullWritable:当<key,value>中的key或value为空时使用。
(9)ArrayWritable:存储属于Writable类型的值数组(要使用ArrayWritable类型作为Reduce输入的value类型,需要创建ArrayWritable的子类来指定存储在其中的Writable值的类型)。
运行最新版hadoop的mapreduce的时候经常会有各种报错,最好是有linux环境可以用。windows环境下设置HADOOP_HOME,并把wintils放在bin目录下:
https://github.com/steveloughran/winutils
demo
maven
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
code:
public class MyDriver {
public static final Configuration configuration = new Configuration();
static {
// configuration.set("fs.defaultFS", "hdfs://192.168.31.101:9000");
System.setProperty("HADOOP_USER_NAME", "hadoop");
// System.setProperty("HADOOP_HOME", "D:\\tools\\hadoop-3.3.0");
}
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
remortJob();
// localJob();
}
private static void remortJob() throws IOException, ClassNotFoundException, InterruptedException, URISyntaxException {
System.setProperty("HADOOP_HOME", "D:\\tools\\hadoop-3.3.0");
Path inputPath=new Path("/wcinput/logs");
Path outputPath=new Path("/wcoutput/test");
configuration.set("fs.defaultFS","hdfs://192.168.31.101:9000");
configuration.set("fs.defaultFS","hdfs://192.168.31.101:9000");
FileSystem fs=FileSystem.get(new URI("hdfs://192.168.31.101:9000"),configuration,"root");
if (fs.exists(outputPath)) {
fs.delete(outputPath, true);
}
// 创建job
Job job=Job.getInstance(configuration);
job.setJobName("wordcount test");
// 设置job
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
// 准备序列化器
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 输入输出目录
FileInputFormat.setInputPaths(job,inputPath);
FileOutputFormat.setOutputPath(job,outputPath);
// 运行job
job.waitForCompletion(true);
}
private static void localJob() throws IOException, InterruptedException, ClassNotFoundException {
System.setProperty("HADOOP_HOME", "D:\\tools\\hadoop-3.3.0");
Path inputPath=new Path("d:/mrinput/");
Path outputPath=new Path("d:/mroutput/wd");
FileSystem fs=FileSystem.get(configuration);
// 创建job
Job job=Job.getInstance(configuration);
job.setJobName("wordcount test");
// 设置job
job.setMapperClass(MyMapper.class);
job.setReducerClass(MyReducer.class);
// 准备序列化器
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
// 输入输出目录
FileInputFormat.setInputPaths(job,inputPath);
FileOutputFormat.setOutputPath(job,outputPath);
// 运行job
job.waitForCompletion(true);
}
}
public class MyMapper extends Mapper<LongWritable, Text, Text, IntWritable> {
private Text outKey=new Text();
private IntWritable outValue=new IntWritable(1);
/**
* 每个 keyin valuein 执行一次
* 每个 keyvalue都转成(word,1)
* @param key
* @param value
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
// super.map(key, value, context);
System.out.println(" key:"+key +" value:"+value);
String[] split = value.toString().split("\t");
for (String word :
split) {
outKey.set(word);
context.write(outKey,outValue);
}
}
}
public class MyReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
private IntWritable outValue;
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
// super.reduce(key, values, context);
int sum=0;
for (IntWritable item :
values) {
sum += item.get();
}
outValue = new IntWritable(sum);
context.write(key,outValue);
}
}
InputFormat的作用:
- 验证输入目录中文件格式,是否符合当前Job的要求
- 生产切片,每个切片都会交给一个MapTask处理
- 提供RecordReader,由RR从切片中读取记录,交给Mapper处理。
方法 List getSplits: 切片
RecordReader<K,V> createRecordReader: 创建RR
默认使用的是TextInputFormat , LineRecordReader
切片策略
TextInputFormat:
常用于输入目录中全是文本文件
RecordReader: LineRecordReader 一次处理一行,将一行内容偏移量作为key,内容value
NLineInputFormat:
切分n行,执行逻辑复杂情况
KeyValueTextInputFormat:
键值对格式
CombineTextInputText:
多个小文件
FileInputFormat
public List<InputSplit> getSplits(JobContext job) throws IOException {
StopWatch sw = new StopWatch().start();
long minSize = Math.max(getFormatMinSplitSize(), getMinSplitSize(job));// getFormatMinSplitSize 其实是1,getMinSplitSize 是取配置 mapreduce.input.fileinputformat.split.maxsize
long maxSize = getMaxSplitSize(job); //mapreduce.input.fileinputformat.split.maxsize
// generate splits
List<InputSplit> splits = new ArrayList<InputSplit>();
List<FileStatus> files = listStatus(job); // 输入目录所有文件的状态信息
boolean ignoreDirs = !getInputDirRecursive(job)
&& job.getConfiguration().getBoolean(INPUT_DIR_NONRECURSIVE_IGNORE_SUBDIRS, false);
for (FileStatus file: files) {
if (ignoreDirs && file.isDirectory()) {
continue;
}
Path path = file.getPath();
long length = file.getLen();
if (length != 0) {
BlockLocation[] blkLocations;
if (file instanceof LocatedFileStatus) {
blkLocations = ((LocatedFileStatus) file).getBlockLocations();
} else {
FileSystem fs = path.getFileSystem(job.getConfiguration());
blkLocations = fs.getFileBlockLocations(file, 0, length);
}
if (isSplitable(job, path)) {// 判断方法与实现类相关,各个类自己实现,默认true
long blockSize = file.getBlockSize();
long splitSize = computeSplitSize(blockSize, minSize, maxSize);
long bytesRemaining = length; // 循环切片
while (((double) bytesRemaining)/splitSize > SPLIT_SLOP) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, splitSize,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
bytesRemaining -= splitSize;
}
if (bytesRemaining != 0) {
int blkIndex = getBlockIndex(blkLocations, length-bytesRemaining);
splits.add(makeSplit(path, length-bytesRemaining, bytesRemaining,
blkLocations[blkIndex].getHosts(),
blkLocations[blkIndex].getCachedHosts()));
}
} else { // not splitable 不可切,直接传入文件
if (LOG.isDebugEnabled()) {
// Log only if the file is big enough to be splitted
if (length > Math.min(file.getBlockSize(), minSize)) {
LOG.debug("File is not splittable so no parallelization "
+ "is possible: " + file.getPath());
}
}
splits.add(makeSplit(path, 0, length, blkLocations[0].getHosts(),
blkLocations[0].getCachedHosts()));
}
} else {
//Create empty hosts array for zero length files
splits.add(makeSplit(path, 0, length, new String[0]));
}
}
// Save the number of input files for metrics/loadgen
job.getConfiguration().setLong(NUM_INPUT_FILES, files.size());
sw.stop();
if (LOG.isDebugEnabled()) {
LOG.debug("Total # of splits generated by getSplits: " + splits.size()
+ ", TimeTaken: " + sw.now(TimeUnit.MILLISECONDS));
}
return splits;
}
片与块的关系
默认的片大小是文件的块大小,文件默认128M
片: InputSplit 计算MR时进行切片,临时的逻辑区,与输入格式相关
块:Block HDFS的存储单位,实际物理存在
建议,片大小=块大小。减少磁盘IO,网络IO
提交流程
public boolean waitForCompletion(boolean verbose
) throws IOException, InterruptedException,
ClassNotFoundException {
if (state == JobState.DEFINE) {
submit();
}
if (verbose) {
monitorAndPrintJob();
} else {
// get the completion poll interval from the client.
int completionPollIntervalMillis =
Job.getCompletionPollInterval(cluster.getConf());
while (!isComplete()) {
try {
Thread.sleep(completionPollIntervalMillis);
} catch (InterruptedException ie) {
}
}
}
return isSuccessful();
}
public void submit()
throws IOException, InterruptedException, ClassNotFoundException {
ensureState(JobState.DEFINE);
setUseNewAPI();
connect();
final JobSubmitter submitter =
getJobSubmitter(cluster.getFileSystem(), cluster.getClient());
status = ugi.doAs(new PrivilegedExceptionAction<JobStatus>() {
public JobStatus run() throws IOException, InterruptedException,
ClassNotFoundException {
return submitter.submitJobInternal(Job.this, cluster);
}
});
state = JobState.RUNNING;
LOG.info("The url to track the job: " + getTrackingURL());
}
关键设置
MapTask数量,认为设置无效,只能通过切片的方式设置,MapTask取决于切片数。
Job提交流程阶段总结
准备
运行job.waitForCompletion(),生成一下信息
job.split 当前job的切片信息,有几个切片对象
job.splitmetainfo 切片对象的属性信息
job.xml job的属性配置
提交
本地模式:LocalJobRunner提交,创建LocalJobRunner.Job()
Map阶段: 线程池,提交多个MapTaskRunnable
每个MapTaskRunnable线程上,实例化一个MapTask对象
每个MapTask对象实例化一个Mapper
线程运行结束,在线程的作业目录中生成file.out文件,报错MapTask输出的所有Key-value
map:使用RR将切片中的数据读入到Mapper.map() context.write(key,value)
Reduce阶段:
线程池提交多个ReduceTaskRunnable
每个ReduceTask对象,实例化一个Reducer, reducer.run()
线程运行结束,在输出目录中生成,part-r-000x文件,保存ReduceTask输出的所有Key-value
copy: shuffle线程拷贝MapTask指定的分区数据
sort:将拷贝的所有分区数据汇总后,排序
reduce:排好序的数据,进行 合并