HDFS介绍
我们前面已经知道了HDFS是一个分布式的文件系统,具体这个分布式文件系统是如何实现的呢?
HDFS的全称是Hadoop Distributed File System ,Hadoop的 分布式 文件 系统
它是一种允许文件通过网络在多台主机上分享的文件系统,可以让多台机器上的多个用户分享文件和存储空间
其实分布式文件管理系统有很多,HDFS只是其中一种实现而已
还有 GFS(谷歌的)、TFS(淘宝的)、S3(亚马逊的)
为什么会有多种分布式文件系统呢?这样不是重复造轮子吗?
不是的,因为不同的分布式文件系统的特点是不一样的,HDFS是一种适合大文件存储的分布式文件系统,不适合小文件存储,什么叫小文件,例如,几KB,几M的文件都可以认为是小文件
HDFS的Shell介绍
针对HDFS,我们可以在shell命令行下进行操作,就类似于我们操作linux中的文件系统一样,但是具体命令的操作格式是有一些区别的
格式如下:
使用hadoop bin目录的hdfs命令,后面指定dfs,表示是操作分布式文件系统的,这些属于固定格式。
HDFS的schema是hdfs,authority是集群中namenode所在节点的ip和对应的端口号,把ip换成主机名也是一样的,path是我们要操作的文件路径信息
其实后面这一长串内容就是core-site.xml配置文件中fs.defaultFS属性的值,这个代表的是HDFS的地址。
bin/hdfs dfs -ls / # 看hdfs根目录下的内容,功能类似于 -ls hdfs://bigdata01:9000/
bin/hdfs dfs -put README.txt / # 从本地上传文件
bin/hdfs dfs -cat /README.txt # 查看HDFS文件内容
bin/hdfs dfs -get /README.txt README.txt.bak # 下载文件到本地,并重命名
bin/hdfs dfs -rm -r /README.txt # 删除文件/文件夹
bin/hdfs dfs -mkdir -p /abc/test # 创建文件夹
其实后面hdfs的url这一串内容在使用时默认是可以省略的,因为hdfs在执行的时候会根据HADOOP_HOME自动识别配置文件中的fs.defaultFS属性
Java代码操作HDFS
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>3.2.0</version>
</dependency>
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.CommonConfigurationKeysPublic;
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.InputStream;
/**
* 操作hdfs
*/
public class TestHdfsClient {
public static void main(String[] args) throws Exception {
testPut();
}
private static FileSystem fileSystem() throws Exception {
Configuration configuration = new Configuration();
//设置hdfs地址
configuration.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, "hdfs://ip:9000");
return FileSystem.get(configuration);
}
private static void testPut() throws Exception {
//文件上传
FileSystem fileSystem = fileSystem();
FSDataOutputStream output = fileSystem.create(new Path("/abc.txt"));
InputStream input = new FileInputStream("C:\\D-myfiles\\testjar\\hadoop\\abc.txt");
IOUtils.copyBytes(input, output, 1024, true);
}
}
测试将本地文件上传到远程hdfs
遇到的问题
第一个问题
Exception in thread "main" org.apache.hadoop.security.AccessControlException: Permission denied: user=xxx, access=WRITE, inode="/":root:supergroup:drwxr-xr-x
at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.check(FSPermissionChecker.java:399)
at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.checkPermission(FSPermissionChecker.java:255)
at org.apache.hadoop.hdfs.server.namenode.FSPermissionChecker.checkPermission(FSPermissionChecker.java:193)
at org.apache.hadoop.hdfs.server.namenode.FSDirectory.checkPermission(FSDirectory.java:1869)
at org.apache.hadoop.hdfs.server.namenode.FSDirectory.checkPermission(FSDirectory.java:1853)
at org.apache.hadoop.hdfs.server.namenode.FSDirectory.checkAncestorAccess(FSDirectory.java:1812)
at org.apache.hadoop.hdfs.server.namenode.FSDirWriteFileOp.resolvePathForStartFile(FSDirWriteFileOp.java:323)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.startFileInt(FSNamesystem.java:2472)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.startFile(FSNamesystem.java:2416)
at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.create(NameNodeRpcServer.java:775)
解决办法有3种
- 第一种:去掉hdfs的用户权限检验机制,通过在hdfs-site.xml中配置dfs.permissions.enabled为false即可
- 第二种:把代码打包到linux中执行
- 第三种:将windows用户添加到supergroup中
adduser xxx
groupadd supergroup
usermod -a -G supergroup xxx
在这里为了在本地测试方便,我们使用第一种方式。
1:停止Hadoop集群
2:修改hdfs-site.xml配置文件
3:启动Hadoop集群
<property>
<name>dfs.permissions.enabled</name>
<value>false</value>
</property>
第二个问题
Exception in thread "main" org.apache.hadoop.ipc.RemoteException(java.io.IOException): File /abc.txt could only be written to 0 of the 1 minReplication nodes. There are 1 datanode(s) running and 1 node(s) are excluded in this operation.
at org.apache.hadoop.hdfs.server.blockmanagement.BlockManager.chooseTarget4NewBlock(BlockManager.java:2135)
at org.apache.hadoop.hdfs.server.namenode.FSDirWriteFileOp.chooseTargetForNewBlock(FSDirWriteFileOp.java:294)
at org.apache.hadoop.hdfs.server.namenode.FSNamesystem.getAdditionalBlock(FSNamesystem.java:2771)
at org.apache.hadoop.hdfs.server.namenode.NameNodeRpcServer.addBlock(NameNodeRpcServer.java:876)
at org.apache.hadoop.hdfs.protocolPB.ClientNamenodeProtocolServerSideTranslatorPB.addBlock(ClientNamenodeProtocolServerSideTranslatorPB.java:567)
at org.apache.hadoop.hdfs.protocol.proto.ClientNamenodeProtocolProtos$ClientNamenodeProtocol$2.callBlockingMethod(ClientNamenodeProtocolProtos.java)
at org.apache.hadoop.ipc.ProtobufRpcEngine$Server$ProtoBufRpcInvoker.call(ProtobufRpcEngine.java:524)
at org.apache.hadoop.ipc.RPC$Server.call(RPC.java:1025)
at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:876)
at org.apache.hadoop.ipc.Server$RpcCall.run(Server.java:822)
at java.security.AccessController.doPrivileged(Native Method)
at javax.security.auth.Subject.doAs(Subject.java:422)
at org.apache.hadoop.security.UserGroupInformation.doAs(UserGroupInformation.java:1730)
at org.apache.hadoop.ipc.Server$Handler.run(Server.java:2682)
折腾了大半天,终于解决了,具体原因是:
NameNode节点存放的是文件目录,也就是文件夹、文件名称,本地可以通过公网访问 NameNode,所以可以进行文件夹的创建,当上传文件需要写入数据到DataNode时,NameNode 和 DataNode 是通过局域网进行通信,NameNode返回地址为 DataNode 的私有 IP,本地无法访问。
解决方案:返回的IP地址无法返回公网IP,只能返回主机名,通过主机名与公网地址的映射便可以访问到DataNode节点,问题将解决。
private static FileSystem fileSystem() throws Exception {
Configuration configuration = new Configuration();
//设置hdfs地址
configuration.set(CommonConfigurationKeysPublic.FS_DEFAULT_NAME_KEY, "hdfs://bigdata01:9000");
configuration.set("dfs.client.use.datanode.hostname", "true");
return FileSystem.get(configuration);
}
本地还需要配置一下DNS域名映射,在C:\Windows\System32\drivers\etc的hosts文件中配置,
ip bigdata01
注意:这边建议把原文件复制一份到外面操作,完成之后再粘贴回来。
HDFS体系结构
HDFS支持主从结构,主节点称为 NameNode ,是因为主节点上运行的有NameNode进程,NameNode支持多个,目前我们的集群中只配置了一个
从节点称为 DataNode ,是因为从节点上面运行的有DataNode进程,DataNode支持多个。HDFS中还包含一个 SecondaryNameNode 进程,这个进程从字面意思上看像是第二个NameNode的意思,其实不是。
在这大家可以这样理解:
- 公司BOSS:NameNode
- 秘书:SecondaryNameNode
- 员工:DataNode
NameNode介绍
首先是NameNode,NameNode是整个文件系统的管理节点
它主要维护着整个文件系统的文件目录树,文件/目录的信息 和 每个文件对应的数据块列表,并且还负责接收用户的操作请求
- 目录树:表示目录之间的层级关系,就是我们在hdfs上执行ls命令可以看到的那个目录结构信息。
- 文件/目录的信息:表示文件/目录的的一些基本信息,所有者 属组 修改时间 文件大小等信息
- 每个文件对应的数据块列表:如果一个文件太大,那么在集群中存储的时候会对文件进行切割,这个时候就类似于会给文件分成一块一块的,存储到不同机器上面。所以HDFS还要记录一下一个文件到底被分了多少块,每一块都在什么地方存储着
我们现在可以到集群的9870界面查看一下,随便找一个文件看一下,点击文件名称,可以看到Block information 但是文件太小,只有一个块 叫Block 0
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中同名的参数。
<property>
<name>dfs.namenode.name.dir</name>
<value>file://${hadoop.tmp.dir}/dfs/name</value>
</property>
这个属性的值是由hadoop.tmp.dir属性控制的,这个属性的值默认在core-default.xml文件中。大家还有没有印象,我们在修改core-site.xml的时候设置的有hadoop.tmp.dir属性的值,值是/root/test_hadoop/tmp
,所以说core-site.xml中的hadoop.tmp.dir属性会覆盖掉core-default.xml中的值,最终dfs.namenode.name.dir属性的值就是:root/test_hadoop/tmp/dfs/name
- fsimage: 元数据镜像文件,存储某一时刻NameNode内存中的元数据信息,就类似是定时做了一个快照操作。【这里的元数据信息是指文件目录树、文件/目录的信息、每个文件对应的数据块列表】
- edits: 操作日志文件【事务文件】,这里面会实时记录用户的所有操作
- seentxid: 是存放transactionId的文件,format之后是0,它代表的是namenode里面的edits*文件的尾数,namenode重启的时候,会按照seen_txid的数字,顺序从头跑edits_0000001~到seen_txid的数字。如果根据对应的seen_txid无法加载到对应的文件,NameNode进程将不会完成启动以保护数据一致性。
- VERSION:保存了集群的版本信息
SecondaryNameNode介绍
SecondaryNameNode主要负责定期的把edits文件中的内容合并到fsimage中
这个合并操作称为checkpoint,在合并的时候会对edits中的内容进行转换,生成新的内容保存到fsimage文件中。
注意:在NameNode的HA架构中没有SecondaryNameNode进程,文件合并操作会由standby NameNode负责实现
所以在Hadoop集群中,SecondaryNameNode进程并不是必须的。
DataNode介绍
DataNode是提供真实文件数据的存储服务
针对datanode主要掌握两个概念,一个是block,一个是replication
首先是block
HDFS会按照固定的大小,顺序对文件进行划分并编号,划分好的每一个块称一个Block,HDFS默认Block大小是 128MB
Blokc块是HDFS读写数据的基本单位,不管你的文件是文本文件 还是视频 或者音频文件,针对hdfs而言 都是字节。
我们之前上传的一个user.txt文件,他的block信息可以在fsimage文件中看到,也可以在hdfs web界面上面看到, 里面有block的id信息,并且也会显示这个数据在哪个节点上面
datanode中数据的具体存储位置是由dfs.datanode.data.dir来控制的
<property>
<name>dfs.datanode.data.dir</name>
<value>file://${hadoop.tmp.dir}/dfs/data</value>
</property>
下面看一下副本
副本表示数据有多少个备份
我们现在的集群有两个从节点,所以最多可以有2个备份,这个是在hdfs-site.xml中进行配置的,dfs.replication
默认这个参数的配置是3。表示会有3个副本。
副本只有一个作用就是保证数据安全。
NameNode总结
注意:block块存放在哪些datanode上,只有datanode自己知道,当集群启动的时候,datanode会扫描自己节点上面的所有block块信息,然后把节点和这个节点上的所有block块信息告诉给namenode。这个关系是每次重启集群都会动态加载的【这个其实就是集群为什么数据越多,启动越慢的原因】
namenode维护了两份关系:
- 第一份关系:file 与block list的关系,对应的关系信息存储在fsimage和edits文件中,当NameNode启动的时候会把文件中的元数据信息加载到内存中
- 第二份关系:datanode与block的关系,对应的关系主要在集群启动的时候保存在内存中,当DataNode启动时会把当前节点上的Block信息和节点信息上报给NameNode
注意了,刚才我们说了NameNode启动的时候会把文件中的元数据信息加载到内存中,然后每一个文件的元数据信息会占用150字节的内存空间,这个是恒定的,和文件大小没有关系,咱们前面在介绍HDFS的时候说过,HDFS不适合存储小文件,其实主要原因就在这里,不管是大文件还是小文件,一个文件的元数据信息在NameNode中都会占用150字节,NameNode节点的内存是有限的,所以它的存储能力也是有限的,如果我们存储了一堆都是几KB的小文件,最后发现NameNode的内存占满了,确实存储了很多文件,但是文件的总体大小却很小,这样就失去了HDFS存在的价值
HDFS的回收站
HDFS会为每一个用户创建一个回收站目录:/user/用户名/.Trash/,每一个被用户在Shell命令行删除的文件/目录,会进入到对应的回收站目录中,在回收站中的数据都有一个生存周期,也就是当回收站中的文件/目录在一段时间之内没有被用户恢复的话,HDFS就会自动的把这个文件/目录彻底删除,之后,用户就永远也找不回这个文件/目录了。
默认情况下hdfs的回收站是没有开启的,需要通过一个配置来开启,在core-site.xml中添加如下配置,value的单位是分钟,1440分钟表示是一天的生存周期
<property>
<name>fs.trash.interval</name>
<value>1440</value>
</property>
回收站的文件也是可以下载到本地的。其实在这回收站只是一个具备了特殊含义的HDFS目录。
注意:如果删除的文件过大,超过回收站大小的话会提示删除失败 需要指定参数 -skipTrash ,指定这个参数表示删除的文件不会进回收站
HDFS的安全模式
大家在平时操作HDFS的时候,有时候可能会遇到这个问题,特别是刚启动集群的时候去上传或者删除文件,会发现报错,提示NameNode处于safe mode。
这个属于HDFS的安全模式,因为在集群每次重新启动的时候,HDFS都会检查集群中文件信息是否完整,例如副本是否缺少之类的信息,所以这个时间段内是不允许对集群有修改操作的,如果遇到了这个情况,可以稍微等一会,等HDFS自检完毕,就会自动退出安全模式。
或者通过hdfs命令也可以查看当前的状态
bin/hdfs dfsadmin -safemode get
Safe mode is OFF
HDFS写数据过程
总结
使用google搜索也可能搜不到想要的结果,这种时候可以换一个搜索关键词,将使用场景带上去,如hadoop文件上传,hdfs文件上传等。
参考
解决File ~ could only be written to 0 of the 1 minReplication nodes.HDFS服务器使用命令可以上传文件,但客户端上传失败问题File ~ could only be written to 0 of the 1 minReplication nodes.