hadoop概述
Hadoop 历史
- Hadoop最早起源于Nutch。Nutch的设计目标是一个网络爬虫引擎,但随着抓取网页数据量的增大,Nutch遇到了严重的性能扩展问题。2003年,2004年谷歌发布两篇论文为该问题提供了一个解决方案一个是HDFS的前身GFS,用于海量网页的存储;另一个是分布式计算框架MAPERDUCE。Nutch的创始人根据论文的指导用了·两年的时间实现了HDFS和MapReduce代码。并将其从Nutch剥离出来,成为独立项目Hadoop。2008年Hadoop成为Apache顶级项目。
- 早期的Hadoop并非现在大家所熟悉的Hadoop分布式开源软件,而是指代大数据的一个生态圈,包括很多其他的软件。在2010前后,Hbase,HIVE,Zookeeper等依次脱离Hadoop项目成为Apache的顶级项目。2011年,Hadoop发布2.0,在架构上进行了重大更新,引入Yarn框架专注于资源的管理精简了MapReduce的职责。同时Yarn框架作为一个通用的资源调度和管理模块,同时支持多种其他的编程模型,比如最出名的Spark。
- 由于HADOOP的版本管理复杂,复杂集群的部署安装配置等需要编写大量的配置文件然后分发到每一台节点上,容易出错效率低下。所以很多公司会在基础的Hadoop进行商业化后进行再发行。目前Hadoop发行版非常多,有华为发行版、Intel发行版、Cloudera发行版(CDH)等。
Hadoop(2.0)的组成
HDFS
HDFS的组成
NameNode:是整个HDFS集群的管理者,
- 管理HDFS的名称空间
- 管理副本策略
- 管理数据块在DataNode的位置映射信息
- 与客户端交互处理来自于客户端的读写操作。
DataNode:实际文件的存储者。
- 存储实际的数据块
- 执行数据块的读、写操作
- DataNode启动后向NameNode注册并每6小时向NameNode上报所有的块信息
- DataNode的心跳是每3秒一次,心跳返回结果带有NameNode给该DataNode的命令,例如:复制数据到另一台机器;删除某个数据块。
- 若NameNode超过10分钟没有收到某个DataNode的心跳,则认为该节点不可用。
客户端
- HDFS提供的工具包,面向开发者,封装了对HDFS的操作调用。
- 负责文件切分:文件上传到HDFS时,Client负责与NameNode和DataNode交互,将文件切分为一个一个的block进行上传。
Secondary NameNode:辅助NameNode,分担其工作量。
- 定期合并Fsimage和Edits,并推送给NameNode。
- 辅助恢复NameNode。
- 并非NameNode的热备。当NameNode挂掉的时候,它并不能马上替换NameNode并提供服务
HDFS读写流程
HDFS上传文件
- client向NameNode请求上传文件,NameNode进行合规性检测,并创建相应的目录元数据。并返回是否可以上传。
- client将文件切分,再次询问NameNode第一个Block需要上传到哪几个DataNode服务端上。NameNode 返回 3 个 DataNode 节点,分别为 dn1、dn2、dn3。
- client将第一个block数据上传给dn1,dn1 收到请求会继续调用dn2,然后 dn2 调用 dn3,将这个通信管道建立完成。
- client开始往 dn1 上传第一个 Block(先从磁盘读取数据放到一个本地内存缓存),以 Packet 为单位,dn1 收到一个 Packet 就会传给 dn2,dn2 传给 dn3。
- 当一个Block传输完成之后,客户端再次请求NameNode上传第二个Block,然后重复1~4步骤。
NameNode节点选择策略-节点距离计算
- 如果Client与HADOOP在同一个集群,则NameNode会选择距离待上传数据最近距离的DataNode接收数据。节点距离:两个节点到达最近的共同祖先的距离总和。
- 如果Client与HADOOP不在同一个集群,则NameNode随机选一个机架上的一个节点。第二个副本在另一个机架的随机的一个节点,第三个副本在第二个副本所在的机架的随机节点。
HDFS读流程
- client向NameNode请求下载文件,NameNode返回文件块所在的DataNode地址。
- client根据节点距离挑选一台最近的DataNode服务器,然后开始读取数据。DataNode以Packet来传输数据。
- client接收到一个packet数据后,并进行校验。校验通过后,将packet写入目标文件,再请求第二个packet。整个过程为串行的过程。(因为IO本身就是速度最慢的流程)
- 读取数据的过程中,如果client端与dn数据结点通信时出现错误,则尝试连接包含此数据块的下一个dn数据结点。失败的dn数据结点将被client记录,并且以后不再连接。
SecondaryNameNode
NameNode是机器的文件管理容易造成单点读写性能问题与数据存储安全问题。
SecondaryNameNode与Name协助解决读写性能问题:NameNode的数据既存储在内存中又存储在磁盘中
- Fsimage文件:HDFS文件系统元数据的一个永久性的检查点,其中包含HDFS文件系统的所有目录和文件inode的序列化信息。
- Edits文件:存放HDFS文件系统的所有更新操作的路径,文件系统客户端执行的所有写操作首先会被记录到Edits文件中。Edits文件只进行追加操作,效率很高。每当元数据有更新或者添加元数据时,修改内存中的元数据并追加到Edits中。
- NameNode启动的时候都会将Fsimage文件读入内存,加载Edits里面的更新操作,保证内存中的元数据信息是最新的、同步的。
- 长时间添加数据到 Edits 中,会导致该文件数据过大,效率降低,而且一旦断电,恢复元数据需要的时间过长。因此2NN,专门用于FsImage和Edits的合并。
SecondaryNameNode 工作机制
NameNode启动后,会创建FsImage和Edits文件。如果不是第一次启动,则直接加载FsImage和Edits文件。
- fsimage_0000000000000000002 文件最新的FsImage。
- edits_inprogress_0000000000000000003 正在进行中的edits。
- seen_txid 是一个txt的文本文件,记录是最新的edits_inprogress文件末尾的数字。
Secondary NameNode 工作
- 2NN询问NN是需要CheckPoint。
- NN将现在进行中的edits文件和最新的fsimage文件拷贝到2NN并更新seen_txid里的数字,然后重新生成edits文件。
- 2NN加载编辑日志和镜像文件到内存,并合并生成新的镜像文件fsimage.chkpoint。然后拷贝回NN。NN将fsimage.chkpoint重新命名为fsImage完成一次滚写。
HDFS总结
优点
1. 高容错性:数据自动保存多个副本,通过增加副本的形式,提高容错性。某一个副本丢失后,可以自动恢复。 2. 适合处理大数据:能够处理的数据规模达到GB,TB甚至PB级别,文件数量可以达到百万规模以上。 3. 可构建在廉价的机器上:通过多副本机制,提高可靠性。
缺点
1. 不适合低时延的数据访问 2. 无法高效的对大量小文件进行存储,大量的小文件会占用NameNode大量的内存来存储文件目录和块信息,同时小文件的寻址时间会超过读取时间,它违反了HDFS的设计目标 3. 不支持并发写入,文件随机修改。仅支持数据追加的。
DataNode与NameNode源代码导读
代码阅读前的准备工作:Hadoop Rpc框架指南
Rpc 协议
public interface MyInterface {
Object versionID = 1;
boolean demo();
}
Rpc provider
public class MyHadoopServer implements MyInterface {
@Override
public boolean demo() {
return false;
}
public static void main(String[] args) {
Server server = new RPC.Builder(new Configuration())
.setBindAddress("localhost")
.setPort(8888)
.setProtocol(MyInterface.class)
.setInstance(new MyHadoopServer())
.build();
server.start();
}
}
Rpc consuemr
public class MyHadoopClient {
public static void main(String[] args) throws Exception {
MyInterface client = RPC.getProxy(
MyInterface.class,
MyInterface.versionID,
new InetSocketAddress("localhost", 8888),
new Configuration());
client.demo();
}
}
NameNode 启动源码
- 启动9870端口服务
- 加载镜像文件和编辑日志
- 初始化NN的RPC服务端:用于接收DataNode的RPC请求
- NN启动资源检测
NN对心跳超时判断(启动一个线程去判断DataNode是否超时)
- HDFS默认DataNode掉线容忍时间未 timeout = 2 heartbeat.recheck.interval + 10 dfs.heartbeat.interval(2*5+30)超过这个时间会被认为DataNode超时
DataNode 启动源码
工作流程
源码图示
MapReduce
MapReduce示例
- 需求:有一个大小为300M的存文本文件。统计其每一个字母出现的总次数。要求:[a-p]一个结果文件,[q-z]一个结果文件。
实现:
public class WordCountMapper extends Mapper<LongWritable, Text, Text, IntWritable> { private Text outK = new Text(); private IntWritable outV = new IntWritable(1); @Override protected void map(LongWritable key, Text value, Context context) throws IOException, InterruptedException { // 1 获取一行 String line = value.toString(); // 2 切割 String[] words = line.split(" "); // 3 循环写出 for (String word : words) { // 封装outk outK.set(word); // 写出 context.write(outK, outV); } } }
public class WordCountReducer extends Reducer<Text, IntWritable,Text,IntWritable> {
private IntWritable outV = new IntWritable();
@Override
protected void reduce(Text key, Iterable<IntWritable> values, Context context) throws IOException, InterruptedException {
int sum = 0;
// 累加
for (IntWritable value : values) {
sum += value.get();
}
outV.set(sum);
// 写出
context.write(key,outV);
}
}
切片规则决定MapTask的数量。
- MapReduce将数据的读取抽象为一个InputFormat。常用的FileInputFormat是针对文件读取的一个具体实现。
- FileInputFormat的默认分片规则为:将待处理的数据文件进行逻辑分片,每128M为一个数据切片。一个数据切片交给一个MapTask进行并行处理。
分片数量过大,开启过多的MapTask浪费资源。分片数量过小,MapTask阶段处理慢。
ReduceTask的数量需要手工指定。
- 在Map阶段需要对每一个输出数据会通过分区算法计算分区。
- 若ReduceTask=0,则表示没有Reduce阶段,输出文件个数和Map个数一致。
- 若ReduceTask=1,所有输出文件为一个。
- ReduceTask数量要大于分区的结果不同值的数,否则数据无法被消费会产生异常。
- 若ReduceTask的数量大于分区数量,则会有部分reduceTask处于闲置状态。
MapReduce详细的工作流程。
Map阶段
- read阶段:通过RecordReader从InputFormat分片中将读取数据并将数据解析成一个个key/value。
- map阶段:用户自定义的Mapper.map方法执行。将输入的key/value转为输出的key/value。
collect阶段:接收输出Key/Value 并调用分区算法,将输出数据写入对应的分区的环形内存缓存区中。
- spill阶段:当内存缓存区的使用率超过一定的阈值后,将触发溢写线程。该线程现在内存中进行快速排序,然后将数据溢写到磁盘上。
- Combine阶段:当所有数据处理完成后,MapTask对所有临时文件进行一次合并,以确保最终只会生成一个数据文件。
Reduce阶段
- Copy阶段:ReduceTask从所有的MapTask中拷贝同一分区的数据,每一个ReduceTask负责处理一个分区,互不影响。如果文件大小超过一定的阈值,则溢写到磁盘上,否则存储在内存中。
- Merge阶段:ReduceTask将所有相同分区的资料合并成一个大文件,
- sort阶段:将合并后的大文件进行归并排序。由于mapTask本身保证了分区内区内有序,因此ReduceTask只需要对所有数据进行一次归并排序即可。
- reduce阶段:执行用户的reduce方法,并将结果写到HDFS。
MapReduce优缺点
优点
- 实现简单,封装度高。
- 扩展性强:可以快速增加机器来扩展它的计算能力。
- 高容错性:当某个节点挂掉,它会自动将任务转移到领一个节点运行,中间不需要人工参与。
- 适合PB级别以上海量数据的离线处理。
缺点
- 不擅长实时计算。
- 不擅长流式计算:MapReduce的输入数据集是静态的。
- 重IO:每个 MapReduce 作业的输出结果都会写入到磁盘,会造成大量的磁盘IO。
MapTask源代码导读
MapTask.run方法是MapTask的入口.
- 读取配置,初始化MapTask生成jobId
- 判断使用的api选择使用runNewMapper或者runOldMapper,并执行。
- MapTask执行结束,做一些清理工作。
runNewMapper
- 实例化默认的inputFormat,计算切片。根据设置的reduceTask数量实例化输出对象。实例化输入对象。
mapper.run(mapperContext)执行
- 循环确认每组kv,执行用户的map逻辑。
- map方法中调用collector.collect方法。将数据写入环形缓冲区。
output.close(mapperContext)执行最终调用MapTask的close方法
调用MapTask的flush方法。
- sortAndSpill 内存排序,并溢写到文件。每一个分区一个临时文件。区内有序
- mergeParts 归并排序,将多个临时文件合并成一个文件。
- 调用MapTask的close方法,该方法是一个空方法。
ReduceTask源代码导读:其入口为RecuceTask的run方法。
- 首先初始化copy,sort,reduce的状态器。
- initialize初始化outputformat为TextOutputFormat。
- shuffleConsumerPlugin.init(shuffleContext)方法执行。初始化inMemoryMerger和onDiskMerger。
shuffleConsumerPlugin.run();
- 创建Fetcher抓取数据,数据抓取完成后,将状态切为sort
- merger.close();里的finanlMerge执行,将内存中的数据和磁盘内的数据合并。
将状态切为reduce。runNewReducer。用户自定义的Reduce方法执行。
用户自定义的reduce方法执行,并调用context.write方法写数据。最终调用TextOutputFormat的write方法。
- 先写key
- 再写value
- 最后写入一个换行
Yarn
Yarn组成
ResourceManager(RM):全局资源的管理者
- 由两部分组成:一个是可插拔式的调度Scheduler,一个是ApplicationManager
- Scheduler :一个存粹的调度器,不负责应用程序的监控
- ApplicationManager:主要负责接收job的提交请求,为应用分配第一个Container来运行ApplicationMaster,还有就是负责监控ApplicationMaster,在遇到失败时重启ApplicationMaster运行的Container
- NodeManager(NM)
- 接收ResourceManager的请求,分配Container给应用的某个任务
- 和ResourceManager交换信息以确保整个集群平稳运行。ResourceManager就是通过收集每个NodeManager的报告信息来追踪整个集群健康状态的,而NodeManager负责监控自身的健康状态。
- 管理每个Container的生命周期
- 管理每个节点上的日志
- 执行Yarn上面应用的一些额外的服务,比如MapReduce的shuffle过程
Container
- 是Yarn框架的计算单元,是具体执行应用task
- 是一组分配的系统资源内存,cpu,磁盘,网络等
- 每一个应用程序从ApplicationMaster开始,它本身就是一个container(第0个),一旦启动,ApplicationMaster就会根据任务需求与Resourcemanager协商更多的container,在运行过程中,可以动态释放和申请container。
ApplicationMaster(AM)
- ApplicationMaster负责与scheduler协商合适的container,跟踪应用程序的状态,以及监控它们的进度
- 每个应用程序都有自己的ApplicationMaster,负责与ResourceManager协商资源(container)和NodeManager协同工作来执行和监控任务
- 当一个ApplicationMaster启动后,会周期性的向resourcemanager发送心跳报告来确认其健康和所需的资源情况
Yarn执行过程
- 客户端程序向ResourceManager提交应用并请求一个ApplicationMaster实例,ResourceManager在应答中给出一个applicationID
- ResourceManager找到可以运行一个Container的NodeManager,并在这个Container中启动ApplicationMaster实例
- ApplicationMaster向ResourceManager进行注册,注册之后客户端就可以查询ResourceManager获得自己ApplicationMaster的详细信息
- 在平常的操作过程中,ApplicationMaster根据resource-request协议向ResourceManager发送resource-request请求,ResourceManager会根据调度策略尽可能最优的为ApplicationMaster分配container资源,作为资源请求的应答发给ApplicationMaster
- ApplicationMaster通过向NodeManager发送container-launch-specification信息来启动Container
- 应用程序的代码在启动的Container中运行,并把运行的进度、状态等信息通过application-specific协议发送给ApplicationMaster,随着作业的执行,ApplicationMaster将心跳和进度信息发给ResourceManager,在这些心跳信息中,ApplicationMaster还可以请求和释放一些container。
- 在应用程序运行期间,提交应用的客户端主动和ApplicationMaster交流获得应用的运行状态、进度更新等信息,交流的协议也是application-specific协议
Yarn调度器与调度算法
Yarn Scheduler 策略
FIFO:将所有的Applications放到队列中,先按照作业的优先级高低、再按照到达时间的先后,为每个app分配资源
- 优点:简单,不需要配置
- 缺点:不适合共享集群
Capacity Scheduler:用于一个集群中运行多个Application的情况,目标是最大化吞吐量和集群利用率
- CapacityScheduler允许将整个集群的资源分成多个部分,每个组织使用其中的一部分,即每个组织有一个专门的队列,每个组织的队列还可以进一步划分成层次结构(Hierarchical Queues),从而允许组织内部的不同用户组的使用。每一个队列指定可以使用的资源范围.
- 每一个队列内部,按照FIFO的方式调度Application.当某个队列的资源空闲时,可以将它的剩余资源共享给其他队列.
Yarn源码
参考
- https://www.cnblogs.com/dan2/...
- https://www.cnblogs.com/dan2/...
- https://www.cnblogs.com/dan2/...
- https://www.bilibili.com/vide...)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。