完全分布式部署Hadoop-04https://blog.csdn.net/kxj19980524/article/details/88954645
上面是hadoop集群的搭建
连接客户端前先下载相应的jar包,和hadoop编译过的源码
win10
链接:https://pan.baidu.com/s/1yXfyBesrUeZuR-KPPUG1eQ
提取码:mnvl
win7
链接:https://pan.baidu.com/s/1dW9L3F6pKNPtU9b3EIcRJA
提取码:9wl0
下载好相应版本的jar包后,配置环境变量,下载好的包放到一个无中文目录下,lib里面是客户端需要的jar包,下面是代码.
配置环境变量和path路径,跟java一样
然后创建一个java项目,把lib下面的jar包,除了source和tests外,剩下的包都导入项目中
在hosts中配置好跟集群相对应的信息
复制本地文件到hdfs的案例
package com.buba.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.net.URI;
public class HDFSClient {
public static void main(String[] args)throws Exception {
//0获取配置信息
Configuration configuration = new Configuration();
configuration.set("fs.defaultFS","hdfs://hadoop102:8020");
//1获取文件系统
FileSystem fileSystem = FileSystem.get(configuration);
//2拷贝本地数据到集群
fileSystem.copyFromLocalFile(new Path("F:\\kxj.txt"),new Path("/user/kxj/kxj3.txt"));
//3.关闭文件系统
fileSystem.close();
}
}
如果这样直接启动的话会报错,说没有用户名,没有权限访问hadoop集群,需要带参数运行
-DHADOOP_USER_NAME=kxj 指定hadoop集群上的用户.然后再运行就可以了
第二种种写发就无需添加参数,使用FileSystem的另一个有参构造函数.
package com.buba.hdfs;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;
import java.net.URI;
public class HDFSClient {
public static void main(String[] args)throws Exception {
//-DHADOOP_USER_NAME=kxj
//0获取配置信息
Configuration configuration = new Configuration();
//configuration.set("fs.defaultFS","hdfs://hadoop102:8020");
//1获取文件系统
//FileSystem fileSystem = FileSystem.get(configuration);
FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration, "kxj");
//2拷贝本地数据到集群
fileSystem.copyFromLocalFile(new Path("F:\\kxj.txt"),new Path("/user/kxj/kxj3.txt"));
//3.关闭文件系统
fileSystem.close();
}
}
第三种获取文件系统方式,把core-site.xml配置文件放项目下
也是可以获取到文件系统的,只不过ugi变为了Admin,但是想要执行copy之类的操作的话就会报错,因为集群搭建的时候是使用的kxj用户名.
这三种获取文件系统的优先级是 :java代码最高>java项目配置文件>远程linux的hadoop配置文件
@Test
public void getFileSystem()throws Exception{
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(configuration);
System.out.println(fileSystem);
//fileSystem.copyFromLocalFile(new Path("F:\\kxj.txt"),new Path("/user/kxj/kxj4.txt"));
}
其它对hdfs的操作都使用FileSystem这个对象来操作,调它里面的api就可以了.
文件上传:
参数1是否删除本地文件true为删除 参数2 要上传的文件 参数3 上传的位置,不加第一个参数默认为false
fileSystem.copyFromLocalFile(true,new Path("F:\\kxj.txt"),new Path("/user/kxj/kxj3.txt"));
文件下载:
三个重载方法,第一个布尔值还是是否删除原文件,最后一个布尔值,是否进行校验,如果从hdfs下载下来的东西没内容就加上校验.
fileSystem.copyToLocalFile(new Path("/user/kxj/output/kxj.txt"),new Path("F:\\kxj.txt"));
创建目录:
第二个参数是给权限,这两个方法可以直接创建多级目录
fileSystem.mkdirs(new Path("/kxj/user"));
删除目录:
fileSystem.delete(new Path("/kxj"),true);
重命名:
参数1:旧名称 参数2:新名称
fileSystem.rename(new Path("/user/kxj/kxj.txt"),new Path("ch.txt"));
文件的详细信息: 里面还有其它信息
RemoteIterator<LocatedFileStatus> remoteIterator = fileSystem.listFiles(new Path("/"), true);
while (remoteIterator.hasNext()){
LocatedFileStatus next = remoteIterator.next();
//文件名称
System.out.println(next.getPath().getName());
//块大小
System.out.println(next.getBlockSize());
//文件长度
System.out.println(next.getLen());
//权限
System.out.println(next.getPermission());
BlockLocation[] blockLocations = next.getBlockLocations();
//存储块的详细信息
for(BlockLocation b:blockLocations){
//从哪个块开始存储
System.out.println(b.getOffset());
//拥有者
String[] hosts = b.getHosts();
for(String s:hosts){
System.out.println(s);
}
}
System.out.println("----------------");
}
判断一个目录下的所有文件是文件夹还是文件,没有递归功能,只能在那一级目录下
FileStatus[] listStatus = fileSystem.listStatus(new Path("/"));
// 3 遍历所有文件状态
for (FileStatus status : listStatus) {
if (status.isFile()) {
//是文件
System.out.println("f--" + status.getPath().getName());
} else {
//是文件夹
System.out.println("d--" + status.getPath().getName());
}
}
使用IO流的方式进行上传文件
看下图,相对于java程序来说,本地文件传到java程序种属于输入流,java程序上传到hdfs属于输出流
@Test
public void putFileToHDFS() throws Exception{
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration, "kxj");
// 创建输入流
FileInputStream inStream = new FileInputStream(new File("F:/hello.txt"));
// 创建输出流
FSDataOutputStream outStream = fileSystem.create(new Path("/user/kxj/input/hello.txt"));
// 流对接
try{
IOUtils.copyBytes(inStream, outStream, configuration);
}catch(Exception e){
e.printStackTrace();
}finally{
IOUtils.closeStream(inStream);
IOUtils.closeStream(outStream);
}
}
使用IO流的方式进行文件下载,思路跟上面正好相反,输入输出反了一下,调用open方法获取输入流
@Test
public void getFileToHDFS() throws Exception{
Configuration configuration = new Configuration();
FileSystem fileSystem = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration, "kxj");
// 创建输出流
FileOutputStream fileOutputStream = new FileOutputStream(new File("F:/hello.txt"));
// 创建输入流
FSDataInputStream open = fileSystem.open(new Path("/user/kxj/input/hello.txt"));
// 流对接
try{
IOUtils.copyBytes(open, fileOutputStream, configuration);
}catch(Exception e){
e.printStackTrace();
}finally{
IOUtils.closeStream(open);
IOUtils.closeStream(fileOutputStream);
}
}
定位文件获取
之前都是下载的单块的数据,以前上传到hdfs上的hadoop安装包份了两块共185兆左右,那怎么把它完全的下载下来呢?就需要份块下载了,第一次下载128兆,因为一个block为128兆,分两次下载,然后拼接到一块.1024byte=1kb
@Test
// 定位下载第一块内容
public void readFileSeek1() throws Exception {
// 创建配置信息对象
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration, "kxj");
// 获取输入流路径
FSDataInputStream fis = fs.open(new Path("hdfs://hadoop102:8020/user/kxj/input/hadoop-2.7.2.tar.gz"));
// 创建输出流
FileOutputStream fos = new FileOutputStream("F:/hadoop-2.7.2.tar.gz.part1");
// 流对接 1024byte是1Kb 1024*1024=1Mb 一个block128Mb
byte[] buf = new byte[1024];
for (int i = 0; i < 128 * 1024; i++) {
fis.read(buf);
fos.write(buf);
}
// 关闭流
IOUtils.closeStream(fis);
IOUtils.closeStream(fos);
}
使用seek方法定位到第二块的开头
@Test
// 定位下载第二块内容
public void readFileSeek2() throws Exception {
// 创建配置信息对象
Configuration configuration = new Configuration();
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop102:8020"), configuration, "kxj");
// 获取输入流路径
FSDataInputStream fis = fs.open(new Path("hdfs://hadoop102:8020/user/kxj/input/hadoop-2.7.2.tar.gz"));
// 创建输出流
FileOutputStream fos = new FileOutputStream("F:/hadoop-2.7.2.tar.gz.part2");
// 定位偏移量(第二块的首位)
fis.seek(1024*1024*128);
//流拼接
IOUtils.copyBytes(fis,fos,configuration);
// 关闭流
IOUtils.closeStream(fis);
IOUtils.closeStream(fos);
}
然后使用cmd命令把第二块拼接到第一块里面,可以看到大小明显改变了
type hadoop-2.7.2.tar.gz.part2>> hadoop-2.7.2.tar.gz.part1
然后把part1重命名,就可以解压了,可以看到已经拼接好了
定位文件获取的作用,有时候海量数据的时候,不需要把全部数据下载下来,只需要其中的一块,这时候就需要定位了.
保证数据一致性问题
一个客户端上传数据原先版本为10,三个副本也都是10,然后他又改变数据了,改为11了,副本同步是需要时间的,不是时时的,在这期间另一个客户端读数据,离节点3比较近,那他读取的就是旧数据,假如你这数据非常重要需要时时变化,这该怎么办呢?
调用 hflush (); //清理客户端缓冲区数据,被其他client立即可见,调用它的时候底层会实现锁使其它客户端访问不了,这就会影响效率,看情况而定需要的时候再加.
@Test
public void writeFile() throws Exception{
// 1 创建配置信息对象
Configuration configuration = new Configuration();
fs = FileSystem.get(configuration);
// 2 创建文件输出流
Path path = new Path("hdfs://hadoop102:9000/user/atguigu/hello.txt");
FSDataOutputStream fos = fs.create(path);
// 3 写数据
fos.write("hello".getBytes());
// 4 一致性刷新
fos.hflush();
fos.close();
}