Hadoop是干什么的?
Hadoop是一个处理大数据任务的框架,处理大量的数据进行业务逻辑。其中包括2个部分,第一个部分是hdfs海量数据存储,第二个部分是处理相应的业务逻辑的。例如:100TB的销售数据,根据相应的业务逻辑来统计,排名等等。
Hadoop目前有3个版本,1.0/2.0/3.0,本文用的是2.7.1版本,3.0版本目前还在测试阶段,还是先用2版本的稳定。。
Hadoop1.0版本两个核心:HDFS+MapReduce
Hadoop2.0版本,引入了Yarn(Yet Another Resource Negotiator)。核心:HDFS+Yarn+Mapreduce
Yarn是资源调度框架。能够细粒度的管理和调度任务。此外,还能够支持其他的计算框架,比如spark等。
第一个部分HDFS
启动hadoop后会有一共5个进程,HDFS包括三个进程:1.NameNode 2.DataNode 3.SecondaryNameNode
HDFS是一个存储文件的系统,任何上传上去的文件都会被切成块,2.0是128Mb一块,每个块默认是保存3份在不同的服务器上。
NameNode:主要是负责记录每个块保存到哪个DataNode,并且记录下来。
DataNode: 负责存储文件。
SecondaryNameNode:负责将这一段时间的文件块与
DataNode的映射关系合并到整体的映射关系中。
NameNode每次记录并不是修改记录整个映射关系的fsimage文件(总帐本),而是记录到另一个文件edits(小账本)中,每次当1小时后/或这个文件达到64m的时候,SecondaryNameNode负责将小账本与总账合并,之后再发送过来覆盖原来的总帐本,在复制的过程中,NameNode会记录到edits.new这个文件中,当SecondaryNameNode将合并好的fsimag发来并覆盖后,edits.new重命名为edits覆盖原文件。
DataNode:每三秒与NameNode主动发送心跳与其联系,事实上NameNode不会主动联系DataNode,指令发送是再DataNode与其联系的时候发送给DataNode。
如果10分钟都没收到DataNode的心跳,则认为其已经lost,并copy其上的block(文件块,上面说128m的那个)到其他DataNode
HDFS优缺点
优点:
1.支持超大文件。
超大文件在这里指的是几百M,几百GB,甚至几TB大小的文件。一般来说hadoop的文件系统会存储TB级别或者PB级别的数据。所以在企业的应用中,数据节点有可能有上千个。
2.检测和快速应对硬件故障
在集群的环境中,硬件故障是常见的问题。因为有上千台服务器连接在一起,这样会导致高故障率。因此故障检测和自动恢复是hdfs文件系统的一个设计目标。
3. 流式数据访问:
Hdfs的数据处理规模比较大,应用一次需要访问大量的数据,同时这些应用一般都是批量处理,而不是用户交互式处理。应用程序能以流的形式访问数据集。主要的是数据的吞吐量,而不是访问速度。
4. 简化的一致性模型:
大部分hdfs操作文件时,需要一次写入,多次读取。在hdfs中,一个文件一旦经过创建、写入、关闭后,一般就不需要修改了。这样简单的一致性模型,有利于提高吞吐量。
5. 高容错性:
数据自动保存多个副本,副本丢失后自动恢复
6. 可构建在廉价机器上:
构建在廉价机器上可以轻松的通过扩展机器数量来近乎线性的提高集群存储能力
缺点:
1. 低延迟数据访问:
如和用户进行交互的应用,需要数据在毫秒或秒的范围内得到响应。由于hadoop针对高数据吞吐量做了优化,牺牲了获取数据的延迟,所以对于低延迟来说,不适合用hadoop来做。
2. 大量的小文件:
Hdfs支持超大的文件,是通过数据分布在数据节点(datanode),数据的元数据保存在名字节点(namenode)上。名字节点的内存大小,决定了hdfs文件系统可保存的文件数量。虽然现在的系统内存都比较大,但大量的小文件还是会影响名字节点的性能。
3. 多用户写入文件、修改文件:
Hdfs的文件只能有一次写入,不支持修改和追加写入(2.0版本支持追加),也不支持修改。只有这样数据的吞吐量才能大。
4. 不支持超强的事务:
没有像关系型数据库那样,对事务有强有力的支持。
Block副本放置策略:
第一个副本:如果上传文件的服务器本身就是DataNode,就放置在上传文件的DN;如果是外部客户端向集群上传,就随机选择一台磁盘不太满,cpu不太忙的节点
第二个副本:放置在第一个副本不同机架的节点上
第三个副本:放置在与第一个副本相同机架的节点上(机架内通讯比机架间通讯块)
如果保存更多副本:随机节点
HDFS写流程:
通过RPC请求,NameNode会给一个pipeline分配每个块放到那个Nd的信息,客户端会将文件块分为很多的包一个包大小为64kb,这些小包是用一个队列管理的,当第一个包发给第一个dataNode时,dataNode会发送给第二个/第二个发送给第三个,当全部接受完毕会有一个返回通知,接到返回通知后,再发第二个包,以此反复。
如果传输过程中,有某个datanode出现了故障,那么当前的pipeline会被关闭,出现故障的datanode会从当前的pipeline中移除, 剩余的block会继续剩下的datanode中继续以pipeline的形式传输,同时Namenode会分配一个新的datanode,保持设定的备份数量。
HDFS读流程:
通过RPC请求,Namenode会视情况返回文件的部分或者全部block列表,对于每个block,Namenode都会返回有该block拷贝的DataNode地址,
客户端会选取离客户端最接近的DataNode来读取block;如果客户端本身就是DataNode,那么将从本地直接获取数据.
读取完当前block的数据后,关闭与当前的DataNode连接,并为读取下一个block寻找最佳的DataNode;当读完列表的block后,且文件读取还没有结束,客户端开发库会继续向Namenode获取下一批的block列表。读取完一个block都会进行checksum验证
如果读取datanode时出现错误,客户端会通知Namenode,然后再从下一个拥有该block拷贝的datanode继续,它也会记住那个故障节点的datanode,以保证不会再对之后的块进行徒劳无益的尝试。
HDFS的删除流程:
先在NameNode上执行节点名字的删除。
当NameNode执行delete方法时,它只标记操作涉及的需要被删除的数据块,而不会主动联系这些数据块所在的DataNode节点。
当保存着这些数据块的DataNode节点向NameNode节点发送心跳时,在心跳应答里,NameNode节点会向DataNode发出指令,从而把数据删除掉。
所以在执行完delete方法后的一段时间内,数据块才能被真正的删除掉。
安全模式:
在重新启动HDFS后,会立即进入安全模式,此时不能操作hdfs中的文件,只能查看目录文件名等,读写操作都不能进行。
namenode启动时,需要载入fsimage文件到内存,同时执行edits文件中各项操作
一旦在内存中成功建立文件系统元数据的映射,则创建一个新的fsimage文件(这个步骤不需要SNN的参与)和一个空的编辑文件。
此时namenode文件系统对于客户端来说是只读的。
在此阶段NameNode收集各个DataNode的报告,当数据块达到最小复本数以上时,会被认为是“安全”的,在一定比例的数据块被确定为安全后,再经过若干时间,安全模式结束
当检测到副本数不足的数据块时,该块会被复制直到到达最小副本数,系统中数据块的位置并不是namenode维护的,而是以块列表的形式存储在datanode中。
hadoop dfsadmin -safemode get查看安全模式开关状态
hadoop dfsadmin -safemode leave 离开安全模式
hadoop dfsadmin –safemode enter 进入安全模式
hadoop dfsadmin -report 查看存活的datanode节点信息
HDFS/JavaAPI:
//--创建文件夹
boolean fs.mkdirs(new Path(".."));
新建一个Map/Reduce Project:
public class HDFSDemo {
@Test
public void testConnect() throws Exception{
Configuration conf = new Configuration();
//还可以通过conf.set(name,value)指定属性的的值,如果指定了以它为主。
//conf.set("dfs.replication", "1");
//如果不指定则使用Configuration默认值
//FileSystem是Hadoop的文件系统的抽象类,HDFS分布式文件系统只是Hadoop文件系统中的一种,对应的实现类:
//org.apache.hadoop.hdfs.DistributedFileSystem。HDFS是Hadoop开发者最常用的文件系统,
//因为HDFS可以和MapReduce结合,从而达到处理海量数据的目的
//Hftp:基于HTTP方式去访问HDFS文件提供(只读)
//hdfs:可以读取、写入、改等操作。
//get(URI:制定位置"ip:9000",conf)
FileSystem fs = FileSystem.get(
new URI("hdfs://192.168.80.98:9000"), conf);
System.out.println(fs);
//关闭
fs.close();
}
@Test
public void testMkdir() throws Exception, URISyntaxException{
//get(URI,Conf..,"User")
FileSystem fs = FileSystem.get(
new URI("hdfs://192.168.80.98:9000"),
new Configuration(),"root");
//创建目录
fs.mkdirs(new Path("/park4"));
fs.close();
}
//从hdfs上下载文件到本地
@Test
public void testCopyToLocal() throws Exception, InterruptedException, URISyntaxException{
FileSystem fs = FileSystem.get(
new URI("hdfs://192.168.80.98:9000"),
new Configuration(),"root");
//FSDataInputStream是InputStream类的后代类
FSDataInputStream fsin = fs.open(new Path("/park1/1.txt"));
OutputStream out = new FileOutputStream(
new File("1.txt"));
/*int len = -1;
byte[] bts = new byte[1024];
while((len=fsin.read(bts))!=-1){
out.write(bts, 0, len);
}*/
IOUtils.copy(fsin, out, 1024);
out.close();
fsin.close();
fs.close();
}
//上传:从本地->hdfs
//FSDataOutputStream类OutputStream类的后代类
@Test
public void testCopyFromLocal() throws Exception{
Configuration conf = new Configuration();
//如果没有通过conf.set("dfs.replication","1")指定副本的数量,
//默认是3份(即使在hdfs-site.xml中配置了1)
conf.set("dfs.replication", "1");
FileSystem fs = FileSystem.get(
new URI("hdfs://192.168.80.98:9000"), conf, "root");
FSDataOutputStream out = fs.create(new Path("/park3/1.txt"));
InputStream in = new FileInputStream(new File("1.txt"));
IOUtils.copy(in, out, 1024);
in.close();
out.close();
fs.close();
}
//查看文件的块信息
@Test
public void testGetBlockLocation() throws Exception{
FileSystem fs = FileSystem.get(new URI("hdfs://192.168.80.98:9000"),
new Configuration(),"root");
/**参数一:文件路径对应的path对象
* 参数二:从哪开始读取
* 参数三:读取多长
* 后两个参数写出0,Long.MAX_VALUE:表示获取该文件的所有块的信息
*/
BlockLocation[] bls =fs.getFileBlockLocations(
new Path("/park1/hadoop.tar.gz"), 0, Long.MAX_VALUE);
for (BlockLocation bl : bls) {
System.out.println(bl);
}
fs.close();
}
//查看该文件夹下的所有文件信息,但是不递归查看
@Test
public void testStatus() throws Exception{
FileSystem fs = FileSystem.get(new URI("hdfs://192.168.80.98:9000"),
new Configuration(),"root");
FileStatus[] fstats = fs.listStatus(new Path("/park1"));
for (FileStatus fstat : fstats) {
System.out.println(fstat.toString());
System.out.print(fstat.getPath().toString());
System.out.print(fstat.getOwner());
System.out.print(fstat.getGroup());
System.out.println(fstat.getPermission());
}
}
//删除文件
@Test
public void testDelete() throws Exception{
FileSystem fs = FileSystem.get(new URI("hdfs://192.168.80.98:9000"),
new Configuration());
//true:表示递归删除
//false:只能删除空文件夹;如果删除非空文件夹,
//则提示:/park1 is non empty': Directory is not empty
boolean result = fs.delete(
new Path("/park1"),false);
System.out.println(result);
}
}
Eclipse中Hadoop插件的使用
1.下载hadoop插件,注意:插件的版本要和用的hadoop版本保持一致
2.将插件jar包放在eclipse安装目录的plugins目录下
3.将hadoop安装包解压到指定的一个目录(后面要用这个安装目录)
4.重启eclipse,windows->Preferences=>下发现多出Map/Reduce选项卡,点击=》选择hadoop的安装目录,然后点击apply,点击确定
5.点击open perspective,调出map/reduce视图,或者show view ,调出map/reduce 视图
6.在map/reduce视图下,点击蓝色的大象,新建hadoop客户端连接
7.在选项卡里,填好namenode节点的ip地址,及相应的端口号
端口号只要改DFS中的即可
8.打开视图
之后就可以右键进行操作:上传/下载/删除/创建文件夹。
插件使用常见问题解决办法
1.修改hdfs-site.xml中的配置dfs.permissions为false关闭hadoop的权限认证,更改完之后,需要重新启动hadoop
2.也可以在windows的环境变量中配置HADOOP_USER_NAME指定连接hadoop时使用的名称,这种方式配置完要重启eclipse。