1. 程式人生 > 實用技巧 >你想了解的分散式檔案系統HDFS,看這一篇就夠了

你想了解的分散式檔案系統HDFS,看這一篇就夠了

1、分散式檔案系統

計算機叢集結構

分散式檔案系統把檔案分佈儲存到多個節點(計算機)上,成千上萬的計算機節點構成計算機叢集。

分散式檔案系統使用的計算機叢集,其配置都是由普通硬體構成的,與用多個處理器和專用高階硬體的並行化處理裝置相比,前者大大降低了硬體上的開銷。

分散式檔案系統的結構

分散式檔案系統在物理結構上是由眾多階段及節點構成的,而這些節點中分為兩類。一類是主節點(Master Node),又被稱為名稱節點(NameNode),另一類是從節點(Slave Node),又被稱為資料節點(DataNode)。

2、HDFS簡介

官方使用者指南:http://hadoop.apache.org/docs/stable/hadoop-project-dist/hadoop-hdfs/HdfsUserGuide.html

Hadoop是由HDFS和MapReduce兩大元件組成的,HDFS全稱為Hadoop Distributed File System(Hadoop 分散式檔案系統)。

它和現有的分散式檔案系統有很多共同點。但同時,它和其他的分散式檔案系統的區別也是很明顯的。HDFS是一個高度容錯性的系統,適合部署在廉價的機器上。HDFS能提供高吞吐量的資料訪問,非常適合大規模資料集上的應用。

HDFS要實現的目標:

  • 相容廉價的硬體裝置
  • 流資料讀寫
  • 大資料集
  • 簡單的檔案型別
  • 強大的跨平臺相容性

HDFS侷限性:

  • 不適合低延遲資料訪問
  • 無法高效儲存大量小檔案(與自身實現有關)
  • 不支援多使用者寫入及任意修改檔案

3、HDFS相關概念

“塊”在HDFS中作為最小儲存單位,預設一個塊為64MB。在HDFS中,一個檔案將會被分割成多個塊,儲存到各個資料節點。塊的大小遠遠高於普通檔案系統,可以最小化定址開銷。

HDFS中抽象的塊模型可以帶來如下好處:

  • 支援大規模檔案儲存

單個檔案被分成若干個塊,分別儲存到若干個資料節點中,其檔案大小不會受到單個節點容量的限制。

  • 簡化系統設計

檔案塊大小是固定的,可以很容易計算出一個節點中可以儲存多少個檔案塊。方便了元資料的管理,元資料不需要和檔案塊一起儲存,可以由其它系統負責管理元資料。

  • 適合資料備份

每個檔案塊都可以冗餘的儲存到多個數據節點上,當一個節點資料出錯時,就可以根據其他副本節點恢復資料。大大提高了系統的容錯性與高可用性。

名稱節點(NameNode)和資料節點(DataNode)

NameNode與SecondaryNameNode同為“名稱節點”。SecondaryNameNode作為二級名稱節點,它與NameNode的關係是:SecondaryNameNode是NameNode的冷備份。

屬性 功能 位置 內容
NameNode 儲存元資料 元資料儲存在記憶體中 儲存檔案、block、DataNode之間的對映關係
DataNode 儲存檔案內容 檔案內容儲存到磁碟 維護了block id到DataNode本地檔案的對映關係

名稱節點的資料結構

在HDFS中,名稱節點(NameNode)負責管理分散式檔案系統的名稱空間 (Namespace),儲存了兩個核心的資料結構,即FsImage和EditLog 。名稱節點記錄了每個檔案中各個塊所在的資料節點的位置資訊。

  • FsImage

用於維護檔案系統樹以及檔案樹中所有的檔案和資料夾的元資料 。

  • EditLog

操作日誌檔案,其中記錄了所有針對檔案的建立、刪除、重新命名等操作 。

FsImage

