HDFS深度剖析(架构、读写、使用)

HDFS架构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6wKVjhbf-1591947367225)(Hadoop笔记/imgs/clipboard.png)]

HDFS为了保证数据存储的可靠性和读取性能,对数据进行切块后进行复制并存储在集群的多个节点中。

​ HDFS中存在一个名字节点NameNode和多个数据节点DataNode

​ NameNode

​ 存储元数据信息

​ 元数据保存在内存/磁盘中

​ 保存文件、block、datanode之间的映射关系

SecondaryNameNode

​ 帮助NameNode实现元数据的合并

​ DataNode

​ 存储block内容

​ 存储在磁盘中

​ 维护了block id到文件的映射关系

HDFS优点

(1)支持超大文件

(2)检测和快速应对硬件故障

(3)流式数据访问

(4)简化的一致性模型

一个文件存储在HDFS上后,适合一次写入,多次写出的场景once-write-read-many。因为存储在HDFS上的文件都是超大文件,当上传完这个文件到hadoop集群后,会进行文件切块,分发,复制等操作。如果文件被修改,会导致重新出发这个过程,而这个过程耗时是最长的。所以在hadoop里,不允许对上传到HDFS上文件做修改(随机写),在2.0版本时可以在后面追加数据。但不建议。

(5)高容错性

(6)可构建在廉价机器上

HDFS不适合的场景:

(1)低延迟数据访问

(2)大量的小文件

(3)写入文件,修改文件

(4)不支持超强的事物

Block块概念:

每台机器上最基本的存储单位

NameNode:

(1)维护中HDFS中元信息,包括文件和Block之间关系的信息、Block数量信息、Block和DataNode之间的关系

(2)NameNode中的元数据信息存储在内存/文件中,内存中为实时信息,文件中为数据镜像作为持久化存储使用。

(3)fsimage 元数据镜像文件。存储某NameNode元数据信息,并不是实时同步内存中的数据。

​ edits 操作日志文件

(4)当有写请求时,NameNode会首先写editlog到磁盘edits文件中,成功后才会修改内存,并向客户端返回这才请求的结果。所以,fsimage中的数据并不是实时的数据,而是在达到条件时再进行更新,更新过程需要SNN参与。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3v0RSrSt-1591947367227)(Hadoop笔记/imgs/clipboard.png)]

SecondaryNameNode

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dPkkfbxN-1591947367228)(Hadoop笔记/imgs/clipboard.png)]

DataNode:

存储Block

3秒一次向NameNode发送心跳报告

NameNode10分钟没收到心跳报告,则认为其已经lost

Block副本放置策略:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xzKEMUEm-1591947367232)(Hadoop笔记/imgs/clipboard.png)]

HDFS读流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rIPaDFHf-1591947367234)(Hadoop笔记/imgs/clipboard.png)]

(1)使用HDFS提供的客户端开发库Client,向远程的Namenode发起RPC请求;

(2)Namenode会视情况返回文件的部分或者全部block列表,对于每个block,Namenode都会返回有该block拷贝的DataNode地址;

(3)客户端开发库Client会选取离客户端最接近的DataNode来读取block;如果客户端本身就是DataNode,那么将从本地直接获取数据.

(4)读取完当前block的数据后,关闭与当前的DataNode连接,并为读取下一个block寻找最佳的DataNode;

(5)当读完列表的block后,且文件读取还没有结束,客户端开发库会继续向Namenode获取下一批的block列表。

(6)读取完一个block都会进行checksum验证,如果读取datanode时出现错误,客户端会通知Namenode,然后再从下一个拥有该block拷贝的datanode继续读。

(7)当文件最后一个块也都读取完成后,datanode会连接namenode告知关闭文件。

补充:

a.如果client与datanode通信时遇到一个错误,那么它就会去尝试对这个块来说下一个最近的块。它也会记住那个故障节点的datanode,以保证不会再对之后的块进行徒劳无益的尝试。

b.client直接联系DataNode去检索数据,DataNode返回校验和

HDFS写流程:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7tchbdnP-1591947367235)(Hadoop笔记/imgs/clipboard.png)]

(1)使用HDFS提供的客户端开发库Client,向远程的Namenode发起RPC请求;

(2)Namenode会检查要创建的文件是否已经存在,创建者是否有权限进行操作,成功则会为文件创建一个记录,否则会让客户端抛出异常;

