3. HDFS(Hadoop Distributed File System) 3.1 Hadoop分布式文件系统
操作格式:bin/hdfs dfs -xxx(HDFS操作) scheme://authority/path
3.2 HSFS常用操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 # 查看目录文件信息 hdfs dfs -ls / hdfs dfs -ls -R / # 递归查看所有目录及其子目录 # 上传文件 hdfs dfs -put README.txt / # 查看文件信息 hdfs dfs -cat /README.txt # 下载文件到本地 hdfs dfs -get /README.txt . # 创建文件夹 hdfs dfs -mkdir /test hdfs dfs -mkdir -p /test/a/b # 创建递归目录 # 删除目录或文件 hdfs dfs -rm /README.txt # 删除文件 hdfs dfs -rm -r /text # 删除目录
HDFS具体案例
需求:统计HDFS中文件的个数和每个文件的大小
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 [root@master hadoop-3.2.0]# hdfs dfs -ls / | wc -l 4 [root@master hadoop-3.2.0]# hdfs dfs -ls / Found 3 items -rw-r--r-- 2 root supergroup 150569 2021-12-01 10:40 /LICENSE.txt -rw-r--r-- 2 root supergroup 22125 2021-12-01 10:40 /NOTICE.txt -rw-r--r-- 2 root supergroup 1361 2021-12-01 10:40 /README.txt # 可以看出HDFS将第一行的Found 3 items也当作一条记录输出了,想要准确得到文件数量需要过滤第一条 [root@master hadoop-3.2.0]# hdfs dfs -ls / | grep / -rw-r--r-- 2 root supergroup 150569 2021-12-01 10:40 /LICENSE.txt -rw-r--r-- 2 root supergroup 22125 2021-12-01 10:40 /NOTICE.txt -rw-r--r-- 2 root supergroup 1361 2021-12-01 10:40 /README.txt [root@master hadoop-3.2.0]# hdfs dfs -ls / | grep / | wc -l 3 [root@master hadoop-3.2.0]# hdfs dfs -ls / | grep / | awk '{print $5,$8}' 150569 /LICENSE.txt 22125 /NOTICE.txt 1361 /README.txt
3.3 JAVA代码操作HDFS 3.3.1 安装MAVEN 在windows系统中解压apache-maven-3.8.4
文件
然后再系统环境变量中配置
将bin目录加入到PATH中
修改settings.xml
文件,D:\Program Files\apache-maven-3.8.4\conf\settings.xml
,把maven仓库的地址修改到其他盘,默认在C盘用户目录下。
将localRepository
标签从注释中移出来,然后将值改为 D:\.m2
,效果如下: 这里的目录名字可以随意起,只要易于识别就可以
这样修改之后,maven管理的依赖jar包都会保存到D:\.m2目录下了。
打开IDE配置maven。
点击设置,配置MAVEN
开启自动加载依赖
在pom.xml中加入Maven hadoop支持,并等其自动加载java依赖。
1 2 3 4 5 6 7 <dependencies > <dependency > <groupId > org.apache.hadoop</groupId > <artifactId > hadoop-client</artifactId > <version > 3.2.0</version > </dependency > </dependencies >
构建文件
构建HdfsOp类,并输入以下代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.imooc.hdfs;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.FSDataOutputStream;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IOUtils;import java.io.FileInputStream;public class HdfsOp { public static void main (String[] args) throws Exception{ Configuration conf = new Configuration (); conf.set("fs.defaultFS" , "hdfs://master:9000" ); FileSystem fileSystem = FileSystem.get(conf); FileInputStream fils = new FileInputStream ("D:\\config.ini" ); FSDataOutputStream fos = fileSystem.create(new Path ("/user.txt" )); IOUtils.copyBytes(fils, fos, 1024 , true ); } }
右键运行java代码,如果出现java: 错误: 不支持发行版本 5
错误,参考https://blog.csdn.net/qq_22076345/article/details/82392236。
出现错误,权限拒绝:
解决办法:
将jar包打包到集群上运行
关闭集群权限校验机制
1 2 3 # 关闭集群 sbin/stop-all.sh # 修改 etc/hadoop下hdfs-site.xml文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [root@master hadoop]# vim hdfs-site.xml <?xml version="1.0" encoding="UTF-8" ?> <?xml-stylesheet type="text/xsl" href="configuration.xsl" ?> <configuration > <property > <name > dfs.replication</name > <value > 2</value > </property > <property > <name > dfs.namenode.secondary.http-address</name > <value > master:50090</value > </property > <property > <name > dfs.permissions.enable</name > <value > false</value > </property > </configuration >
并同步到其他2台机器
1 2 [root@master hadoop]# scp -rq hdfs-site.xml slave1:/data/soft/hadoop-3.2.0/etc/hadoop/ [root@master hadoop]# scp -rq hdfs-site.xml slave2:/data/soft/hadoop-3.2.0/etc/hadoop/
重新运行代码
3.3.2 上传文件,下载文件,删除文件方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 package com.imooc.hdfs;import org.apache.hadoop.conf.Configuration;import org.apache.hadoop.fs.FSDataInputStream;import org.apache.hadoop.fs.FSDataOutputStream;import org.apache.hadoop.fs.FileSystem;import org.apache.hadoop.fs.Path;import org.apache.hadoop.io.IOUtils;import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.IOException;public class HdfsOp { public static void main (String[] args) throws Exception{ Configuration conf = new Configuration (); conf.set("fs.defaultFS" , "hdfs://master:9000" ); FileSystem fileSystem = FileSystem.get(conf); delete(fileSystem); } private static void delete (FileSystem fileSystem) throws IOException { boolean flag = fileSystem.delete(new Path ("/LICENSE.txt" ), true ); if (flag){ System.out.println("删除成功!" ); }else { System.out.println("删除失败!" ); } } private static void get (FileSystem fileSystem) throws IOException { FSDataInputStream fis = fileSystem.open(new Path ("/README.txt" )); FileOutputStream fos = new FileOutputStream ("D:\\README1.txt" ); IOUtils.copyBytes(fis, fos, 1024 , true ); } private static void put (FileSystem fileSystem) throws IOException { FileInputStream fils = new FileInputStream ("D:\\config.ini" ); FSDataOutputStream fos = fileSystem.create(new Path ("/user.txt" )); IOUtils.copyBytes(fils, fos, 1024 , true ); } }
消除警告信息
加入slf4j-api依赖
在pom.xml文件中加入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-api</artifactId > <version > 1.7.10</version > </dependency > <dependency > <groupId > org.slf4j</groupId > <artifactId > slf4j-log4j12</artifactId > <version > 1.7.10</version > </dependency >
然后再resources中创建log4j.properties文件,文件内容为
1 2 3 4 5 6 7 8 log4j.rootLogger = debug,stdout log4j.appender.stdout = org.apache.log4j.ConsoleAppender log4j.appender.stdout.Target = System.out log4j.appender.stdout.layout = org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern = [%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
3.4 HDFS体系结构
3.4.1 NameNode NameNode是整个文件系统的管理节点
它主要维护着整个文件系统的文件目录树,文件/目录的信息每个文件对于的数据块列表,并且还负责接收用户操作请求。
NameNode主要包含以下文件:
这些文件所在的路径是由hdfs-default.xml
的dfs.namenode.name.dir
属性控制的
hdfs-default.xml
文件在hadoop-3.2.0\share\hadoop\hdfs\hadoop-hdfs-3.2.0.jar
中,这个文件中包含了HDFS相关的所有默认参数,咱们在配置集群的时候会修改一个hdfs-site.xml
文件,hdfs-site.xml
文件属于hdfs-default.xml
的一个扩展,它可以覆盖掉hdfs-default.xml
中同名的参数。
那我们来看一下这个文件中的dfs.namenode.name.dir属性
1 2 3 4 5 6 7 8 <property > <name > dfs.namenode.name.dir</name > <value > file://${hadoop.tmp.dir}/dfs/name</value > <description > Determines where on the local filesystem the DFS name node should store the name table(fsimage). If this is a comma-delimited list of directories then the name table is replicated in all of the directories, for redundancy. </description > </property >
这个属性的值是由hadoop.tmp.dir
属性控制的,这个属性的值默认再core-default.xml
文件中。
在修改core-site.xml
的时候设置的有hadoop.tmp.dir
属性的值是/data/hadoop_repo
,所以说core-site.xml
中的hadoop.tmp.dir属性会覆盖掉core-default.xml
中的${hadoop.tmp.dir}
,最终dfs.namenode.name.dir
属性的值就是:/data/hadoop_repo/dfs/name
。
在master节点中的/data/hadoop_repo/dfs/name
目录下发现这个下面会有一个current
目录,表示当前的意思,还有一个in_use.lock
这个只是一个普通文件,但是它其实有特殊含义,其文件名后缀值lock
表示是锁的意思,文件名是in_use
表示这个文件现在正在使用,不允许你再启动namenode
。
当我们启动namonde
的时候 会判断这个目录下是否有in_use.lock
这个相当于一把锁,如果没有的话,才可以启动成功,启动成功之后就会加一把锁, 停止的时候会把这个锁去掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 [root@master hadoop-3.2.0]# cd /data/hadoop_repo/dfs/name [root@master name]# ll 总用量 8 drwxr-xr-x. 2 root root 4096 12月 1 15:19 current -rw-r--r--. 1 root root 11 12月 1 14:18 in_use.lock [root@master name]# cd current/ [root@master current]# ll 总用量 4152 -rw-r--r--. 1 root root 42 11月 30 17:54 edits_0000000000000000001-0000000000000000002 -rw-r--r--. 1 root root 1048576 11月 30 17:54 edits_0000000000000000003-0000000000000000003 -rw-r--r--. 1 root root 42 11月 30 17:59 edits_0000000000000000004-0000000000000000005 -rw-r--r--. 1 root root 1048576 11月 30 17:59 edits_0000000000000000006-0000000000000000006 -rw-r--r--. 1 root root 42 12月 1 10:20 edits_0000000000000000007-0000000000000000008 -rw-r--r--. 1 root root 2547 12月 1 11:20 edits_0000000000000000009-0000000000000000042 -rw-r--r--. 1 root root 42 12月 1 12:20 edits_0000000000000000043-0000000000000000044 -rw-r--r--. 1 root root 42 12月 1 13:20 edits_0000000000000000045-0000000000000000046 -rw-r--r--. 1 root root 1048576 12月 1 13:20 edits_0000000000000000047-0000000000000000047 -rw-r--r--. 1 root root 42 12月 1 14:19 edits_0000000000000000048-0000000000000000049 -rw-r--r--. 1 root root 529 12月 1 15:19 edits_0000000000000000050-0000000000000000058 -rw-r--r--. 1 root root 1048576 12月 1 15:50 edits_inprogress_0000000000000000059 -rw-r--r--. 1 root root 652 12月 1 14:19 fsimage_0000000000000000049 -rw-r--r--. 1 root root 62 12月 1 14:19 fsimage_0000000000000000049.md5 -rw-r--r--. 1 root root 664 12月 1 15:19 fsimage_0000000000000000058 -rw-r--r--. 1 root root 62 12月 1 15:19 fsimage_0000000000000000058.md5 -rw-r--r--. 1 root root 3 12月 1 15:19 seen_txid -rw-r--r--. 1 root root 218 12月 1 10:19 VERSION
里面有edits
文件和fsimage
文件
3.4.1.1 fsimage文件 fsimage
文件有两个文件名相同的,有一个后缀是md5
(md5
是一种加密算法),这个其实主要是为了做md5
校验的,为了保证文件传输的过程中不出问题,相同内容的md5
是一样的,所以后期如果我把这个fsimage
和对应的fsimage.md5
发给你 然后你根据md5
对fsimage
的内容进行加密,获取一个值和fsimage.md5
中的内容进行比较,如果一样,说明你接收到的文件就是完整的。
在这里可以把fsimage
拆开fs
是文件系统filesystem image
是镜像。
说明是文件系统镜像,就是给文件照了一个像,把文件的当前信息记录下来。
我们可以看一下这个文件,这个文件需要使用特殊的命令进行查看。
-i 输入文件 -o 输出文件。
1 [root@master current]# hdfs oiv -p XML -i fsimage_0000000000000000058 -o fsimage56.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?xml version="1.0" ?> <fsimage > <version > <layoutVersion > -65</layoutVersion > <onDiskVersion > 1</onDiskVersion > <oivRevision > e97acb3bd8f3befd27418996fa5d4b50bf2e17bf</oivRevision > </version > <NameSection > <namespaceId > 276211861</namespaceId > <genstampV1 > 1000</genstampV1 > <genstampV2 > 1005</genstampV2 > <genstampV1Limit > 0</genstampV1Limit > <lastAllocatedBlockId > 1073741829</lastAllocatedBlockId > <txid > 58</txid > </NameSection > 2021-12-01 16:27:49,417 INFO offlineImageViewer.FSImageHandler: Loading 5 strings 2021-12-01 16:27:49,455 INFO namenode.FSDirectory: GLOBAL serial map: bits=29 maxEntries=536870911 2021-12-01 16:27:49,455 INFO namenode.FSDirectory: USER serial map: bits=24 maxEntries=16777215 2021-12-01 16:27:49,455 INFO namenode.FSDirectory: GROUP serial map: bits=24 maxEntries=16777215 2021-12-01 16:27:49,455 INFO namenode.FSDirectory: XATTR serial map: bits=24 maxEntries=16777215 <ErasureCodingSection > <erasureCodingPolicy > <policyId > 5</policyId > <policyName > RS-10-4-1024k</policyName > <cellSize > 1048576</cellSize > <policyState > DISABLED</policyState > <ecSchema > <codecName > rs</codecName > <dataUnits > 10</dataUnits > <parityUnits > 4</parityUnits > </ecSchema > </erasureCodingPolicy > <erasureCodingPolicy > <policyId > 2</policyId > <policyName > RS-3-2-1024k</policyName > <cellSize > 1048576</cellSize > <policyState > DISABLED</policyState > <ecSchema > <codecName > rs</codecName > <dataUnits > 3</dataUnits > <parityUnits > 2</parityUnits > </ecSchema > </erasureCodingPolicy > <erasureCodingPolicy > <policyId > 1</policyId > <policyName > RS-6-3-1024k</policyName > <cellSize > 1048576</cellSize > <policyState > ENABLED</policyState > <ecSchema > <codecName > rs</codecName > <dataUnits > 6</dataUnits > <parityUnits > 3</parityUnits > </ecSchema > </erasureCodingPolicy > <erasureCodingPolicy > <policyId > 3</policyId > <policyName > RS-LEGACY-6-3-1024k</policyName > <cellSize > 1048576</cellSize > <policyState > DISABLED</policyState > <ecSchema > <codecName > rs-legacy</codecName > <dataUnits > 6</dataUnits > <parityUnits > 3</parityUnits > </ecSchema > </erasureCodingPolicy > <erasureCodingPolicy > <policyId > 4</policyId > <policyName > XOR-2-1-1024k</policyName > <cellSize > 1048576</cellSize > <policyState > DISABLED</policyState > <ecSchema > <codecName > xor</codecName > <dataUnits > 2</dataUnits > <parityUnits > 1</parityUnits > </ecSchema > </erasureCodingPolicy > </ErasureCodingSection > <INodeSection > <lastInodeId > 16395</lastInodeId > <numInodes > 4</numInodes > <inode > <id > 16385</id > <type > DIRECTORY</type > <name > </name > <mtime > 1638340273212</mtime > <permission > root:supergroup:0755</permission > <nsquota > 9223372036854775807</nsquota > <dsquota > -1</dsquota > </inode > <inode > <id > 16393</id > <type > FILE</type > <name > NOTICE.txt</name > <replication > 2</replication > <mtime > 1638326445633</mtime > <atime > 1638326445602</atime > <preferredBlockSize > 134217728</preferredBlockSize > <permission > root:supergroup:0644</permission > <blocks > <block > <id > 1073741827</id > <genstamp > 1003</genstamp > <numBytes > 22125</numBytes > </block > </blocks > <storagePolicyId > 0</storagePolicyId > </inode > <inode > <id > 16394</id > <type > FILE</type > <name > README.txt</name > <replication > 2</replication > <mtime > 1638326445728</mtime > <atime > 1638339974053</atime > <preferredBlockSize > 134217728</preferredBlockSize > <permission > root:supergroup:0644</permission > <blocks > <block > <id > 1073741828</id > <genstamp > 1004</genstamp > <numBytes > 1361</numBytes > </block > </blocks > <storagePolicyId > 0</storagePolicyId > </inode > <inode > <id > 16395</id > <type > FILE</type > <name > user.txt</name > <replication > 3</replication > <mtime > 1638339650726</mtime > <atime > 1638339650078</atime > <preferredBlockSize > 134217728</preferredBlockSize > <permission > ming_log:supergroup:0644</permission > <blocks > <block > <id > 1073741829</id > <genstamp > 1005</genstamp > <numBytes > 20</numBytes > </block > </blocks > <storagePolicyId > 0</storagePolicyId > </inode > </INodeSection > <INodeReferenceSection > </INodeReferenceSection > <SnapshotSection > <snapshotCounter > 0</snapshotCounter > <numSnapshots > 0</numSnapshots > </SnapshotSection > <INodeDirectorySection > <directory > <parent > 16385</parent > <child > 16393</child > <child > 16394</child > <child > 16395</child > </directory > </INodeDirectorySection > <FileUnderConstructionSection > </FileUnderConstructionSection > <SecretManagerSection > <currentId > 0</currentId > <tokenSequenceNumber > 0</tokenSequenceNumber > <numDelegationKeys > 0</numDelegationKeys > <numTokens > 0</numTokens > </SecretManagerSection > <CacheManagerSection > <nextDirectiveId > 1</nextDirectiveId > <numDirectives > 0</numDirectives > <numPools > 0</numPools > </CacheManagerSection > </fsimage >
里面最外层是一个fsimage
标签,看里面的inode
标签, 这个inode
表示是hdfs
中的每一个目录或者文件信息。
例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <inode > <id > 16394</id > <type > FILE</type > <name > README.txt</name > <replication > 2</replication > <mtime > 1638326445728</mtime > <atime > 1638339974053</atime > <preferredBlockSize > 134217728</preferredBlockSize > <permission > root:supergroup:0644</permission > <blocks > <block > <id > 1073741828</id > <genstamp > 1004</genstamp > <numBytes > 1361</numBytes > </block > </blocks > <storagePolicyId > 0</storagePolicyId > </inode >
这个文件中其实就维护了整个文件系统的文件目录树,文件/目录的元信息和每个文件对应的数据块列 表,所以说fsimage中存放了hdfs最核心的数据。
3.4.1.2 edits文件 当我们上传一个文件的时候,上传一个10G的文件,假设传到9G的时候上传失败了,这个时候就需要重 新传,那hdfs怎么知道这个文件失败了呢?这个是在edits文件中记录的。
当我们上传大文件的时候,一个大文件会分为多个block,那么edits文件中就会记录这些block的上传状 态,只有当全部block都上传成功了以后,这个时候edits中才会记录这个文件上传成功了,那么我们执行 hdfs dfs -ls 的时候就能查到这个文件了,所以当我们在hdfs中执行ls命令的时候,其实会查询fsimage和edits中的内容。
为什么会有这两个文件呢?
首先,我们固化的一些文件内容是存储在fsimage文件中,当前正在上传的文件信息是存储在edits文件 中。
这个时候我们来查看一下这个edits文件的内容,挑一个edits文件内容多一些的文件
1 hdfs oev -i edits_0000000000000000009-0000000000000000042 -o edits.xml
这个edits.xml中可以大致看一下,里面有很多record。每一个record代表不同的操作,
例如 OP_ADD,OP_CLOSE 等等,具体挑一个实例进行分析。
OP_ADD:执行上传操作
OP_ALLOCATE_BLOCK_ID:申请block块id
OP_SET_GENSTAMP_V2:设置GENSTAMP
OP_ADD_BLOCK:添加block块
OP_CLOSE:关闭上传操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 <?xml version="1.0" encoding="UTF-8" standalone="yes" ?> <EDITS > <EDITS_VERSION > -65</EDITS_VERSION > <RECORD > <OPCODE > OP_ADD</OPCODE > <DATA > <TXID > 10</TXID > <LENGTH > 0</LENGTH > <INODEID > 16386</INODEID > <PATH > /README.txt._COPYING_</PATH > <REPLICATION > 2</REPLICATION > <MTIME > 1638325559657</MTIME > <ATIME > 1638325559657</ATIME > <BLOCKSIZE > 134217728</BLOCKSIZE > <CLIENT_NAME > DFSClient_NONMAPREDUCE_-1470139500_1</CLIENT_NAME > <CLIENT_MACHINE > 192.168.128.130</CLIENT_MACHINE > <OVERWRITE > true</OVERWRITE > <PERMISSION_STATUS > <USERNAME > root</USERNAME > <GROUPNAME > supergroup</GROUPNAME > <MODE > 420</MODE > </PERMISSION_STATUS > <ERASURE_CODING_POLICY_ID > 0</ERASURE_CODING_POLICY_ID > <RPC_CLIENTID > b59e1a54-f5a6-46e6-a494-e3ec486335cc</RPC_CLIENTID > <RPC_CALLID > 3</RPC_CALLID > </DATA > </RECORD > <RECORD > <OPCODE > OP_ALLOCATE_BLOCK_ID</OPCODE > <DATA > <TXID > 11</TXID > <BLOCK_ID > 1073741825</BLOCK_ID > </DATA > </RECORD > <RECORD > <OPCODE > OP_SET_GENSTAMP_V2</OPCODE > <DATA > <TXID > 12</TXID > <GENSTAMPV2 > 1001</GENSTAMPV2 > </DATA > </RECORD > <OPCODE > OP_ADD_BLOCK</OPCODE > <DATA > <TXID > 13</TXID > <PATH > /README.txt._COPYING_</PATH > <BLOCK > <BLOCK_ID > 1073741825</BLOCK_ID > <NUM_BYTES > 0</NUM_BYTES > <GENSTAMP > 1001</GENSTAMP > </BLOCK > <RPC_CLIENTID /> <RPC_CALLID > -2</RPC_CALLID > </DATA > </RECORD > <RECORD > <OPCODE > OP_CLOSE</OPCODE > <DATA > <TXID > 14</TXID > <LENGTH > 0</LENGTH > <INODEID > 0</INODEID > <PATH > /README.txt._COPYING_</PATH > <REPLICATION > 2</REPLICATION > <MTIME > 1638325561339</MTIME > <ATIME > 1638325559657</ATIME > <BLOCKSIZE > 134217728</BLOCKSIZE > <CLIENT_NAME /> <CLIENT_MACHINE /> <OVERWRITE > false</OVERWRITE > <BLOCK > <BLOCK_ID > 1073741825</BLOCK_ID > <NUM_BYTES > 1361</NUM_BYTES > <GENSTAMP > 1001</GENSTAMP > </BLOCK > <PERMISSION_STATUS > <USERNAME > root</USERNAME > <GROUPNAME > supergroup</GROUPNAME > <MODE > 420</MODE > </PERMISSION_STATUS > </DATA > </RECORD > </EDITS >
这里面的每一个record
都有一个事务id
,txid
,事务id
是连续的,其实一个put
操作会在edits
文件中产生很多的record
,对应的就是很多步骤,这些步骤对我们是屏蔽的。
注意了,根据我们刚才的分析,我们所有对hdfs
的增删改操作都会在edits
文件中留下信息,那么fsimage
文件中的内容是从哪来的?
其实是这样的,edits
文件会定期合并到fsimage
文件中。
有同学可能有疑问了,edits
文件和fsimage
文件中的内容是不一样的,这怎么能是合并出来的呢?
1 2 3 4 注意,这个其实是框架去做的,在合并的时候会对edits中的内容进行转换,生成新的内容,其实 edits中保存的内容是不是太细了,单单一个上传操作就分为了好几步,其实上传成功之后,我们 只需要保存文件具体存储的block信息就行了把,所以在合并的时候其实是对edits中的内容进行了 精简。
他们具体合并的代码我们不用太过关注,但是我们要知道是那个进程去做的这个事情,其实就是我们之前提到的secondarynamenode
这个进程就是负责定期的把edits中的内容合并到fsimage
中。他只做一件事,这是一个单独的进程,在实际工作中部署的时候,也需要部署到一个单独的节点上面。
3.4.1.3 seen_txid文件 current
目录中还有一个seen_txid
文件,HDFS format
之后是0,它代表的是namenode
里面的edits_*
文 件的尾数,namenode
重启的时候,会按照seen_txid
的数字,顺序从头跑edits_0000001~seen_txid
的 数字。如果根据对应的seen_txid
无法加载到对应的文件,NameNode
进程将不会完成启动以保护数据一 致性。
1 2 [root@master current]# cat seen_txid 77
3.4.1.4 VERSION文件 1 2 3 4 5 6 7 8 [root@master current]# cat VERSION #Wed Dec 01 10:19:09 CST 2021 namespaceID=276211861 clusterID=CID-a2d22775-7406-4788-ba91-13c7aaf13a6b cTime=1638265933793 storageType=NAME_NODE blockpoolID=BP-820095417-192.168.128.130-1638265933793 layoutVersion=-65
这里面显示的集群的一些信息、当重新对hdfs
格式化 之后,这里面的信息会变化。
之前我们说过 在使用hdfs
的时候只格式化一次,不要格式化多次,为什么呢?
一会在讲datanode
的时候会详细解释。
3.4.1.5 总结
fsimage
:元数据镜像文件,存储某一时刻NameNode
内存中的元数据信息,就类似是定时做了一个快照 操作。【这里的元数据信息是指文件目录树、文件/目录的信息、每个文件对应的数据块列表】。
edits
:操作日志文件【事物文件】。这里面会实时记录用户的所有操作。
seen_txid
: 是存放transactionId
的文件,format
之后是0,它代表的是namenode
里面的edits_*
文件的尾数,namenode
重启的时候,会按照seen_txid
的数字,顺序从头跑edits_0000001~到seen_txid
的数字。如果根据对应的seen_txid
无法加载到对应的文件,NameNode
进程将不会完成启动以保护数据一致性。
VERSION
:保存了集群的版本信息
3.4.2 SecondaryNameNode
主要负责定期的把edits
文件中的内容合并到fsimage
中
这个合并操作称为cheakpoint
,在合并的时候会对edits
中的内容进行转换,生成新的内容保存到fsimage
文件中。
注意:在NameNode
的HA架构中没有SecondaryNameNode
进程,文件合并操作会由standby NameNode
负责实现。
3.4.3 DataNode 提供真实文件数据的存储服务。
HDFS
会按照固定的大小,顺序对文件进行划分并编号,划分好的每一个块称一个Block
,HDFS
默认Block
大小是128MB
。
datanode
中数据的具体存储位置是由dfs.datanode.data.dir
来控制的,通过查询hdfs-default.xml
可以知道,具体的位置在这里。
1 2 3 4 5 6 7 8 9 10 11 12 13 <property > <name > dfs.datanode.data.dir</name > <value > file://${hadoop.tmp.dir}/dfs/data</value > <description > Determines where on the local filesystem an DFS data node should store its blocks. If this is a comma-delimited list of directories, then data will be stored in all named directories, typically on different devices. The directories should be tagged with corresponding storage types ([SSD]/[DISK]/[ARCHIVE]/[RAM_DISK]) for HDFS storage policies. The default storage type will be DISK if the directory does not have a storage type tagged explicitly. Directories that do not exist will be created if local filesystem permission allows. </description > </property >
连接到slave1
这个节点上去看一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 [root@slave1 ~]# cd /data/hadoop_repo/dfs/data/ [root@slave1 data]# ll 总用量 4 drwxr-xr-x. 3 root root 71 11月 30 17:53 current -rw-r--r--. 1 root root 11 12月 2 09:40 in_use.lock [root@slave1 data]# cd current/ [root@slave1 current]# ll 总用量 4 drwx------. 4 root root 54 12月 2 09:40 BP-820095417-192.168.128.130-1638265933793 -rw-r--r--. 1 root root 229 12月 2 09:40 VERSION [root@slave1 current]# cd BP-820095417-192.168.128.130-1638265933793/ [root@slave1 BP-820095417-192.168.128.130-1638265933793]# ll 总用量 4 drwxr-xr-x. 4 root root 64 12月 1 17:53 current -rw-r--r--. 1 root root 166 11月 30 17:53 scanner.cursor drwxr-xr-x. 2 root root 6 12月 2 09:40 tmp [root@slave1 BP-820095417-192.168.128.130-1638265933793]# cd current/ [root@slave1 current]# ll 总用量 8 -rw-r--r--. 1 root root 19 12月 1 17:53 dfsUsed drwxr-xr-x. 3 root root 21 12月 1 10:26 finalized drwxr-xr-x. 2 root root 6 12月 1 15:49 rbw -rw-r--r--. 1 root root 145 12月 2 09:40 VERSION [root@slave1 current]# cd finalized/ [root@slave1 finalized]# ll 总用量 0 drwxr-xr-x. 3 root root 21 12月 1 10:26 subdir0 [root@slave1 finalized]# cd subdir0/ [root@slave1 subdir0]# ll 总用量 0 drwxr-xr-x. 2 root root 168 12月 1 15:50 subdir0 [root@slave1 subdir0]# cd subdir0/ [root@slave1 subdir0]# ll 总用量 44 -rw-r--r--. 1 root root 22125 12月 1 10:40 blk_1073741827 -rw-r--r--. 1 root root 183 12月 1 10:40 blk_1073741827_1003.meta -rw-r--r--. 1 root root 1361 12月 1 10:40 blk_1073741828 -rw-r--r--. 1 root root 19 12月 1 10:40 blk_1073741828_1004.meta -rw-r--r--. 1 root root 20 12月 1 14:20 blk_1073741829 -rw-r--r--. 1 root root 11 12月 1 14:20 blk_1073741829_1005.meta
这里面就有很多block
块了
根据前面看到的blockid
信息到这对应的找到文件,可以直接查看,发现文件内容是我们之前上传上去的内容。
1 2 3 [root@slave1 subdir0]# cat blk_1073741829 [pagecfg] index=2
注意:这个block
中的内容可能只是文件的一部分,如果你的文件较大的话,就会分为多个block
存储,默认 hadoop3
中一个block
的大小为128M
。根据字节进行截取,截取到128M
就是一个block
。如果文件大小没有默认的block
块大,那最终就只有一个block
。
HDFS
中,如果一个文件小于一个数据块的大小,那么并不会占用整个数据块的存储空间。
size
是表示我们上传的实际大小,blocksize
是指文件的最大块大小。
注意;这个block
块是hdfs产生的,如果我们直接把文件上传到这个block
文件所在的目录,这个时候hdfs
是不识别的,没有用的。
假设我们上传了两个10M
的文件 又上传了一个200M
的文件 问1:会产生多少个block
块? 4个 问2:在hdfs
中会显示几个文件?3个
下面看一下副本,副本表示数据有多少个备份 我们现在的集群有两个从节点,所以最多可以有2个备份,这个是在hdfs-site.xml
中进行配置的,dfs.replication
默认这个参数的配置是3。表示会有3个副本。 副本只有一个作用就是保证数据安全。
3.4.4 NameNode 总结 NameNode
维护了两份关系:
第一份关系:File与Block list的关系
,对应的关系信息存储在fsimage
和edits
文件中(当NameNode
启动的时候会把文件中的源数据信息加载到内存中)
第二份关系:DataNode与Block的关系
,当DataNode
启动时会把当前节点上的Block
信息和节点信息上报给NameNode
。
注意了,刚才我们说了NameNode
启动的时候会把文件中的元数据信息加载到内存中,然后每一个文件的元数据信息会占用150字节
的内存空间,这个是恒定的,和文件大小没有关系,咱们前面在介绍HDFS
的时候说过,HDFS不适合存储小文件,其实主要原因就在这里,不管是大文件还是小文件,一个文件的元数据信息在NameNode
中都会占用150字节,NameNode
节点的内存是有限的,所以它的存储能力也是有限的,如果我们存储了一堆都是几KB的小文件,最后发现NameNode
的内存占满了,确实存储了很多文件,但是文件的总体大小却很小,这样就失去了HDFS
存在的价值。
最后,在datanode
的数据目录下面的current
目录中也有一个VERSION
文件
这个VERSION
和namenode
的VERSION
文件是有一些相似之处的,我们来具体对比一下两个文件的内 容。
namenode
的VERSION
文件
1 2 3 4 5 6 7 8 [root@master current]# cat VERSION # Thu Dec 02 09:40:18 CST 2021 namespaceID=276211861 clusterID=CID-a2d22775-7406-4788-ba91-13c7aaf13a6b cTime=1638265933793 storageType=NAME_NODE blockpoolID=BP-820095417-192.168.128.130-1638265933793 layoutVersion=-65
datanode
的VERSION
文件
1 2 3 4 5 6 7 8 [root@slave1 current]# cat VERSION # Thu Dec 02 09:40:22 CST 2021 storageID=DS-9ed24dd5-32ab-4d81-93c1-136b7b5c8949 clusterID=CID-a2d22775-7406-4788-ba91-13c7aaf13a6b cTime=0 datanodeUuid=88d2c438-11ce-46ec-9081-f1b8ed515d69 storageType=DATA_NODE layoutVersion=-57
我们前面说了namenode
不要随便格式化,因为格式化了以后VERSION
里面的clusterID
会变,但是datanode
的VERSION
中的clusterID
并没有变,所以就对应不上了。
咱们之前说过如果确实要重新格式化的话需要把/data/hadoop_repo
数据目录下的内容都清空,全部都重新生成是可以的。
3.5 HDFS的回收站 我们windows
系统里面有一个回收站,当想恢复删除的文件的话就可以到这里面进行恢复,HDFS
也有回收站。
HDFS
会为每一个用户创建一个回收站目录:/user/用户名/.Trash/
,每一个被用户在Shell命令行删除的文件/目录,会进入到对应的回收站目录中,在回收站中的数据都有一个生存周期,也就是当回收站中的文件/目录在一段时间之内没有被用户恢复的话,HDFS
就会自动把这个文件/目录彻底删除,之后,用户就永远也找不回这个文件/目录了。
默认情况下hdfs的回收站是没有开启的,需要通过一个配置来开启,在core-site.xml
中添加如下配置,value
的单位是分钟,1440分钟表示是一天的生存周期。
1 2 3 4 <property > <name > fs.trash.interval</name > <value > 1440</value > </property >
在修改配置信息之前先验证一下删除操作,显示的是直接删除掉了。
1 2 [root@master current]# hdfs dfs -rm /NOTICE.txt Deleted /NOTICE.txt
修改回收站配置,现在master
上操作,然后再同步到其他两个节点,先停止集群。
1 [root@master hadoop-3.2.0]# sbin/start-all.sh
修改core-site.xml文件后发送给其他两个节点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 [root@master hadoop-3.2.0]# vim etc/hadoop/core-site.xml Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. See accompanying LICENSE file. --> <!-- Put site-specific property overrides in this file. --> <configuration> <property> <name>fs.defaultFS</name> <value>hdfs://master:9000</value> </property> <property> <name>hadoop.tmp.dir</name> <value>/data/hadoop_repo</value> </property> <property> <name>fs.trash.interval</name> <value>1440</value> </property> </configuration> "etc/hadoop/core-site.xml" 32L, 1061C 已写入 [root@master hadoop-3.2.0]# scp -rq etc/hadoop/core-site.xml s slave1:/data/soft/hadoop-3.2.0/etc/hadoop/ [root@master hadoop-3.2.0]# scp -rq etc/hadoop/core-site.xml slave2:/data/soft/hadoop-3.2.0/etc/hadoop/
再次启动集群。
1 [root@master hadoop-3.2.0]# sbin/staop-all.sh
删除README.txt
文件
1 2 [root@master hadoop-3.2.0]# hdfs dfs -rm /README.txt 2021-12-02 11:00:32,864 INFO fs.TrashPolicyDefault: Moved: 'hdfs://master:9000/README.txt' to trash at: hdfs://master:9000/user/root/.Trash/Current/README.txt
在网页目录中也可以看到多了一个user文件夹。
打开文件夹可以看到
在目录/user/root/.Trash/Current
下有我们刚删除的README.txt
文件。
回收站的文件也是可以下载到本地的。其实在这回收站只是一个具备了特殊含义的HDFS
目录。
1 2 注意:如果删除的文件过大,超过回收站大小的话会提示删除失败 需要指定参数 -skipTrash ,指定这个参数表示删除的文件不会进回收站
1 2 [root@master hadoop-3.2.0]# hdfs dfs -rm -skipTrash /user.txt Deleted /user.txt
不止文件过大可以用,只要加上-skipTrash
参数就不会进入回收站。类似与windows
中的shitf+delete
永久删除。
3.6 HDFS的安全模式 集群刚启动时HDFS会进入安全模式,此时无法执行写操作。(此时耐心等待HDFS自检完退出安全模式即可)
查看安全模式:hdfs dfsadmin -safemode get
离开安全模式:hdfs dfsadmin -safemode leave
1 2 [root@master hadoop-3.2.0]# hdfs dfsadmin -safemode get Safe mode is OFF
3.7 HDFS实战:定时上传数据至HDFS 日志文件格式:access_2020_01_01.log
日志每天产生一个,需要每天凌晨上传到hdfs。
HDFS
中的目录格式为:20200101
第一步:我们需要获取到昨天的日志文件的名称。
第二步:在HDFS
上面使用昨天的日期创建目录
第三步:将昨天的日志文件上传到刚创建的HDFS
目录中
第四步:要考虑脚本重跑,补数据的情况
第五步:配置crontab
任务
3.7.1 编写shell脚本 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # 获取昨天日期字符串,为了方便以后可以上传其他日期的数据,在这可加一个判断,如果给了日期就上传指定日期的数据,如果没给就同步昨天的数据 yesterday=$1 if [ "$yesterday" = "" ] then yesterday=`date +%Y_%m_%d --date="1 days ago"` fi # 拼接日志文件 logpath=/data/log/access_${yesterday}.log # 将日期字符串中的_去掉,并且拼接成hdfs的路径 hdfsPath=/log/${yesterday//_/} # 在hdfs上面创建目录 hdfs dfs -mkdir -p ${hdfsPath} # 将数据上传到hdfs的指定目录中 hdfs dfs -put ${logpath} ${hdfsPath}
3.7.2 配置crontab任务 1 0 1 * * * root sh /data/shell/uploadLogData.sh >> /data/shell/uploadLogData.log
每天凌晨1点进行同步,并将同步日期重定向到/data/shell/uploadLogData.log
目录
3.8 HDFS的高可用和高扩展
NameNode
节点宕机了怎么办?
我们前面分析了NameNode
负责接收用户的操作请求,所有的读写请求都会经过它,如果它挂了怎么办?
这个时候集群是不是就无法正常提供服务了?是的,那现在我们这个集群就太不稳定了,因为NameNode
只有一个,是存在单点故障的,咱们在现实生活中,例如,县长,是有正的和副的,这样就是为了解决当正县长遇到出差的时候,副县长可以顶上去。
所以在HDFS
的设计中,NameNode
也是可以支持多个的,一个主的多个备用的,当主的挂掉了,备用的可以顶上去,这样就可以解决NameNode
节点宕机导致的单点故障问题了,也就实现了HDFS
的高可用
还有一个问题是,前面我们说了NameNode
节点的内存是有限的,只能存储有限的文件个数,那使用一个主NameNode
,多个备用的NameNode
能解决这个问题吗?
不能!
一个主NameNode
,多个备用的NameNode
的方案只能解决NameNode的单点故障问题,无法解决单个NameNode
内存不够用的问题,那怎么办呢?不用担心,官方提供了Federation
机制,可以翻译为联邦,它可以解决单节点内存不够用的情况,具体实现思路我们稍后分析,这个就是HDFS的高扩展
NameNode
节点内存不够用怎么办?
3.8.1 高可用(HA
) HDFS
的HA
,指的是在一个集群中存在多个NameNode
,分别运行在独立的物理节点上。在任何时间点,只有一个NameNode
是处于Active状态,其它的是处于Standby
状态。 Active NameNode
(简写为Active NN
)负责所有的客户端的操作,而Standby NameNode
(简写为Standby NN
)用来同步ActiveNameNode
的状态信息,以提供快速的故障恢复能力。
为了保证Active NN
与Standby NN
节点状态同步,即元数据保持一致。除了DataNode
需要向这些NameNode
发送block
位置信息外,还构建了一组独立的守护进程”JournalNodes
”(简写为JN
),用来同步Edits
信息。当Active NN
执行任何有关命名空间的修改,它需要持久化到一半以上的JNs
上。而Standby NN
负责观察JNs
的变化,读取从Active NN
发送过来的Edits
信息,并更新自己内部的命名空间。一旦Active NN
遇到错误,Standby NN
需要保证从JNs
中读出了全部的Edits
,然后切换成Active
状态,如果有多个Standby NN
,还会涉及到选主的操作,选择一个切换为Active
状态。
需要注意一点,为了保证Active NN
与Standby NN
节点状态同步,即元数据保持一致。
这里的元数据包含两块,一个是静态的,一个是动态的
静态的是fsimage
和edits
,其实fsimage
是由edits
文件合并生成的,所以只需要保证edits
文件内容的一致性。这个就是需要保证多个NameNode
中edits
文件内容的事务性同步。这块的工作是由JournalNodes
集群进行同步的。
动态数据是指block
和DataNode
节点的信息,这个如何保证呢? 当DataNode
启动的时候,上报数据信息的时候需要向每个NameNode
都上报一份。这样就可以保证多个NameNode
的元数据信息都一样了,当一个NameNode down
掉以后,立刻从Standby NN
中选择一个进行接管,没有影响,因为每个NameNode
的元数据时刻都是同步的。
注意:使用HA的时候,不能启动SecondaryNameNode
,会出错。之前是SecondaryNameNode
负责合并edits
到fsimage
文件 那么现在这个工作被standby NN
负责了。
NameNode
切换可以自动切换,也可以手工切换,如果想要实现自动切换,需要使用到zookeeper
集群。
使用zookeeper
集群自动切换的原理是这样的
当多个NameNode
启动的时候会向zookeeper
中注册一个临时节点,当NameNode
挂掉的时候,这个临时节点也就消失了,这属于zookeeper
的特性,这个时候,zookeeper
就会有一个watcher
监视器监视到,就知道这个节点down
掉了,然后会选择一个节点转为Active
,把down
掉的节点转为Standby
。 注意:下面的配置步骤建议大家有空闲时间了再来操作就行,作为一个课外扩展,因为在工作中这个配置是不需要我们做的,我们只需要知道这种特性即可。
总结:
HDFS
的HA
,表示一个集群中存在多个NameNode
,只有一个NameNode
是Active
状态,其它的是Standby
状态
ActiveNameNode(ANN)
负责左右客户端的操作,StandbyNameNode(SNN)
用来同步ANN的状态信息,以提供快速故障恢复能力。
使用HA
的时候,不能启动SecondaryNameNode
,会出错。
3.8.2 高扩展(Federation
) Federation可解决单一命名空间的一些问题,提供以下特征:
如果真用到了Federation,一般也会和前面我们讲的HA结合起来使用,来看这个图
这里面用到了4个NameNode
和6个DataNode
NN-1
、NN-2
、NN-3
、NN-4
DN-1
、DN-2
、DN-3
、DN-4
、DN-5
、DN-6
其中NN-1
、和NN-3
配置了HA
,提供了一个命令空间,/share
,其实可理解为一个顶级目录
NN-2
和NN-4
配置了HA
,提供了一个命名空间,/user
这样后期我们存储数据的时候,就可以根据数据的业务类型来区分是存储到share
目录下还是user
目录下,此时HDFS
的存储能力就是/share
和/user
两个命名空间的总和了。
注意:由于Federation+HA
需要的机器比较多,大家本地的机器开不了那么多虚拟机,所以暂时在这就不再提供对应的安装步骤了,大家主要能理解它的原理就可以了,在工作中也不需要我们去配置。