0  架构图如下:

 

hadoop_hdfs_架构图_hadoop


 

 

 

问题1:  为何拆分,拆分后读写时是怎么读写的

a) 空间利用率上升,eg:传一个10G文件,但是datanode只有9G,那么按块存放最起码还能放9G,

b) 便于管理维护,你传一个10G文件,按块上传,突然中断最起码上传了一部分,否则只能从0开始

c) 针对以上可以类比与网上卖枪,整个不会让卖,拆成零件,分批发货,到目的地组装,实现双赢,

d) hdfs对文件块的切分对用户屏蔽,迅雷下载电影,多线程下的每一块都是一个block,下载完后迅雷会

把这些零件在组装给人类看

e) 拆分后,读写时,客户端会一块块的去namenode上询问,去哪里写/读,然后客户端带着位置去datanode上

写/读,操作好一块,就在namenode的edits上做一个记录,然后在执行下一个block

 

问题2:上传文件逻辑上给人展示是一份,物理上位置如果你配置的副本是3,则存储三份

 

具体细节请看 《hadoop1.1.2分布式安装(完备版)》之

 11 部署成功后,通过http 查看datanode节点确认是否安装成功

 

 

问题2.1   hdfs客户端 何时和 namenode ,  datanode 交互

 

在创建文件夹  查看文件目录时,仅仅和namenode交互,

在 读写文件,查看文件内容时, 才会通过 namenode 去和 datanode交互

在 集群初始化 hadoop namenode -format 时,也仅仅是namenode自己的事(此时没有数据)

     仅仅是创建一个大纲而已,大纲对应的文章具体章节(datanode对应的数据)还没有,自然没有datanode事

 

 

问题3:上传文件保存datanode节点哪里,是谁给指定的

 

default-defalut.xml  --->  dfs.data.dir ---> ${hadoop.tmp.dir}/dfs/data 

而在 core-site.xml中对${hadoop.tmp.dir}/ 定义了路径:

 <property>
        <name>hadoop.tmp.dir</name>
        <value>/usr/local/hadoop/tmp</value>
    </property>  
因此存储实际数据的路径为:/usr/local/hadoop/tmp/dfs/data/

实际路径为: /usr/local/hadoop/tmp/dfs/data/current
看如下代码:

[root@hadoop2 current]# pwd
/usr/local/hadoop/tmp/dfs/data/current
[root@hadoop2 current]# ls -lh
total 82M
-rw-r--r-- 1 root root  17M Nov 19 20:42 blk_-2805289631903080889
-rw-r--r-- 1 root root 136K Nov 19 20:42 blk_-2805289631903080889_1006.meta
-rw-r--r-- 1 root root    4 Nov 19 19:53 blk_3018722244877550495
-rw-r--r-- 1 root root   11 Nov 19 19:53 blk_3018722244877550495_1004.meta
-rw-r--r-- 1 root root  398 Nov 19 20:01 blk_5410919615090442736
-rw-r--r-- 1 root root   11 Nov 19 20:01 blk_5410919615090442736_1005.meta
-rw-r--r-- 1 root root  64M Nov 19 20:42 blk_8663227687956733630
-rw-r--r-- 1 root root 513K Nov 19 20:42 blk_8663227687956733630_1006.meta
-rw-r--r-- 1 root root  385 Nov 19 20:49 dncp_block_verification

 

 

 

问题4: block默认的64M,如果实际业务中,需要上传的文件大多在 64M以上/一下怎嘛办?

 

比如实际文件大多在120M 那么建议修改默认值为128M, 如果过大,则建议使用默认值,将工作交给hdfs来处理

 

问题5: 文件大于64M下,剩余部分仍旧占64M呢还是一小块呢

 

剩余部分是多少则占用多少空间,可以看看问题3中贴出来的实际上传data切分后的大小,都是实际大小。

 

下面看 datanode脑图, 解释下一些细节参数  VERSION

hadoop_hdfs_架构图_hadoop_02


 

 VERSION表示版本之意,在每次执行 hadoop namenode -format的时候,都会重新产生一个随机数,作为

