隨着數據量愈來愈大,在一個操做系統中存不下全部的數據。須要將這些數據分配到更多的操做系統中,帶來的問題是多操做系統不方便管理和維護。須要一種系統來管理多臺機器上的文件,這就是分佈式文件管理系統。HDFS是分佈式文件管理系統中的一種html
HDFS(Hadoop Distributed File System)它是一個文件系統,用於存儲文件,經過目錄樹來定位文件。其次,他是分佈式的,由不少服務器聯合起來實現其功能,集羣中的服務器有各自的角色java
HDFS 的使用場景:適合一次寫,屢次讀的場景,且不支持文件的修改。適合用來作數據分析node
HDFS 中的文件在物理上是分塊存儲(Block),塊的大小能夠手動配置參數 dfs.blocksize
來修改(Hadoop 2.x 是 128m,以前是 64m)linux
廣泛認爲,尋址時間(即查找目標 block 的時間)爲 10ms,而尋址時間爲傳輸時間的 1% 時,爲 HDFS 運行的理想最佳狀態。此時傳輸時間爲 10ms / 1% = 1000ms = 1s,而目前硬盤的傳輸速度廣泛爲 100m/s ,所以 block 的大小取 1s*100m/s = 100m。離它最近的 2 的次冪就是 128 了。這塊能夠看出,影響 block 大小的主要因素就是硬盤的讀取速度。所以當採用固態硬盤的時候徹底能夠把數值調整到 256 m 甚至更多。git
塊過小的時候,會增長尋址時間github
但當塊變得很大時,就要想辦法避免熱點數據的頻繁讀取了。這一點在 Google 的論文中有提到,論文中給到的解決思路是客戶端緩存,可是並無提具體實現 https://www.cnblogs.com/zzjhn/p/3834729.htmlshell
bin/hadoop fs 具體命令 OR bin/hdfs dfs 具體命令apache
dfs是fs的實現類緩存
命令 | 解釋 | 示例 | 備註 |
---|---|---|---|
-ls | 顯示目錄信息 | ||
-mkdir | 在HDFS上建立目錄 | hadoop fs -mkdir -p /user/keats/love | -p 建立多級目錄 |
-moveFromLocal | 從本地剪切粘貼到HDFS | hadoop fs -moveFromLocal ./yaya.txt /user/keats/love/ | 前面是來源路徑 後面是目標路徑,下同 |
-appendToFile | 追加一個文件到已經存在的文件末尾 | ||
-cat | 顯示文件內容 | ||
-chgrp 、-chmod、-chown | Linux文件系統中的用法同樣,修改文件所屬權限 | ||
-copyFromLocal | 從本地文件系統中拷貝文件到HDFS路徑去 | 同 -put | |
-copyToLocal | 從HDFS拷貝到本地 | 同 -get | |
-getmerge | 合併下載多個文件 | hadoop fs -getmerge /user/keats/love/* ./yaya.txt | |
-tail | 顯示一個文件的末尾 | ||
-rm | 刪除文件或文件夾 | ||
-rmdir | 刪除空目錄 | ||
-du | 統計文件夾的大小信息 | 能夠理解爲 disk use | |
-setrep | 設置HDFS中文件的副本數量 | 裏設置的副本數只是記錄在NameNode的元數據中,是否真的會有這麼多副本,還得看DataNode的數量。由於目前只有3臺設備,最多也就3個副本,只有節點數的增長到10臺時,副本數才能達到10 |
項目地址 https://github.com/keatsCoder/HdfsClientDemo安全
建立 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.10.1</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-client</artifactId> <version>2.10.1</version> </dependency> <dependency> <groupId>org.apache.hadoop</groupId> <artifactId>hadoop-hdfs</artifactId> <version>2.10.1</version> </dependency> <dependency> <groupId>jdk.tools</groupId> <artifactId>jdk.tools</artifactId> <version>1.8</version> <scope>system</scope> <systemPath>${JAVA_HOME}/lib/tools.jar</systemPath> </dependency> </dependencies>
建立 Java 類,HdfsClient 主要進行了三步操做
public class HdfsClient { static FileSystem fs; static { Configuration conf = new Configuration(); conf.set("fs.defaultFS", "hdfs://linux102:9000"); try { fs = FileSystem.get(conf); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) throws IOException { // 執行操做 mkDir(); // 釋放資源 fs.close(); } private static void mkDir() throws IOException { fs.mkdirs(new Path("/john/keats")); } }
嘗試運行,會獲得第一個錯誤,大意是權限被拒絕,這個時候就須要配置 JVM 參數 -DHADOOP_USER_NAME=root
來告訴集羣,使用 root 用戶進行操做
配置好以後再運行,會遇到第二個錯誤
Could not locate Hadoop executable: D:\develop\hadoop\bin\winutils.exe -see https://wiki.apache.org/hadoop/WindowsProblems
這是由於咱們以前配置環境的時候,解壓的 hadoop 文件 bin 目錄下沒有 winutils.exe 這個文件,根據後面地址 wiki 百科的指示,能夠下載該文件放在 bin 目錄下。可是目前那個文件的最新版本是 2.8.1,也許會存在某些方面不兼容的問題,目前還暫時沒有發現。所以能夠直接下載該版本使用 https://github.com/steveloughran/winutils/releases
FileSystem.get() 有一個重載方法,三個參數,第一個是 hadoop namenode 地址,第二個是 conf 對象,第三個是用戶名。能夠一次配好
測試方法詳見示例代碼 HdfsClient2.java 類
fs = FileSystem.get(new URI("hdfs://linux102:9000"), conf, "root");
在項目 resource 目錄下建立文件 zhangsan.txt
調用 copyFromLocalFile 方法上傳文件
private static void uploadFile() throws IOException { URL systemResource = ClassLoader.getSystemResource("zhangsan.txt"); String path = systemResource.getPath(); fs.copyFromLocalFile(new Path(path), new Path("/john/keats/love")); }
copyFromLocalFile 還有三個重載方法,分別提供如下功能
以前咱們在 hadoop 集羣配置的副本數量是 3 ,而 hadoop client 也支持兩種方式配置參數
加上默認的 default-xxxx.xml 一共四種配置的方式。他們的優先級是
conf > resources 下的配置文件 > hadoop 集羣配置文件 > default
ConfigFileTest.java 類對此處的配置進行的說明與測試,讀者能夠運行體驗
fs.copyToLocalFile(new Path("/three.txt"), new Path("D://zhangsan.txt"));
copyToLocalFile 還有兩個重載方法,分別添加了。具體代碼可參考 DownLoadFileTest.java
// 是否刪除源文件 boolean delSrc
// 是否使用RawLocalFileSystem做爲本地文件系統 // 默認是 false,目前比較直觀的就是 false 狀態下下載文件會同時生成 .crc 格式的校驗文件,設置爲 true 時不會生成 boolean useRawLocalFileSystem
刪除文件的API,第二個參數表示是否遞歸刪除。若是要刪除的 Path 對應的是文件夾,recursive 須要設置爲 true ,不然會拋異常。其實就至關於 rm -r
中的參數 -r
public abstract boolean delete(Path f, boolean recursive) throws IOException;
private static void deleteFile() throws IOException { // /john/keats 是文件夾目錄,遞歸設置爲 false 會報錯 PathIsNotEmptyDirectoryException: ``/john/keats is non empty': Directory is not empty // fs.delete(new Path("/john/keats"), false); // 先上傳,再刪除 HdfsClient2.uploadFile(true); fs.delete(new Path("/john/keats/love/zhangsan.txt"), true); }
這塊我在刪除以前添加了上傳操做,目的是爲了防止文件不存在。而上傳又存在一種可能就是目標文件已存在。這是個薛定諤的文件- -,所以我查看了 FileSystem 的上傳 API,他是提供 overwrite 開關的,默認是 true 即覆蓋目標文件
private static void renameFile() throws IOException { String dstFileName = "wangwu.txt"; HdfsClient2.uploadFile(); deleteFile(dstFileName); // 目標文件不存在,則改名成功 boolean rename = fs.rename(new Path("/john/keats/love/zhangsan.txt"), new Path("/john/keats/love/", dstFileName)); Assert.assertTrue(rename); // 目標文件存在,則改名失敗 boolean renameButDstIsExist = fs.rename(new Path("/john/keats/love/zhangsan.txt"), new Path("/john/keats/love/", dstFileName)); Assert.assertFalse(renameButDstIsExist); }
public static void listFiles() throws IOException { // 獲取文件詳情 RemoteIterator<LocatedFileStatus> listFiles = fs.listFiles(new Path("/"), true); while (listFiles.hasNext()) { LocatedFileStatus status = listFiles.next(); // 輸出詳情 // 文件名稱 System.out.println(status.getPath().getName()); // 長度 System.out.println(status.getLen()); // 權限 System.out.println(status.getPermission()); // 分組 System.out.println(status.getGroup()); // 獲取存儲的塊信息 BlockLocation[] blockLocations = status.getBlockLocations(); for (BlockLocation blockLocation : blockLocations) { // 獲取塊存儲的主機節點 String[] hosts = blockLocation.getHosts(); for (String host : hosts) { System.out.println(host); } } System.out.println("-----------分割線----------"); } }
判斷某路徑下的內容是文件仍是文件夾
public static void isFile() throws IOException { // FileStatus[] listStatus = fs.listStatus(new Path("/")); FileStatus[] listStatus = fs.listStatus(new Path("/three.txt")); for (FileStatus fileStatus : listStatus) { // 若是是文件 if (fileStatus.isFile()) { System.out.println("f:"+fileStatus.getPath().getName()); }else { System.out.println("d:"+fileStatus.getPath().getName()); } } }
// 從本地上傳到HDFS public static void copyFileFromDiskByIO() throws IOException { // 2 建立輸入流 FileInputStream fis = new FileInputStream(new File("D:/zhangsan.txt")); // 3 獲取輸出流 FSDataOutputStream fos = fs.create(new Path("/zhangsan.txt")); // 4 流對拷 IOUtils.copyBytes(fis, fos, conf); } // 從HDFS拷貝到本地 public static void copyFileFromHDFSByIO() throws IOException { FSDataInputStream fis = fs.open(new Path("/zhangsan.txt")); // 3 獲取輸出流 FileOutputStream fos = new FileOutputStream(new File("D:/zhangsan1.txt")); // 4 流的對拷 IOUtils.copyBytes(fis, fos, conf); }
/** * 從某個位置開始拷貝文件,用於讀取某個完整文件的部份內容 */ public static void copyFileSeek() throws Exception{ // 2 打開輸入流 FSDataInputStream fis = fs.open(new Path("/hadoop-2.10.1.tar.gz")); // 3 定位輸入數據位置 fis.seek(1024*1024*128); // 4 建立輸出流 FileOutputStream fos = new FileOutputStream(new File("D:/hadoop-2.7.2.tar.gz.part2")); // 5 流的對拷 IOUtils.copyBytes(fis, fos, conf); }
1)客戶端經過Distributed FileSystem模塊向NameNode請求上傳文件,NameNode檢查目標文件是否已存在,父目錄是否存在
2)NameNode返回是否能夠上傳
3)客戶端請求第一個 Block上傳到哪幾個DataNode服務器上(根據服務器距離以及負載排序,取前副本數個服務器返回)
4)NameNode返回3個DataNode節點,分別爲dn一、dn二、dn3
5)客戶端經過FSDataOutputStream模塊請求dn1上傳數據,dn1收到請求會繼續調用dn2,而後dn2調用dn3,將這個通訊管道創建完成
6)dn一、dn二、dn3逐級應答客戶端
7)客戶端開始往dn1上傳第一個Block(先從磁盤讀取數據放到一個本地內存緩存),以Packet爲單位,dn1收到一個Packet就會傳給dn2,dn2傳給dn3;dn1每傳一個packet會放入一個應答隊列等待應答
8)當一個Block傳輸完成以後,客戶端再次請求NameNode上傳第二個Block的服務器。(重複執行3-7步)
1)客戶端經過Distributed FileSystem向NameNode請求下載文件,NameNode經過查詢元數據,找到文件塊所在的DataNode地址
2)挑選一臺DataNode(就近原則,而後隨機)服務器,請求讀取數據
3)DataNode開始傳輸數據給客戶端(從磁盤裏面讀取數據輸入流,以Packet爲單位來作校驗)
4)客戶端以Packet爲單位接收,先在本地緩存,而後寫入目標文件
在HDFS寫數據的過程當中,NameNode會選擇距離待上傳數據最近距離的DataNode接收數據。那麼這個最近距離怎麼計算呢?
節點距離:兩個節點到達最近的共同祖先的距離總和
機架感知說明
For the common case, when the replication factor is three, HDFS’s placement policy is to put one replica on one node in the local rack, another on a different node in the local rack, and the last on a different node in a different rack.
這樣佈置,第一考慮的是速度,也兼顧了容災的要求
首先,咱們作個假設,若是存儲在NameNode節點的磁盤中,由於常常須要進行隨機訪問,還有響應客戶請求,必然是效率太低。所以,元數據須要存放在內存中。但若是隻存在內存中,一旦斷電,元數據丟失,整個集羣就沒法工做了。所以產生在磁盤中備份元數據的FsImage
這樣又會帶來新的問題,當在內存中的元數據更新時,若是同時更新FsImage,就會致使效率太低,但若是不更新,就會發生一致性問題,一旦NameNode節點斷電,就會產生數據丟失。所以,引入Edits文件(只進行追加操做,效率很高)。每當元數據有更新或者添加元數據時,修改內存中的元數據並追加到Edits中。這樣,一旦NameNode節點斷電,能夠經過FsImage和Edits的合併,合成元數據。
可是,若是長時間添加數據到Edits中,會致使該文件數據過大,效率下降,並且一旦斷電,恢復元數據須要的時間過長。所以,須要按期進行FsImage和Edits的合併,若是這個操做由NameNode節點完成,又會效率太低。所以,引入一個新的節點SecondaryNamenode,專門用於FsImage和Edits的合併
總體的操做機制和 Redis 差很少,FsImage 至關於 Redis 中的 RDB 快照,Edits 至關於 Redis 中的 AOF 日誌,二者結合。而 Redis 合併兩個文件是採用的 Fork 進程的方式
第一階段:NameNode啓動
(1)第一次啓動NameNode格式化後,建立Fsimage和Edits文件。若是不是第一次啓動,直接加載編輯日誌和鏡像文件到內存
(2)客戶端對元數據進行增刪改的請求
(3)NameNode記錄操做日誌,更新滾動日誌
(4)NameNode在內存中對數據進行增刪改
第二階段:Secondary NameNode工做
(1)Secondary NameNode詢問NameNode是否須要CheckPoint。直接帶回NameNode是否檢查結果
(2)Secondary NameNode請求執行CheckPoint
(3)NameNode滾動正在寫的Edits日誌
(4)將滾動前的編輯日誌和鏡像文件拷貝到Secondary NameNode
(5)Secondary NameNode加載編輯日誌和鏡像文件到內存,併合並
(6)生成新的鏡像文件fsimage.chkpoint
(7)拷貝fsimage.chkpoint到NameNode
(8)NameNode將fsimage.chkpoint從新命名成fsimage
1)一個數據塊在DataNode上以文件形式存儲在磁盤上,包括兩個文件,一個是數據自己,一個是元數據包括數據塊的長度,塊數據的校驗和,以及時間戳
2)DataNode啓動後向NameNode註冊,經過後,週期性(1小時)的向NameNode上報全部的塊信息
3)心跳是每3秒一次,心跳返回結果帶有NameNode給該DataNode的命令如複製塊數據到另外一臺機器,或刪除某個數據塊。若是超過10分鐘沒有收到某個DataNode的心跳,則認爲該節點不可用
4)集羣運行中能夠安全加入和退出一些機器
DataNode 也能夠配置成多個目錄,每一個目錄存儲的數據不同。不一樣於 NameNode 多目錄配置,NameNode 多個目錄直接的數據是同樣的,僅作備份和容災用。我想是由於 DataNode 已經使用副原本作備份了,若是還繼續在本機複製多份,不是頗有必要。而 NameNode 在未作高可用以前並無足夠的備份,所以產生了差別
hdfs-site.xml
<property> <name>dfs.datanode.data.dir</name> <value>file:///${hadoop.tmp.dir}/dfs/data1,file:///${hadoop.tmp.dir}/dfs/data2</value> </property>
(1)在hadoop104主機上再克隆一臺hadoop105主機
(2)修改IP地址和主機名稱
(3)刪除原來HDFS文件系統留存的文件(/opt/module/hadoop/data和log)
(4)source一下配置文件
(5)直接啓動DataNode,便可關聯到集羣
若是數據不均衡,可使用 ./start-balancer.sh
命令實現集羣的再均衡
可是這樣存在一個問題:若是某些惡意分子知道了 NameNode 的地址,即可以鏈接集羣並克隆出集羣的數據,這樣是極不安全的
只容許白名單內的地址鏈接 NameNode
在 NameNode 的 /opt/module/hadoop/etc/hadoop目錄下建立 dfs.hosts 文件,並添加以下主機名稱
linux102 linux103 linux104
在 NameNode 的 hdfs-site.xml 配置文件中增長 dfs.hosts 屬性
<property> <name>dfs.hosts</name> <value>/opt/module/hadoop/etc/hadoop/dfs.hosts</value> </property>
文件分發
xsync hdfs-site.xml
刷新NameNode
hdfs dfsadmin -refreshNodes
打開 Web 頁面,能夠看到不在白名單的 DataNode 會被下線
在黑名單上的節點會被強制退出
黑名單的配置 key 以下
<property> <name>dfs.hosts.exclude</name> <value>/opt/module/hadoop/etc/hadoop/dfs.hosts.exclude</value> </property>
須要注意
bin/hadoop distcp hdfs://linux102:9000/user/keats/hello.txt hdfs://linux103:9000/user/keats/hello.txt
開啓回收站功能,能夠將刪除的文件在不超時的狀況下,恢復原數據,起到防止誤刪除、備份等做用
快照至關於對目錄作一個備份,並不會馬上覆制全部文件。而是記錄文件變化