(3)当客户端开始写入文件的时候,开发库会将文件切分成多个packets(64kb),并在内部以数据队列"data queue"的形式管理这些packets,并向Namenode申请新的blocks。,获取用来存储replicas的合适的datanodes列表, 列表的大小根据在Namenode中对replication的设置而定

(4)开始以pipeline(管道)的形式将packet写入所 有的replicas中。开发库把packet以流的方式写入第一个datanode,该datanode把该packet存储之后,再将其传递给在此 pipeline中的下一个datanode,直到最后一个datanode,这种写数据的方式呈流水线的形式。

(5)最后一个datanode成功存储之后会返回一个ack packet,在pipeline里传递至客户端,在客户端的开发库内部维护着"ack queue",成功收到datanode返回的ack packet后会从"ack queue"移除相应的packet。

(6)如果传输过程中,有某个datanode出现了故障,那么当前的pipeline会被关闭,出现故障的datanode会从当前的pipeline中移除, 剩余的block会继续剩下的datanode中继续以pipeline的形式传输,同时Namenode会分配一个新的datanode,保持 replicas设定的数量。

(7)重复如上过程将所有package都上传完成 之后 向NameNode报告成功 关闭文件

HDFS删除流程:

(1)客户端发起请求连接NameNode表示要删除文件

(2)NameNode执行元数据的删除。

(3)当NameNode执行delete方法时,它只标记操作涉及的需要被删除的数据块,而不会主动联系这些数据块所在的DataNode节点。

(4)当保存着这些数据块的DataNode节点向NameNode节点发送心跳时,在心跳响应中,NameNode节点会向DataNode发出指令,要求删除这些block。

(5)DataNode收到指令后删除对应的Block

(6)所以在执行完delete方法后的一段时间内,数据块才能被真正的删除掉。

HDFS启动流程:

(1)NameNode启动,进入安全模式,只能查看目录结构无法进行其他操作

(1)开始合并元数据,注意这次合并由NameNode自己来进行,合并完成后 生成新的fsimage和新的空的edits文件

(2)再将fsimage中的数据恢复到内存中

(3)接着等待 等待DataNode的心跳报告 DataNode在心跳报告中除了告知NameNode当前DataNode存活以外,还会在心跳中带着其所持有的Block信息

(4)NameNode根据DataNode心跳中的信息 在内存中构建出 Block对应的DataNode信息

当NameNode获取到足够数量的Block信息后 解除安全模式 开始对外提供服务

MapReduce框架的节点组成结构:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zOwE5Yuq-1591947367235)(Hadoop笔记/imgs/clipboard.png)]

(1)JobTracker/ResourceManager:

A.知道管理哪些机器,即管理哪些NodeManager。

B.要有检测机制,能够检测到NodeManager的状态变换,通过RPC心跳来实现。

C.任务的分配和调度,ResourceManager能够做到细粒度的任务分配,比如某一个任务需要占用多大内存,需要多少计算资源。

(2)TaskTracker/NodeManager:

能够收到ResourceManager发过来的任务,并进行任务的处理。这里处理任务指的是Map任务或Reduce任务。

Hadoop2.x的MR执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j7TGJOc2-1591947367236)(Hadoop笔记/imgs/clipboard.png)]

(1)客户端提交一个mr的jar包给JobClient(提交方式:hadoop jar …)

(2)JobClient通过RPC和ResourceManager进行通信,返回一个存放jar包的地址(HDFS)和jobId

(3)client将jar包写入到HDFS当中(path = hdfs上的地址 + jobId)

(4)开始提交任务(任务的描述信息,不是jar, 包括jobid,jar存放的位置,配置信息等等)

(5)ResourceManager进行初始化任务

(6)读取HDFS上的要处理的文件,开始计算输入分片,每一个分片对应一个MapperTask

(7)NodeManager通过心跳机制领取任务(任务的描述信息)

(8)下载所需的jar,配置文件等

(9)NodeManager启动一个java child子进程,用来执行具体的任务(MapperTask或ReducerTask)

(10)将结果写入到HDFS当中

序列化机制:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mLM1c5Nt-1591947367237)(Hadoop笔记/imgs/clipboard.png)]

将自定义对象序列化:

实现Writable接口,实现其中的write和readFields方法