新版版本号,集群格式化,启动后,对应datanode也会有这么一个对应的版本,版本号相同下,

namenode和datanode才会通信(类比于地下党接头对暗语),真正版本号是参数namespaceID=244348493,

这是个随机产生的,只有在hadoop namenode -format才产生。

如果版本号不一致,则会报错,比如找不到对应启动的datanode节点,无法上传文件等,此时仅需要

将版本号统一即可,谁拷贝谁的无所谓,如下是集群下 两种类型节点的VERSION的信息,

注意观察 namespaceID是否一致:

namenode节点:

[root@hadoop2 current]# more VERSION 
#Wed Nov 19 19:53:40 PST 2014
namespaceID=244348493
storageID=DS-2088459708-192.168.1.112-50010-1416455620923
cTime=0
storageType=DATA_NODE
layoutVersion=-32


datanode节点:

[root@hadoop0 name]# cd current/
[root@hadoop0 current]# ls
edits  edits.new  fsimage  fstime  VERSION
[root@hadoop0 current]# more VERSION 
#Wed Nov 19 19:53:31 PST 2014
namespaceID=244348493
cTime=0
storageType=NAME_NODE
layoutVersion=-32
[root@hadoop0 current]#

 

下面看 seconddatanode脑图:

hadoop_hdfs_架构图_数据_03


  

 

hadoop_hdfs_架构图_hadoop_04

 

 

1.拓扑距离

1 下图简单讲下hadoop的网络拓扑距离的计算(带宽是稀缺资源)

hadoop_hdfs_架构图_数据结构与算法_05


 

计算两个节点间的间距,采用最近距离的节点进行操作,如果你对数据结构比较熟悉的话,可以看出这里是距离测量算法的一个实例。

如果用数据结构表示的话,这里可以表示为tree,两个节点的距离计算就是寻找公共祖先的计算。

在现实情况下比较典型的情景如下,

tree结构的节点由数据中心data center,这里表示为d1,d2,机架rack,这里表示为r1,r2,r3,还有服务器节点node,这里表示为n1,n2,n3,n4

1.distance(d1/r1/n1,d1/r1/n1)=0 (相同节点)

2.distance(d1/r1/n1,d1/r1/n2)=2 (相同机架不同节点)

3.distance(d1/r1/n1,d1/r2/n3)=4 (相同数据中心不同机架)

4.distance(d1/r1/n1,d2/r3/n4)=6 (不同数据中心)  

 

2.副本存放

namenode节点选择一个datanode节点去存储block副本得过程就叫做副本存放,

这个过程的策略其实就是在可靠性和读写带宽间得权衡.

那么我们来看两个极端现象:

1.把所有的副本存放在同一个节点上,写带宽是保证了,但是这个可靠性是完全假的,一旦这个节点挂掉,数据就全没了,而且跨机架的读带宽也很低。

2.所有副本打散在不同的节点上,可靠性提高了,但是带宽有成了问题。

我们来讲下hadoop默认的方案:

1.把第一副本放在和客户端同一个节点上,如果客户端不在集群中,那么就会随即选一个节点存放。

2.第二个副本会在和第一个副本不同的机架上随机选一个

3.第三个副本会在第二个副本相同的机架上随机选一个不同的节点

4.剩余的副本就完全随机节点了。

如果重复因子是3的话,就会形成下图这样的网络拓扑:

 

hadoop_hdfs_架构图_数据结构与算法_06

 

可以看出这个方案比较合理:
1.可靠性:block存储在两个机架上
2.写带宽:写操作仅仅穿过一个网络交换机
3.读操作:选择其中得一个机架去读
4.block分布在整个集群上。

 

3.读文件解析

hadoop_hdfs_架构图_数据_07


 

1.首先调用FileSystem对象的open方法,其实是一个DistributedFileSystem的实例

2.DistributedFileSystem通过rpc获得文件的第一批个block的locations,同一block按照重复数会返回多个locations,这些locations按照hadoop拓扑结构排序,距离客户端近的排在前面.(数据结构知识)

