HDFS入门
Hadoop Distribute File System hadoop分布式文件系统
- 分布式多台机器 解决文件存不下的问题
- 存储元数据 解决数据查询不方便的问题
- 分块存储 解决数据上传下载问题
- 副本机制 解决数据丢失安全问题
特性
- 主从架构 主节点从节点各司其职
- 分块存储 hadoop2.X 默认128M hadoop1.X默认64M 可以通过配置文件修改
- namenode 主节点 master 需要大量内存( RAM )
- 负责存储元数据( 目录结构以及分块位置信息 datanode节点信息 )
- 分块位置信息不会持久化存储, 这些信息在系统启动时从数据节点重建
- namenode关闭, HDFS/Hadoop 集群就无法访问了 所以是个单点故障
- datanode 从节点 slave 需要大量磁盘
- 负责存储数据块(block) 默认每隔6小时向master报告自己存储的块信息
- 心跳机制 默认每3秒向namenode发送心跳, 如果namenode长时间(3*10)没收到心跳, 会认为dn失效
- 副本机制 每个文件都有副本 默认3个副本
- 一次写入 多次读出 HDFS适合写一次,多次读,不支持文件修改
1.txt 100M block-1: 1-100M 2.txt 300M block-1: 1-128M block-2: 128M-256M block-3:256M-300M
shell命令行
- hadoop fs -ls hdfs://node-1:9000/ 操作hdfs文件系统 - hadoop fs -ls file:///root 操作本地文件系统 - hadoop fs -ls / 如果不指定 就采用默认文件系统
- hadoop fs -ls [-h] [-R] / 显示文件,目录信息
- hadoop fs -mkdir [-p] /data 创建目录
- hadoop fs -put [-f] [-p] localsrc dst 上传linux文件到hdfs文件系统
- hadoop fs -put /root/1.txt /data
- hadoop fs -get [-f] [-p] src localsrc 下载hdfs文件系统中的文件到linux
- hadoop -get /data/1.txt /root
- hadoop fs -appendToFile localsrc dst 追加一个文件到已经存在的文件末尾
- hadoop fs -appendToFile /root/2.txt /data/1.txt
- hadoop fs -cat path 显示文件内容
- hadoop fs -tail path 动态输出文件内容
- hadoop fs -chgrp|-chmod|-chown 更新文件的组/权限/拥有者
- hadoop fs -mv|-cp|-rm 剪切/复制/删除 文件
- hadoop fs -getmerge /data/*.txt ./log.txt 合并下载多个文件
- hadoop fs -setrep -w num path 改变文件的副本系数 ( 不推荐使用 )
- hadoop fs -setrep -w 5 /data/1.txt 速度太慢,影响集群效率
默认三副本策略
理想状态:
机架(rack)与机架之间通过交换机连接, 机架中的服务器通过交换机连接, 网络拓扑图的远近是指两个服务器之间通信 , 要经过几个交换机
- 第一个副本, 如果客户端所在机器有datanode, 且datanode状态很好, 磁盘够,则第一个副本存储在这个datanode, 如果不满足, 则找离客户端最近( datanode在网络拓扑图的远近 )的一台机器作为第一个副本所在地.( 一般为同一机架 )
- 第二个副本, 在不同于第一个副本所在机架的其他机架上 , 随机找一个datanode存储
- 第三个副本, 在第二个副本所在机架上随机找一个datanode存储( 排除第二个副本所在datanode )
非理想状态: 副本数不为3 或者没有机架…
- 第一个副本存储和理想转态一样
- 其他副本随机datanode存储
文件上传过程
HDFS上传机制:
启动一个线程, 将文件的第一块(block-1) 进行上传到dn1, 由dn1复制到dn2,dn2复制到dn3( 默认3副本 )
第一块上传完成后, 开始第二块(block-2)的上传
- 客户端RPC请求服务端( namenode )上传文件
- 服务端根据请求信息, 查询元数据判断是否可以上传
- 服务端返回信息给客户端
- 客户端请求上传1.txt block-1( 文件1.txt的第一个分块)
- 服务端根据请求, 结合dn ( datanode )信息返回3台可用的dn信息( ip )
- 服务端将上传的dn信息传给客户端
- 建立传输数据的管道( pipeline )
- 管道建立完毕, 开始上传, dn将接受到的包( packet 64k) 保存到指定目录, 并且把包往后面发( 备份 )
- 反方向逐个ACK应答,保证packet安全稳定
- block-1上传完毕后, 客户端重新发送请求, 上传1.txt block-2( 第二个分块 , 如果有的话)
- …
- 所有块上传完毕后, 客户端通知namenode, namenode把信息同步到元数据中
hadoop认为在一起请求中(块上传请求), 只要有一个副本上传成功, 则本次上传就算成功, 不足的副本在上传完毕后 指定其他dn进行副本的复制, 最终副本数=设定的副本数
文件下载过程
todo
客户端向namenode发起RPC请求, 请求文件下载
服务端接收请求, 确定客户端可以下载这个文件, 视情况返回部分block或全部block, 对于每个block, namenode都会返回含有该副本的datanode地址
1.txt 300M 2 {block-1,block-2,block-3}
{block-1:node-1,node2;block-2:node-2,node-3;block-3:node-1,node-3}
返回的dn地址 是经过排序的
- 网络拓扑图中距离Client近的靠前
- 心跳机制最后那个超时汇报的靠后
客户端选排名靠前的dn读取block, 如果客户端本身就是dn, 那么直接从本地获取
- 数据传输的底层是IO流
- 读完一个block,都会进行checksum验证, 如果出现错误, 客户端会通知namenode, 然后从下一个拥有该block的dn继续读
- read方法是并行读取block信息 例如 namenode返回三个block 则开启三个线程,并行读取
读取完这一批的block后, 如果还没有结束, 客户端继续向namenode获取下一批block列表
最终读取的block会合并成完整的最终文件
java api
windows平台做hadoop开发, 需要设置hadoop环境, 在window平台编译hadoop
不然会报 winutils.exe null的异常, 这个文件缺失
Failed to locate the winutils binary in the hadoop binary path java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.
<dependencies> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-common</artifactId> <version>2.7.4</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> <version>2.7.4</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>2.7.4</version> </dependency> </dependencies>
package com.hrh; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.*; import org.apache.hadoop.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.protocol.DatanodeInfo; import java.net.URI; /** * @Title: Test * @ProjectName BigData * @Description: TODO */ public class Test { public static void main(String[] args) throws Exception { //加载配置文件 // new Configuration();的时候,它就会去加载jar包中的hdfs-default.xml //默认是file:/// 本地文件系统, 而不是hdfs // 然后再加载classpath下的hdfs-site.xml //参数优先级: //1、客户端代码中设置的值 2、classpath下的用户自定义配置文件 3、然后是服务器的默认配置 Configuration conf=new Configuration(); //获得一个hdfs的访问客户端 FileSystem fs = FileSystem.get(new URI("hdfs://node-1:9000"),conf,"root"); //本地上传文件 fs.copyFromLocalFile(new Path("E:\\hr.txt"),new Path("/")); //删除文件 boolean flag = fs.delete(new Path("/hr.txt"), false); // 创建目录 fs.mkdirs(new Path("/a1/b1/c1")); // 删除文件夹 ,如果是非空文件夹,参数2必须给值true fs.delete(new Path("/aaa"), true); // 重命名文件或文件夹 fs.rename(new Path("/a1"), new Path("/a2")); //获取dn信息 DatanodeInfo[] dataNodeStats = ((DistributedFileSystem) fs).getDataNodeStats(); for (DatanodeInfo dataNodeStat : dataNodeStats) { System.out.println(dataNodeStat.getHostName()); System.out.println(dataNodeStat.getName()); System.out.println(dataNodeStat.getLevel()); } //获得指定目录下的文件 分块信息 RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true); while (listFiles.hasNext()){ LocatedFileStatus fileStatus = listFiles.next(); System.out.println(fileStatus.getPath().getName()); System.out.println(fileStatus.getBlockSize()); System.out.println(fileStatus.getPermission()); System.out.println(fileStatus.getLen()); BlockLocation[] blockLocations = fileStatus.getBlockLocations(); for (BlockLocation blockLocation : blockLocations) { System.out.println(blockLocation.getLength()); System.out.println(blockLocation.getOffset()); String[] hosts = blockLocation.getHosts(); for (String host : hosts) { System.out.println(host); } } } //关闭连接 fs.close(); } }
shell定时采集数据到HDFS
编写shell脚本步骤
- 加载环境变量
- 定义变量 常量 方便下面的逻辑代码引用
- 写业务逻辑时, 充分考虑各种情况, 多加判断
上传每天的日志到hdfs
#!/bin/bash
#set java env
export JAVA_HOME=/export/servers/jdk1.8.0_65
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib:${JRE_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
#set hadoop env
export HADOOP_HOME=/export/servers/hadoop-2.7.4
export PATH=${HADOOP_HOME}/bin:${HADOOP_HOME}/sbin:$PATH
#日志文件存放的目录
log_src_dir=/root/logs/log/
#待上传文件存放的目录
log_toupload_dir=/root/logs/toupload/
#日志文件上传到hdfs的根路径
date1=`date -d last-day +%Y_%m_%d`
hdfs_root_dir=/data/clickLog/$date1/
#打印环境变量信息
echo "envs: hadoop_home: $HADOOP_HOME"
#读取日志文件的目录,判断是否有需要上传的文件
echo "log_src_dir:"$log_src_dir
ls $log_src_dir | while read fileName
do
if [[ "$fileName" == access.log.* ]]; then
# if [ "access.log" = "$fileName" ];then
date=`date +%Y_%m_%d_%H_%M_%S`
#将文件移动到待上传目录并重命名
#打印信息
echo "moving $log_src_dir$fileName to $log_toupload_dir"xxxxx_click_log_$fileName"$date"
mv $log_src_dir$fileName $log_toupload_dir"xxxxx_click_log_$fileName"$date
#将待上传的文件path写入一个列表文件willDoing
echo $log_toupload_dir"xxxxx_click_log_$fileName"$date >> $log_toupload_dir"willDoing."$date
fi
done
#找到列表文件willDoing
ls $log_toupload_dir | grep will |grep -v "_COPY_" | grep -v "_DONE_" | while read line
do
#打印信息
echo "toupload is in file:"$line
#将待上传文件列表willDoing改名为willDoing_COPY_
mv $log_toupload_dir$line $log_toupload_dir$line"_COPY_"
#读列表文件willDoing_COPY_的内容(一个一个的待上传文件名) ,此处的line 就是列表中的一个待上传文件的path
cat $log_toupload_dir$line"_COPY_" |while read line
do
#打印信息
echo "puting...$line to hdfs path.....$hdfs_root_dir"
hadoop fs -mkdir -p $hdfs_root_dir
hadoop fs -put $line $hdfs_root_dir
done
mv $log_toupload_dir$line"_COPY_" $log_toupload_dir$line"_DONE_"
done
[root@node-1 ~]# sh uploadFile2HDFS.sh
uploadFile2HDFS.sh: line 1: et: command not found
envs: hadoop_home: /export/servers/hadoop-2.7.4
log_src_dir:/root/logs/log/
moving /root/logs/log/access.log.1 to /root/logs/toupload/xxxxx_click_log_access.log.12018_11_15_20_52_03
moving /root/logs/log/access.log.2 to /root/logs/toupload/xxxxx_click_log_access.log.22018_11_15_20_52_03
moving /root/logs/log/access.log.3 to /root/logs/toupload/xxxxx_click_log_access.log.32018_11_15_20_52_03
moving /root/logs/log/access.log.4 to /root/logs/toupload/xxxxx_click_log_access.log.42018_11_15_20_52_03
toupload is in file:willDoing.2018_11_15_20_52_03
puting.../root/logs/toupload/xxxxx_click_log_access.log.12018_11_15_20_52_03 to hdfs path...../data/clickLog/2018_11_14/
puting.../root/logs/toupload/xxxxx_click_log_access.log.22018_11_15_20_52_03 to hdfs path...../data/clickLog/2018_11_14/
puting.../root/logs/toupload/xxxxx_click_log_access.log.32018_11_15_20_52_03 to hdfs path...../data/clickLog/2018_11_14/
puting.../root/logs/toupload/xxxxx_click_log_access.log.42018_11_15_20_52_03 to hdfs path...../data/clickLog/2018_11_14/
[root@node-1 ~]# cd logs/log
You have new mail in /var/spool/mail/root
[root@node-1 log]# ll
total 4
-rw-r--r--. 1 root root 4 Nov 15 20:50 access.log
[root@node-1 log]# cd ..
[root@node-1 logs]# cd toupload/
[root@node-1 toupload]# cat willDoing.2018_11_15_20_52_03_DONE_
/root/logs/toupload/xxxxx_click_log_access.log.12018_11_15_20_52_03
/root/logs/toupload/xxxxx_click_log_access.log.22018_11_15_20_52_03
/root/logs/toupload/xxxxx_click_log_access.log.32018_11_15_20_52_03
/root/logs/toupload/xxxxx_click_log_access.log.42018_11_15_20_52_03
[root@node-1 toupload]#