public class FlowBean implements Writable{

​ private String phone;

​ private String addr;

​ private String name;

​ private long flow;

//属性顺序要一致

@Override

​ public void write(DataOutput out) throws IOException {

​ out.writeUTF(phone);

​ out.writeUTF(addr);

​ out.writeUTF(name);

​ out.writeLong(flow);

​ }

​ @Override

​ public void readFields(DataInput in) throws IOException {

​ phone = in.readUTF();

​ addr = in.readUTF();

​ name = in.readUTF();

​ flow = in.readLong();

​ }

}

分区:Partitioner

(1)写一个类继承Partitioner类,重写getPartition方法

public class FlowPatitioner extends Partitioner<Text,FlowBean> {

​ private static Map<String,Integer> addrMap;

​ static{

​ addrMap =new HashMap<String,Integer>();

​ addrMap.put(“bj”, 0);

​ addrMap.put(“sh”, 1);

​ addrMap.put(“sz”, 2);

​ }

​ public int getPartition(Text key, FlowBean value, int numPartitions) {

​ String addr = value.getAddr();

​ Integer num = addrMap.get(addr);

​ if(num==null){

​ return 3;

​ }

​ return num;

​ }

}

(2)在Driver里增加Partitioner配置

//设置Partitioner类

​ job.setPartitionerClass(FlowPartitioner.class);

​ //指定Reducer的数量

​ job.setNumReduceTasks(4);

排序:Sort

Map执行过后,在进入reduce之前,数据会按照mokey进行排序

如何按照指定规则排序:

让类实现WritableComparable接口,实现里面的write、readFields、compareTo方法

合并:Combiner

(1)写Combiner类继承Reducer类

public class WCCombiner extends Reducer<Text, LongWritable, Text, LongWritable> {

public void reduce(Text key, Iterable values, Context context)

throws IOException, InterruptedException {

​ Iterator it = values.iterator();

​ long count = 0;

​ while(it.hasNext()){

​ long c = it.next().get();

​ count += c;

​ }

​ context.write(key, new LongWritable(count));

​ }

(2)在Driver添加Combiner配置

job.setCombinerClass(WCReducer.class);

shuffle:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6Gqz42Pg-1591947367238)(Hadoop笔记/imgs/20160717195042350.jpg)]

(1)溢写(spill)过程:

map将输出不断写入到这个缓冲区中,当缓冲区使用量达到一定比例之后,一个后台线程开始把缓冲区的数据写入磁盘,这个写入的过程叫spill。开始spill的Buffer比例默认为0.80,可以通过mapreduce.map.sort.spill.percent配置。在后台线程写入的同时,map继续将输出写入这个环形缓冲,如果缓冲池写满了,map会阻塞直到spill过程完成,而不会覆盖缓冲池中的已有的数据。

(2)写磁盘前:

写磁盘前,要partition,sort,Combiner。如果有后续的数据,将会继续写入环形缓冲区中,最终写入下一个溢出文件中。

​ 等最后记录写完,合并全部溢出写文件为一个分区且排序的文件。

​ 如果在最终合并时,被合并的文件大于等于3个,则合并完会再执行一次Combiner,否则不会。

8.Map和Reduce的数量:

(1)mapper的数量由split的数量决定,split的大小默认等于block,可以通过mapred.min.split.size控制

(2)reducer的数量可以通过代码控制 job.setNumReduceTasks(3),reducer的数量等于最终结果文件的数量

9.数据倾斜:

(1)什么是数据倾斜:

数据倾斜在MapReduce编程模型中十分常见,用最通俗易懂的话来说,数据倾斜无非就是大量的相同key被partition分配到一个分区里,造成了’一个人累死,其他人闲死’的情况,这种情况是我们不能接受的,这也违背了并行计算的初衷,首先一个节点要承受着巨大的压力,而其他节点计算完毕后要一直等待这个忙碌的节点,也拖累了整体的计算时间,可以说效率是十分低下的。

(2)解决:

a.增加jvm内存

b.增加reducer数量

c.自定义分区,Partitioner

d.重新设计key

e.map端合并,combiner

10.两张表实现join:

(1)建立两张表合并之后的数据的bean(各自的字段+表名)

(2)map端

a.获取表名

FileSplit fs = (FileSplit) context.getInputSplit();

​ String name = fs.getPath().getName();

b.对于两张表的数据,分别输出(即bean不为null的字段不同)

(3)reduce端

根据传来的数据的表名字段不同,分别处理

拼接结果

11.二次排序:

