(1) mr
public class WordCount {
public static class TokenizerMapper
extends Mapper<Object, Text, Text, IntWritable>{
private final static IntWritable one = new IntWritable(1);
private Text word = new Text();
public void map(Object key, Text value, Context context
) throws IOException, InterruptedException {
StringTokenizer itr = new StringTokenizer(value.toString());
while (itr.hasMoreTokens()) {
word.set(itr.nextToken());
context.write(word, one);
}
}
}
public static class IntSumReducer
extends Reducer<Text,IntWritable,Text,IntWritable> {
private IntWritable result = new IntWritable();
public void reduce(Text key, Iterable values,
Context context
) throws IOException, InterruptedException {
int sum = 0;
for (IntWritable val : values) {
sum += val.get();
}
result.set(sum);
context.write(key, result);
}
}
public static void main(String[] args) throws Exception {
Configuration conf = new Configuration();
Job job = Job.getInstance(conf, "word count");
job.setJarByClass(WordCount.class);
job.setMapperClass(TokenizerMapper.class);
job.setCombinerClass(IntSumReducer.class);
job.setReducerClass(IntSumReducer.class);
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(IntWritable.class);
FileInputFormat.addInputPath(job, new Path(args[0]));
FileOutputFormat.setOutputPath(job, new Path(args[1]));
System.exit(job.waitForCompletion(true) ? 0 : 1);
}
}
(2) spark
(3) spark sql
(1). hive内外部表:内部表和对应的目录硬连接,删除表也会将数据永久删除;外部表也叫扩展表,和目录数据的关系是软连接关系。指向数据路径,数据删除后,其他人依然可以访问,删除表也不会删除目录数据。
(2). 分区作用:防止数据倾斜,快速指定到分区目录下查询,查询速度快,分区后对应的是子目录.下面举个分区例子
use ${env:dbname};
create external table if not exists TABLE_NAME_${env:paymentdate}
()
row format delimited
fields terminated by '\1'
PARTITIONED BY (year INT,month INT,day INT) --分区写法
location '此处是HDFS路径';
(3). UDF函数:用户自定义的函数(主要解决格式,计算问题),需要继承UDF类
编写Apache Hive用户自定义函数(UDF)有两个不同的接口,一个非常简单,另一个…就相对复杂点。
如果你的函数读和返回都是基础数据类型(Hadoop&Hive 基本writable类型,如Text,IntWritable,LongWriable,DoubleWritable等等),那么简单的API(org.apache.hadoop.hive.ql.exec.UDF)可以胜任.
但是,如果你想写一个UDF用来操作内嵌数据结构,如Map,List和Set,那么你要去熟悉org.apache.hadoop.hive.ql.udf.generic.GenericUDF这个API
简单API: org.apache.hadoop.hive.ql.exec.UDF
复杂API: org.apache.hadoop.hive.ql.udf.generic.GenericUDF
class SimpleUDFExample extends UDF {
public Text evaluate(Text input) {
return new Text("Hello " + input.toString());
}
}
因为该UDF是一个简单的函数,你可以在规范的测试工具测试它,如JUnit。
public class SimpleUDFExampleTest {
@Test
public void testUDF() {
SimpleUDFExample example = new SimpleUDFExample();
Assert.assertEquals("Hello world", example.evaluate(new Text("world")).toString());
}
}
(4). Hive优化:看做mapreduce处理
(1) 排序优化:sort by 效率高于 order by
(2) 分区:使用静态分区
(3) (statu_date="20170516",location="beijin"),每个分区对应hdfs上的一个目录
(4) 减少job和task数量:使用表链接操作
(5) 解决groupby数据倾斜问题:设置hive.groupby.skewindata=true ,那么hive会自动负载均衡
(6) 小文件合并成大文件:表连接操作
(7) 使用UDF或UDAF函数:http://www.cnblogs.com/ggjucheng/archive/2013/02/01/2888819.html
rowkey:hbase三维存储中的关键(rowkey:行键 ,columnKey(family+quilaty):列键 ,timestamp:时间戳)
rowkey字典排序、越短越好,使用id+时间:9527+20160517
使用hash散列:dsakjkdfuwdsf+9527+20160518
应用中,rowkey 一般10~100bytes,8字节的整数倍,有利于提高操作系统性能
Hbase优化
分区:RegionSplit()方法 NUMREGIONS=9
column不超过3个
硬盘配置,便于regionServer管理和数据备份及恢复
分配合适的内存给regionserver
查询的时候在服务器端做限制,减少网络传输
一篇关于awk的: https://www.cnblogs.com/ggjucheng/archive/2013/01/13/2858470.html
(1). 线程实现方式
第一种:直接继承Thread线程类
注意:run();仅仅是对象调用方法。而线程创建了,并没有运行。
public class CreateThreadMethod extends Thread {
@Override
public void run() {
System.out.println("线程启动");
}
public static void main(String[] args) {
CreateThreadMethod thread = new CreateThreadMethod();
thread.start();
}
}
优点: 继承了父类中对于线程的操作
缺点: 单继承的局限性。
第二种:实现接口 Runnable
public class CreateThreadMethod2 implements Runnable {
@Override
public void run() {
System.out.println("线程启动");
}
public static void main(String[] args) {
new Thread(new CreateThreadMethod2()).start();
}
}
实现Runnable接口相对于继承Thread类来说,有如下的显著优势:
以上两种方式执行的线程都是没有返回值的。当出现异常的时候,只能内部消化,或者使用全局变量。
因此出现了第三种方式,Callable,下面说说Runnable与Callable
相同点:
两者都是接口;(废话)
两者都可用来编写多线程程序;
两者都需要调用Thread.start()启动线程;
不同点:
两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;
Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
注意点:
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!
public class CallableImpl implements Callable<String> {
private String acceptStr;
public CallableImpl(String acceptStr) {
this.acceptStr = acceptStr;
}
@Override
public String call() throws Exception {
// 任务阻塞 1 秒
Thread.sleep(1000);
return this.acceptStr + " append some chars and return it!";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable callable = new CallableImpl("my callable test!");
FutureTask task = new FutureTask<>(callable);
long beginTime = System.currentTimeMillis();
// 创建线程
new Thread(task).start();
// 调用get()阻塞主线程,反之,线程不会阻塞
String result = task.get();
long endTime = System.currentTimeMillis();
System.out.println("hello : " + result);
System.out.println("cast : " + (endTime - beginTime) / 1000 + " second!");
}
}
(2). 链表操作
(3). 排序
http://write.blog.csdn.net/postlist/6503257/all
设计模式,分为4类
(1). 创建模式:如工厂模式、单例模式
(2). 结构模式:代理模式
(3). 行为模式:观察者模式
(4). 线程池模式
(1) hdfs原理
http://blog.csdn.net/u013160024/article/details/52161198
http://www.daniubiji.cn/archives/596
(2) 各个模块的功能
根据源码进行说明
(3) mapreduce原理
https://www.cnblogs.com/ahu-lichang/p/6645074.html
(4) mapreduce优化
要知道怎么对MapReduce作业进行调优前提条件是需要对Map-Reduce的过程了然于胸。
1.从磁盘读取数据并分片
默认每个block对应一个分片,一个map task
2.进行map处理
运行自定义的map业务过程
3.输出数据到缓冲区中
map输出的数据并不是直接写入磁盘的,而是会先存储在一个预定义的buffer中
4、分区、排序分组的过程
对map输出的数据进行分区,按照key进行排序和分组
5、归约(可选)
相当于本地端的reduce过程
6、合并写入磁盘
对map的最终数据进行merge之后输出到磁盘中等待shuffle过程
1.从map端复制数据
2.对数据进行合并
以上两个步骤即为shuffle过程
3.对数据进行排序
4.进行reduce操作
5.输出到磁盘
Combiner在Map端提前进行了一次Reduce处理。
可减少Map Task中间输出的结果,从而减少各个Reduce Task的远程拷贝数据量,最终表现为Map Task和Reduce Task执行时间缩短。
为应用程序处理的数据选择合适的Writable类型可大大提升性能。
比如处理整数类型数据时,直接采用IntWritable比先以Text类型读入在转换为整数类型要高效。
如果输出整数的大部分可用一个或两个字节保存,那么直接采用VIntWritable或者VLongWritable,它们采用了变长整型的编码方式,可以大大减少输出数据量。
这是map阶段的第一步,从磁盘读取数据并切片,每个分片由一个map task处理
当输入的是海量的小文件的时候,会启动大量的map task,效率及其之慢,有效的解决方式是使用CombineInputFormat自定义分片策略对小文件进行合并处理
从而减少map task的数量,减少map过程使用的时间
另外,map task的启动数量也和下面这几个参数有关系:
当mapred.min.split.size小于dfs.block.size的时候,一个block会被分为多个分片,也就是对应多个map task
当mapred.min.split.size大于dfs.block.size的时候,一个分片可能对应多个block,也就是一个map task读取多个block数据
集群的网络、IO等性能很好的时候,建议调高dfs.block.size
根据数据源的特性,主要调整mapred.min.split.size来控制map task的数量
该阶段是map side中将结果输出到磁盘之前的一个处理方式,通过对其进行设置的话可以减少map任务的IO开销,从而提高性能
由于map任务运行时中间结果首先存储在buffer中,默认当缓存的使用量达到80%的时候就开始写入磁盘,这个过程叫做spill(溢出)
这个buffer默认的大小是100M可以通过设定io.sort.mb的值来进行调整
当map产生的数据非常大时,如果默认的buffer大小不够看,那么势必会进行非常多次的spill,进行spill就意味着要写磁盘,产生IO开销
这时候就可以把io.sort.mb调大,那么map在整个计算过程中spill的次数就势必会降低,map task对磁盘的操作就会变少
如果map tasks的瓶颈在磁盘上,这样调整就会大大提高map的计算性能
但是如果将io.sort.mb调的非常大的时候,对机器的配置要求就非常高,因为占用内存过大,所以需要根据情况进行配置
map并不是要等到buffer全部写满时才进行spill,因为如果全部写满了再去写spill,势必会造成map的计算部分等待buffer释放空间的情况。
所以,map其实是当buffer被写满到一定程度(比如80%)时,才开始进行spill
可以通过设置io.sort.spill.percent的值来调整这个阈值
这个参数同样也是影响spill频繁程度,进而影响map task运行周期对磁盘的读写频率
但是通常情况下只需要对io.sort.mb进行调整即可
该阶段是map产生spill之后,对spill进行处理的过程,通过对其进行配置也可以达到优化IO开销的目的
map产生spill之后必须将些spill进行合并,这个过程叫做merge
merge过程是并行处理spill的,每次并行多少个spill是由参数io.sort.factor指定的,默认为10个
如果产生的spill非常多,merge的时候每次只能处理10个spill,那么还是会造成频繁的IO处理
适当的调大每次并行处理的spill数有利于减少merge数因此可以影响map的性能
但是如果调整的数值过大,并行处理spill的进程过多会对机器造成很大压力
我们知道如果map side设置了Combiner,那么会根据设定的函数对map输出的数据进行一次类reduce的预处理
但是和分组、排序分组不一样的是,combine发生的阶段可能是在merge之前,也可能是在merge之后
这个时机可以由一个参数控制:min.num.spill.for.combine,默认值为3
当job中设定了combiner,并且spill数最少有3个的时候,那么combiner函数就会在merge产生结果文件之前运行
例如,产生的spill非常多,虽然我们可以通过merge阶段的io.sort.factor进行优化配置,但是在此之前我们还可以通过先执行combine对结果进行处理之后再对数据进行merge
这样一来,到merge阶段的数据量将会进一步减少,IO开销也会被降到最低
这个阶段是map side的最后一个步骤,在这个步骤中也可以通过压缩选项的配置来得到任务的优化
其实无论是spill的时候,还是最后merge产生的结果文件,都是可以压缩的
压缩的好处在于,通过压缩减少写入读出磁盘的数据量。对中间结果非常大,磁盘速度成为map执行瓶颈的job,尤其有用
控制输出是否使用压缩的参数是mapred.compress.map.output,值为true或者false
启用压缩之后,会牺牲CPU的一些计算资源,但是可以节省IO开销,非常适合IO密集型的作业(如果是CPU密集型的作业不建议设置)
设置压缩的时候,我们可以选择不同的压缩算法
Hadoop默认提供了GzipCodec,LzoCodec,BZip2Codec,LzmaCodec等压缩格式
通常来说,想要达到比较平衡的cpu和磁盘压缩比,LzoCodec比较合适,但也要取决于job的具体情况
如果想要自行选择中间结果的压缩算法,可以设置配置参数:
mapred.map.output.compression.codec=org.apache.hadoop.io.compress.DefaultCodec
//或者其他用户自行选择的压缩方式
从上面提到的几点可以看到,map端的性能瓶颈都是频繁的IO操作造成的,所有的优化也都是针对IO进行的,而优化的瓶颈又很大程度上被机器的配置等外部因素所限制
map端调优的相关参数:
选项 | 类型 | 默认值 | 描述 |
---|---|---|---|
mapred.min.split.size | int | 1 | Input Split的最小值 |
mapred.max.split.size | int | . | Input Split的最大值 |
io.sort.mb | int | 100 | map缓冲区大小 |
io.sort.spill.percent | float | 0.8 | 缓冲区阈值 |
io.sort.factor | int | 10 | 并行处理spill的个数 |
min.num.spill.for.combine | int | 3 | 最少有多少个spill的时候combine在merge之前进行 |
mapred.compress.map.output | boolean | false | map中间数据是否采用压缩 |
mapred.map.output.compression.codec | String | . | 压缩算法 |
1.Copy
由于job的每一个map都会根据reduce(n)数将数据分成map 输出结果分成n个partition,所以map的中间结果中是有可能包含每一个reduce需要处理的部分数据的
为了优化reduce的执行时间,hadoop中等第一个map结束后,所有的reduce就开始尝试从完成的map中下载该reduce对应的partition部分数据
在这个shuffle过程中,由于map的数量通常是很多个的,而每个map中又都有可能包含每个reduce所需要的数据
所以对于每个reduce来说,去各个map中拿数据也是并行的,可以通过mapred.reduce.parallel.copies这个参数来调整,默认为5
当map数量很多的时候,就可以适当调大这个值,减少shuffle过程使用的时间
还有一种情况是:reduce从map中拿数据的时候,有可能因为中间结果丢失、网络等其他原因导致map任务失败
而reduce不会因为map失败就永无止境的等待下去,它会尝试去别的地方获得自己的数据(这段时间失败的map可能会被重跑)
所以设置reduce获取数据的超时时间可以避免一些因为网络不好导致无法获得数据的情况
mapred.reduce.copy.backoff,默认300s
一般情况下不用调整这个值,因为生产环境的网络都是很流畅的
2.Merge
由于reduce是并行将map结果下载到本地,所以也是需要进行merge的,所以io.sort.factor的配置选项同样会影响reduce进行merge时的行为
和map一样,reduce下载过来的数据也是存入一个buffer中而不是马上写入磁盘的,所以我们同样可以控制这个值来减少IO开销
控制该值的参数为:
mapred.job.shuffle.input.buffer.percent,默认0.7,这是一个百分比,意思是reduce的可用内存中拿出70%作为buffer存放数据
reduce的可用内存通过mapred.child.java.opts来设置,比如置为-Xmx1024m,该参数是同时设定map和reduce task的可用内存,一般为map buffer大小的两倍左右
设置了reduce端的buffer大小,我们同样可以通过一个参数来控制buffer中的数据达到一个阈值的时候开始往磁盘写数据:mapred.job.shuffle.merge.percent,默认为0.66
sort的过程一般非常短,因为是边copy边merge边sort的,后面就直接进入真正的reduce计算阶段了
之前我们说过reduc端的buffer,默认情况下,数据达到一个阈值的时候,buffer中的数据就会写入磁盘,然后reduce会从磁盘中获得所有的数据
也就是说,buffer和reduce是没有直接关联的,中间多个一个写磁盘->读磁盘的过程,既然有这个弊端,那么就可以通过参数来配置
使得buffer中的一部分数据可以直接输送到reduce,从而减少IO开销:mapred.job.reduce.input.buffer.percent,默认为0.0
当值大于0的时候,会保留指定比例的内存读buffer中的数据直接拿给reduce使用
这样一来,设置buffer需要内存,读取数据需要内存,reduce计算也要内存,所以要根据作业的运行情况进行调整
和map阶段差不多,reduce节点的调优也是主要集中在加大内存使用量,减少IO,增大并行数
reduce调优主要参数:
选项 | 类型 | 默认值 | 描述 |
---|---|---|---|
mapred.reduce.parallel.copies | int | 5 | 每个reduce去map中拿数据的并行数 |
mapred.reduce.copy.backoff | int | 300 | 获取map数据最大超时时间 |
mapred.job.shuffle.input.buffer.percent | float | 0.7 | buffer大小占reduce可用内存的比例 |
mapred.child.java.opts | String | . | -Xmx1024m设置reduce可用内存为1g |
mapred.job.shuffle.merge.percent | float | 0.66 | buffer中的数据达到多少比例开始写入磁盘 |
mapred.job.reduce.input.buffer.percent | float | 0.0 | 指定多少比例的内存用来存放buffer中的数据 |
Map Task和Reduce Task调优的一个原则就是
减少数据的传输量
尽量使用内存
减少磁盘IO的次数
增大任务并行数
除此之外还有根据自己集群及网络的实际情况来调优
在集群部署完毕之后,根据机器的配置情况,我们就可以通过一定的公式知道每个节点上container的大小和数量
1.mapper数量
每个作业启动的mapper由输入的分片数决定,每个节点启动的mapper数应该是在10-100之间,且最好每个map的执行时间至少一分钟
如果输入的文件巨大,会产生无数个mapper的情况,应该使用mapred.tasktracker.map.tasks.maximum参数确定每个tasktracker能够启动的最大mapper数,默认只有2
以免同时启动过多的mapper
2.reducer数量
reducer的启动数量官方建议是0.95或者1.75*节点数*每个节点的container数
使用0.95的时候reduce只需要一轮就可以完成
使用1.75的时候完成较快的reducer会进行第二轮计算,并进行负载均衡
增加reducer的数量会增加集群的负担,但是会得到较好的负载均衡结果和减低失败成本
一些详细的参数:
选项 | 类型 | 默认值 | 描述 |
---|---|---|---|
mapred.reduce.tasks | int | 1 | reduce task数量 |
mapred.tasktracker.map.tasks.maximum | int | 2 | 每个节点上能够启动map task的最大数量 |
mapred.tasktracker.reduce.tasks.maximum | int | 2 | 每个节点上能够启动reduce task的最大数量 |
mapred.reduce.slowstart.completed.maps | float | 0.05 | map阶段完成5%的时候开始进行reduce计算 |
map和reduce task是同时启动的,很长一段时间是并存的
共存的时间取决于mapred.reduce.slowstart.completed.maps的设置
如果设置为0.6.那么reduce将在map完成60%后进入运行态
如果设置的map和reduce参数都很大,势必造成map和reduce争抢资源,造成有些进程饥饿,超时出错,最大的可能就是socket.timeout的出错
reduce是在33%的时候完成shuffle过程,所以确保reduce进行到33%的时候map任务全部完成,可以通过观察任务界面的完成度进行调整
当reduce到达33%的时候,map恰好达到100%设置最佳的比例,可以让map先完成,但是不要让reduce等待计算资源
(5) 数据倾斜
首先要定位到哪些数据 导致数据倾斜。确定完之后常见的处理方法有:
1. 在加个combiner函数,加上combiner相当于提前进行reduce,就会把一个mapper中的相同key进行了聚合,减少shuffle过程中数据量,以及reduce端的计算量。这种方法可以有效的缓解数据倾斜问题,但是如果导致数据倾斜的key 大量分布在不同的mapper的时候,这种方法就不是很有效了。
2. 局部聚合加全局聚合。第二种方法进行两次mapreduce,第一次在map阶段对那些导致了数据倾斜的key 加上1-n的随机前缀,这样之前相同的key 也会被分到不同的reduce中,进行聚合,这样的话就有那些倾斜的key进行局部聚合,数量就会大大降低。然后再进行第二次mapreduce这样的话就去掉随机前缀,进行全局聚合。这样就可以有效地降低mapreduce了。不过进行两次mapreduce,性能稍微比一次的差些。
http://www.jianshu.com/p/7f518ac363f5
http://blog.csdn.net/u011546655/article/details/52175550
mapreduce.job.jvm.num.tasks
默认为1,设置为 -1,重用jvm
具体的数据分片是这样的,InputFormat在默认情况下会根据hadoop集群HDFS块大小进行分片,每一个分片会由一个map任务来进行处理,当然用户还是可以通过参数mapred.min.split.size参数在作业提交客户端进行自定义设置。还有一个重要参数就是mapred.map.tasks,这个参数设置的map数量仅仅是一个提示,只有当InputFormat决定了map任务的个数比mapred.map.tasks值小时才起作用。同样,Map任务的个数也能通过使用JobConf的conf.setNumMapTasks(int num)方法来手动地设置。这个方法能够用来增加map任务的个数,但是不能设定任务的个数小于Hadoop系统通过分割输入数据得到的值
http://blog.csdn.net/haohaixingyun/article/details/52819457
org.apache.hadoop.mapreduce.lib.output.MultipleOutputs 类来实现。
job.setNumReduceTasks(n)
FileInputFormat的子类:
TextInputFormat(默认类型,键是LongWritable类型,值为Text类型,key为当前行在文件中的偏移量,value为当前行本身);
KeyValueTextInputFormat(适合文件自带key,value的情况,只要指定分隔符即可,比较实用,默认是\t分割);
源码:
String sepStr =job.get(“mapreduce.input.keyvaluelinerecordreader.key.value.separator”,”\t”);
注意:在自定义输入格式时,继承FileInputFormat父类
参考:http://www.cnblogs.com/vichao/archive/2013/06/06/3118100.html
InputSplit是MapReduce对文件进行处理和运算的输入单位,只是一个逻辑概念,每个InputSplit并没有对文件实际的切割,只是记录了要处理的数据的位置(包括文件的path和hosts)和长度(由start和length决定),默认情况下与block一样大。
拓展:需要在定义InputSplit后,展开讲解mapreduce的原理
JobTracker, 创建一个InputFormat的 实例,调用它的getSplits()方法,把输入目录的文件拆分成FileSplist作 为Mapper task 的输入,生成Mapper task加入Queue。
源码中体现了拆分的数量
long goalSize = totalSize / (numSplits == 0 ? 1 : numSplits);
long minSize = Math.max(job.getLong(org.apache.hadoop.mapreduce.lib.input.
FileInputFormat.SPLIT_MINSIZE, 1), minSplitSize);//minSplitSize默认是1
job是工作的入口,负责控制、追踪、管理任务,也是一个进程
包含map task和reduce task
Tasks是map和reduce里面的步骤,主要用于完成任务,也是线程
结果查看监控日志,得知产生这种现象的原因是数据倾斜问题
解决:
(1)调整拆分mapper的数量(partition数量)
(2)增加jvm
(3)适当地将reduce的数量变大
用可执行文件作为Mapper和Reducer,接受的都是标准输入,输出的都是标准输出
参考:http://www.web520.cn/archives/9220
-HDFS块大小为64MB
–输入类型为FileInputFormat
–有3个文件的大小分别是:64k 65MB 127MB
Hadoop框架会把这些文件拆分为多少块?
答案:
64k——->一个block
65MB—->两个文件:64MB是一个block,1MB是一个block
127MB—>两个文件:64MB是一个block,63MB是一个block
属于split和mapper之间的一个过程将inputsplit输出的行为一个转换记录,成为key-value的记录形式提供给mapper
MR一共有四个阶段,split map shuff reduce 在执行完map之后,可以对map的输出结果进行分区,
分区:这块分片确定到哪个reduce去计算(汇总)
排序:在每个分区中进行排序,默认是按照字典顺序。
Group:在排序之后进行分组
Partitioner是在map函数执行context.write()时被调用。
用户可以通过实现自定义的?Partitioner来控制哪个key被分配给哪个?Reducer。
查看源码知道:
如果没有定义partitioner,那么会走默认的分区Hashpartitioner
public class HashPartitioner<K, V> extends Partitioner<K, V> {
/** Use {@link Object#hashCode()} to partition. */
public int getPartition(K key, V value, int numReduceTasks) {
return (key.hashCode() & Integer.MAX_VALUE) % numReduceTasks;
}
}
参考:http://blog.csdn.net/gamer_gyt/article/details/47339755
这是一个hadoop优化性能的步骤,它发生在map与reduce之间
目的:解决了数据倾斜的问题,减轻网络压力,实际上时减少了maper的输出
源码信息如下:
public void reduce(Text key, Iterator values,
OutputCollector output, Reporter reporter)
throws IOException {
LongWritable maxValue = null;
while (values.hasNext()) {
LongWritable value = values.next();
if (maxValue == null) {
maxValue = value;
} else if (value.compareTo(maxValue) > 0) {
maxValue = value;
}
}
output.collect(key, maxValue);
}
在collect实现类中,有这样一段方法
public synchronized void collect(K key, V value)
throws IOException {
outCounter.increment(1);
writer.append(key, value);
if ((outCounter.getValue() % progressBar) == 0) {
progressable.progress();
}
}
下面是说明输出数量达到10000时,开始合并为一个maper
public static final long DEFAULT_COMBINE_RECORDS_BEFORE_PROGRESS = 10000;
Mapreduce原理详解:
http://my.oschina.net/itblog/blog/275294
第二篇:http://blog.csdn.net/xfg0218/article/details/52514585
第三篇:http://www.offcn.com/it/2017/0517/9051.html
参考资料
* http://blog.csdn.net/qq1010885678/article/details/50922812
* http://blog.csdn.net/high2011/article/details/51594928