FsImage檔案包含檔案系統中所有目錄和檔案inode的序列化形式。每個inode是一 個檔案或目錄的元資料的內部表示,幷包含此類資訊:檔案的複製等級、修改和訪問 時間、訪問許可權、塊大小以及組成檔案的塊。對於目錄,則儲存修改時間、許可權和配 額元資料 。

FsImage檔案沒有記錄塊儲存在哪個資料節點。而是由名稱節點把這些對映保留在 記憶體中,當資料節點加入HDFS叢集時,資料節點會把自己所包含的塊列表告知給名 稱節點,此後會定期執行這種告知操作,以確保名稱節點的塊對映是最新的。

名稱節點的啟動

在名稱節點啟動的時候,它會將FsImage檔案中的內容載入到記憶體中,之後再執行 EditLog檔案中的各項操作,使得記憶體中的元資料和實際的同步,存在記憶體中的元數 據支援客戶端的讀操作。

一旦在記憶體中成功建立檔案系統元資料的對映,則建立一個新的FsImage檔案和一個空的EditLog檔案。

名稱節點起來之後,HDFS中的更新操作會重新寫到EditLog檔案中,因為FsImage 檔案一般都很大(GB級別的很常見),如果所有的更新操作都往FsImage檔案中添 加,這樣會導致系統執行的十分緩慢,但是,如果往EditLog檔案裡面寫就不會這樣 ,因為EditLog 要小很多。每次執行寫操作之後,且在向客戶端傳送成功程式碼之前, edits檔案都需要同步更新。

名稱節點執行期間EditLog不斷變大的問題

在名稱節點執行期間,HDFS的所有更新操作都是直接寫到EditLog中,久而久之, EditLog文 件將會變得很大 。

雖然這對名稱節點執行時候是沒有什麼明顯影響的,但是,當名稱節點重啟的時候,名稱節點 需要先將FsImage裡面的所有內容映像到記憶體中,然後再一條一條地執行EditLog中的記錄,當EditLog檔案非常大的時候,會導致名稱節點啟動操作非常慢,而在這段時間內HDFS系統處於安全模式,一直無法對外提供寫操作,影響了使用者的使用。

名稱節點執行期間EditLog不斷變大的問題,如何解決?答案是:SecondaryNameNode第二名稱節點

第二名稱節點是HDFS架構中的一個組成部分,它是用來儲存名稱節點中對HDFS元資料資訊的備份,並減少名稱節點重啟的時間。SecondaryNameNode一般是單獨執行在一臺機器上。

SecondaryNameNode的工作情況:

(1)SecondaryNameNode會定期和NameNode 通訊,請求其停止使用EditLog檔案,暫時將新的寫操作寫到一個新的檔案edit.new上來,這個操作是瞬間完成,上層寫日誌的函式完全感覺不到差別。

(2)SecondaryNameNode通過HTTP GET方式從NameNode上獲取到FsImage和EditLog檔案,並下載到本地的相應目錄下。

(3)SecondaryNameNode將下載下來的FsImage載入到記憶體,然後一條一條地執行EditLog檔案中的各項更新操作,使得記憶體中的 FsImage保持最新;這個過程就是EditLog和 FsImage檔案合併。

(4)SecondaryNameNode執行完(3)操作之後,會通過post方式將新的FsImage檔案傳送到NameNode節點上 。

(5)NameNode將從SecondaryNameNode接收到的新的FsImage替換舊的FsImage檔案, 同時將edit.new替換EditLog檔案,通過這個過程EditLog就變小了。

資料節點(DataNode)

資料節點是分散式檔案系統HDFS的工作節點,負責資料的儲存和讀取,會根據客 戶端或者是名稱節點的排程來進行資料的儲存和檢索,並且向名稱節點定期傳送自己 所儲存的塊的列表 。

每個資料節點中的資料會被儲存在各自節點的本地Linux檔案系統中。

4、HDFS體系結構

概述

