Hadoop-5-HDFS
1、概述
Hadoop Distributed File System (HDFS™),是一種分佈式文件管理系統,用於管理多個機器上的文件
它主要用於存儲文件,通過目錄樹結構來定位文件的位置。適合一次寫入,多次讀出的場景,不支持文件的修改,所以用它做數據的分析,而不是當作網盤
2、優缺點
【1】優點
- 高容錯性。容錯性體現在數據保存到多個副本上(DataNode),並且可以通過增加副本的數量(replication),來提高這種特性。並且當一個副本的數據丟失後,它還可以自動恢復
- 大數據量。一是處理數據的量級,可以達到TB、PB的級別。二是文件的數量,能夠處理百萬以上的規模
- 流式數據。一次寫入,多次寫出。只能追加,不能修改。保證數據的一致性
- 高可用性。可以將數據節點部署到多個廉價機器上
【2】缺點
- 不能實現低延遲的數據訪問。無法做到毫秒級的海量數據存儲
- 無法高效地存儲多個小文件。處理大量的小文件的存儲、目錄結構以及塊信息,會佔用NameNode的大量內存資源。此外小文件存儲時的尋址時間會超過其讀取時間,這違反了HDFS設計的初衷
- 不能併發寫入和隨機修改。對於一個文件,在同一時間內只能有一個寫入,不允許多個線程同時寫入。僅支持文件的追加,不支持文件的修改
3、架構設計
主要由NameNode,DataNode和SecondaryNameNode組成
需要注意的是SecondaryNameNode不是NameNode的熱備,所以當NameNode節點掛掉時,其並不能立即替換NameNode並提供服務。SecondaryNameNode是一個用於監控HDFS狀態的後臺輔助程序,它每隔一段時間就會獲取NameNode(HDFS元數據)的快照
4、塊
HDFS中的文件在物理上是分塊存儲(block),塊的大小可以通過etc/hadoop/hdfs-site.xml
中的dfs.blocksize
來設置。apache hadoop 2.9.2默認大小爲134217728(128MB)
HDFS的塊的大小比物理磁盤的塊的大小要大,這是爲了減少尋址的開銷。如果塊設置得足夠大,那麼數據傳輸的時間要明顯地久於塊尋址的時間
尋址時間爲傳輸時間的1%時,是最佳狀態。計算公式:文件傳輸時間 = 磁盤尋址時間 / 0.01
塊的大小 = 文件傳輸時間 * 磁盤傳輸速率
根據公式:假如尋址時間是10s,傳輸速率是100MB/s,那麼塊的大小 = 10s / 0.01 * 100MB/s = 100MB
5、命令操作【常用命令】
所有命令都是通過bin/hadoop fs 命令
的方式執行的。注意命令前面都有一個橫槓
【1】創建目錄
-mkdir -p
,選項p表示遞歸,和Linux命令一樣,不帶參數僅創建一級目錄
【2】查看目錄
-ls -R
,選項R表示遞歸,它會一直到目錄的最裏面
【3】刪除目錄
-rmdir
【4】上傳文件
-copyFromLocal /本地文件 /HDFS目錄
,複製本地文件到HDFS
-put /本地文件 /HDFS目錄
,和copyFromLocal命令一樣
-moveFromLocal /本地文件 /HDFS目錄
,移動本地文件到HDFS
【5】下載文件
-copyToLocal /HDFS文件 /本地目錄
,複製HDFS文件到本地
-get /HDFS文件 /本地目錄
,和copyToLocal命令一樣
-moveToLocal /HDFS文件 /本地目錄
,移動HDFS文件到本地。
Hadoop 2.9.2,提示:moveToLocal: Option ‘-moveToLocal’ is not implemented yet.
【6】追加文件
-appendToFile /本地文件 /HDFS文件
,將本地文件的內容追加到HDFS文件的末端
【7】查看文件
-cat /HDFS文件或目錄
,查看文件,可以使用*通配符,和Linux命令一樣
-tail -f /HDFS文件
,查看文件末尾,後41行,可以追加f選項,和Linux命令一樣
【8】刪除文件
-rm -r /HDFS目錄或文件
,刪除文件或目錄,和Linux命令一樣
6、Java客戶端操作
【1】準備環境
- Java
- Maven
- 和服務端一致的Hadoop,並解壓縮到無中文目錄
- 下載winutils,並將hadoop-2.8.3/bin目錄裏的
winutils.exe
複製到解壓縮後的hadoop的bin目錄當中 - 配置環境變量:
HADOOP_HOME
到解壓縮目錄 - 打開IDE。一定要先配置好環境變量再打開IDE
【2】MAVEN依賴
<properties>
<!-- 和服務端版本一致 -->
<hadoop.version>2.9.2</hadoop.version>
</properties>
<dependency>
<!-- hadoop-client間接依賴hadoop-common -->
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-client</artifactId>
<version>${hadoop.version}</version>
</dependency>
<dependency>
<groupId>org.apache.hadoop</groupId>
<artifactId>hadoop-hdfs</artifactId>
<version>${hadoop.version}</version>
</dependency>
【3】獲取文件系統
通過org.apache.hadoop.fs.FileSystem
對象的靜態方法來獲取
-
FileSystem get(Configuration conf) throws IOException
,需要傳遞一個org.apache.hadoop.conf.Configuration
對象,該配置對象的屬性,可以從etc/hadoop/core-site.xml
中獲取Configuration conf = new Configuration(); // NameNode服務器的IP,9000是默認端口 conf.set("fs.defaultFS", "hdfs://???.???.???.???:9000"); FileSystem fileSystem = null; try { fileSystem = FileSystem.get(conf); System.out.println(fileSystem); } catch (IOException e) { e.printStackTrace(); } finally { if (null != fileSystem) { try { fileSystem.close(); } catch (IOException e) { e.printStackTrace(); } } }
-
FileSystem get(final URI uri, final Configuration conf, String user) throws IOException, InterruptedException
URI uri = null; try { // NameNode服務器的IP,9000是默認端口 uri = new URI("hdfs://???.???.???.???:9000"); } catch (URISyntaxException e) { e.printStackTrace(); return; } Configuration conf = new Configuration(); FileSystem fileSystem = null; try { // 操作文件系統的用戶名 fileSystem = FileSystem.get(uri, conf, "???"); System.out.println(fileSystem); } catch (IOException | InterruptedException e) { e.printStackTrace(); } finally { if (null != fileSystem) { try { fileSystem.close(); } catch (IOException e) { e.printStackTrace(); } } }
-
【推薦】
FileSystem get(URI uri, Configuration conf) throws IOException
URI uri = null; try { // NameNode服務器的IP,9000是默認端口 uri = new URI("hdfs://???.???.???.???:9000"); } catch (URISyntaxException e) { e.printStackTrace(); return; } // 操作文件系統的用戶名。這句話也可以配置到vm參數當中-DHADOOP_USER_NAME=??? System.setProperty("HADOOP_USER_NAME", "???"); Configuration conf = new Configuration(); FileSystem fileSystem = null; try { fileSystem = FileSystem.get(uri, conf); System.out.println(fileSystem); } catch (IOException e) { e.printStackTrace(); } finally { if (null != fileSystem) { try { fileSystem.close(); } catch (IOException e) { e.printStackTrace(); } } }
【4】參數配置的優先級
通過org.apache.hadoop.conf.Configuration
對象配置參數的優先級是最高的。接着是classpath路徑下的配置文件,如core-site.xml
等。最後纔是默認配置的參數
【5】創建目錄
try {
boolean mkdirs = fileSystem.mkdirs(new Path("/?/?/?"));
System.out.println(mkdirs ? "成功" : "失敗");
} catch (IOException e) {
e.printStackTrace();
}
【6】查看目錄
try {
RemoteIterator<LocatedFileStatus> remoteIterator = fileSystem.listFiles(new Path("/"), true); // 第二個參數表示是否遞歸
System.out.println("Permission\tOwner\tGroup\t\tSize\tLast Modified\tPath\t\tReplication");
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("MM-dd HH:mm");
while (remoteIterator.hasNext()) {
LocatedFileStatus locatedFileStatus = remoteIterator.next();
String permission = locatedFileStatus.getPermission().toString();
StringBuilder line = new StringBuilder(permission).append("\t");
String owner = locatedFileStatus.getOwner();
line.append(owner).append("\t");
String group = locatedFileStatus.getGroup();
line.append(group).append("\t");
long size = locatedFileStatus.getLen();
line.append(size).append("\t");
long lastModified = locatedFileStatus.getModificationTime();
String time = simpleDateFormat.format(new Date(lastModified));
line.append(time).append("\t\t");
Path path = locatedFileStatus.getPath();
String pathStr = path.toString();
line.append(pathStr).append("\t\t");
short replication = locatedFileStatus.getReplication();
line.append(replication);
BlockLocation[] blockLocations = locatedFileStatus.getBlockLocations();
line.append(Arrays.asList(blockLocations)).append("\t");
System.out.println(line);
}
} catch (IOException e) {
e.printStackTrace();
}
【7】刪除目錄或文件
try {
boolean delete = fileSystem.delete(new Path("/?"), true); // 第二個參數表示是否遞歸
System.out.println(delete ? "成功" : "失敗");
} catch (IOException e) {
e.printStackTrace();
}
【8】上傳文件
// 複製文件
try {
fileSystem.copyFromLocalFile(new Path("?:\\???"), new Path("/???/???"));
} catch (IOException e) {
e.printStackTrace();
}
// 剪切文件
try {
fileSystem.moveFromLocalFile(new Path("?:\\???"), new Path("/???/???"));
} catch (IOException e) {
e.printStackTrace();
}
// 複製文件,流式方式
File file = new File("?:\\???");
try {
FileInputStream fis = new FileInputStream(file);
FSDataOutputStream fsos = fileSystem.create(new Path("/???/???"));
/*
* 第三個參數,緩衝容器大小
* 第四個參數,是否關閉輸入流和輸入流
*/
IOUtils.copyBytes(fis, fsos, 4096, true);
} catch (IOException e) {
e.printStackTrace();
}
【9】下載文件
try {
/*
* 第一個參數,是否刪除源文件。true: 剪切,false: 複製
* 最後一個參數,是否將RawLocalFileSystem作爲本地文件系統。推薦使用
*/
fileSystem.copyToLocalFile(false, new Path("?:\\???"), new Path("/???/???"), true);
} catch (IOException e) {
e.printStackTrace();
}
// 下載文件,流式方式
try {
FSDataInputStream fsis = fileSystem.open(new Path("/???/???");
File file = new File("?:\\???");
FileOutputStream fos = new FileOutputStream(file);
/*
* 第三個參數,緩衝容器大小
* 第四個參數,是否關閉輸入流和輸入流
*/
IOUtils.copyBytes(fsis, fos, 4096, true);
} catch (IOException e) {
e.printStackTrace();
}
// 下載2塊文件
try {
FSDataInputStream fsis = fileSystem.open(new Path("/???/jdk-8u212-linux-x64.tar.gz"));
File part1 = new File("?:\\part1");
FileOutputStream fos1 = new FileOutputStream(part1);
int bufferSize = 4096; // 緩衝大小
byte[] buffer = new byte[bufferSize];
long blockSize = 128 * 1024 * 1024; // 128MB -- 塊大小
for (int offset = 0; offset < blockSize; offset += bufferSize) {
int read = fsis.read(buffer, 0, bufferSize);
fos1.write(buffer, 0, read);
}
IOUtils.closeStream(fos1); // 關閉輸出流
File part2 = new File("?:\\part2");
FileOutputStream fos2 = new FileOutputStream(part2);
fsis.seek(blockSize);
IOUtils.copyBytes(fsis, fos2, bufferSize, true); // 複製塊1,並關閉輸入流和輸出流
} catch (IOException e) {
e.printStackTrace();
}
【10】重命名目錄或文件(移動操作)
try {
boolean rename = fileSystem.rename(new Path("/?/?"), new Path("/?/?"));
System.out.println(rename ? "成功" : "失敗");
} catch (IOException e) {
e.printStackTrace();
}
【11】判斷是否是文件還是目錄
try {
Path path = new Path("/?/?");
boolean file = fileSystem.isFile(path);
boolean directory = fileSystem.isDirectory(path);
System.out.println(file ? "是文件" : "不是文件!");
System.out.println(directory ? "是目錄" : "不是目錄!");
} catch (IOException e) {
e.printStackTrace();
}
注意:如果目錄或文件不存在時,這2種判斷都會返回false
7、機架感知(Rack Awareness)
Hadoop具有機架感知的功能。 將一個塊的分片複製品放在不同的機架上,通過這種塊放置的方式,外加使用機架感知來實現容錯。 這可以在羣集發生網絡切換故障或分區時,提供數據的可用性
就默認分片數爲3而言:如果本機DataNode需要存儲數據,則Hadoop會將DataNode的副本放到本機上,否則就隨機放到其他DataNode上。同時會在另外一機架上的節點放置副本。最後一副本放到同一機架上的不同節點
這樣做可以減少機架間的寫入流量,從而提高寫入效率。因爲機架故障的可能性遠小於節點故障的可能性,所以這種策略不會影響數據的可靠性和可用性。此外也減少了讀取數據時使用的聚合網絡帶寬,因爲塊放到了2個唯一的機架上,而不是3個。三分之一的副本存放到一個節點上,三分之二的副本存放到一個機架上。這種策略可以提高寫入性能,而不會影響數據的可靠性或可讀性
8、文件上傳和文件下載的工作流程
【1】文件上傳
- 客戶端請求NameNode,是否可以進行文件的上傳
- 當客戶端收到可以上傳的響應後,依次順序上傳block0(0 ~ 128MB),block1(128MB ~ 256MB)到DataNode
- 獲取所有需要上傳的DataNode的節點信息,依次請求建立傳輸通道
- 收到DataNode響應,傳輸通道建立完畢,開始傳輸數據
【2】文件下載
- 客戶端請求NameNode,獲取block信息
- 收到NameNode返回的元數據信息,請求對應的DataNode,獲取所需的block數據
- 合併所有的block數據爲一個完整的數據,最終得到完整數據
9、NameNode和SecondaryNameNode工作機制
【1】NameNode啓動時的工作機制
- NameNode將鏡像文件(FsImage)加載到內存中
- NameNode將編輯日誌(EditLog)加載到內存中
- 保存檢查點位置(checkpoint)
- 進入安全模式(Safe mode)
這一過程,可以通過NameNode的管理頁面來查看,在Startup Progress
選項卡頁面當中
Elapsed Time: ??? sec, Percent Complete: 100%
Phase | Completion | Elapsed Time |
---|---|---|
Loading fsimage /???/dfs/name/current/fsimage_??? ??? KB | 100% | ??? sec |
inodes (???/???) | 100% | |
delegation tokens (???/???) | 100% | |
cache pools (???/???) | 100% | |
Loading edits | 100% | ??? sec |
/???/dfs/name/current/edits_???-??? ??? MB (???/???) | 100% | |
Saving checkpoint | 100% | ??? sec |
inodes /???/dfs/name/current/fsimage.ckpt_??? (???/???) | 100% | |
delegation tokens /???/dfs/name/current/fsimage.ckpt_??? (???/???) | 100% | |
cache pools /???/dfs/name/current/fsimage.ckpt_??? (???/???) | 100% | |
Safe mode | 100% | ??? sec |
awaiting reported blocks (???/???) | 100% |
每一個block佔元數據的大小爲150字節,由於NameNode在啓動時,會將這些都加載到內存當中,而內存是有上限的。所以這就是Hadoop不宜存儲過多小文件的原因,它會佔用大量的內存空間
【2】SecondaryNameNode運行時的工作機制
- 請求NameNode是否需要檢查點(Checkpoint)
- 如果需要Checkpoint,則執行
- 複製NameNode的編輯日誌(edits),包括已經執行完畢的(例如:edits_0000000000000000001-0000000000000000002)以及正在執行的(例如:edits_inprogress_0000000000000010453)
- 加載到內存中,合併生成新的鏡像文件(fsimage.checkpoint)
- 複製鏡像文件(fsimage.checkpoint)到NameNode
將編輯日誌(EditLog)中的所有事務保存到鏡像文件(FsImage)裏,並加載到內存中,然後結轉舊的編輯日誌(EditLog),這個過程稱爲檢查點(checkpoint)
觸發SecondaryNameNode請求NameNode查看是否需要執行檢查點(checkpoint),是由2個參數配置來決定的,在etc/hadoop/hdfs-site.xml
中
<!-- 執行檢查點的週期,單位:秒。默認爲1小時 -->
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>3600</value>
</property>
<!-- 執行編輯日誌的事務次數。默認爲一百萬次 -->
<property>
<name>dfs.namenode.checkpoint.txns</name>
<value>1000000</value>
</property>
可以通過前端管理頁面來查看SecondaryNameNode的概覽,鏈接是:http://SecondaryNameNode服務主機的IP:50090
包括了:Hadoop的版本(Version),NameNode的服務地址(NameNode Address),啓動時間(Started),上次檢查點時間(Last Checkpoint)以及檢查點週期(Checkpoint Period)和HDFS事務執行次數上限(Checkpoint Transactions)
注意:hadoop-2.9.2
版本的SecondaryNameNode管理頁面JS有一個名爲moment
的function未定義,所以在加載頁面時不會展示數據。解決辦法是打開瀏覽器的開發者工具裏的Sources
選項卡,修改moment
函數
'date_tostring' : function (v) {
// return moment(Number(v)).format('ddd MMM DD HH:mm:ss ZZ YYYY');
return new Date(v); // 直接新建時間對象並返回即可
},
在dfs-dust.js:61
處打斷點,修改並保存JS,放開斷點即可
【3】NameNode運行時的工作機制
接收SecondaryNameNode發來的鏡像文件(fsimage.checkpoint),並重命名爲fsimage
10、編輯日誌和鏡像文件
【1】概念
EditLog文件位於??/dfs/name/current/
目錄裏,以edits_
開頭。它保存了HDFS的所有更新操作的記錄的序列化信息
seen_txid文件保存了正在執行過程中的EditLog文件(edits_inprogress_???
)的事務ID
FsImage文件位於??/dfs/name/current/
目錄裏,以fsimage_
開頭。它保存了HDFS的所有目錄和文件的idnode的序列化信息,是HDFS元數據的檢查點(checkpoint)的持久化形態
VERSION文件,保存了NameNode的唯一標識(namespaceID),全局唯一的集羣ID(clusterID),以及存儲形態(storageType=NAME_NODE)
SecondaryNameNode和NameNode相比,其???/dfs/namesecondary/current/
目錄裏,僅僅少了正在執行過程中的EditLog文件(edits_inprogress_???)和seen_txid文件。這樣設計是爲了方便當NameNode節點故障時,並且沒有及時備份數據,可以通過SecondaryNameNode來恢復NameNode
【2】反序列化EditLog和FsImage
可以反序列化編輯日誌,使用命令:bin/hdfs oev -p 反序列化的文件類型 -i 編輯日誌路徑 -o 反序列化後的文件路徑
可選的反序列化的文件類型有:binary (native binary format that Hadoop uses), xml (default, XML format), stats (prints statistics about edits file)。可以看到默認是XML類型
反序列化鏡像文件,使用命令:bin/hdfs oiv -p 反序列化的文件類型 -i 鏡像文件路徑 -o 反序列化後的文件路徑
可選的反序列化的文件類型有:(XML|FileDistribution|ReverseXML|Web|Delimited) The default is Web。可以看到默認是Web類型。推薦使用XML
【3】反序列化後的EditLog
<?xml version="1.0" encoding="UTF-8"?>
<EDITS>
<!-- EditLog版本號 -->
<EDITS_VERSION>-63</EDITS_VERSION>
<RECORD>
<!-- 操作類型:開始記錄EditLog -->
<OPCODE>OP_START_LOG_SEGMENT</OPCODE>
<DATA>
<!-- 事務ID -->
<TXID>1</TXID>
</DATA>
</RECORD>
<RECORD>
<!-- 操作類型:結束記錄EditLog -->
<OPCODE>OP_END_LOG_SEGMENT</OPCODE>
<DATA>
<TXID>2</TXID>
</DATA>
</RECORD>
<!-- 另外一個EditLog -->
<RECORD>
<!-- 操作類型:創建目錄 -->
<OPCODE>OP_MKDIR</OPCODE>
<DATA>
<TXID>4</TXID>
<!-- 文件大小:0,目錄 -->
<LENGTH>0</LENGTH>
<!-- inodeID -->
<INODEID>16386</INODEID>
<!-- 路徑 -->
<PATH>/tmp</PATH>
<!-- 創建時間戳 -->
<TIMESTAMP>1565635741266</TIMESTAMP>
<!-- 權限 -->
<PERMISSION_STATUS>
<!-- 所有者 -->
<USERNAME>hadoop</USERNAME>
<!-- 所屬組 -->
<GROUPNAME>supergroup</GROUPNAME>
<MODE>504</MODE>
</PERMISSION_STATUS>
</DATA>
</RECORD>
<RECORD>
<!-- 操作類型:新建文件 -->
<OPCODE>OP_ADD</OPCODE>
<DATA>
<TXID>10315</TXID>
<LENGTH>0</LENGTH>
<INODEID>18322</INODEID>
<PATH>/???/_temporary/???/_temporary/attempt_1565637203418_0001_r_000000_0/part-r-00000</PATH>
<!-- 分片數 -->
<REPLICATION>4</REPLICATION>
<MTIME>1565637361625</MTIME>
<ATIME>1565637361625</ATIME>
<!-- 塊大小:128MB -->
<BLOCKSIZE>134217728</BLOCKSIZE>
<!-- 客戶端名稱 -->
<CLIENT_NAME>DFSClient_attempt_1565637203418_0001_r_000000_0_950599497_1</CLIENT_NAME>
<!-- 客戶端IP -->
<CLIENT_MACHINE>???.???.???.???</CLIENT_MACHINE>
<OVERWRITE>false</OVERWRITE>
<PERMISSION_STATUS>
<USERNAME>hadoop</USERNAME>
<GROUPNAME>supergroup</GROUPNAME>
<MODE>420</MODE>
</PERMISSION_STATUS>
<RPC_CLIENTID>fb6e1e9c-f8a4-4428-a6f0-12304607e6c1</RPC_CLIENTID>
<RPC_CALLID>4</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<!-- 操作類型:分配塊ID -->
<OPCODE>OP_ALLOCATE_BLOCK_ID</OPCODE>
<DATA>
<TXID>10316</TXID>
<BLOCK_ID>1073743480</BLOCK_ID>
</DATA>
</RECORD>
<RECORD>
<!-- 操作類型:添加塊 -->
<OPCODE>OP_ADD_BLOCK</OPCODE>
<DATA>
<TXID>10318</TXID>
<PATH>/???/_temporary/???/_temporary/attempt_1565637203418_0001_r_000000_0/part-r-00000</PATH>
<BLOCK>
<BLOCK_ID>1073743480</BLOCK_ID>
<NUM_BYTES>0</NUM_BYTES>
<GENSTAMP>2656</GENSTAMP>
</BLOCK>
<RPC_CLIENTID></RPC_CLIENTID>
<RPC_CALLID>-2</RPC_CALLID>
</DATA>
</RECORD>
<RECORD>
<!-- 操作類型:刪除臨時目錄 -->
<OPCODE>OP_DELETE</OPCODE>
<DATA>
<TXID>10324</TXID>
<LENGTH>0</LENGTH>
<PATH>/???/_temporary</PATH>
<TIMESTAMP>1565637361871</TIMESTAMP>
<RPC_CLIENTID>4e4b34de-f566-4b67-a350-e014ebc27e39</RPC_CLIENTID>
<RPC_CALLID>39</RPC_CALLID>
</DATA>
</RECORD>
</RECORD>
<RECORD>
<!-- 操作類型:重命名(執行MapReduce任務) -->
<OPCODE>OP_RENAME</OPCODE>
<DATA>
<TXID>10354</TXID>
<LENGTH>0</LENGTH>
<SRC>/tmp/hadoop-yarn/staging/history/done_intermediate/hadoop/job_1565637203418_0001_conf.xml</SRC>
<DST>/tmp/hadoop-yarn/staging/history/done/2019/08/13/000000/job_1565637203418_0001_conf.xml</DST>
<TIMESTAMP>1565637363513</TIMESTAMP>
<OPTIONS>TO_TRASH</OPTIONS>
<RPC_CLIENTID>696288b7-d3ff-4bf2-a17b-5dc108925fc9</RPC_CLIENTID>
<RPC_CALLID>20</RPC_CALLID>
</DATA>
</RECORD>
<!-- 另外一個EditLog -->
<RECORD>
<!-- 操作類型:重命名(上傳文件) -->
<OPCODE>OP_RENAME_OLD</OPCODE>
<DATA>
<TXID>25</TXID>
<LENGTH>0</LENGTH>
<SRC>/???/???._COPYING_</SRC>
<DST>/???/???</DST>
<TIMESTAMP>1566211667239</TIMESTAMP>
<RPC_CLIENTID>a3bffd43-b49b-4c0b-b4d1-394feb9fd9a2</RPC_CLIENTID>
<RPC_CALLID>8</RPC_CALLID>
</DATA>
</RECORD>
</EDITS>
【4】反序列化後的FsImage
<?xml version="1.0"?>
<fsimage>
<!-- 版本信息 -->
<version>
<layoutVersion>-63</layoutVersion>
<onDiskVersion>1</onDiskVersion>
<oivRevision>826afbeae31ca687bc2f8471dc841b66ed2c6704</oivRevision>
</version>
<!-- NameNode信息 -->
<NameSection>
<!-- 對應VERSION文件裏的namespaceID -->
<namespaceId>598335362</namespaceId>
<genstampV1>1000</genstampV1>
<genstampV2>2670</genstampV2>
<genstampV1Limit>0</genstampV1Limit>
<lastAllocatedBlockId>1073743492</lastAllocatedBlockId>
<txid>10455</txid>
</NameSection>
<!-- 目錄和文件信息 -->
<INodeSection>
<!-- 最後一個InodeId -->
<lastInodeId>18346</lastInodeId>
<numInodes>42</numInodes>
<inode>
<id>16385</id>
<!-- 這是個目錄 -->
<type>DIRECTORY</type>
<!-- /:根路徑 -->
<name/>
<mtime>1566131716332</mtime>
<!-- 權限,所有者:所屬組:0權限信息 -->
<permission>hadoop:supergroup:0755</permission>
<nsquota>9223372036854775807</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>16386</id>
<type>DIRECTORY</type>
<!-- 目錄名稱 -->
<name>tmp</name>
<mtime>1565636758639</mtime>
<permission>hadoop:supergroup:0770</permission>
<nsquota>-1</nsquota>
<dsquota>-1</dsquota>
</inode>
<inode>
<id>18346</id>
<!-- 這是個文件 -->
<type>FILE</type>
<!-- 文件名稱 -->
<name>jdk-8u212-linux-x64.tar.gz</name>
<!-- 分片數量 -->
<replication>4</replication>
<mtime>1566132732553</mtime>
<atime>1566132726115</atime>
<preferredBlockSize>134217728</preferredBlockSize>
<permission>hadoop:supergroup:0644</permission>
<blocks>
<block>
<id>1073743491</id>
<genstamp>2669</genstamp>
<numBytes>134217728</numBytes>
</block>
<block>
<id>1073743492</id>
<genstamp>2670</genstamp>
<numBytes>60795424</numBytes>
</block>
</blocks>
<storagePolicyId>0</storagePolicyId>
</inode>
</INodeSection>
<INodeReferenceSection/>
<!-- 快照信息 -->
<SnapshotSection>
<snapshotCounter>0</snapshotCounter>
<numSnapshots>0</numSnapshots>
</SnapshotSection>
<!-- 目錄的父子關係 -->
<INodeDirectorySection>
<directory>
<parent>16385</parent>
<child>18340</child>
<child>16392</child>
<child>16386</child>
</directory>
<directory>
<parent>16386</parent>
<child>16387</child>
<child>18243</child>
</directory>
</INodeDirectorySection>
<FileUnderConstructionSection/>
<SecretManagerSection>
<currentId>0</currentId>
<tokenSequenceNumber>0</tokenSequenceNumber>
<numDelegationKeys>0</numDelegationKeys>
<numTokens>0</numTokens>
</SecretManagerSection>
<CacheManagerSection>
<nextDirectiveId>1</nextDirectiveId>
<numDirectives>0</numDirectives>
<numPools>0</numPools>
</CacheManagerSection>
</fsimage>
11、滾動編輯日誌
使用命令bin/hdfs dfsadmin -rollEdits
,這樣會結轉當前正在執行中的EditLog(edits_???-???
),並生成一個新的正在執行過程中的EditLog(edits_inprogress_???
)
Successfully rolled edit logs.
New segment starts at txid ???
12、通過SecondaryNameNode來恢復NameNode
【1】手動方式
- 模擬NameNode故障,
jps
查看NameNode的進程號,通過命令kill -9 NameNode的進程ID
強制停止NameNode - 首先刪除
???/dfs/name
目錄裏的所有文件 - 使用
scp
複製SecondaryNameNode的???/dfs/namesecondary
目錄裏的所有文件到NameNode的???/dfs/name
目錄裏,並刪除複製後的name目錄裏的in_use.lock
文件 - 啓動NameNode,
sbin/hadoop-daemon.sh start namenode
- 查看是否可以訪問NameNode的前端管理頁面,並測試HDFS的文件上傳和文件下載
【2】命令方式
-
模擬NameNode故障,
jps
查看NameNode的進程號,通過命令kill -9 NameNode的進程ID
強制停止NameNode -
修改NameNode節點的
etc/hadoop/hdfs-site.xml
文件<!-- 修改執行檢查點的週期爲2分鐘,這是爲了保證能夠及時同步SecondaryNameNode的數據。默認爲1小時 --> <property> <name>dfs.namenode.checkpoint.period</name> <value>120</value> </property> <!-- 指定NameNode的name目錄的路徑。默認爲file://${hadoop.tmp.dir}/dfs/name --> <property> <name>dfs.namenode.name.dir</name> <value>/???/dfs/name</value> </property>
-
使用
scp
複製SecondaryNameNode的???/dfs/namesecondary
目錄到NameNode的???/dfs
目錄下,和???/dfs/name
平級即可。並刪除複製後的namesecondary目錄裏的in_use.lock
文件 -
刪除
???/dfs/name
目錄裏的所有文件 -
執行導入檢查點數據命令,
bin/hdfs namenode -importCheckpoint
執行命令後,會發現NameNode被啓動起來。命令執行過程較長,如果此時訪問NameNode的前端管理頁面,會發現提示信息:
Upgrade in progress. Not yet finalized.
當看到process的信息後,就可以Ctrl + C停止命令了
-
啓動NameNode,
sbin/hadoop-daemon.sh start namenode
-
查看是否可以訪問NameNode的前端管理頁面,並測試HDFS的文件上傳和文件下載
13、安全模式(Safemode)
【1】概念
當NameNode在剛啓動時,會進入安全模式,此時HDFS對於客戶端來說是隻讀的
在系統正常運行期間,NameNode會在內存中保存所有的塊位置的映射信息。而在安全模式下,每個DataNode會向NameNode發送最新的塊列表信息。NameNode瞭解的塊位置信息越多,它可越高效地運行文件系統
一旦滿足最小副本條件,NameNode會在30秒後退出安全模式。最小副本條件是指,在整個文件系統當中,99.9%的塊滿足最小副本級別,最小副本級別可以通過etc/hadoop/hdfs-site.xml
的dfs.replication.min
參數來配置,默認值爲1
啓動一個剛格式化的HDFS集羣時,不會進入安全模式,是因爲此時還沒有任何塊信息
【2】相關命令
-
查看當前是否在安全模式當中
bin/hdfs dfsadmin -safemode get
-
進入安全模式
bin/hdfs dfsadmin -safemode enter
-
退出安全模式
bin/hdfs dfsadmin -safemode leave
-
等待安全模式退出
bin/hdfs dfsadmin -safemode wait
#!/bin/bash
bin/hdfs dfsadmin -safemode wait
echo "退出安全模式!"
# 執行HDFS文件操作
# bin/hadoop fs -???
14、NameNode多目錄配置
在etc/hadoop/hdfs-site.xml
中配置
<!-- 指定NameNode的name目錄的路徑,多個目錄之間用逗號隔開。默認爲file://${hadoop.tmp.dir}/dfs/name -->
<property>
<name>dfs.namenode.name.dir</name>
<value>file://${hadoop.tmp.dir}/dfs/name1,file://${hadoop.tmp.dir}/dfs/name2</value>
</property>
注意:配置此項後,再次啓動集羣時需要格式化NameNode
15、DataNode工作機制
- DataNode啓動後,向NameNode發起註冊
- 註冊成功後,每隔1個小時上傳一次所有塊的位置信息
- NameNode每隔3秒鐘(可配置),執行一次心跳檢測,DataNode接收到心跳檢測並返回給NameNode發來的命令信息
- 如果超過10分鐘(可配置)[外加30秒],NameNode沒有收到DataNode傳來的心跳檢測信息,則認爲該節點不可用
DataNode不同於NameNode,其無需進行格式化,在初始化過程中,創建好數據目錄
DataNode保存了數據,數據長度(文件大小),校驗和(CRC),以及時間戳等信息。這些位於???/dfs/data/current
目錄當中
DataNode校驗和使用CRC算法(循環冗餘校驗)。對原始數據進行CRC的校驗計算,然後和傳輸過來的CRC校驗位進行對比,查看結果是否一致
心跳檢測週期和心跳超時時間,可以在etc/hadoop/hdfs-site.xml
中來配置
<!-- 心跳檢測DataNode的超時時間,單位:毫秒。默認爲300秒(5分鐘) -->
<property>
<name>dfs.namenode.heartbeat.recheck-interval</name>
<value>300000</value>
</property>
<!-- DataNode的心跳檢測間隔,單位:秒。默認爲3秒 -->
<property>
<name>dfs.heartbeat.interval</name>
<value>3</value>
</property>
心跳超時時間 = 2 * dfs.namenode.heartbeat.recheck-interval + 10 * dfs.heartbeat.interval
所以默認心跳超時時間是10分鐘
一旦NameNode將最近沒有發送heartbeat消息的DataNode標記爲已死亡,就不會再將新的IO請求轉發給它,並且對於HDFS來說,它是不可用的。這就有可能降低某些block的分片數量(replication factor)
16、DataNode多目錄配置
在etc/hadoop/hdfs-site.xml
中配置
<!-- 指定NameNode的name目錄的路徑,多個目錄之間用逗號隔開。默認爲file://${hadoop.tmp.dir}/dfs/data -->
<property>
<name>dfs.datanode.data.dir</name>
<value>file://${hadoop.tmp.dir}/dfs/data1,file://${hadoop.tmp.dir}/dfs/data2</value>
</property>
注意:配置此項後,再次啓動集羣時需要格式化NameNode
17、集羣節點的動態增加和動態移除
【1】動態新增節點
-
準備好新的機器,配置好靜態IP,host和防火牆,以及Java運行環境和環境變量,並新建執行Hadoop的用戶
-
使用
scp
命令複製NameNode服務節點的Hadoop目錄到新的機器上 -
配置好其他節點的host
-
使用命令
ssh-copy-id 用戶名@新機器host
,爲NameNode節點和ResourceManager節點,創建新的機器的免密登錄 -
① 在所有節點的
etc/hadoop
目錄下,創建名爲dfs.hosts
的文件,並添加所有節點的IP到文件中。每個IP佔一行,不允許有空格② 在所有節點的
etc/hadoop/hdfs-site.xml
中,修改配置<!-- 修改 --> <property> <!-- 配置副本數 --> <name>dfs.replication</name> <value>增加機器後的DataNode的數量</value> </property> <!-- 新增 --> <property> <!-- 指定可以連接到NameNode的主機列表。如果該值爲空,則允許所有主機。默認爲空 --> <name>dfs.hosts</name> <value>/???/可以連接到NameNode的主機列表文件</value> </property>
③ 在所有節點的
etc/hadoop/slaves
中,將新機器的IP添加到裏面 -
在NameNode節點機器上,執行命令
bin/hdfs dfsadmin -refreshNodes
,刷新DataNode -
在ResourceManager節點機器上,執行命令
bin/yarn rmadmin -refreshNodes
,刷新NodeManager -
在NameNode節點機器上,執行命令
sbin/start-balancer.sh
,實現集羣數據的平衡(Blocks) -
此時查看NameNode的前端管理頁面,在Datanodes選項頁面裏,應該新增了一個DataNode節點,其Admin State爲Dead
-
到新機器上,分別執行命令
sbin/hadoop-daemon.sh start datanode
啓動DataNode,和命令sbin/yarn-daemon.sh start nodemanager
啓動NodeManager -
再回到NameNode的前端管理頁面,刷新可以看到,新節點的Admin State爲In Service
-
上傳文件和下載文件,進行測試
【2】動態移除節點
-
① 在所有節點的
etc/hadoop
目錄下,創建名爲dfs.hosts.exclude
的文件,並添加要移除的節點的IP到文件中。每個IP佔一行,不允許有空格② 在所有節點的
etc/hadoop/hdfs-site.xml
中,修改配置<!-- 修改 --> <property> <!-- 配置副本數 --> <name>dfs.replication</name> <value>移除機器後的DataNode的數量</value> </property> <!-- 新增 --> <property> <!-- 指定不允許連接到NameNode的主機列表。如果該值爲空,則不排除任何主機。默認爲空 --> <name>dfs.hosts.exclude</name> <value>/???/不允許連接到NameNode的主機列表文件</value> </property>
③ 在所有節點的
etc/hadoop/slaves
中,將準備移除的機器的IP刪除 -
在NameNode節點機器上,執行命令
bin/hdfs dfsadmin -refreshNodes
,刷新DataNode -
在ResourceManager節點機器上,執行命令
bin/yarn rmadmin -refreshNodes
,刷新NodeManager -
在NameNode節點機器上,執行命令
sbin/start-balancer.sh
,實現集羣數據的平衡(Blocks) -
此時查看NameNode的前端管理頁面,在Datanodes選項頁面裏,那些準備移除的節點機器的Admin State應該爲Decommission In Progress。等待數據傳輸給其他的DataNode完畢,狀態變爲Decommission
-
在準備退役的機器上,分別執行命令
sbin/yarn-daemon.sh stop nodemanager
停止NodeManager,和命令sbin/hadoop-daemon.sh stop datanode
停止DataNode -
如果在
etc/hadoop/hdfs-site.xml
中配置了dfs.hosts
,則還應刪掉對應的退役機器的IP,並執行命令重新刷新DataNode和NodeManager
18、集羣間的數據拷貝
使用命令,bin/hadoop distcp hdfs://源服務機器IP:9000/?/? hdfs://目標服務機器IP:9000/?/?
注意:數據拷貝的時候,會執行一個MapReduce任務,所以必須確保2個集羣的相關資源(YARN等)都在正常運行中
19、數據的歸檔
由於NameNode啓動時會加載所有的元數據到內存當中,所以過多的小文件,會佔用大量的內存資源。我們可以把這些小文件歸檔(壓縮)起來,這樣就能節省很多的內存空間
數據歸檔本質上是一個MapReduce程序(確保YARN相關資源已啓動)
相關命令:
-
創建歸檔文件,
bin/hadoop archive -archiveName 歸檔文件名.har -p 要歸檔的目錄 歸檔文件輸出目錄
-
查看歸檔文件,
bin/hadoop fs -ls -R har:///???/???.har
-
提取歸檔文件,其實就是複製歸檔文件裏的內容,
bin/hadoop fs -cp har:///???/歸檔文件名.har/歸檔文件 /目標路徑
注意:只能複製歸檔文件的內容,不能移動(mv),否則會報錯:
mv: `har:///???/???.har/???': Does not match target filesystem
20、快照
快照相當於是對目錄進行一個備份,它並不會立即複製所有的文件,而是先創建一個索引文件指向目標文件。當有新的文件寫入時,它纔會產生新的文件
相關命令:
-
開啓指定目錄的快照功能,
bin/hdfs dfsadmin -allowSnapshot /指定目錄
-
創建指定目錄的快照,
bin/hdfs dfs -createSnapshot /指定目錄 快照文件名稱
快照文件名稱可以省略,默認的名稱爲
/指定目錄/.snapshot/s日期-時間.毫秒
-
重命名快照,
bin/hdfs dfs -renameSnapshot /快照目錄 舊名稱 新名稱
注意:系統默認創建的快照名稱是無法重命名的,會報錯:
renameSnapshot: Modification on a read-only snapshot is disallowed
-
展示當前用戶下的所有的已開啓快照功能的目錄,
bin/hdfs lsSnapshottableDir
-
比較兩個快照目錄的不同,
bin/hdfs snapshotDiff /開啓快照功能的目錄 快照名稱1 快照名稱2
快照名稱可以使用
.
來代替,表示當前開啓了快照功能的目錄注意:比較結果是用右邊的快照和左邊的快照相比較
-
刪除快照,
bin/hdfs dfs -deleteSnapshot /開啓快照功能的快照目錄 快照名稱
注意快照目錄和快照名稱之間的空格
-
禁用指定目錄的快照功能,
bin/hdfs dfsadmin -disallowSnapshot /指定目錄
注意:禁用快照功能前,需要先刪除當前目錄的所有快照,否則報錯:
disallowSnapshot: The directory /???/??? has snapshot(s). Please redo the operation after removing all the snapshots.
使用快照進行恢復,則使用bin/hadoop fs -cp /快照目錄裏的文件 /指定目錄
。注意不能使用移動(mv)命令,因爲快照文件是隻讀的
21、回收站
回收站功能默認是關閉的,需要在etc/hadoop/core-site.xml
配置文件中,設置2個參數來開啓它
<!-- 回收站內的文件的有效期,單位:分鐘。默認爲0,表示禁用回收站功能 -->
<property>
<name>fs.trash.interval</name>
<value>0</value>
</property>
<!--
檢查回收站的週期,單位:分鐘。該值要小於等於fs.trash.interval。如果爲0,則設置爲fs.trash.interval的值。
每當回收站運行的時候,它都會創建一個新的檢查點,用於刪除超過fs.trash.interval時間(文件有效期)之後的文件
-->
<property>
<name>fs.trash.checkpoint.interval</name>
<value>0</value>
</property>
同時還要注意:默認擁有查看回收站的權限的用戶名是dr.who
,需要修改它爲操作Hadoop集羣的用戶。否則訪問NameNode的前端管理頁面時會報錯:Permission denied: user=dr.who, access=READ_EXECUTE, inode="/user":???:supergroup:drwx------
在etc/hadoop/core-site.xml
中
<!-- 擁有查看HDFS Web頁面(NameNode前端管理頁面)的權限的用戶名 -->
<property>
<name>hadoop.http.staticuser.user</name>
<value>dr.who</value>
</property>
刪除的文件目錄,位於回收站的目錄/user/執行Hadoop的用戶名/.Trash/Current/???
裏
恢復回收站裏的文件的方法是,將回收站裏的文件移動出來,使用命令bin/hadoop fs -mv /回收站目錄 /恢復目錄
。注意,如果移動文件夾,可能因爲權限問題而無法查看
刪除回收站目錄,可以使用命令bin/hadoop fs -rm -r -skipTrash /user/???
。加上-skipTrash
選項