隨著業務的發展,系統會越來越龐大,原本簡單穩定的功能,可能在不斷迭代後複雜度上升,潛在的風險也隨之暴露,導致最終服務不穩定,造成業務價值的損失。而為了減少這種情況,其中一種比較好的方式就是提高代碼質量,比如通過代碼審查,從而降低錯誤風險,但是,代碼審查難度大,代碼缺陷、漏洞不易發現,且審查工作隨著代 ...
HDFS簡介
一、什麼是HDFS
HDFS全稱是Hadoop Distributed File System,簡稱HDFS。這是一個分散式文件系統,當數據規模大小超過一臺物理電腦的存儲能力時,就有必要進行分區並存儲到若幹台物理電腦上。管理網路中跨多台電腦的文件系統稱為分散式文件系統。
Hadoop的文件系統是一個抽象的概念,java的抽象類是org.apache.hadoop.fs.FileSystem,在創建一個FileSystem的時候,FileSystem使用文件系統URI的Schema作為查詢配置或者SPI尋找實現類(類似JDBC)。FileSystem有很多實現,HDFS只是其中的一個實現,它的實現類是org.apache.hadoop.hdfs.DistributedFileSystem,它的URI schema為hdfs。Hadoop預設配置的文件系統實現是org.apache.hadoop.fs.LocalFileSystem,URI schema為file,用於管理本地文件。
二、HDFS的基礎概念
1、數據塊
塊設備(例如硬碟)一般會有一個預設塊大小,這是設備讀寫的最小單位,這類設備的塊大小一般為幾千位元組。HDFS同樣有塊的概念,預設情況下HDFS的塊大小為128MB。但是與硬碟的文件系統不一樣,當文件小於HDFS的塊大小時,不會占用整個塊的空間,例如一個1MB大小的文件只占用了1MB的空間,而不是128MB。HDFS的塊比硬碟的大是為了最小化定址開銷,如果塊足夠大,從磁碟傳輸數據的時間會明顯大於定位這個塊位置需要的時間,所以傳輸一個大文件的時間取決於磁碟傳輸速率。
使用塊來管理分散式文件系統有很多好處,例如一個文件可以大於某個物理設備的容量,文件並不需要存儲在一個物理設備上。另一個好處是塊可以存儲在多個節點上,防止某個設備因為故障而丟失數據。
2、namenode和datanode
HDFS集群有兩種節點,分別是namenode和datanode。以管理節點——工作節點方式對外提供服務,即client連接namenode,一個namenode節點負責管理多個datanode。
namenode負責管理文件系統的命名空間,它維護文件系統元數據信息,例如目錄和文件名稱。這些信息以文件的形式永久保存在硬碟上:分別是命令空間鏡像文件和編輯日誌文件。namenode也記錄每個文件的塊所在的數據節點信息,但它並不永久保存塊的位置,這些信息會在啟動的時候根據數據節點的數據重建。
datanode就是文件系統的工作節點。它們根據需要存儲數據塊信息,並且定期向namenode發送所存儲的塊列表。HDFS通過把數據塊冗餘到多個datanode實現數據的安全性,預設副本數量為3。
namenode對外提供服務的時候需要把所有的文件元數據載入到記憶體,重啟的時候會利用鏡像文件和編輯日誌重建數據,鏡像文件類似於Redis的rdb文件。namenode會周期性歸檔編輯日誌來生成一個更加新的鏡像文件,由於歸檔編輯日誌的時候namenode也會對外提供服務,這段時間的操作會寫入到編輯日誌中,所以namenode需要鏡像文件和編輯日誌一起重建文件元數據。一個大規模的HDFS的恢復是非常消耗時間的(取決於所管理的數據規模),由於namenode是管理節點,沒有namenode整個文件系統將無法使用,所以相對datanode,namenode的高可用非常重要。本節主要為對HDFS簡單介紹,故先不討論HDFS的高可用方案。
3、聯邦HDFS
由於namenode在記憶體中維護系統內文件和數據塊的關係,很明顯namenode運行機器的記憶體會限制整個集群能存儲的文件數量。Hadoop在2.x版本引入的聯邦namenode,在聯邦環境下,每個namenode管理一個命名空間的一部分。例如一個namenode管理/a目錄下的所有文件,另一個namenode管理/b目錄下的所有文件。在聯邦環境下,每個namenode是獨立的,其中一個namenode失效了也不會影響其他namenode。
三、hadoop命令行
Hadoop命令行的fs參數提供了一些方便訪問文件系統的操作,所有的參數和格式可以在官網文檔FileSystemShell中查詢到,這裡簡單列舉一些常用的操作。
-cat:讀取文件,並且輸出到標準輸出
格式:hadoop fs -cat [-ignoreCrc] URI [URI ...]
例如:
-
列印本地文件系統下的text.txt內容
hadoop fs -cat file:///D:/test.txt
-
列印hdfs上根目錄下的a.txt內容
hadoop fs -cat hdfs://192.168.73.130:8082/a.txt
-ls:列出路徑下的文件內容
格式:hadoop fs -ls URI
例如:
-
列出hdfs上根目錄的文件內容
hadoop fs -ls hdfs://192.168.73.130:8082/
輸出每一列含義為:許可權,副本數,所屬用戶,所屬用戶組,文件大小,修改時間,文件名。許可權欄位由有7個標識位,第一個標識位含義是文件類型,如果是目錄則為d,然後緊跟的6個標識位表示所屬用戶、所屬用戶組、其他用戶的是否可讀、可寫、可執行,可執行許可權可以忽略,因為不能在HDFS中執行。第二列為副本數,目錄的元數據保存在namenode上,所以沒有副本數。
示例輸入如下:
hadoop fs -ls hdfs://192.168.73.130:8082/
Found 2 items
-rw-r--r-- 3 debian supergroup 19481 2023-01-25 15:30 hdfs://192.168.73.130:8082/a.txt
drwxr-xr-x - debian supergroup 0 2023-01-28 09:08 hdfs://192.168.73.130:8082/test
-copyFromLocal:複製本地文件到HDFS
格式:hadoop fs -copyFromLocal localsrc URI
例如:
-
把本地的文件a.txt複製到HDFS的/testDir/a.txt上
hadoop fs -copyFromLocal a.txt hdfs://192.168.73.130:8082/testDir/a.txt
-copyToLocal:複製HDFS文件到本地
格式:hadoop fs -copyToLocal URI localsrc
例如:
-
把HDFS的/testDir/a.txt複製到本地
hadoop fs -copyToLocal hdfs://localhost:8082/testDir/a.txt a.txt
四、FileSystem常用api
1、文件的讀取
使用方法FileSystem#open()可以打開一個FSDataInputStream輸入流,然後可以像讀取本地文件一樣文件中的數據。但是與一般的輸入流不一樣,FSDataInputStream實現了Seekable和介面PositionedReadable,它們分別支持隨機讀取和指定位置讀取。
Seekable的聲明如下:
public interface Seekable {
/**
* 設置下一次讀取的時,使用的偏移量
*/
void seek(long pos) throws IOException;
/**
* 返回當前讀取偏移量
*/
long getPos() throws IOException;
}
getPos返回當前讀取偏移量,seek設置下一次read的偏移量,偏移量是當前距離文件起始位置的位元組數。例如可以用如下代碼重覆讀取文件開頭的2048個位元組;
FSDataInputStream in = .....;
byte[] buff = new buff[2048];
in.read(buff);
in.seek(0);
in.read(buff);
seek使用起來很方便,但是是一個相對高開銷的操作,需要慎重使用。
PositionedReadable的聲明如下:
public interface PositionedReadable {
/**
* 從position指定的位置開始讀取length個長度的數據,複製到buffer的offset處,返回實際讀取到的位元組數
*/
int read(long position, byte[] buffer, int offset, int length)
throws IOException;
/**
* 從position指定的位置開始讀取length個長度的數據,複製到buffer的offset處
* 如果到達文件結尾拋出EOFException
*/
void readFully(long position, byte[] buffer, int offset, int length) throws IOException;
/**
* 從position指定的位置開始讀取buffer.length個長度的數據,複製到buffer的offset處
* 如果到達文件結尾拋出EOFException
*/
void readFully(long position, byte[] buffer) throws IOException;
}
read函數把指定偏移量數據讀取到buffer中,但是實際讀取的位元組數需要調用者接受返回值進行判斷。readFully效果也類似,但是如果到達文件結尾會拋出EOFException。PositionedReadable所有的方法都不會改變當前流讀取文件的位置,同時它的方法也都是線程安全的。
2、文件的寫入
傳入一個Path對象,然後使用FileSystem#create可以創建一個新文件,並且返回一個FSDataOutputStream對象。與java其他的api不一樣,調用create方法會自動創建父級目錄。
使用FileSystem#append()可以向一個已存在的文件尾追加內容,需要說明的是這個方法是一個可選的實現,並不是每一個文件系統都正常此方法。
3、目錄創建
調用FileSystem#mkdirs()方法可以新建一個目錄,通常創建文件不需要顯示調用此方法,因為創建文件會自動創建對應的父級目錄。
4、文件的刪除
調用FileSystem#delete()可以刪除一個文件或者目錄。
5、文件元數據信息
使用FileSystem#getFileStatus方法可以返回一個FileStatus對象來獲取文件或者目錄的的狀態信息,例如是否為目錄、許可權、文件長度等數據。
FileSystem#listStatus可以列出目錄下所有的文件信息。listStatus有多個重載方法,可以額外傳入一個org.apache.hadoop.fs.PathFilter用來過濾目錄的文件。
6、HDFS的一致性模型
對於創建一個目錄,HDFS可以保證操作是立即可見的。但是對於寫入數據並不能保證其可見性。例如對於以下一段Java程式代碼:
OutputStream out = ...;
out.write(buff);
out.flush();
如果是操作本地文件,調用flush方法,會把緩衝區數據刷新到硬碟上,保證其可見性。然而對於HDFS,即使調用了flush方法也不能保證可見性,需要等到數據超過一個塊之後才能對其他讀取進程可見。但是HDFS的實現提供了兩個方法用於保證可見性,分別是FSDataOutputStream#hflush和FSDataOutputStream#hsync,hsync和操作系統的sync方法類似,保證數據已經存儲在datanode的硬碟上,而hflush僅僅保證數據寫入到datanode的記憶體。調用close關閉流會自動調用一次hflush。
五、demo程式
我使用FileSystem常用api實現了一個客戶端demo,代碼地址在github 的hdfsApiExample模塊,打包此模塊可以得到一個hdfs-api-example-1.0-SNAPSHOT.jar的文件,它的使用方法如下:
參數:
- fs:指定hdfs的namenode地址,假設你有一個namenode地址是hdfs://192.168.73.130:8082
- u:操作hdfs的用戶,根據hdfs配置情況,寫入的時候可能需要這個參數
- o:實際需要執行的命令,分別支持ls(查詢目錄文件),rm(刪除),mkdir(創建目錄),cp(複製文件)
示例:
-
列出hdfs上,根目錄的文件
hadoop jar hdfs-api-example-1.0-SNAPSHOT.jar -fs hdfs://192.168.73.130:8082 -u debian -o ls /
-
刪除hdfs上,/cnblog目錄。使用額外的r參數遞歸刪除非空目錄
hadoop jar hdfs-api-example-1.0-SNAPSHOT.jar -fs hdfs://192.168.73.130:8082 -u debian -o rm r /cnblog
-
在根目錄下創建一個cnblog目錄
hadoop jar hdfs-api-example-1.0-SNAPSHOT.jar -fs hdfs://192.168.73.130:8082 -u debian -o mkdir /cnblog
-
複製本地文件test.txt到hdfs的根目錄上
hadoop jar hdfs-api-example-1.0-SNAPSHOT.jar -fs hdfs://192.168.73.130:8082 -u debian -o cp text.txt hdfs:/test.text
-
複製hdfs的根目錄文件text.txt到本地目錄上
hadoop jar hdfs-api-example-1.0-SNAPSHOT.jar -fs hdfs://192.168.73.130:8082 -u debian -o cp hdfs:/test.text text.txt