所谓二次排序即, 就是首先按照第一字段排序,然后再对第一字段相同的行按照第二字段排序,注意不能破坏第一次排序的结果。

创建一个Bean implements WritableComparable,

compare()方法中先按照第一字段排序,第一字段相同再按照第二字段排序。

1.HDFS小文件处理:

hadoop自带的解决小文件问题的方案(以工具的形式提供),包括Hadoop Archive,Sequence file和CombineFileInputFormat

2.HDFS高可用集群配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1E4xTitq-1591947367239)(Hadoop笔记/imgs/屏幕截图.jpg)]

(1)NameNode:

NameNode会被配置在两台独立的机器上,在任何时间上,一个NameNode处于活动状态,而另一个NameNode处于备份状态,活动状态的NameNode会响应集群中所有的客户端,备份状态的NameNode只是作为一个副本,保证在必要的时候提供一个快速的转移。

(2)JN集群:

共享存储系统是实现NameNode 的高可用最为关键的部分,共享存储系统保存了 NameNode 在运行过程中所产生的 HDFS 的元数据。主NameNode和备NameNode 通过共享存储系统实现元数据同步。在进行主备切换的时候,新的主 NameNode 在确认元数据完全同步之后才能继续对外提供服务。

(3)FailoverController:

主要负责将NN挂机的情况传递给zookeeper。

为了支持快速failover,Standby node持有集群中blocks的最新位置是非常必要的。为了达到这一目的,DataNodes上需要同时配置这两个Namenode的地址,同时和它们都建立心跳链接,并把block位置发送给它们。

(4)zookeeper:为主备切换控制器提供主备选举支持。

3.MapReduce的输入输出控制:

(1)常见的InputFormat:

TextInputFormat 作为默认的文件输入格式,用于读取纯文本文件,文件被分为一系列以LF或者CR结束的行,key是每一行的位置偏移量,是LongWritable类型的,value是每一行的内容,为Text类型。

​ KeyValueTextInputFormat 同样用于读取文件,如果行被分隔符(缺省是tab)分割为两部分,第一部分为key,剩下的部分为value;如果没有分隔符,整行作为 key,value为空。

​ SequenceFileInputFormat 用于读取sequence file。 sequence file是Hadoop用于存储数据自定义格式的binary文件。它有两个子类:SequenceFileAsBinaryInputFormat,将 key和value以BytesWritable的类型读出;SequenceFileAsTextInputFormat,将key和value以Text类型读出。

​ SequenceFileInputFilter 根据filter从sequence文件中取得部分满足条件的数据,通过 setFilterClass指定Filter,内置了三种 Filter,RegexFilter取key值满足指定的正则表达式的记录;PercentFilter通过指定参数f,取记录行数%f0的记录;MD5Filter通过指定参数f,取MD5(key)%f0的记录。

​ NLineInputFormat 0.18.x新加入,可以将文件以行为单位进行split,比如文件的每一行对应一个map。得到的key是每一行的位置偏移量(LongWritable类型),value是每一行的内容,Text类型。

​ CompositeInputFormat,用于多个数据源的join。

可以通过job.setInputFormatClass(XxxInputFormat.class)来设定选用哪种InputFormat

(2)自定义InputFormat:

继承FileInputFormat类

继承isSplitable方法

重写createRecordReader方法,返回自定义的RecordReader类

@Override

public RecordReader<Text, Text> createRecordReader(InputSplit split, TaskAttemptContext context)

throws IOException, InterruptedException {

return new ScoreRecordReader();

}

自定义的RecordReader,表明了如何将一个InputSplit切割出一个个的Recorder

(3)分组GroupingComparator

一般继承WritableComparator类,

覆写public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2)方法

通过job.setGroupingComparatorClass(MyGroupingComparator.class)配置分组规则

(4)排序SortComparator

一般继承WritableComparator类,

覆写public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2)方法

OException, InterruptedException {

return new ScoreRecordReader();

}

自定义的RecordReader,表明了如何将一个InputSplit切割出一个个的Recorder

(3)分组GroupingComparator

一般继承WritableComparator类,

覆写public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2)方法

通过job.setGroupingComparatorClass(MyGroupingComparator.class)配置分组规则

(4)排序SortComparator

一般继承WritableComparator类,

覆写public int compare(byte[] b1, int s1, int l1, byte[] b2, int s2, int l2)方法

通过job.setSortComparatorClass (MySortComparator.class)配置排序规则。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值