3 前两步会返回一个FSDataInputStream对象,该对象会被封装成DFSInputStream对象,DFSInputStream可以方便的管理datanode和namenode数据流。客户端调用read方法,DFSInputStream最会找出离客户端最近的datanode并连接

4 数据从datanode源源不断的流向客户端。

5 如果第一块的数据读完了,就会关闭指向第一块的datanode连接,接着读取下一块。这些操作对客户端来说是透明的,客户端的角度看来只是读一个持续不断的流。

6 如果第一批block都读完了,DFSInputStream就会去namenode拿下一批blocks的location,然后继续读,如果所有的块都读完,这时就会关闭掉所有的流

 

如果在读数据的时候,DFSInputStream和datanode的通讯发生异常,就会尝试正在读的block的排第二近的datanode,并且会记录哪个datanode发生错误,剩余的blocks读的时候就会直接跳过该datanode。

DFSInputStream也会检查block数据校验和,如果发现一个坏的block,就会先报告到namenode节点,然后DFSInputStream在其他的datanode上读该block的镜像。

 

该设计的方向就是客户端直接连接datanode来检索数据并且namenode来负责为每一个block提供最优的datanode,
namenode仅仅处理block location的请求,这些信息都加载在namenode的内存中,
hdfs通过datanode集群可以承受大量客户端的并发访问。

 

 

4.写文件解析

hadoop_hdfs_架构图_hadoop_08


 

 

1.客户端通过调用DistributedFileSystem的create方法创建新文件
2.DistributedFileSystem通过RPC调用namenode去创建一个没有blocks关联的新文件,
创建前,namenode会做各种校验,比如文件是否存在,客户端有无权限去创建等。
如果校验通过,namenode就会记录下新文件,否则就会抛出IO异常.

3 前两步结束后会返回FSDataOutputStream的对象,FSDataOutputStream被封装成DFSOutputStream
DFSOutputStream可以协调namenode和datanode。
户端开始写数据到DFSOutputStream的时候,DFSOutputStream会把数据切成一个个小packet,然后排成队列data quene。

4.DataStreamer会去处理接受data quene,他先问询namenode这个新的block最适合存储的在哪几个datanode里,
比如重复数是3,那么就找到3个最适合的datanode,然后把这三个datanode排成一个pipeline,
DataStreamer把packet按队列输出到pipeline的第一个datanode中,第一个datanode又把packet输出到第二个datanode中,以此类推。

5 DFSOutputStream还有一个对列叫ack quene,也是由packet组成,等待datanode的收到响应,
当pipeline中的所有datanode都表示已经收到的时候,这时akc quene才会把对应的packet包移除掉。(这个对应的包应该就是 data quene的数据)

6.客户端完成写数据后调用close方法关闭写入流

7.DataStreamer把剩余得包都刷到pipeline里然后等待ack信息,收到最后一个ack后,通知datanode把文件标示为已完成。

 

 

注:
1 客户端执行write操作后,写完得block才是可见的,正在写的block对客户端是不可见的,
只有调用sync方法,客户端才确保该文件被写操作已经全部完成,当客户端调用close方法时会默认调用sync方法。
是否需要手动调用取决你根据程序需要在数据健壮性和吞吐率之间的权衡。

2
如果在写的过程中某个datanode发生错误,会采取以下几步:
1) pipeline被关闭掉;
2)为了防止防止丢包ack quene里的packet会同步到data quene里;
3)把产生错误的datanode上当前在写但未完成的block删掉;
4)block剩下的部分被写到剩下的两个正常的datanode中;
5)namenode找到另外的datanode去创建这个块的复制。当然,这些操作对客户端来说是无感知的。

  

hdfs操作执行过程:

 client ---> DistributedFileSystem.create()文件 + 在namenode中关联文件  --->  DFSOutputStream将数据处理成 data quene ---> DataStreamer将 data quene依次写入datanode ---> DFSOutputStream的ack quene清空socket   ---> 通知客户端执行完,client执行close()