很多不是真正理解hadoop技术体系的人,经常会觉得HDFS可用于网盘类应用。但实际并非如此。要想将技术准确用在恰当的地方,必须对技术有深刻的理解。
客户端要向HDFS写数据,首先要跟NameNode通信以确认可以写文件并获得接收文件block的DataNode,然后,客户端按顺序将文件逐个以block传递给相应DataNode,并由接收到block的DataNode负责向其他DataNode复制block的副本。
客户端将要读取的文件路径发送个NameNode,NameNode获取文件的元信息(主要是block的存放位置信息)返回给客户端,客户端根据返回的信息找到相应的DataNode,逐个获取文件的block并在客户端本地进行数据追加合并,从而获得整个文件。
诸如此类问题的回答,都需要基于对NameNode自身的工作原理的深刻理解。
NameNode主要的两个职责:
1. 负责客户端请求的响应
2. 元数据的管理(查询、修改)
NameNode对数据的管理采用了三种存储形式:
内存元数据(NameSystem)
磁盘元数据镜像文件
数据操作日志文件(可通过日志运算出元数据)
先查看一下 hdpdata中的数据结构:
[hadoop@hadoop01 dfs]$ pwd
/home/hadoop/apps/hdpdata/dfs
[hadoop@hadoop01 dfs]$
[hadoop@hadoop01 dfs]$ tree
.
├── data
│ ├── current
│ │ ├── BP-60221078-172.16.29.161-1514955036901
│ │ │ ├── current
│ │ │ │ ├── dfsUsed
│ │ │ │ ├── finalized
│ │ │ │ │ └── subdir0
│ │ │ │ │ └── subdir0
│ │ │ │ │ ├── blk_1073741832
│ │ │ │ │ ├── blk_1073741832_1008.meta
│ │ │ │ │ ├── blk_1073741833
│ │ │ │ │ ├── blk_1073741833_1009.meta
│ │ │ │ │ ├── blk_1073741843
│ │ │ │ │ ├── blk_1073741843_1019.meta
│ │ │ │ │ ├── blk_1073741850
│ │ │ │ │ ├── blk_1073741850_1026.meta
│ │ │ │ │ ├── blk_1073741851
│ │ │ │ │ ├── blk_1073741851_1027.meta
│ │ │ │ │ ├── blk_1073741853
│ │ │ │ │ ├── blk_1073741853_1029.meta
│ │ │ │ │ ├── blk_1073741864
│ │ │ │ │ ├── blk_1073741864_1040.meta
│ │ │ │ │ ├── blk_1073741865
│ │ │ │ │ ├── blk_1073741865_1041.meta
│ │ │ │ │ ├── blk_1073741867
│ │ │ │ │ ├── blk_1073741867_1043.meta
│ │ │ │ │ ├── blk_1073741868
│ │ │ │ │ └── blk_1073741868_1044.meta
│ │ │ │ ├── rbw
│ │ │ │ └── VERSION
│ │ │ ├── dncp_block_verification.log.curr
│ │ │ ├── dncp_block_verification.log.prev
│ │ │ └── tmp
│ │ └── VERSION
│ └── in_use.lock
├── name
│ ├── current
│ │ ├── edits_0000000000000000001-0000000000000000340
│ │ ├── edits_0000000000000000341-0000000000000000341
│ │ ├── edits_0000000000000000342-0000000000000000343
│ │ ├── edits_0000000000000000344-0000000000000000345
│ │ ├── edits_0000000000000000346-0000000000000000347
│ │ ├── edits_0000000000000000348-0000000000000000349
│ │ ├── edits_0000000000000000350-0000000000000000351
│ │ ├── edits_0000000000000000352-0000000000000000353
│ │ ├── edits_0000000000000000354-0000000000000000355
│ │ ├── edits_0000000000000000356-0000000000000000357
│ │ ├── edits_0000000000000000358-0000000000000000359
│ │ ├── edits_0000000000000000360-0000000000000000361
│ │ ├── edits_0000000000000000362-0000000000000000386
│ │ ├── edits_inprogress_0000000000000000420
│ │ ├── fsimage_0000000000000000417
│ │ ├── fsimage_0000000000000000417.md5
│ │ ├── fsimage_0000000000000000419
│ │ ├── fsimage_0000000000000000419.md5
│ │ ├── seen_txid
│ │ └── VERSION
│ └── in_use.lock
└── namesecondary
├── current
│ ├── edits_0000000000000000001-0000000000000000340
│ ├── edits_0000000000000000341-0000000000000000341
│ ├── edits_0000000000000000342-0000000000000000343
│ ├── edits_0000000000000000344-0000000000000000345
│ ├── edits_0000000000000000346-0000000000000000347
│ ├── edits_0000000000000000348-0000000000000000349
│ ├── edits_0000000000000000350-0000000000000000351
│ ├── edits_0000000000000000352-0000000000000000353
│ ├── edits_0000000000000000354-0000000000000000355
│ ├── edits_0000000000000000356-0000000000000000357
│ ├── edits_0000000000000000358-0000000000000000359
│ ├── edits_0000000000000000360-0000000000000000361
│ ├── edits_0000000000000000362-0000000000000000386
│ ├── fsimage_0000000000000000417
│ ├── fsimage_0000000000000000417.md5
│ ├── fsimage_0000000000000000419
│ ├── fsimage_0000000000000000419.md5
│ └── VERSION
└── in_use.lock
可以通过hdfs的一个工具来查看edits中的信息。该命令需要以下参数:
必须参数:
-i,–inputFile 输入edits文件,如果是xml后缀,表示XML格式,其他表示二进制。
-o,–outputFile 输出文件,如果存在,则会覆盖。
可选参数:
-p,–processor 指定转换类型: binary (二进制格式), xml (默认,XML格式),stats (打印edits文件的静态统计信息)
-h,–help 显示帮助信息
-f,–fix-txids 重置输入edits文件中的transaction IDs
-r,–recover 使用recovery模式,跳过eidts中的错误记录。
-v,–verbose 打印处理时候的输出。
例子1:
hdfs oev -i /home/hadoop/apps/hdpdata/dfs/name/current/edits_0000000000000000362-0000000000000000386 -o /home/hadoop/apps/edits.xml
未指定-p选项,默认转换成xml格式,查看edits.xml文件:
<EDITS>
<EDITS_VERSION>-60EDITS_VERSION>
<RECORD>
<OPCODE>OP_START_LOG_SEGMENTOPCODE>
<DATA>
<TXID>362TXID>
DATA>
RECORD>
<RECORD>
<OPCODE>OP_MKDIROPCODE>
<DATA>
<TXID>363TXID>
<LENGTH>0LENGTH>
<INODEID>16473INODEID>
<PATH>/aaaPATH>
<TIMESTAMP>1516160391094TIMESTAMP>
<PERMISSION_STATUS>
<USERNAME>hadoopUSERNAME>
<GROUPNAME>supergroupGROUPNAME>
<MODE>493MODE>
PERMISSION_STATUS>
DATA>
RECORD>
<RECORD>
<OPCODE>OP_END_LOG_SEGMENTOPCODE>
<DATA>
<TXID>386TXID>
DATA>
RECORD>
EDITS>
在输出的xml文件中,每个RECORD记录了一次操作,比如图中的OP_ADD代表添加文件操作,OP_MKDIR代表创建目录操作。里面还记录了文件路径(PATH),修改时间(MTIME)、添加时间(ATIME)、客户端名称(CLIENT_NAME)、客户端地址(CLIENT_MACHINE)、权限(PERMISSION_STATUS)等非常有用的信息。
当edits文件破损进而导致HDFS文件系统出现问题时,可以通过将原有的binary文件转换为xml文件,并手动编辑xml文件然后转回binary文件来实现。
例子2:
hdfs oev -i /home/hadoop/apps/hdpdata/dfs/name/current/edits_0000000000000000362-0000000000000000386 -o /home/hadoop/apps/edits.txt -p stats
指定-p stats选项,用于输出该edits文件中的统计信息:
查看edits.txt,打印出该edits文件中每种操作的数量。
VERSION : -60
OP_ADD ( 0): 4
OP_RENAME_OLD ( 1): 2
OP_DELETE ( 2): null
OP_MKDIR ( 3): 2
OP_SET_REPLICATION ( 4): null
OP_DATANODE_ADD ( 5): null
OP_DATANODE_REMOVE ( 6): null
OP_SET_PERMISSIONS ( 7): 1
OP_SET_OWNER ( 8): null
OP_CLOSE ( 9): 4
OP_SET_GENSTAMP_V1 ( 10): null
OP_SET_NS_QUOTA ( 11): null
OP_CLEAR_NS_QUOTA ( 12): null
OP_TIMES ( 13): 1
OP_SET_QUOTA ( 14): null
OP_RENAME ( 15): null
OP_CONCAT_DELETE ( 16): null
OP_SYMLINK ( 17): null
OP_GET_DELEGATION_TOKEN ( 18): null
OP_RENEW_DELEGATION_TOKEN ( 19): null
OP_CANCEL_DELEGATION_TOKEN ( 20): null
OP_UPDATE_MASTER_KEY ( 21): null
查看fsimage中的信息:
hdfs oiv -i /home/hadoop/apps/hdpdata/dfs/name/current/fsimage_0000000000000000417 -p XML -o /home/hadoop/apps/fsimage.xml
[hadoop@hadoop01 current]$
[hadoop@hadoop01 apps]$
[hadoop@hadoop01 apps]$ vim fsimage.xml
里面的内容:
<fsimage><NameSection>
<genstampV1>1000genstampV1><genstampV2>1044genstampV2><genstampV1Limit>0genstampV1Limit><lastAllocatedBlockId>1073741868la
stAllocatedBlockId><txid>417txid>NameSection>
<INodeSection><lastInodeId>16480lastInodeId><inode><id>16385id><type>DIRECTORYtype><name>name><mtime>1516163790530mtime>
<permission>hadoop:supergroup:rwxr-xr-xpermission><nsquota>9223372036854775807nsquota><dsquota>-1dsquota>inode>
<inode><id>16387id><type>DIRECTORYtype><name>inputname><mtime>1516123196956mtime><permission>hadoop:supergroup:rwxr-xr-x
permission><nsquota>-1nsquota><dsquota>-1dsquota>inode>
<directory><parent>16400parent><inode>16413inode><inode>16412inode><inode>16414inode><inode>16432inode><inode>16431ino
de><inode>16433inode><inode>16451inode><inode>16450inode>
INodeDirectorySection>
<FileUnderConstructionSection>FileUnderConstructionSection>
<SnapshotDiffSection><diff><inodeid>16385inodeid>diff>SnapshotDiffSection>
<SecretManagerSection><currentId>0currentId><tokenSequenceNumber>0tokenSequenceNumber>SecretManagerSection><CacheManagerSect
ion><nextDirectiveId>1nextDirectiveId>CacheManagerSection>
fsimage>
每隔一段时间,会secondary NameNode 将NameNode上积累的所有edits和一个最新的fsimage下载到本地,并加载到内存进行merge,这个过程称作 checkpoint
checkpoint的详细过程如下图:
checkpoint 操作的触发条件配置参数
dfs.namenode.checkpoint.check.period=60 #检查触发条件是否满足的频率,60秒
dfs.namenode.checkpoint.dir=file://${hadoop.tmp.dir}/dfs/namesecondary
#以上两个参数做checkpoint操作时,secondary namenode的本地工作目录
dfs.namenode.checkpoint.edits.dir=${dfs.namenode.checkpoint.dir}
dfs.namenode.checkpoint.max-retries=3 #最大重试次数
dfs.namenode.checkpoint.period=3600 #两次checkpoint之间的时间间隔3600秒
dfs.namenode.checkpoint.txns=1000000 #两次checkpoint之间最大的操作记录
checkpoint附带作用
NameNode和secondary NameNode 的工作目录存储结构完全相同,所有,当NameNode故障退出需要重新恢复时,可以从secondary NameNode的工作目录中将fsimage拷贝到NameNode的工作目录,以恢复NameNode的元数据。
在第一次部署好hadoop集群的时候,我们需要在NameNode节点上格式化磁盘:
hdfs namenode -format
格式化完成之后,将会在$dfs.namenode.name.dir/current目录下产生如下的文件结构:
current/
|-- VERSION
|-- edits_*
|-- fsimage_0000000000008547077
|-- fsimage_0000000000008547077.md5
|-- seen_txid # 文件中记录的是edits滚动的序号,每次重启namenode时,namenode就知道要将哪些edits进行加载edits
其中 dfs.name.dir实在hdfs-site.xml文件中配置的,默认值如下:
<property>
<name>dfs.name.dirname>
<value>file://${hadoop.tmp.dir}/dfs/namevalue>
property>
<property>
<name>hadoop.tmp.dirname>
<value>/tmp/hadoop-${user.name}value>
<description>A base for other temporary directories.description>
property>
dfs.namenode.name.dir属性可以配置多个目录,如/data1/dfs/name, /data2/dfs/name, /data3/dfs/name …. 各个目录存储的文件结构和内容都完全一样,相当于备份,这样做的好处是当其中一个目录损坏了,也不会影响到hadoop的元数据,特别是当其中一个目录是NFS(网络文件系统 Network File System, NFS)之上,及时你这台机器损坏了,元数据也得到保存。
下面对 $dfs.namenode.name.dir/current/目录下的文件进行解释。
一、. VERSION 文件时Java属性文件,内容大致如下:
#Fri Nov 15 19:47:46 CST 2013
namespaceID=934548976
clusterID=CID-cdff7d73-93cd-4783-9399-0a22e6dce196
cTime=0
storageType=NAME_NODE
blockpoolID=BP-893790215-192.168.24.72-1383809616115
layoutVersion=-47
其中:
1. namespaceID是文件系统的唯一标识符,在文件系统首次格式化之后生成的;
2. storageType说明这个文件存储的是什么进程的数据结构信息(如果是DataNode,storageType=DATA_NODE);
3. cTime表示NameNode存储时间的创建时间,由于我的NameNode没有更新过,所以这里的记录值为0,以后对NameNode升级之后,cTime将会记录更新时间戳;
4. layoutVersion表示HDFS永久性数据结构的版本信息,只要数据结构变更,版本号也要递减,此时HDFS也需要升级,否则磁盘仍旧是使用旧版本的数据结构,这会到孩子新版本的NameNode无法使用
5. clusterID是系统生成或手动指定的集群ID,在clusterID选项中可以使用它;如下说明:
a. 使用如下命令格式化一个NameNode:
$HADOOP_HOME/bin/hdfs namenode -format [-clusterId ]
选择一个唯一的cluster_id,并且这个cluster_id不能与环境中其他集群有冲突。如果没有提供cluster_id,则会自动生成一个唯一的ClusterID。
b、使用如下命令格式化其他Namenode:
$HADOOP_HOME/bin/hdfs namenode -format -clusterId
c、升级集群至最新版本。在升级过程中需要提供一个ClusterID,例如:
$HADOOP_PREFIX_HOME/bin/hdfs start namenode --config $HADOOP_CONF_DIR -upgrade -clusterId
如果没有提供ClusterID,则会自动生成一个ClusterID。
二、$dfs.namenode.name.dir/current/seen_txid非常重要,是存放transactionId的文件,format之后是0,它代表的是namenode里面的edits_*文件的尾数,namenode重启的时候,会按照seen_txid的数字,循序从头跑edits_0000001~到seen_txid的数字。所以当你的hdfs发生异常重启的时候,一定要比对seen_txid内的数字是不是你edits最后的尾数,不然会发生建置namenode时metaData的资料有缺少,导致误删Datanode上多余Block的资讯。
三、$dfs.namenode.name.dir/current目录下在format的同时也会生成fsimage和edits文件,及其对应的md5校验文件。
<property>
<name>dfs.blockreport.intervalMsecname>
<value>3600000value>
<description>Determines block reporting interval in milliseconds.description>
property>
datanode进程死亡或者网络故障造成datanode无法与namenode通信,namenode不会立即把该节点判定为死亡,要经过一段时间,这段时间暂称作超时时长。HDFS默认的超时时长为10分钟+30秒。如果定义超时时间为timeout,则超时时长的计算公式为:
timeout = 2 * heartbeat.recheck.interval + 10 * dfs.heartbeat.interval。
而默认的heartbeat.recheck.interval 大小为5分钟,dfs.heartbeat.interval默认为3秒。
需要注意的是hdfs-site.xml 配置文件中的heartbeat.recheck.interval的单位为毫秒,dfs.heartbeat.interval的单位为秒。所以,举个例子,如果heartbeat.recheck.interval设置为5000(毫秒),dfs.heartbeat.interval设置为3(秒,默认),则总的超时时间为40秒。
<property>
<name>heartbeat.recheck.intervalname>
<value>2000value>
property>
<property>
<name>dfs.heartbeat.intervalname>
<value>1value>
property>
上传一个文件,观察文件的block具体的物理存放情况:
在每一台datanode机器上的这个目录中能找到文件的切块:
/home/hadoop/apps/hdpdata/dfs/data/current/BP-60221078-172.16.29.161-1514955036901/current/finalized