MapReduce
Map端:中间级映射。
Reduce端:相同的key为一组,组的迭代计算。一组key迭代一次。
MapTask 和 ReduceTask 计算向数据移动
块:
一个文件切成若干个快(实实在在被切割的块),
散列的存储在各个节点上
然后计算(map)向数据(block)移动
split:片
(逻辑上被切割的片,默认一个片大小就是一个块大小=128M)
map和split是一一对应的
也就是说,数量上,块<=片=Map
为什么要有片?
假如一个文件对应的·1有十个块,如果没有片,块太大了,一个块128M对应一个Map,10个Map,每个Map同时计算每个的128M需要10分钟。
如果有了片,Map端就灵活了,假如每个物理块切成十个逻辑片,每个片对应一个Map,一共有100个Map,每个Map计算12.8M的逻辑片,需要的时间就是1分钟,效率就大大提高。
MapReduce流程
Map 先做完k,v集映射,生成了大量的k,v,然后交给reduce迭代计算。相同的k做一组迭代计算。(如果最后有几组k,就准备几个reduce迭代计算,是可以的,但是不够优化,有负载均衡问题),一个reduce可以处理多组相同的k的。但是一组k必须在同一个reduce里迭代计算。(这也是mr框架最死板的地方。到了strom可以进行不同的分发的)。这是mr的分发策略。
例如:统计各个城市的房价
北京统计200万条
上海统计100万条
深圳统计50万条
青岛统计10万条
map端:k,v中间级映射。k->城市 v->房价。 map的数量取决于split逻辑片>=物理块。最后共生成360万条k,v。
reduce端:相同的k只能在同一个reduce迭代计算,一个reduce可以迭代计算多组k。所以reduce<=k的种类数目,reduce端的效率取决于最高组k的或者说任务量最大的那个reduce。
MapReduce分为四个阶段:
1:split逻辑切片
2 : 拿到逻辑上切片之后,生成map开始计算
3:shuffle
4:reduce
shuffle和缓冲区
如果没有shuffle,大量的k,v 直接就拉取到reduce进行迭代计算,这样的话,整个mr框架的效率就会非常低。会有磁盘上大量的交互。就好比:外面点了12瓶啤酒,一瓶一瓶的送·过来,虽然每次送的速度非常快,但是效率还是很低。
map task流程
input split ->map->buffer in memory ->partition,sort and spill to disk partitions -> merge on disk
缓冲区
split切片后,生成map,每个map之后会开辟一块内存缓冲区
分区的时候根据reduce task数量决定分区的数量(不设定是默认一个reduce task),怎么分到每个分区里是根据哈希分的,相同的k一组。
一个内存缓冲区会有多个partition分区,partition分区,每个partition分区对应一个reduce
有几个partion分区就有几个reduce。每个reduce都知道自己拉取对应的哪个partiton分区。
一次排序:(按照不同partition分区来排到对应的partition分区)
在缓冲区里边会进行排序,每个分区都有排好序的北京上海青岛深圳,reduce端正好也有对应的去接收。
reduce1只拉取所有排好序的内存缓冲区的第一部分的k->北京,reduce2只拉取第二部分的k->上海…
reduce有多少个由自己设定,有多少个reduce就决定有多少个分区编号。(分区不是内存缓冲区,而是在内存缓冲区里进行的分区,分区编号从0开始,0,1,2,3.)
二次排序:(按照partition分区内部来排)
假如自己定义两个reduce,reduce1->北京上海,reduce2->青岛深圳。缓冲区里有分区,一次排序后,每个缓冲区里生成了两个分区,分区1是北京上海,但是北京上海是乱序的,分区是2青岛深圳,他们内部也是乱序的。如果就这样把乱序的北京上海直接交给reduce1,那reduce得到的就是乱序的。相同的k调用一组reduce方法,如果直接这样,会有大量的IO查找相同的k,遍历很多次。
所以就在分区内部进行第二次排序,同一个分区里的北京上海进行二次排序,青岛深圳之间也进行相互排序。
除了排序,还要进行combiner压缩,如果在reduce端还是要sum value ,直接在内存缓冲区完成计算压缩。最后每个缓冲区的分区里的一组k只有一个k,v传递过去,减少与IO磁盘数据量的交互。
三次排序;每个map在生成k.v的时候还会生成一个缓冲区,缓冲区里有partition分区,每次缓冲区快满的时候会发生溢写操作,生成文件在磁盘上,时间长了,会有好多个溢写的小文件,这些小文件之间会进行上诉的combiner,生成大文件生成大文件之间会进行一次排序,叫做归并。
四次排序:每个map最后生成的多个小文件文件会进行多次归并一个大文件,多个map之间每个map都有一个归并的大文件,这些大文件之间还要进行多次排序归并,到最后归并成只有几个的大文件。reduce端才会拉取,不一定非要只归并成一个大文件才拉取,因为每次的归并也是IO流的交互。
reduce task
相同的k调用一组reduce方法。
总流程各个分工
map:
1 读懂数据
2 映射为kv模型 自己根据需求情况设定
3 并行分布式 多个map 在多个并行服务器上运行,多个reduce在多个服务器上运行
4 计算向数据移动 把map task 和reduce task 向block块上移动
reduce:
1 数据全量/分量加工(partiton/group)
partiton>group
partiton:每个reduce的k可以是多个,但是只有一个partition
group:每组k对应一个group,也就是说一个partition包含一个到多个group
2 reduce中可以包含不同的key
3 相同的key汇聚到一个reduce中(死板的分发策略)
4 相同的key调用一次reduce方法
排序实现key的汇聚
k,v使用自定义数据类型(所有基本数据类型都不支持,必须是特殊的封装类,都是对象类型)
1 作为参数传递,节省开发成本,提高程序自由度
2 Writable 序列化:能使分布式程序相互据交互(对象类型需要序列化)
3 Comparable比较器:实现具体排序(字典序,数值序等)
mr版本
hadoop 1.x -> mr 1.x
Clients: 任务(job)是clients完成规划的,这个任务需要切多少片,需要多少map,map能跑到哪些节点之上,从哪里读取这些数据呢,又把这些数据输出到哪里呢,Clients完成它的作业是通过API环境,通过Java这种环境来写的。
客户端设计作业:需要多少rerduce->多少分区->块的大小可以通过client完成->知道块的数量->知道切片数量->知道map数量
设计好了打好jar包给hdfs,给job有缺陷,job是单点,没备份。
Job Tracter :
任务调度:指定相关的节点去开辟map task reduce task,需要跟Namenode沟通,才能知道节点具体位置在哪里,才能指定Task Tracter 开辟相关任务
资源管理:整个资源的利用情况由job Tracker维护,每个Task Tracker 掌握当前节点的资源利用情况,所以Job Tracker 与Task Tracker 时时刻刻保持通讯情况
Task Tracter:任务跟踪器
掌握自己节点的资源利用信息
开启map task reduce task
还得知道自己要干什么,所以要从namenode拿jar包提取任务下载到自己的datanode节点之上,开启计算任务
总结:
mr 1 角色:
job tracker
核心,主,单点
调度所有作业(按照client提交的作业,按照吩咐做,哪些开辟map,哪些reduce)
监控整个集群的资源负载(job 和 task 共同用完成的,通过task汇报,自己能掌控下层的资源使用情况)
对jar包的获知
task tracker
从,自身节点资源管理
和job tracker心跳,汇报资源,获取task(按照job 的吩咐在我的节点上开启相应的任务,还要主动的申请)
client
作业为单位,打jar包(Job)
规划作业的计算分布(用到哪些节点了,切多少片啊)
提交作业资源到HDFS(打jar包提交)
最终提交作业到JOBTracker
弊端:
job tracter:负载过重,单点故障
资源管理与计算调度强耦合,其他计算框架需要重复实现资源管理
hadoop计算框架job tracker 在node1 node3 开辟了相应的map task和redece task,这时又来了个计算框架 strom 主节点nimbus 在 从节点node1上也想开辟map任务,但是nimbus和job tracker 他们之间是不通信的,所以nimbus不知道这个节点的资源已经被占用了。
不同框架对资源不能全局管理
mr 2 版本
在mr1 升级到2 后,mr拆分成 mr2和yarn
基于yarn的资源管理和分配(放权,不让jobTracker 这一组件承担过多的功能)
yarn:资源管理框架
zookeeper:大数据分布式协调框架
原job tracker:
Resource Manager :资源管理
Application master:任务调度和任务监控
一个job作业对应一个app mast(如果app mast挂了,resources manager 会赶紧创建一个新的)
原 TaskTracker:
NodeManager:负责执行原task tracker任务
Container:容器,默认1G
长服务:从集群搭建开始,一直到关闭,这个服务(进程)一直存在
1.x:namenode, datanode ,secondary namenode
2.x:node namager ,resource manager
短服务:app mstr(提交任务过来,resource manager才会创建app mstr)
mr2总结:
yarn:解耦资源与计算
ResourceManager
主,核心
集群节点资源管理
NodeManager(通过管理Container的生命周期,专门管理自己本节点的资源情况)
与RM汇报资源
管理Container生命周期
计算框架中角色都以Container表示
Container:[节点NM,CPU.MEM.I/O大小,启动命令]
默认NodeManager启动线程监控Container大小,超出申请资源额度,kill
支持Linux内核的Cgroup
mr:
mr-ApplicationMaster-Container(任务调度作业角色,在哪个节点之上开辟container,container上开启map task reduce task等)
作业为单位,避免单点故障,负载到不同的节点
创建Task需要resourcemanager申请资源(Container / MR 1024MB)
task container
Client:
RM-Client:请求资源创建AM
AM-Client:与AM交互
两个版本总结:
yarn:yet another resource negotiator
hadoop2.0
核心思想:将MRv1中JobTracker的资源管理和任务调度两个功能分开,分别由ResourceManager和ApplicationMaster进程实现
ResourceManager:负责整个集群的资源管理和调度
ApplicationMaster:负责应用程序相关的事务,比如任务调度,任务监控和容错等。
yarn的引入,使得多个计算框架可运行在一个集群中
每个应用程序对应一个ApplicationMaster
目前多个计算框架可以运行在YARN上,比如Map Reduce,Spark,Storm等
高可用搭建环境配置
node nn-1 nn-2 DN ZK ZKFC JNN RS NM
6 * * *
7 * * * * * *
8 * * * * * *
9 * * *
RS 是长服务 (分散) 放在 8 9
ResourceManager High Avaliability
NM也是长服务(分散) 放在 7 8 9
从官网查看 yarn HA 和 single
重命名mapred-site.xml
修改/hadoop-2.6.5/etc/hadoop/mapred-site.xml
<configuration>
<property>
<name>mapreduce.framework.name</name>
<value>yarn</value>
</property>
</configuration>
修改/hadoop-2.6.5/etc/hadoop/yarn-site.xml
//低可用
<configuration>
<property>
<name>yarn.nodemanager.aux=services</name>
<value>mapreduce_shuffle</value>
</property>
//HA
<property>
<name>yarn.resourcemanager.ha.enabled</name>
<value>true</value>
</property>
<property>
<name>yarn.resourcemanager.cluster-id</name>
<value>cluster1</value>
</property>
<property>
<name>yarn.resourcemanager.ha.rm-ids</name>
<value>rm1,rm2</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm1</name> master1 改成 node8
<value>master1</value>
</property>
<property>
<name>yarn.resourcemanager.hostname.rm2</name> master2 改成 node9
<value>master2</value>
</property>
<property>
<name>hadoop.zk.address</name>
<value>zk1:2181,zk2:2181,zk3:2181</value>//zk1改成 node7 zk2改成node2 zk3 改成node3
</property>
</configuration>
修改完成之后配置文件分发到其他节点 在hadoop目录上执行命令
# scp mapred-site.ml yarn-site.xml node7:`pwd`
# scp mapred-site.ml yarn-site.xml node8:`pwd`
# scp mapred-site.ml yarn-site.xml node9:`pwd`
node8 node9 自身免密钥 相互免密钥
自己免密钥
1 生成密钥
# ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
2 公钥追加到钥匙集合
# cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
ssh 对方免密钥
1 生成密钥
# ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
2 把公钥分发到对方节点
# scp ~/.ssh/id_rsa.pub node9:`pwd`/node8.pub
3 在对方节点上输入命令
# cat node8.pub >>authorized_keys
启动集群
1 先启动 zk
all# zkServer.sh start
2 再启动hdfs
# start-dfs.sh
3 启动 yarn
# start-yarn.sh
启动hdfs和yarn可以一起启动
# start-all.sh
4 最后在RM的两个主节点启动RM(node7,node8)
# yarn-daemon.sh start resourcemanager
5 查看yarn
node8:8088
利用自带的mapreduce的wordcount jar包计算
进入软件包里的分享目录里的hadoop里的mr,里面有自带的jar包
/opt/sxt/hadoop-2.6.5/share/hadoop/mapreduce
执行计算命令
# hadoop jar hadoop-mapreduce-examples-2.6.5.jar wordcount /user/root/text.txt /wordcount
执行完成hdfs的/wordcount目录里会出现 两个文件
success(只是告诉你这个任务计算成功了)
part-r-00000(这里面是计算的结果)
part 代表 partition分区 000000代表分区号,因为没有规定分区,所以默认一个分区,分区号00000
查看part-r-00000
1.用eclipse或者idea利用hafs文件系统对象配合IO流查看
2.利用命令查看
# hadoop fs -cat /wordcount/part-r-00000
# hdfs dfs -cat /wordcount/part-r-00000
关闭集群
1 先在node7和node8上关闭 resourcemanager
# yarn-daemon.sh stop resourcemanager
2 然后在主节点上关闭 hdfs和yarn
# stop.all.sh
3.然后关闭zookeeper
all# zkServer.sh stop
idea写MapReduce代码
1 MRDemo类
package mapreduce;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Path;
;
import org.apache.hadoop.io.IntWritable;
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;
import java.io.IOException;
/**
* 创建配置文件对象
* new Configuration
* 创建一个Job对象,封城jar包提交
* Job.getInstance(conf)
*/
public class MRDemo {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
// 1.创建配置文件对象
Configuration conf = new Configuration();
//2 创建Job对象
Job job = Job.getInstance(conf);
//3.指定打Jar包的类
job.setJarByClass(MRDemo.class);
//4.设置作业名称,也可以不设置
job.setJobName("myjob");
//5.指定输入获取路径
Path inPath = new Path("/user/root/text.txt");
//6.通过这个格式化这个类添加要获取的文件和写出的文件
//这个类作用很多,继承InputFormat,先于Job执行,切片,添加路径都是这个类
FileInputFormat.addInputPath(job,inPath);
Path outPath = new Path("/output/wordcount/");
//如果输出路径存在,则删除
if (outPath.getFileSystem(conf).exists(outPath)){
outPath.getFileSystem(conf).delete(outPath,true);
}
FileOutputFormat.setOutputPath(job,outPath);
//设置map和reduce 服务器节点之间数据传输需要序列化
//但是这个内部已经实现序列化,但是还要告知内部我这个已经序列化的类型
job.setMapperClass(MyMapper.class);
//告知类型
job.setMapOutputKeyClass(Text.class);
job.setMapOutputValueClass(IntWritable.class);
job.setReducerClass(MyReducer.class);
//提交当前的job作业
job.waitForCompletion(true);
}
}
- MyMapper类
package mapreduce;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Mapper;
import java.io.IOException;
import java.util.StringTokenizer;
/**
* Mapper:
* 1.四个自定义泛型类型<KEYIN,VALUEIN,KEYOUT,VSLUEOUT>
* KEYIN:输入给map之前的数据 (一行数据的Key,当前行的首字母下标索引,因为还没有split切割)
* 数字类型:不能写基本类型 Object(LongWritable)
* VALUEIN:一行数据的Value
* 文本类型:Text(不能写String)
*
* KEYOUT:
* 文本类型:Text(切割后)
* VALUEOUT:
* map的数据 INTWritable
*
* 1.map方法执行一次就只有一行数据,run方法的作用就是循环多次执行map方法
* 为什么重写map方法,而不重写run方法
* 因为run方法,本身就是实现了有数据就循环执行map方法,不需要重写
* 而map方法本身只是原样输出,想要实现自己的需求,自己必须重写
*
*/
public class MyMapper extends Mapper<Object, Text, Text, IntWritable> {
//one 对应的是VALUEOUT
private final static IntWritable one = new IntWritable(1);
//word 对应的是KEYOUT
private Text word = new Text();
@Override
protected void map(Object key, Text value, Context context) throws IOException, InterruptedException {
//迭代器 传进来VALUEIN,对一整行文本进行split切割,扔到迭代器itr中
StringTokenizer itr = new StringTokenizer(value.toString());//value对应VALUEIN 是一行文本
//遍历,是否有下一个单词,有的话获取
while (itr.hasMoreTokens()){
//把itr的String类型封装到word中,每次循环,都封装一次新的单词
word.set(itr.nextToken());
//把单词对应值连接起来,所以最后map的结果都是
// value全是1(zz,1)(zz,1)(yy,1)(zz,1)(yy,1)(xx,1)
context.write(word,one);
}
}
}
3 MyReduce类
package mapreduce;
import org.apache.hadoop.io.IntWritable;
import org.apache.hadoop.io.Text;
import org.apache.hadoop.mapreduce.Reducer;
import java.io.IOException;
/**
* Reducer:
* 四个泛型:
* 前两个泛型类型对应Mapper的后两个泛型类型 Text,IntWritable
* 后两个泛型类型和前两个可以一样 Text,IntWritable
*
* 1.迭代计算
* 同一组key做一次迭代计算
* map端传过来的数据是(因为map端已经做了排序) (xx,1)(xx,1)(yy,1)(zz,1)(zz,1)(zz,1)
* 所有key相同做一次迭代计算
* xx迭代计算做完,当前迭代计算就结束了一次
* 然后就是迭代yy
*
*/
public class MyReducer extends Reducer<Text, IntWritable, Text, IntWritable> {
//迭代计算 这个是Reduce结果的value
private IntWritable result = new IntWritable();
@Override//values 多个value
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
//做累加
int sum = 0;
//遍历values 累加
for (IntWritable val: values
) {
//sum累加 1 1 1
sum += val.get();
}
//封装成IntWritable类型
result.set(sum);
//key,value连接
context.write(key,result);
}
}
提交代码
idea打jar包
进入project sturcture
选择 Artifacts
点击左上角 +
选择JAR -> From modules with depe…
然后指定好执行类
然后选择Build -> Build Artifacts
选择 …:jar -> Build
然后从out/artifacts目录里找到jar包
Linux执行Jar
hadoop jar Jar包名 执行类名路径
# hadoop jar hadoop_demo.jar mapreduce.MRDemo
然后查看执行结果
1 node8:8088 浏览器查看
2 命令查看
# hadoop fs -cat /output/wordcount/part-r-00000