分布式文件系统HDFS
HDFS的基础架构
1、NameNode是一个中心服务器,负责管理文件系统的名字空间以及客户端对文件的访问
2、文件操作,namenode是负责文件元数据的数据,datanode负责处理文件的读写请求,跟文件相关的数据流不经过namenode,值询问数据和哪个datanode有联系
3、副本的存放位置由namenode来控制,根据全局情况来决定,读取文件时namenode尽量让用户先读取最近的副本
4、Namenode全权管理数据库的赋值,他周期性的从集群中每个DataNode接收心跳信号和状态报告,心跳信号意味着节点工作正常,块状态报告包含了Datanode上所有的数据列表
文件副本机制和block块存储
所有的文件都是以block块的方式存放在HDFS的文件系统中,在Hadoop1.x中默认的块大小为64M,Hadoop2.x中,默认的块大小为128M,block的块大小可以通过hdfs-site.xml当中的配置dfs.block.size进行指定
HDFS的元数据信息FSimage、edits以及secondaryNameNode的作用
edits:客户端对hdfs进行写文件时会首先被记录在edits中,edits修改时原数据也会更新,在hdfs更新时,edits先更新后客户端才会看到最新的信息。
fsimage:namenode中的元数据的镜像,一般称为检查点
随着edits内容的增多,就需要在一定的时间点和fsimage合并
fsimage的文件信息查看
命令: hdfs oiv
edits的文件信息查看
命令: hdfs oev
secondarynamenode管理FSimage和edits
1、secondarynamenode先通知namenode需要合并fsimage和edits,让namenode暂时将新的操作放入一个新的文件(edits.new)中
2、secondarynamenode从namenode中通过http get 获得edits和fsimage
3、secondarynamenode将fsimage载入内存然后合并edits,合并后生成新的fsimage
4、secondarynamenode通过http post的方式将fsimage发送给namenode
5、namenode从secondarynamenode获得了fsimage之后,替换掉原来的fsimage,同时把edits.new变成edits
合并的条件:
fs.checkpoint.period: 默认是一个小时(3600s)
fs.checkpoint.size: edits达到一定大小时也会触发合并(默认64MB)
HDFS的文件写入过程
1、客户端请求namenode,告诉namenode需要上传的文件
2、namenode接收到客户端的请求,校验客户端是否能上传该文件
3、客户端继续请求namenode第一个block块需要上传到哪个datanode上
4、namenode按照机架感知原理找到让客户端上传的机器返回(机架感知:假如文件的副本有三个,namenode会找找三台机器,第一台机器,里客户端最近的一台机器,第二个,最近的那台机器的相同路由下找一台机器,第三个,不同的路由下找一台机器)
5、客户端与datanode进行通信,建立数据通信管道pipeline,客户端的数据以package的形式发送到第一台机器,然后再发送到其他机器上
6、第一个block块写完后,datanode给客户端一个ack确认机制,客户端重复操作,上传剩下的块
HDFS的文件读取过程
1、客户端发送请求,请求nannode需要读取数据
2、namenode接收客户端请求,校验客户端是否能读取数据,如果可以读取,发送消息告诉客户端可以读取,并查找元数据信息告诉客户端block块的存储位置
3、客户端获取到block的地址后,与datanod通信,通过管道pipeline进行数据传输,直到读取完全不的块,再客户端再拼接成完整的文件
注意:如果客户端在读取block时,中断了,客户端会重新发送请求下载这个块,原先的下载了一部分的内容会删除
HDFS的API操作
由于cdh版本的所有的软件涉及版权的问题,所以并没有将所有的jar包托管到maven仓库当中去,而是托管在了CDH自己的服务器上面,所以我们默认去maven的仓库下载不到,需要自己手动的添加repository去CDH仓库进行下载,以下两个地址是官方文档说明
https://www.cloudera.com/documentation/enterprise/release-notes/topics/cdh_vd_cdh5_maven_repo.html
可能jar包下载 不了,可以修改maven的配置文件
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*,!cloudera</mirrorOf>
<name>Nexus aliyun</name>
<url>
http://maven.aliyun.com/nexus/content/groups/public
</url>
</mirror>
pom.xml
<repositories>
<repository>
<id>cloudera</id>
<url>https://repository.cloudera.com/artifactory/cloudera-repos/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.6.0-mr1-cdh5.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.6.0-cdh5.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.6.0-cdh5.14.0</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-mapreduce-client-core</artifactId>
<version>2.6.0-cdh5.14.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.0</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
<!-- <verbal>true</verbal>-->
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>2.4.3</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<minimizeJar>true</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
<!-- <plugin>
<artifactId>maven-assembly-plugin </artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>cn.itcast.hadoop.db.DBToHdfs2</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>-->
</plugins>
</build>
使用文件系统方式访问数据
获取FileSystem的四种方式
/**
* 获取文件系统(一)
* @throws IOException
*/
@Test
public void getFileSystem() throws IOException {
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS","hdfs://node01:8020");
FileSystem fileSystem = FileSystem.get(configuration);
System.out.println(fileSystem.toString());
}
/**
* h获取文件系统(二)
* @throws URISyntaxException
* @throws IOException
*/
@Test
public void getFileSystem2() throws URISyntaxException, IOException {
Configuration conf = new Configuration();
FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), conf);
System.out.println(fileSystem.toString());
}
/**
* 获取文件系统(三)
* @throws IOException
*/
@Test
public void getFileSystem3() throws IOException {
Configuration conf = new Configuration();
conf.set("fs.defaultFS","hdfs://node01:8020");
FileSystem fileSystem = FileSystem.newInstance(conf);
System.out.println(fileSystem.toString());
}
/**
* 获取文件系统(四)
* @throws URISyntaxException
* @throws IOException
*/
@Test
public void getFileSystem4() throws URISyntaxException, IOException {
Configuration conf = new Configuration();
FileSystem fileSystem = FileSystem.newInstance(new URI("hdfs://node01:8020"), conf);
System.out.println(fileSystem.toString());
}
官方API操作,获取所有文件的路径
/**
* 官方API操作API:获取所有文件的路径
* @throws URISyntaxException
* @throws IOException
*/
@Test
public void listFiles() throws URISyntaxException, IOException {
//先获取文件系统
FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration());
//通过官方API操作:得到所有的文件 第一个参数是路径,第二个参数是是否递归遍历
RemoteIterator<LocatedFileStatus> listFiles = fileSystem.listFiles(new Path("/"), true);
while (listFiles.hasNext()){
LocatedFileStatus next = listFiles.next();
System.out.println(next.getPath().toString());
}
fileSystem.close();
}
下载文件到本地
/**
* 下载文件到本地
*/
@Test
public void downLoadFile() throws URISyntaxException, IOException {
FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration());
//流操作
FSDataInputStream open = fileSystem.open(new Path("/test/install.log"));
FileOutputStream outputStream = new FileOutputStream("d:/install.log");
IOUtils.copy(open,outputStream);
//关闭流
IOUtils.closeQuietly(open);
IOUtils.closeQuietly(outputStream);
//关闭文件系统
fileSystem.close();
}
hdfs上创建文件
/**
* hdfs上创建文件
*/
@Test
public void mkdirs() throws URISyntaxException, IOException {
FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration());
boolean mkdirs = fileSystem.mkdirs(new Path("/test/abc"));
fileSystem.close();
}
hdfs文件上传
/**
* hdfs文件上传
*/
@Test
public void putData() throws URISyntaxException, IOException {
FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration());
fileSystem.copyFromLocalFile(new Path("file:///d:/install.log"),new Path("/test/abc"));
fileSystem.close();
}
HDFS的权限问题以及伪造用户
在hdfs-site.xml配置中有dfs.permissions这个配置,默认是false
更改为true之后,在进行文件操作是需要伪装用户
<property>
<name>dfs.permissions</name>
<value>true</value>
</property>
下载文件
@Test
public void getConfig()throws Exception{
//在获取文件系统时需要伪装用户,获得操作权限
FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration(),"root");
fileSystem.copyToLocalFile(new Path("/config/core-site.xml"),new Path("file:///c:/core-site.xml"));
fileSystem.close();
}
HDFS的小文件合并
Hadoop擅长存储大文件,因为大文件的元数据信息较少,当集群中有较多的小文件时,每个文件都有一份元数据信息,大大增加了集群管理元数据的内存压力,很有必要将小文件合并成大文件来一起处理
可以通过命令将很多的hdfs文件合并成一个大文件下载到本地
hdfs dfs -getmerge /config/*.xml ./hello.xml
通过代码实现在上传时将小文件合并成大文件上传
/**
* 将上传文件合并成大文件上传
*/
@Test
public void mergeFile() throws URISyntaxException, IOException, InterruptedException {
//获取文件系统和创建写入的文件
FileSystem fileSystem = FileSystem.get(new URI("hdfs://node01:8020"), new Configuration(), "root");
FSDataOutputStream fsDataOutputStream = fileSystem.create(new Path("/test.xml"));
//获取本地的文件系统
LocalFileSystem local = FileSystem.getLocal(new Configuration());
//获取文件列表集合
FileStatus[] listStatus = local.listStatus(new Path("D:\\test"));
for (FileStatus status : listStatus) {
//循环写入目标文件
FSDataInputStream open = local.open(status.getPath());
IOUtils.copy(open,fsDataOutputStream);
IOUtils.closeQuietly(open);
}
IOUtils.closeQuietly(fsDataOutputStream);
local.close();
fileSystem.close();
}