hadoop
以下文章都是关于hadoop学习的笔记,不太成体系,知识慢慢积累吧
-
namenode相当于拿着账本的记账员,记录来的货物在哪个仓库里,当然仓库没有备份
-
secondnamenode: 比如此处是个中转站,货物需要再次移动,需要再次记录, secondnamenode相当于记账员的助理,来协助namenode来记录数据
-
datanode:相当于码头上的一块块的分区的货物
hadoop文件合并
文件合并:
将edits文件和fsimage文件通过http get方式获取到。
复制出来后,就不能用旧的edits文件了,namenode中创建edits.new文件,合并过程中客户端发送的数据信息记录在edits.new文件中。
seconednode中将就的edits文件和 get方式获取的文件合并后,生成并返回带有扩展名 fsimage.ckpt文件。 再次传到namenode, 并重命名为 fsimage文件。 最后将edits.new文件改为edits文件。
client新传递的文件将记录在新的edits文件中。
当edits文件大小达到64M,或者时间到了1小时,将在执行一次。
MapReduce是一个分布式计算框架(编程模型),设计了基于HDFS的Mapreduce分布式计算框架
- MR框架对于程序员最大的意义在于,不需要掌握分布式计算编程,不需要考虑分布式编程里可能存在的种种难题,比如任务调度和分配,文件逻辑切块
- MR由两个阶段组成,Mapper和Reduce,用户只需要实现map()和reduce()两个函数,即可实现分布式计算,非常简单,这两个函数的形参是key和value对,表示函数的输入信息
MapReduce框架的节点组成结构
JobTracker / ResourceManage工作职能
- 知道管理哪些机器,及管理哪些NodeManager
- 要有检测机制,能够检测到NodeManager的状态变换,通过RPC心跳来实现
- 任务的分配和调度,ResourceManager能够做到细粒度的任务分配,比如一个任务需要占用多大的内存,需要多少计算资源。
- 注: ResourceManager是hadoop2.0版本之后引入yarn,由yarn来管理hadoop之后,jobtracker就被替换成了ResourceManager
TaskTracker / NodeManager工作职能
- 能够收到ResourceManager发过来的任务,并进行任务的处理。这里处理任务指的是Map任务或Reduce任务。(真正干活的)
Map、Reduce的执行步骤
- map任务处理
读取输入文件内容,解析成key、value对。对输入文件的每一行,解析成key,value对。每一个键值对调用一次map函数。
写自己的逻辑,对输入的key,value处理,转换成新的key,value输出
对输出的key、value进行分区。
对相同分区的数据,按照key进行排序(默认按照字典顺序进行排序)、分组。相同key的value放到一个集合中。
(可选)分组后的数据进行归约
key一般是LongWritable类型的偏移量 - Reduce任务处理
对多个map任务的输出,按照不同的分区,通过网络copy到不同的reduce节点。这个过程并不是map将数据发给reduce的,而是reduce主动去获取的。
对多个map任务的输出进行合并、排序。写reduce函数自己的逻辑。对输入的key、value处理,转换成新的key,value输出。
把reduce的输出保存到文件中(HDFS)
MapReduce实现单词数量统计(WordCount)
- 案例分析
- 导入相关jar包
- WordCountMapper.java
package com.xiaoxu.hadoop;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
/**
* MapReduce实现单词数量统计
* Mapper<LongWritable, 输入KEY的类型 偏移量
* Text 输入value类型, 读取到的一行文本
* Text: 输出key的类型 如 hello或者一个单词 (根据业务来决定)
* LongWritable:输出 value 的类型, 如单词数量 1,2,1,1 (根据业务来决定)
* > 每行内容调用一次map()方法
*
* mapper.txt
* hello tom
* hello rose
* hello joy
* hello jerry
* map到reducer会经过洗牌,将map输出的key相同的值,合并value,组成list传到reduces
*/
public class WordCountMapper extends Mapper<LongWritable, Text,Text,LongWritable> {
/**
*
* @param key 输入的key 及偏移量
* @param value
* @param context 向外输出的对象,输出的key和value的类型
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException {
//获取读取到的文本内容
String line = value.toString();
System.out.println("map读到line: key:"+ key.get() +" 内容: "+ line);
//使用空格进行拆分
String [] words = line.split(" ");
//
for (String word: words) {
System.out.print("输出: "+word+ "1, ");
context.write(new Text(word),new LongWritable(1));
}
}
}
- WordCountReducer.java
package com.xiaoxu.hadoop;
import org.apache.hadoop.io.LongWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
import java.util.Iterator;
/**
* Text:输入的key的类型
* LongWritable: 输入value集合中元素的类型
* Text: 输出key的类型
* LongWritable: 输出value的类型
*/
public class WordCountReducer extends Reducer<Text, LongWritable,Text,LongWritable>{
/**
* @param key 输入的key hello
* @param values 1,1,2,3
* @param context
* @throws IOException
* @throws InterruptedException
*/
@Override
protected void reduce(Text key, Iterable<LongWritable> values, Context context) throws IOException, InterruptedException {
Iterator<LongWritable> iterator = values.iterator();
long count = 0;
while (iterator.hasNext()){
long val = iterator.next().get();
System.out.println("reduce_in: "+ key.toString() + " values: "+ val);
count += val;
}
context.write(key,new LongWritable(count));
System.out.println("reduce_out key: "+key.toString() +" value: "+ count);
}
}
- WordCountDriver.java 启动类
package com.xiaoxu.hadoop;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.io.LongWritable;
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 WordCountDriver {
public static void main(String[] args) throws Exception{
Configuration configuration = new Configuration();
//创建job对象,指定job名称,config对象
Job job = Job.getInstance(configuration,"wordCountJob");
//指定job的执行的类
job.setJarByClass(WordCountDriver.class);
//指定mapper类
job.setMapperClass(WordCountMapper.class);
//指定reduce类
job.setReducerClass(WordCountReducer.class);
//指定mapper,和reduce的输出类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(LongWritable.class);
//指定reduce 的输出类型
job.setOutputKeyClass(Text.class);
job.setOutputValueClass(LongWritable.class);
//指定任务操作的资源的位置 到目录一级
FileInputFormat.setInputPaths(job,new Path("hdfs://127.0.0.1:9000/user/hadoop/word/mapperreduce.txt"));
//指定任务结束后,生成的结果文件位置 hdfs上
FileOutputFormat.setOutputPath(job,new Path("hdfs://127.0.0.1:9000/user/hadoop/word/result"));
//执行任务
job.waitForCompletion(true);
}
}
- 测试文件
控制台输出
结果文件:
主要map到reducer经过shuffle洗牌,将同样的key,value洗到一起, value拼成valueslist。传到reducer.(可以设置reduces个数),若文件比较大,分为三块,则会有三个WordCountMapper来调用mapper方法。
- 问题: mapper传递的参数拼成字符串转换为Text,后续害的split、类型转换、还需要记着原来的顺序,开发时比较麻烦,那么如何解决这个问题那?可以将传递参数封装到一个自定义类的对象中。
自定义类的对象
- 写一个Bean实现Writable接口,实现其中的write和readFields方法,注意这两个方法中属性处理的顺序和类型。此后这个类的对象就可以用于MapperReducer了。
- 案例 统计用户流量使用信息 日志文件 flow.txt 格式: 15001224521 bj lxx 1024
- 省略的部分属性的是set get 方法
- FlowMapper.java
- FlowReducer.java
- 启动类 FlowDriver.java
- 测试文件 flow.txt
- result:
运行结果,这个结果放到hdfs上了,在FlowDriver中指定了位置
分区原理
分区操作是shuffle操作中的一个重要过程,作用就是讲map的结果按照规则分发到不同reduce中进行处理,从而安装分区到多个输出结果。
Partitioner是partitioner的基类,如果需要定制partitioner也需要继承该类。
HashPartitioner是mapreduce的默认partitioner。计算方法是
reducer = (key.hashcode() & Integer.MAXVALUE)%numReduceTasks;
注: 默认情况下,reduceTask数量为 1
很多时候MR自带的分区规则并不能满足我们需求,为了实现特定的效果,可以需要自己来定义分区规则。
比如这里要按 不同的地区 来统计流量信息,北京,上海,深圳 分别统计,并生成三个结果文件。默认的不满足
案例: 改写流量统计案例,根据不同地区分区存放数据
- 开发Partitioner代码,写一个类继承Partitioner类,在其中描述分区规则:
- 在任务调度代码中,增加Partitioner配置 设置Partitioner类
- job.setPartitionerClass(FlowPartitioner.class)
- 指定Reducer的数量
- job.setNumReducerTask(4)
- Partitioner将会将数据发往不同reducer,这就要求reducer的数量应该大于等于Partitioner的数量,如果少于则在执行的过程中会报错。
- 增加 FlowPatitioner.java
运行后,查看到result2下有多个结果文件,下载后打开,验证是按地区统计的流量信息。
排序 Sort
- 默认的字典排序可能不满足业务规则。
- Map执行过后,在数据进入reduce操作之前,数据默认将会按照 map输出key的字典排序传入到reduce。利用这个特性可以实现大数据场景下排序的需求。
案例:计算每月收益(收入-成本),并按照收益进行排序(文件profit.txt), 按每个人的总收益从高到低排序。
月份 | 姓名 | 收入 | 成本 |
---|---|---|---|
1 | ls | 3000 | 200 |
1 | zs | 3000 | 210 |
2 | ww | 3000 | 220 |
1 | wy | 2500 | 230 |
4 | gd | 2000 | 100 |
1 | gh | 1000 | 50 |
2 | gh | 1000 | 50 |
3 | gh | 2500 | 50 |
- 此案例需要两个MR来操作,合并数据(combiner),进行排序, 排序MR
- 创建Bean对象实现WritableComparable接口实现其中的wirte readFields compartTo 方法。
- 在Map操作时,将Bean对象作为key输出,从而在Reduce接收到数据时已经经过了排序。context.write(bean,NullWritable.get())
- 而Reduce操作时,只需要原样输出数据即可。
- 结果: 打印的是toString()方法的结果 3
- 注意:这里进行了两次的mapper – reduce方法,才实现了此功能,以上代码是直接对第一次map reduce后执行的结果文件 进行二次处理的。
原始文件: 1
第一次执行后: 2
这三个截图文件只参考数据格式就行了。
Combiner – 合并
- 每一个MapperTask可能会产生大量的输出,combiner的作用就是在MapperTask端对输出先做一次合并,以减少输出到reducerTask的数据量。
- combiner是实现在Mapper端进行key的归并,combiner具有类似本地的reduce功能。
- 如果不用combiner,那么所有的结果都是reduce完成,效率会相对低下,使用combiner,先完成在Mapper的本地聚合,从而提升速度。
其余的和第一个统计单词个数的案例一致。
测试文件:
结果:
1. Mapper
每一个 MapperTask有一个环形内存缓冲区,用于存储map任务的输出。默认大小是100M,(io.sort.mb属性指定),一旦达到阈值0.8,(io.sort.spill.percent),一个后台线程把内容写到(spill)磁盘的指定目录,(mapred.local.dir)下的新建的一个溢出文件。
写磁盘前,要partition (分区),sort(排序),Combiner(合并).如果有后续的数据,将会继续写入环形缓冲区,最终写入下一个溢出文件中。
等最后记录写完,合并全部溢出写文件为一个分区且排序的文件。
如果最终合并时,被合并的文件大于等于3个,则合并完再执行一次Combiner,否则不会。
2 Reducer
Reducer通过Http方式得到输出文件的分区
NodeManager为分区文件运行Reduce任务。复制阶段把 Map输出复制到Reducer的内存或磁盘上,一旦MAP
任务完成,Reducer就开始复制输出。
3. Mapper数量
Mapper数量在默认情况下不可直接控制干预,Mapper数量由输入的大小和个数决定。
在默认情况下,最终Input占据多少个block,就应该启动多少个Mapper
可以通过配置mapred.min.split.size来控制split的size的最小值。
执行流程:
- 从切块中,取文件,一个文件对应一个mapTask,读到之后,往圆形缓冲区里溢出内容,buffer in memory
- 写的过程中会产生多个溢出文件,(map中向缓冲区写,默认100M,边写边读,当缓冲去的文件大于容量的0.8之后,会生成溢出文件,一个map可能会生成多个溢出文件。)
- 溢出文件如果超过了三个,本身还要进行combiner合并,多个map生成的文件进行merge进行梳理。
- reduce http方式获取分区数据。(通常reduce机器和map不在一个机器上)
生成溢出文件。