HDFS採用了主從(Master/Slave)結構模型,一個HDFS叢集包括一個名稱節點( NameNode)和若干個資料節點(DataNode)。名稱節點作為中心伺服器, 負責管理檔案系統的名稱空間及客戶端對檔案的訪問。叢集中的資料節點一般是一個節點執行 一個數據節點程序,負責處理檔案系統客戶端的讀/寫請求,在名稱節點的統一排程下進行資料 塊的建立、刪除和複製等操作。每個資料節點的資料實際上是儲存在本地Linux檔案系統中的。

HDFS名稱空間管理

HDFS的名稱空間包含目錄、檔案和塊。

在HDFS1.0體系結構中,在整個HDFS叢集中只有一個名稱空間,並且只有唯一一個名稱節點,該節點負責對這個名稱空間進行管理 。

HDFS使用的是傳統的分級檔案體系,因此,使用者可以像使用普通檔案系統一樣,建立、刪除目錄和檔案,在目錄間轉移檔案,重新命名檔案等。

通訊協議

HDFS是一個部署在叢集上的分散式檔案系統,因此,很多資料需要通過網路進行傳輸。

所有的HDFS通訊協議都是構建在TCP/IP協議基礎之上的。

客戶端通過一個可配置的埠向名稱節點主動發起TCP連線,並使用客戶端協議與 名稱節點進行互動。

名稱節點和資料節點之間則使用資料節點協議進行互動。

客戶端與資料節點的互動是通過RPC(Remote Procedure Call)來實現的。在設 計上,名稱節點不會主動發起RPC,而是響應來自客戶端和資料節點的RPC請求。

客戶端

客戶端是使用者操作HDFS最常用的方式,HDFS在部署時都提供了客戶端。

HDFS客戶端是一個庫,暴露了HDFS檔案系統介面,這些介面隱藏了HDFS實現中的大部分複雜性。

嚴格來說,客戶端並不算是HDFS的一部分。

客戶端可以支援開啟、讀取、寫入等常見的操作,並且提供了類似Shell的命令列方式來訪問HDFS中的資料

此外,HDFS也提供了Java API,作為應用程式訪問檔案系統的客戶端程式設計介面。

HDFS體系結構的侷限性

HDFS只設置唯一一個名稱節點,這樣做雖然大大簡化了系統設計,但也帶來了一些 明顯的侷限性,具體如下:

(1)名稱空間的限制:名稱節點是儲存在記憶體中的,因此,名稱節點能夠容納的 物件(檔案、塊)的個數會受到記憶體空間大小的限制。

(2)效能的瓶頸:整個分散式檔案系統的吞吐量,受限於單個名稱節點的吞吐量。

(3)隔離問題:由於叢集中只有一個名稱節點,只有一個名稱空間,因此,無法 對不同應用程式進行隔離。

(4)叢集的可用性:一旦這個唯一的名稱節點發生故障,會導致整個叢集變得不 可用。

5、HDFS儲存原理

冗餘資料儲存

作為一個分散式檔案系統,為了保證系統的容錯性和可用性,HDFS採用了多副 本方式對資料進行冗餘儲存,通常一個數據塊的多個副本會被分佈到不同的資料節點 上,如圖所示,資料塊1被分別存放到資料節點A和C上,資料塊2被存放在資料節 點A和B上。

這種多副本方式具有以下幾個優點:

(1)加快資料傳輸速度。

(2)容易檢查資料錯誤。

(3)保證資料可靠性。

資料存取策略

資料存放

Block的副本放置策略:

第一個副本:放置在上傳檔案的資料節點;如果是叢集外提交,則隨機挑選一臺磁碟 不太滿、CPU不太忙的節點。

第二個副本:放置在與第一個副本不同的機架的節點上。

第三個副本:與第一個副本相同機架的其他節點上。

更多副本:隨機節點。

資料讀取

