HDFS基础
一 启动HDFS
一键开启:
start-dfs.sh
一键停止:
stop-dfs.sh>
二 HDFS基本原理
1.HDFS上传文件流程
- 客户端向NameNode进行请求
- NameNode校验存储空间,空间充足,向客户端返回ok
- 客户端将文件进行切块处理,将切块数量等信息传递给namenode
- NameNode确认所有数据块的存储位置,并将位置信息返回给客户端
- 客户端依据获得的存储位置信息,在文件系统中建立连接,将客户端与目标DataNode相连
- 连接成功向客服端返回ok,不成功则由客户端向NameNode反馈,重新获取存储位置
- 建立连接后,开始进行上传,将文件块进一步拆分成便于传输的文件包进行传输
- 每个块的传输顺序为:客户端—>第一个DataNode—>第二个DataNode—>第三个DataNode(默认3个)
- 传输完毕后,若出现某个数据块全部存储失败则重新上传;在每个块都有至少一个存储成功的情况下,则向客户端反馈上传成功。
2.连接校验
- NameNode的族群ID和数据块池ID
- DataNode的族群ID和datanodeuuID
- DataNode的数据块池ID
- HDFS通过族群ID和数据块池ID确定身份
- 通过datanodeUuid区分不同的DataNode
3.下载文件流程
- 客户端发起下载请求;
- NN将元数据 信息发送至客户端;
- 客户端在每个块的备份中选择一个网络通信良好的DN进行下载;
- 各数据块依次传输并完成拼接。
4.数据存储位置特征
- 一个数据块默认大小是128M;
- 每个数据块会默认至少备份3份在DN中;
- 每个数据块的存储位置会记录在DD的元数据中;
- HDFS中应避免存储大量小文件,因为元数据存储在内存中,大小受到限制,每存储一个文件,元数据都要至少使用150K数据来记录文件信息,小文件过多会降低HDFS的使用效率;
- HDFS中的数据可以多次读取,但是不支持随机修改;
- HDFS读取文件要分别各DN和NN进行多次通信,因此效率不高。
5.NameNode和DataNode的通信机制
- 心跳反馈机制:DN每3秒钟向NN发送一次心跳
- DN会向NN汇报存储状况、数据块状态等信息,若NN发现备份数量缺少,会向DN发送复制任务,保证备份数量
- 若NN和DN的网络通信出现错误,或DN的宕机,NN连续30s没有收到DN的心跳,五分钟后NN会向DN发送通信请求(Ping),若不同,则再过五分钟重新发送,若第二次仍无法通信成功,判定该DN死亡;
- NN将无效的DN剔除时间:(5+5+0.5=10.5m)
6.NameNode职责
- 接收客户端的上传、下载请求,返回元数据给客户端
- 生成集群ID、块池ID,接收DN的注册请求,生成DNUuID
- 存储、维护元数据
- 接收DN的心跳,保证其正常工作
- 统计分析数据块数量,不足时向DN发送复制任务
- 管理DN的存储空间、存储内容
7.DataNode职责
- 接收NN的任务,进行复制等操作
- 存储数据块
- 接收并处理客户端的读写任务
- 向NN注册,发送心跳,汇报存储空间的等信息
8.元数据工作机制
- NN中的原数据实际上是NN内存中的一个Java对象:FsImage
- 用户的每个操作 都会记录到工作日志中,并存储到NN中
- FsImage的数据结构是树形结构
8.1 chockpoint机制
- NN会初始序列化一个FsImage文件
- 引入secondrayName,一个非NN的其他设备
- secondrayName会下载NN序列化的FsImage文件和日志文件
- 将FsImage反序列化为FsImage对象到内存中,结合日志文件,复制用户近一小时的操作
- 得到一个新的FsImage对象,将其序列化为新的FsImage文件,并将其传输给NN
- NN中最多保留两个FsImage文件,如果有新的传进来,会自动删除最旧的FsImage文件
- 以上操作每一小时自动执行一遍(日志文件每小时生成一个)
8.2 备用Namenode
- secondrayName只执行备份,不会执行NN的其他操作
- 为防止NN宕机,引入备用NameNode
- 备用NameNode尽量与NN同步(通过NN传递过来的日志文件进行同步)
- 引入zookeeper系统,监测NN是否正常工作
- 若检测到主NN故障,立刻切换到从NN进行工作
三 在java端操作HDFS
1.常用方法
- 通过java获取HDFS客户端对象
**以下是常用的方法 : **
public class HdfsClient {
public static void main(String[] args) throws Exception {
FileSystem fs = GetHDML.getFS();
FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
for (FileStatus fileStatus : fileStatuses) {
Path path = fileStatus.getPath();
String name = path.getName();
System.out.println(path+"---"+name);
}
fs.close();
}
private static void listFiles(FileSystem fs) throws IOException {
// fs.listFiles()
RemoteIterator<LocatedFileStatus> files = fs.listFiles(new Path("/"), false);
while (files.hasNext()){
LocatedFileStatus status = files.next();
//获取文件大小
long len = status.getLen();
//获取切块大小
long blockSize = status.getBlockSize();
//字符串
String s = status.toString();
//获取切块
BlockLocation[] blockLocations = status.getBlockLocations();
for (BlockLocation blockLocation : blockLocations) {
//
long length = blockLocation.getLength();
String[] hosts = blockLocation.getHosts();
for (String host : hosts) {
System.out.println(len+"---"+blockSize+"---"+s+"---"+length+"---"+host);
}
}
}
}
private static void vi(FileSystem fs) throws IOException {
//写入数据
//覆盖写入
Scanner sc = new Scanner(System.in);
System.out.println("输入要写入的位置:");
String s = sc.nextLine();
Path path = new Path(s);
FSDataOutputStream out = fs.create(path);
System.out.println("输入内容:");
s = sc.nextLine();
out.write(s.getBytes());
}
private static void open(FileSystem fs) throws IOException {
//读取文件
Scanner sc = new Scanner(System.in);
System.out.println("输入要读取的目录:");
String s = sc.nextLine();
Path path1 = new Path(s);
FSDataInputStream open = fs.open(path1);
//转换流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(open));
while ((s=bufferedReader.readLine())!=null)
{
System.out.println(s);
}
}
private static void mkdir(FileSystem fs) throws IOException {
//新建文件夹
Scanner sc = new Scanner(System.in);
System.out.println("输入要创建的目录:");
String s = sc.nextLine();
Path path1 = new Path(s);
boolean b = fs.mkdirs(path1);
}
private static void remove(FileSystem fs) throws IOException {
//删除文件
Scanner sc = new Scanner(System.in);
System.out.println("输入要删除的目录:");
String s = sc.nextLine();
Path path1 = new Path(s);
fs.delete(path1,true);
}
/**
* 移动和重命名
* @param fs
* @throws IOException
*/
private static void move(FileSystem fs) throws IOException {
//移动和重命名
Scanner sc = new Scanner(System.in);
System.out.println("输入源目录、目标目录:");
String s = sc.nextLine();
String[] split = s.split("\\s+");
Path path1 = new Path(split[0]);
Path path2 = new Path(split[1]);
fs.rename(path1,path2);
}
/**
* 下载文件
* @param fs
* @throws IOException
*/
private static void get(FileSystem fs) throws IOException {
Scanner sc = new Scanner(System.in);
System.out.println("输入源路径、目标路径(及是否删除源文件):");
String s = sc.nextLine();
String[] split = s.split("\\s+");
Path path1 = new Path(split[0]);
Path path2 = new Path(split[1]);
boolean b = false;
if (split.length==3)
{
b=Boolean.parseBoolean(split[2]);
}
//下载文件
fs.copyToLocalFile(b,path1,path2);
}
/**
* 上传文件
* @param fs
* @throws IOException
*/
public static void put(FileSystem fs) throws IOException {
Scanner sc = new Scanner(System.in);
System.out.println("输入源路径、目标路径(及是否删除源文件、是否覆盖已有文件):");
String s = sc.nextLine();
String[] split = s.split("\\s+");
Path path1 = new Path(split[0]);
Path path2 = new Path(split[1]);
//是否删除源文件
boolean b1=false;
//是否覆盖已有文件
boolean b2=true;
if (split.length>2)
{
b1=Boolean.parseBoolean(split[2]);
if (split.length>3)
{
b2=Boolean.parseBoolean(split[3]);
}
}
fs.copyFromLocalFile(b1,b2,path1,path2);
}
}
2.修改配置属性
- 方法一:
- 通过配置对象进行设置
public class ConfDEeail {
public static void main(String[] args) {
//创建配置对象
Configuration conf = new Configuration();
//设置副本数量
conf.set("dfs.replication","4");
//设置数据包大小
conf.set("dfs.blocksize","64M");
// FileSystem.newInstance(new URI("hdfs://linux01:8020"),conf,"root");
}
}
- 方法二:
- 将
/etc/hadoop
目录下的hdfs-site.xml
文件复制到maven项目的resources
目录下 - 更改
configuration
标签中的内容删除 - 通过自行添加property标签,进行设置