Hadoop叢集操作
寫在前面
前文地址1 前文地址2我們搭建了hadoop叢集,接下來學習一下如何對HDFS進行操作(即hadoop的分散式檔案系統)。畢竟我們不能白搭建叢集嘛。主要可以分為shell操作和API操作。
shell操作
以下操作都在hadoop目錄下,如我的就在/opt/module/hadoop-2.7.2
這裡有兩種方法,**bin/hadoop fs 具體命令 **或者 bin/hdfs dfs 具體命令,二者區別不大,dfs是fs的實現類。這裡我配置了環境變數,故可以直接hadoop或者hdfs。
-
啟動hadoop叢集
sbin/start-dfs.sh sbin/start-yarn.sh
-
顯示目錄資訊
hadoop fs -ls /
-
在HDFS上建立目錄
hadoop fs -mkdir -p /sanguo/shuguo
-
從本地剪下貼上到HDFS
touch kongming.txt hadoop fs -moveFromLocal ./kongming.txt /sanguo/shuguo
-
追加一個檔案到已經存在的檔案末尾
touch liubei.txt vim liubei.txt 輸入 san gu mao lu hadoop fs -appendToFile liubei.txt /sanguo/shuguo/kongming.txt
-
顯示檔案內容
hadoop fs -cat /sanguo/kongming.txt
-
-chgrp、-chmod,-chown,用法與linux系統中一樣,修改檔案的所屬許可權
hadoop fs -chmod 666 /sanguo/shuguo/kongming.txt hadoop fs -chown liuge:liuge /sanguo/shuguo/kongming.txt
-
把本地系統檔案拷貝到HDFS上
hadoop fs -copyFromLocal README.txt /
-
從HDFS拷貝到本地
hadoop fs -copyToLocal /sanguo/shuguo/kongming.txt ./
-
從HDFS的一個路徑拷貝到另一個路徑
hadoop fs -cp /sanguo/shuguo/kongming.txt /zhuge.txt
-
在HDFS目錄中移動檔案
hadoop fs -mv /zhuge.txt /sanguo/shuguo
-
-get等同於copyToLocal,-put等同於copyFromLocal
-
合併多個下載檔案
hadoop fs -getmerge /user/liuge/test/* ./zaiyiqi.txt
-
顯示一個檔案的末尾
hadoop fs -tail /sanguo/shuguo/kongming.txt
-
刪除檔案或者資料夾
hadoop fs -rm /user/liuge/test/jinlian2.txt
-
刪除空目錄
hadoop fs -mkdir /test hadoop fs -rmdir /test
-
統計資料夾的大小資訊
hadoop fs -du -s -h /user/liuge/test
總的來說,shell操作可以滿足我們大多數的需求了。但有時候還是需要編寫JAVA API的。下面來看看使用JAVA API。
API操作
準備工作
首先準備一個已經編譯好的與linux上的hadoop一個版本的jar包,解壓到一個非中文目錄,配置HADOOP_HOME環境變數:
再接著去配置PATH環境變數,新增一條:%HADOOP_HOME%\bin
建立一個maven專案,匯入如下的依賴:
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.8.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-common</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>2.7.2</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>2.7.2</version>
</dependency>
</dependencies>
同時在resources目錄下新建一個log4j.properties,寫入以下內容:
log4j.rootLogger=INFO, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
編寫一個HDFSClient類,寫入如下程式碼作為測試:
FileSystem fs = getFileSystem();
// 2.在hdfs上建立路徑
fs.mkdirs(new Path("/0529/liuge/banzhang"));
// 3.關閉資源
fs.close();
System.out.println("over");
其中的getFileSystem()方法為:
private static FileSystem getFileSystem() throws Exception {
Configuration conf = new Configuration();
return FileSystem.get(new URI("hdfs://hadoop03:9000"), conf, "liuge");
}
解釋一下,在getFileSystem()方法裡,要寫入你的hadoop叢集地址,以及訪問的使用者,比如我這裡是liuge,就寫入liuge使用者。
之後我們獲取到檔案系統後,就可以在hdfs上進行操作了。
如果能正確輸出over,且在web端上觀察到已經建立了檔案,則說明能夠正確連線到叢集。
HDFS檔案上傳與下載
先直接上程式碼:
/**
* 1. 檔案上傳
* 優先順序:程式碼配置>resources配置檔案>linux中的配置檔案>linux中的預設配置檔案
*/
@Test
public void testCopyFromLocalFile() throws Exception {
// 1.獲取fs物件
Configuration conf =new Configuration();
conf.set("dfs.replication","2");
FileSystem fs = FileSystem.get(new URI("hdfs://hadoop03:9000"), conf, "liuge");
// 2.執行上傳程式碼
fs.copyFromLocalFile(new Path("f:/update.txt"),new Path("/ignore.txt"));
// 3.關閉資源
fs.close();
}
/**
* 2.檔案下載
* @throws Exception
*/
@Test
public void testCopyToLocalFile() throws Exception{
// 1.獲取物件
FileSystem fs = getFileSystem();
// 2.執行下載操作
// fs.copyToLocalFile(new Path("/ignore.txt"),new Path("f:/ignore.txt"));
fs.copyToLocalFile(false,new Path("/ignore.txt"),new Path("f:/ignore.txt"),true);
// 3.關閉資源
fs.close();
}
這裡我們在檔案上傳那裡設定了一個引數,用來設定切片數。通過實驗得出瞭如上註釋的優先順序結論。
在檔案下載裡,我們發現copyToLocalFile有兩個過載方法,我們可以對第三個引數傳入true代表使用原生檔案系統,就不會生成本地的一些檔案了。
HDFS檔案刪除
/**
* 3. 檔案刪除
* @throws Exception
*/
@Test
public void testDelete() throws Exception{
// 1.獲取物件
FileSystem fs=getFileSystem();
// 2.檔案的刪除 第二個變量表示是否遞迴刪除
fs.delete(new Path("/0529"),true);
// 3.關閉資源
fs.close();
}
直接上程式碼,沒什麼好說的。
檔案更名
/**
* 4.檔案更名
*
* @throws Exception
*/
@Test
public void testRename() throws Exception {
// 1.獲取物件
FileSystem fs = getFileSystem();
// 2.檔案的更名
fs.rename(new Path("/update.txt"), new Path("/yanjing.txt"));
// 3.關閉資源
}
同上,也沒什麼好說的。
檢視檔案許可權資訊
先來看程式碼:
/**
* 5.檢視檔案許可權資訊
*
* @throws Exception
*/
@Test
public void testListFiles() throws Exception {
// 1.獲取物件
FileSystem fs = getFileSystem();
// 2.檢視檔案詳情
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.getPermission());
// 長度
System.out.println(fileStatus.getLen());
// 儲存的塊資訊
BlockLocation[] blockLocations = fileStatus.getBlockLocations();
for (BlockLocation blockLocation : blockLocations) {
String[] hosts = blockLocation.getHosts();
for (String host : hosts) {
// 獲取塊儲存的主機節點
System.out.println(host);
}
}
System.out.println("-------------分割線哦~--------------");
}
// 3.關閉資源
fs.close();
}
我們通過API獲得了很多資訊,這裡我們獲取了許可權,長度,塊資訊,並且在塊資訊中也有很多可以獲取的,這裡只獲取了塊儲存的主機節點。這個API還是很有用的。
判斷是資料夾還是檔案
/**
* 判斷是檔案還是資料夾
*
* @throws Exception
*/
@Test
public void testListStatus() throws Exception {
// 1.獲取fs
FileSystem fs = getFileSystem();
// 2.判斷操作
FileStatus[] fileStatuses = fs.listStatus(new Path("/"));
for (FileStatus fileStatus : fileStatuses) {
if (fileStatus.isFile()) {
System.out.println("f:" + fileStatus.getPath().getName());
}
if (fileStatus.isDirectory()) {
System.out.println("d:" + fileStatus.getPath().getName());
}
}
// 3.關閉資源
fs.close();
}
我們通過API可以判斷上傳的檔案是一個檔案還是一個資料夾,從而對其進行不同的操作。
總結
總的來說,用API操作還是十分簡單的。我們只需要獲取一個檔案系統,然後呼叫API就可以了,難度不是很大。
原生IO流操作
有的時候,API不能滿足我們的需求,我們就需要使用原生IO流進行操作,自己寫了。
首先還是先定義一個獲取檔案系統的方法:
private static FileSystem getFileSystem() throws Exception {
Configuration conf = new Configuration();
return FileSystem.get(new URI("hdfs://hadoop03:9000"), conf, "liuge");
}
再定義一個通用的關閉方法,來關閉所有的流:
private void closeStream(InputStream fis, OutputStream fos) throws Exception {
IOUtils.closeStream(fos);
IOUtils.closeStream(fis);
fs.close();
}
這裡使用了IOUtils,這是hadoop提供的工具。使用它我們可以很方便的關閉一些流。
檔案上傳與下載
/**
* 把檔案上傳到HDFS根目錄
*/
@Test
public void putFileToHDFS() throws Exception{
// 1.獲取物件
FileSystem fs = getFileSystem();
// 2.獲取輸入流
FileInputStream fis = new FileInputStream(new File("f:/update.txt"));
// 3.獲取輸出流
FSDataOutputStream fos = fs.create(new Path("/banzhang.txt"));
// 4.流的對拷
IOUtils.copyBytes(fis,fos, conf);
// 5.關閉資源
closeStream(fis, fos);
}
/**
* 從HDFS拷貝檔案到本地
* @throws Exception
*/
@Test
public void getFileFromHDFS() throws Exception{
// 1.獲取物件
FileSystem fs = getFileSystem();
// 2.獲取輸入流
FSDataInputStream fis = fs.open(new Path("/banzhang.txt"));
// 3.獲取輸出流
FileOutputStream fos = new FileOutputStream(new File("f:/banzhang.txt"));
// 4.流的對拷
IOUtils.copyBytes(fis,fos,conf);
// 5.關閉資源
closeStream(fis,fos);
}
可以看到,整個操作也是比較套路化的。我們先獲取到檔案系統物件,然後建立輸入流和輸出流,最後對拷就可以了。
分塊拷貝
假如我現在有這樣的一個需求,我只想要一個大檔案的第二塊(block),不想要前面的塊。要實現這樣的需求,我們就可以使用原生IO流:
/**
* 下載第二塊
* @throws Exception
*/
@Test
public void readFileSeek2() throws Exception{
// 1.獲取物件
fs = getFileSystem();
// 2.獲取輸入流
FSDataInputStream fis = fs.open(new Path("/hadoop-2.7.2.tar.gz"));
// 3.設定指定讀取的起點 單位為B
fis.seek(1024 * 1024 * 128);
// 4.獲取輸出流
FileOutputStream fos = new FileOutputStream(new File("f:/hadoop-2.7.2.tar.gz.part2"));
// 5.流的對拷
IOUtils.copyBytes(fis,fos,conf);
// 6.關閉資源
closeStream(fis,fos);
}
這裡我們傳的大檔案大小大概是188MB。按照hadoop的分片規則,會分為兩塊。
可以看到,因為HDFS預設的塊大小為128MB,我們這裡就按照128MB對輸入流進行了定位,然後獲取到了128MB以後的塊,即第二塊。
總結
總的來說,對HDFS的操作還是比較簡單的。只需要多加練習就好。