HDFS提供了一個API可以確定一個數據節點所屬的機架ID,客戶端也可以呼叫API 獲取自己所屬的機架ID。

當客戶端讀取資料時,從名稱節點獲得資料塊不同副本的存放位置列表,列表中包 含了副本所在的資料節點,可以呼叫API來確定客戶端和這些資料節點所屬的機架ID, 當發現某個資料塊副本對應的機架ID和客戶端對應的機架ID相同時,就優先選擇該副本讀取資料,如果沒有發現,就隨機選擇一個副本讀取資料。

資料錯誤與恢復

HDFS具有較高的容錯性,可以相容廉價的硬體,它把硬體出錯看作一種常態, 而不是異常,並設計了相應的機制檢測資料錯誤和進行自動恢復,主要包括以下幾種 情形:名稱節點出錯、資料節點出錯和資料出錯。

名稱節點出錯

名稱節點儲存了所有的元資料資訊,其中,最核心的兩大資料結構是FsImage和Editlog,如果這兩個檔案發生損壞,那麼整個HDFS例項將失效。因此,HDFS設定了備份機制,把這些核心檔案同步複製到備份伺服器SecondaryNameNode上。當名稱節點出錯時,就可以根據備份伺服器SecondaryNameNode中的FsImage和Editlog資料進行恢復。

資料節點出錯

每個資料節點會定期向名稱節點發送“心跳”資訊,向名稱節點報告自己的狀態。

當資料節點發生故障,或者網路發生斷網時,名稱節點就無法收到來自一些資料節點的心跳資訊,這時,這些資料節點就會被標記為“宕機”,節點上面的所有資料都會被標記為“不可讀”,名稱節點不會再給它們傳送任何I/O請求。

這時,有可能出現一種情形,即由於一些資料節點的不可用,會導致一些資料塊的副本數量小於冗餘因子。

名稱節點會定期檢查這種情況,一旦發現某個資料塊的副本數量小於冗餘因子,就會啟動資料冗餘複製,為它生成新的副本。

HDFS和其它分散式檔案系統的最大區別就是可以調整冗餘資料的位置。

資料出錯

網路傳輸和磁碟錯誤等因素,都會造成資料錯誤。

客戶端在讀取到資料後,會採用md5和sha1對資料塊進行校驗,以確定讀取到正確的資料。

在檔案被建立時,客戶端就會對每一個檔案塊進行資訊摘錄,並把這些資訊寫入到同一個路徑的隱藏檔案裡面。

當客戶端讀取檔案的時候,會先讀取該資訊檔案,然後,利用該資訊檔案對每個讀 取的資料塊進行校驗,如果校驗出錯,客戶端就會請求到另外一個數據節點讀取該檔案塊,並且向名稱節點報告這個檔案塊有錯誤,名稱節點會定期檢查並且重新複製這個塊。

6、HDFS讀寫過程

FileSystem是一個通用檔案系統的抽象基類,可以被分散式檔案系統繼承,所有可能使用 Hadoop檔案系統的程式碼,都要使用這個類。

Hadoop為FileSystem這個抽象類提供了多種具體實現。

DistributedFileSystem就是FileSystem在HDFS檔案系統中的具體實現。

FileSystem的open()方法返回的是一個輸入流FSDataInputStream物件,在HDFS檔案系統中 ,具體的輸入流就是DFSInputStream;FileSystem中的create()方法返回的是一個輸出流 FSDataOutputStream物件,在HDFS檔案系統中,具體的輸出流就是DFSOutputStream。

Configuration conf = new Configuration();
FileSystem fs = FileSystem.get(conf);
FSDataInputStream in = fs.open(new Path(uri));
FSDataOutputStream out = fs.create(new Path(uri));

備註:建立一個Configuration物件時,其構造方法會預設載入工程專案下兩個配置檔案,分別是 hdfs-site.xml以及core-site.xml,這兩個檔案中會有訪問HDFS所需的引數值,主要是 fs.defaultFS,指定了HDFS的地址(比如hdfs://localhost:9000),有了這個地址客戶端就可以 通過這個地址訪問HDFS了。

讀取檔案

import java.io.BufferedReader;
import java.io.InputStreamReader ;
import org.apache.hadoop.conf.Configuration ;
import org.apache.hadoop.fs.FileSystem ;
import org.apache.hadoop.fs.Path ;
import org.apache.hadoop.fs.FSDataInputStream ;
public class Chapter3 {
	public static void main(String[] args) {
		try {
			Configuration conf = new Configuration();
			FileSystem fs = FileSystem.get(conf);
			Path filename = new Path(“hdfs://localhost:9000/user/hadoop/test.txt");
			FSDataInputStream is = fs.open(filename);
			BufferedReader d = new BufferedReader(new InputStreamReader(is));
			String content = d.readLine(); //讀取檔案一行
			System.out.println(content);
			d.close(); //關閉檔案
			fs.close(); //關閉hdfs
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

寫入檔案

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.Path;
public class Chapter3 {
	public static void main(String[] args) {
		try {
			Configuration conf = new Configuration();
			FileSystem fs = FileSystem.get(conf);
			byte[] buff = "Hello world".getBytes(); // 要寫入的內容
			String filename = " hdfs://localhost:9000/user/hadoop/test.txt "; //要寫入的檔名
			FSDataOutputStream os = fs.create(new Path(filename));
			os.write(buff,0,buff.length);
			System.out.println("Create:"+ filename);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

7、HDFS程式設計實踐

首先啟動hadoop

$ cd /usr/local/hadoop
$ ./bin/hdfs namenode -format  # 格式化hdfs檔案系統,初始化時使用,之前執行後就不需再執行
$ ./bin/start-dfs.sh

常用命令

HDFS有很多shell命令,其中,fs命令可以說是HDFS最常用的命令。利用該命令可以 檢視HDFS檔案系統的目錄結構、上傳和下載資料、建立檔案等。

該命令的用法為: hadoop fs [genericOptions] [commandOptions]

備註:Hadoop中有三種Shell命令方式:

  1. hadoop fs適用於任何不同的檔案系統,比如本地檔案系統和HDFS檔案系統。

  2. hadoop dfs只能適用於HDFS檔案系統。

  3. hdfs dfs跟hadoop dfs的命令作用一樣,也只能適用於HDFS檔案系統。

例項

hadoop fs -ls :顯示指定的檔案的詳細資訊

hadoop fs -mkdir :建立指定的資料夾

例中“./”表示“/usr/local/hadoop/bin”路徑。

hadoop fs -cat :將指定的檔案的內容輸出到標準輸出(stdout)

hadoop fs -copyFromLocal :將本地原始檔複製到路徑指定的檔案或資料夾中

WEB管理介面

http://ip:50070,預設埠50070

利用Java API與HDFS進行互動

maven專案中引入

	<dependencies>
		<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-common -->
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-common</artifactId>
			<version>2.6.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-hdfs -->
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-hdfs</artifactId>
			<version>2.6.0</version>
		</dependency>
		<!-- https://mvnrepository.com/artifact/org.apache.hadoop/hadoop-client -->
		<dependency>
			<groupId>org.apache.hadoop</groupId>
			<artifactId>hadoop-client</artifactId>
			<version>2.6.0</version>
		</dependency>
	</dependencies>

寫一個FileSystem獲取工具類:

package com.yl.hdfs;

import java.io.IOException;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.FileSystem;

/**
 * 單例模式生成FileSystem
 * 
 * @author guilin
 *
 */
public class FileSystemFactory {

	private static class FileSystemFactoryHolder{

        public static FileSystem instance;

        static {
        	Configuration conf = new Configuration();
                conf.set("fs.defaultFS","hdfs://172.20.10.6:9000");
                conf.set("fs.hdfs.impl", "org.apache.hadoop.hdfs.DistributedFileSystem");
                try {
			instance = FileSystem.get(conf);
	        } catch (IOException e) {
		        e.printStackTrace();
	        }
            }
	}

	public static FileSystem getInsatnce() {
		return FileSystemFactoryHolder.instance;
	}

}

例項:利用hadoop 的java api檢測偽分散式檔案系統HDFS上是否存在某個檔案?

其中172.20.10.6是我hadoop機器上的ip地址。

package com.yl.hdfs;

import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class HdfsExists {
	public static void main(String[] args) {
		try {
			String filename = "/user/hadoop/input";
			FileSystem fs = FileSystemFactory.getInsatnce();
			if(fs.exists(new Path(filename))){
				System.out.println("檔案存在");
			}else{
				System.out.println("檔案不存在");
			}
			fs.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

驗證一下是否存在:

例項:寫HDFS上的檔案?

package com.yl.hdfs;

import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class HdfsWrite {
	public static void main(String[] args) {
		try {
			FileSystem fs = FileSystemFactory.getInsatnce();
			byte[] buff = "Hello world!".getBytes(); // 要寫入的內容
			String filename = "/user/22113/test"; //要寫入的檔名
			FSDataOutputStream os = fs.create(new Path(filename));
			os.write(buff,0,buff.length);
			System.out.println("Create:"+ filename);
			os.close();
			fs.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

例項:讀HDFS上的檔案?

package com.yl.hdfs;

import java.io.BufferedReader;
import java.io.InputStreamReader;

import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.Path;

public class HdfsRead {
	public static void main(String[] args) {
		try {
			FileSystem fs = FileSystemFactory.getInsatnce();
                        String filename = "/user/22113/test.txt"; //要讀的檔名
                        FSDataInputStream in = fs.open(new Path(filename));
                        BufferedReader bis = new BufferedReader(new InputStreamReader(in));
                        System.out.println(bis.readLine());
                        bis.close();
                        fs.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

常見錯誤:

  • java.net.ConnectException

Connection refused: no further information

此例環境:windows中安裝虛擬機器執行Hadoop。由於hadoop中core-site.xml中設定的fs.defaultFS是hdfs://localhost:9000,所以報錯,應該將lcoalhost替換成自己虛擬機器分配的ip地址,之後重啟hadoop。

  • 記得開啟對應的虛擬機器埠,埠未開啟會報錯。《CentOS7 中開放埠

  • org.apache.hadoop.security.AccessControlException

Permission denied: user=22113, access=WRITE, inode="/user/hadoop":hadoop:supergroup:drwxr-xr-x

沒有寫入許可權,應該設定該資料夾許可權。

檔案許可權由讀、可執行變成讀、寫、可執行。現在/user/22113資料夾皆可以寫入內容了。

  • org.apache.hadoop.ipc.RemoteException(java.io.IOException)

File /user/22113/test.txt could only be replicated to 0 nodes instead of minReplication (=1).  There are 1 datanode(s) running and 1 node(s) are excluded in this operation.

這個錯誤從網上找了很久,都沒解決。有人說是DataNode沒啟動,但是我用jps命令檢視,發現DataNode是在執行。還有人說是format多次NameNode與DataNode導致的,可是這都不是原因。後來突然想起關閉虛擬機器防火牆,發現就可以了,功能正常運作,具體原因待分析。

結尾

本文是根據中國大學MOOC網站上,課程《大資料技術原理與應用》的課件ppt撰寫的一篇博文。由於自己也是正在跟著這門課進行學習,所以很多專業性知識點都是擷取課件ppt上的內容。順便推薦一下這門課程,老師講解的知識點非常細緻,還有對操作步驟詳細記錄的部落格資源。

感謝廈門大學資料庫實驗室,感謝林子雨老師提供的這麼優秀的資源。