目錄
5 NameNode和Second NameNode的工作機制
1 HDFS的概述
隨着數據量越來越大,在一個操作系統管轄的範圍內存不下了,那麼就分配到更多的操作系統管理的磁盤中,但是不方便管理和維護,迫切需要一種系統來管理多臺機器上的文件,這就是分佈式文件管理系統。HDFS只是分佈式文件管理系統中的一種。
1.1 HDFS的概念
HDFS(Hadoop Distributed File System),它是一個文件系統,用於存儲文件,通過目錄樹來定位文件;其次,它是分佈式的,由很多服務器聯合起來實現其功能,集羣中的服務器有各自的角色。集羣不一定是分佈式的,但是分佈式一定是集羣。
HDFS的設計適合一次寫入,多次讀出的場景,且不支持文件的修改。適合用來做數據分析,並不適合用來做網盤應用。
1.2 HDFS優缺點
1.2.1 優點
- 高容錯性
- 數據自動保存多個副本。它通過增加副本的形式,提高容錯性;
- 某一個副本丟失以後,它可以自動恢復。
- 適合大數據處理
- 數據規模:能夠處理數據規模達到GB、TB、甚至PB級別的數據;
- 文件規模:能夠處理百萬規模以上的文件數量,數量相當之大。
- 可構建在廉價機器上,通過多副本機制,提高可靠性。
1.2.2 缺點
- 不適合低延時數據訪問,比如毫秒級的存儲數據,是做不到的。
- 無法高效的對大量小文件進行存儲。
- 存儲大量小文件的話,它會佔用NameNode大量的內存來存儲文件、目錄和塊信息。這樣是不可取的,因爲NameNode的內存總是有限的;
- 小文件存儲的尋址時間會超過讀取時間,它違反了HDFS的設計目標。
- 尋址時間,目前技術水平在10ms左右
- 傳輸尋址時間/傳輸時間=1%,傳輸時間1000ms=1s,磁盤傳輸速度100M/S,計算機是2的n次方,所以hadoop2.x默認塊的大小爲128m
- 不支持併發寫入、文件隨機修改。
- 一個文件只能有一個寫,不允許多個線程同時寫;
- 僅支持數據append(追加),不支持文件的隨機修改。
1.3 HDFS的架構
結構主要有四個部分組成:分別爲HDFS client、NameNode、DataNode和Second NameNode。
1. client:就是客戶端
- 文件切分。文件上傳HDFS時候,Client將文件切分爲一個個的block塊,然後進行存儲;
- 與NameNode交互,獲取文件的存儲位置;
- 與DataNode交互,讀取或寫入數據;
- client提供一些命令來管理HDFS,比如啓動或關閉HDFS;
- client可以通過一些命令來訪問HDFS;
2. NameNode:就是master,是一個主管、管理者
- 管理HDFS的名稱空間:namespace;
- 管理數據塊(block)的映射信息;
- 配置副本策略(默認:3);
- 處理客戶端的請求;
3. DataNode:就是slave。NameNode下達命令,DataNode執行具體操作
- 存儲實際的數據塊;
- 執行數據塊的讀/寫操作;
4. Second NameNode:並非是NameNode的熱備,當NameNode掛掉之後,並不能馬上替換NameNode並提供服務
- 輔助NameNode,並分擔工作量;
- 定期合併Fsimage和Edits,並推送給NameNode;
- 在緊急情況下,可輔助恢復NameNode
1.4 block文件塊的大小
HDFS中的文件在物理上是分塊存儲(block),塊的大小可以通過配置參數( dfs.blocksize)來規定,默認大小在hadoop2.x版本中是128M,老版本中是64M。
2 HDFS的shell客戶端操作
1. 基本語法
bin/hadoop fs 具體命令
2. 命令大全
[root@hadoop101 hadoop-2.7.2]$ bin/hadoop fs
[-appendToFile <localsrc> ... <dst>]
[-cat [-ignoreCrc] <src> ...]
[-checksum <src> ...]
[-chgrp [-R] GROUP PATH...]
[-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH...]
[-chown [-R] [OWNER][:[GROUP]] PATH...]
[-copyFromLocal [-f] [-p] <localsrc> ... <dst>]
[-copyToLocal [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-count [-q] <path> ...]
[-cp [-f] [-p] <src> ... <dst>]
[-createSnapshot <snapshotDir> [<snapshotName>]]
[-deleteSnapshot <snapshotDir> <snapshotName>]
[-df [-h] [<path> ...]]
[-du [-s] [-h] <path> ...]
[-expunge]
[-get [-p] [-ignoreCrc] [-crc] <src> ... <localdst>]
[-getfacl [-R] <path>]
[-getmerge [-nl] <src> <localdst>]
[-help [cmd ...]]
[-ls [-d] [-h] [-R] [<path> ...]]
[-mkdir [-p] <path> ...]
[-moveFromLocal <localsrc> ... <dst>]
[-moveToLocal <src> <localdst>]
[-mv <src> ... <dst>]
[-put [-f] [-p] <localsrc> ... <dst>]
[-renameSnapshot <snapshotDir> <oldName> <newName>]
[-rm [-f] [-r|-R] [-skipTrash] <src> ...]
[-rmdir [--ignore-fail-on-non-empty] <dir> ...]
[-setfacl [-R] [{-b|-k} {-m|-x <acl_spec>} <path>]|[--set <acl_spec> <path>]]
[-setrep [-R] [-w] <rep> <path> ...]
[-stat [format] <path> ...]
[-tail [-f] <file>]
[-test -[defsz] <path>]
[-text [-ignoreCrc] <src> ...]
[-touchz <path> ...]
[-usage [cmd ...]]
3.常用命令實操
(0)啓動Hadoop集羣(方便後續的測試)
[root@hadoop101 hadoop-2.7.2]$ sbin/start-dfs.sh
[root@hadoop102 hadoop-2.7.2]$ sbin/start-yarn.sh
(1)-help:輸出這個命令參數
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -help rm
(2)-ls: 顯示目錄信息
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -ls /
(3)-mkdir:在hdfs上創建目錄
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -mkdir -p /sanguo/shuguo
(4)-moveFromLocal從本地剪切粘貼到hdfs
[root@hadoop101 hadoop-2.7.2]$ touch kongming.txt
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -moveFromLocal ./kongming.txt /sanguo/shuguo
(5)-appendToFile :追加一個文件到已經存在的文件末尾
[root@hadoop101 hadoop-2.7.2]$ touch liubei.txt
[root@hadoop101 hadoop-2.7.2]$ vim liubei.txt
輸入
san gu mao lu
[root@hadoop102 hadoop-2.7.2]$ hadoop fs -appendToFile liubei.txt /sanguo/shuguo/kongming.txt
(6)-cat:顯示文件內容
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -cat /sanguo/shuguo/kongming.txt
(7)-tail:顯示一個文件的末尾
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -tail /sanguo/shuguo/kongming.txt
(8)-chgrp 、-chmod、-chown:linux文件系統中的用法一樣,修改文件所屬權限
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -chmod 666 /sanguo/shuguo/kongming.txt
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -chown root:root /sanguo/shuguo/kongming.txt
(9)-copyFromLocal:從本地文件系統中拷貝文件到hdfs路徑去
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -copyFromLocal README.txt /
(10)-copyToLocal:從hdfs拷貝到本地
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -copyToLocal /sanguo/shuguo/kongming.txt ./
(11)-cp :從hdfs的一個路徑拷貝到hdfs的另一個路徑
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -cp /sanguo/shuguo/kongming.txt /zhuge.txt
(12)-mv:在hdfs目錄中移動文件
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -mv /zhuge.txt /sanguo/shuguo/
(13)-get:等同於copyToLocal,就是從hdfs下載文件到本地
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -get /sanguo/shuguo/kongming.txt ./
(14)-getmerge :合併下載多個文件,比如hdfs的目錄 /aaa/下有多個文件:log.1, log.2,log.3,...
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -getmerge /sanguo/shuguo/* ./zaiyiqi.txt
(15)-put:等同於copyFromLocal
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -put ./zaiyiqi.txt /sanguo/shuguo/
(16)-rm:刪除文件或文件夾
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -rm /user/root/test/jinlian2.txt
(17)-rmdir:刪除空目錄(瞭解)
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -mkdir /test
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -rmdir /test
(18)-du統計文件夾的大小信息 -s 統計制定文件夾的大小 -h 統計制定文件夾下各個文件的大小
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -du -s -h /user/root/test
2.7 K /user/root/test
[root@hadoop102 hadoop-2.7.2]$ hadoop fs -du -h /user/root/test
1.3 K /user/root/test/README.txt
15 /user/root/test/jinlian.txt
1.4 K /user/root/test/zaiyiqi.txt
(19)-setrep:設置hdfs中文件的副本數量
[root@hadoop101 hadoop-2.7.2]$ hadoop fs -setrep 10 /sanguo/shuguo/kongming.txt
這裏設置的副本數只是記錄在NameNode的元數據中,是否真的會有這麼多副本,還得看DataNode的數量。因爲目前只有3臺設備,最多也就3個副本,
3 HDFS的java客戶端操作
官網自行下載或者:請點這裏 提取碼:xv1g
3.1 HDFS客戶端操作
1.根據自己電腦的操作系統拷貝對應的編譯後的hadoop jar包到非中文路徑(例如:D:\Develop\hadoop-2.7.2),如圖所示
2.配置HADOOP_HOME的環境變量
3. 配置Path環境變量
4. 創建maven工程並添加下列依賴
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.10.0</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>
日誌配置文件 log4j.properties
log4j.rootLogger=debug, 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
5. HDFS的API操作
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.*;
import org.apache.hadoop.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import java.io.*;
import java.net.URI;
import java.net.URISyntaxException;
public class HdfsClientDemo {
private FileSystem fileSystem = null;
private Configuration conf = null;
@Before
public void init() throws URISyntaxException, IOException, InterruptedException {
// 1.獲取文件系統
conf = new Configuration();
// 指定副本數目
conf.set("dfs.replication", "10");
//conf.set("fs.defaultFS","hdfs://hadoop101:9000");
//FileSystem fileSystem = FileSystem.get(conf);
fileSystem = FileSystem.get(new URI("hdfs://hadoop101:9000"), conf, "root");
}
@After
public void after() throws IOException {
// 3.釋放資源
fileSystem.close();
}
//創建文件夾
@Test
public void testMkdir() throws URISyntaxException, IOException, InterruptedException {
// 2.創建文件夾
fileSystem.mkdirs(new Path("/1105"));
}
//上傳文件
@Test
public void testCopyFromLocal() throws Exception {
// 2.上傳文件
fileSystem.copyFromLocalFile(new Path("E:/hadoop-2.7.2.rar"), new Path("/java/"));
}
//刪除文件
@Test
public void testCopyToLocal() throws IOException {
fileSystem.copyToLocalFile(true, new Path("/1105/liubei.txt"), new Path("E:/jiezou"));
}
//文件夾刪除
@Test
public void testDeleteDir() throws IOException {
fileSystem.delete(new Path("/bigdata"), true);
}
//文件夾更改
@Test
public void testRename() throws IOException {
fileSystem.rename(new Path("/java/java.txt"), new Path("/java/java2.txt"));
}
//文件狀態測試
@Test
public void testFileStatus() throws IOException {
FileStatus[] fileStatuses = fileSystem.listStatus(new Path("/java"));
for (FileStatus fileStatus : fileStatuses) {
if (fileStatus.isDirectory()) {
System.out.println("i am directory" + fileStatus.getPath().getName());
} else if (fileStatus.isFile()) {
System.out.println("i am file" + fileStatus.getPath().getName());
}
}
}
/**
* 通過io流操作hdfs
* 文件上傳
*/
@Test
public void testUploadTOHdfsByStream() throws IOException {
// 1.定義輸入流
FileInputStream fileInputStream = new FileInputStream(new File("e:/ceshi/bigdata.txt"));
// 2.定義輸出流
FSDataOutputStream fsDataOutputStream = fileSystem.create(new Path("/sanguo"));
// 3.輸入流和輸出流對接
IOUtils.copyBytes(fileInputStream, fsDataOutputStream, conf);
// 4.關閉流
IOUtils.closeStream(fsDataOutputStream);
IOUtils.closeStream(fileInputStream);
}
@Test
public void testDownloadToLocalByStream() throws IOException {
//1.輸入流定義
FSDataInputStream fsDataInputStream = fileSystem.open(new Path("/README.txt"));
//2.輸出流定義
FileOutputStream fileOutputStream = new FileOutputStream("e:/read.txt");
//3.流對接
IOUtils.copyBytes(fsDataInputStream, fileOutputStream, conf);
//4.關閉流
IOUtils.closeStream(fileOutputStream);
IOUtils.closeStream(fsDataInputStream);
}
/**
* 分塊下載,下載第一塊
*/
@Test
public void testReadFileBlock1() throws IOException {
//1.定義輸入流
FSDataInputStream fsDataInputStream = fileSystem.open(new Path("/java/hadoop-2.7.2.rar"));
//2.定義輸出流
FileOutputStream fileOutputStream = new FileOutputStream(new File("E:/ceshi/hadoop-2.7.2.rar.part1"));
byte[] buffer = new byte[1024];
for (int i = 1; i <= 1024 * 128; i++) {
fsDataInputStream.read(buffer);
fileOutputStream.write(buffer);
}
//4.關閉流
IOUtils.closeStream(fileOutputStream);
IOUtils.closeStream(fsDataInputStream);
}
/**
* 分塊下載,下載第二塊
*/
@Test
public void testReadFileBlock2() throws IOException {
//1.定義輸入流
FSDataInputStream fsDataInputStream = fileSystem.open(new Path("/java/hadoop-2.7.2.rar"));
//2.定義輸出流
FileOutputStream fileOutputStream = new FileOutputStream(new File("E:/ceshi/hadoop-2.7.2.rar.part2"));
fsDataInputStream.seek(1024 * 1024 * 128);
IOUtils.copyBytes(fsDataInputStream,fileOutputStream,conf);
IOUtils.closeStream(fileOutputStream);
IOUtils.closeStream(fsDataInputStream);
}
}
4 HDFS的數據流
4.1 HDFS寫數據流程
- 客戶端通過Distributed FileSystem模塊向NameNode請求上傳文件,NameNode檢查目標文件是否存在,父目錄是否存在;
- NameNode返回是否可以上傳;
- 客戶端請求上傳第一個block塊,詢問NameNode存放在那個DataNode;
- NameNode返回三個DataNode節點分別爲DN1、DN2、DN3;
- 客戶端通過FSDataOutputStream模塊請求向DN1上寫數據,DN1收到請求調用DN2、DN2調用DN3,傳輸管道建立完成
- 三個DataNode節點逐級響應客戶端(只要DataNode1反饋響應即可寫入,其他的節點等到恢復後可由DN1數據複製到本地)
- 客戶端開始向DN1上傳第一個block塊(先從磁盤讀取放到本地的內存緩存),以packet爲單位,DN1收到一個packet後寫入磁盤並傳遞給DN2,DN2傳遞給DN3;
- 當一個block上傳完成完成後,客戶端再次請求上傳第二個block(重複3~7),上傳完成後close
4.2 HDFS讀數據流程
- 客戶端通過Distributed FileSystem向NameNode請求下載文件,NameNode查詢元數據之後,找到文件塊所在的DataNode地址;
- NameNode返回目標文件所在的DataNode節點位置;
- 客戶端就近原則選擇一臺DataNode節點,通過FSDataInputStream讀取數據;
- 客戶端以packet爲單位接受數據,現在本地緩存,人後寫入目標文件(其他的數據塊流程類似)
5 NameNode和Second NameNode的工作機制
5.1 NN和2NN的工作流程
1. 第一階段:NameNode啓動
- 第一次啓動NameNode格式化後,創建fsimage和edits文件。如果不是第一次啓動,直接加載編輯日誌和鏡像文件到內存。
- 客戶端對元數據進行增刪改的請求。
- NameNode記錄操作日誌,更新滾動日誌。
- NameNode在內存中對數據進行增刪改查。
2. 第二階段:Secondary NameNode工作
- Secondary NameNode詢問NameNode是否需要checkpoint。直接帶回NameNode是否檢查結果。
- Secondary NameNode請求執行checkpoint。
- NameNode滾動正在寫的edits日誌。
- 將滾動前的編輯日誌和鏡像文件拷貝到Secondary NameNode。
- Secondary NameNode加載編輯日誌和鏡像文件到內存,併合並。
- 生成新的鏡像文件fsimage.chkpoint。
- 拷貝fsimage.chkpoint到NameNode。
- NameNode將fsimage.chkpoint重新命名成fsimage。
NN和2NN工作機制詳解:
Fsimage:namenode內存中元數據序列化後形成的文件。
Edits:記錄客戶端更新元數據信息的每一步操作(可通過Edits運算出元數據)。
namenode啓動時,先滾動edits並生成一個空的edits.inprogress,然後加載edits(歸檔後的)和fsimage(最新的)到內存中,此時namenode內存就持有最新的元數據信息。client開始對namenode發送元數據的增刪改查的請求,這些請求的操作首先會被記錄的edits.inprogress中(查詢元數據的操作不會被記錄在edits中,因爲查詢操作不會更改元數據信息),如果此時namenode掛掉,重啓後會從edits中讀取元數據的信息。然後,namenode會在內存中執行元數據的增刪改查的操作。
由於edits中記錄的操作會越來越多,edits文件會越來越大,導致namenode在啓動加載edits時會很慢,所以需要對edits和fsimage進行合併(所謂合併,就是將edits和fsimage加載到內存中,照着edits中的操作一步步執行,最終形成新的fsimage)。secondarynamenode的作用就是幫助namenode進行edits和fsimage的合併工作。
secondarynamenode首先會詢問namenode是否需要checkpoint(觸發checkpoint需要滿足兩個條件中的任意一個,定時時間到和edits中數據寫滿了)。secondarynamenode執行checkpoint操作,首先會讓namenode滾動edits並生成一個空的edits.inprogress,滾動edits的目的是給edits打個標記,以後所有新的操作都寫入edits.inprogress,其他未合併的edits和fsimage會拷貝到secondarynamenode的本地,然後將拷貝的edits和fsimage加載到內存中進行合併,生成fsimage.chkpoint,然後將fsimage.chkpoint拷貝給namenode,重命名爲fsimage後替換掉原來的fsimage。namenode在啓動時就只需要加載之前未合併的edits和fsimage即可,因爲合併過的edits中的元數據信息已經被記錄在fsimage中。
5.2 checkpoint時間設置
1. 通常情況下,SecondaryNameNode每隔一小時執行一次。如果修改在hdfs-site中
默認值在[hdfs-default.xml]
<property>
<name>dfs.namenode.checkpoint.period</name>
<value>3600</value>
</property>
2. 一分鐘檢查一次操作次數,當操作次數達到1百萬時,SecondaryNameNode執行一次。
<property>
<name>dfs.namenode.checkpoint.txns</name>
<value>1000000</value>
<description>操作動作次數</description>
</property>
<property>
<name>dfs.namenode.checkpoint.check.period</name>
<value>60</value>
<description>1分鐘檢查一次操作次數</description>
</property >
5.3 集羣安全模式
1. 概述
NameNode啓動時,首先將映像文件(fsimage)載入內存,並執行編輯日誌(edits)中的各項操作。一旦在內存中成功建立文件系統元數據的映像,則創建一個新的fsimage文件和一個空的編輯日誌。此時,NameNode開始監聽DataNode請求。但是此刻,NameNode運行在安全模式,即NameNode的文件系統對於客戶端來說是隻讀的。
系統中的數據塊的位置並不是由NameNode維護的,而是以塊列表的形式存儲在DataNode中。在系統的正常操作期間,NameNode會在內存中保留所有塊位置的映射信息。在安全模式下,各個DataNode會向NameNode發送最新的塊列表信息,NameNode瞭解到足夠多的塊位置信息之後,即可高效運行文件系統。
如果滿足“最小副本條件”,NameNode會在30秒鐘之後就退出安全模式。所謂的最小副本條件指的是在整個文件系統中99.9%的塊滿足最小副本級別(默認值:dfs.replication.min=1)。在啓動一個剛剛格式化的HDFS集羣時,因爲系統中還沒有任何塊,所以NameNode不會進入安全模式。
2. 基本語法
集羣處於安全模式,不能執行重要操作(寫操作)。集羣啓動完成後,自動退出安全模式。
(1)bin/hdfs dfsadmin -safemode get (功能描述:查看安全模式狀態)
(2)bin/hdfs dfsadmin -safemode enter (功能描述:進入安全模式狀態)
(3)bin/hdfs dfsadmin -safemode leave (功能描述:離開安全模式狀態)
(4)bin/hdfs dfsadmin -safemode wait (功能描述:等待安全模式狀態,監控安全模式)
安全模式開啓:
3. 案例
模擬等待安全模式
(1)先進入安全模式
[root@hadoop101 hadoop-2.7.2]$ bin/hdfs dfsadmin -safemode enter
(2)執行下面的腳本
編輯一個腳本
#!/bin/bash
bin/hdfs dfsadmin -safemode wait(安全模式關閉)
bin/hdfs dfs -put ~/hello.txt /root/hello.txt
(3)再打開一個窗口,執行
[root@hadoop101 hadoop-2.7.2]$ bin/hdfs dfsadmin -safemode leave
6 NameNode工作機制
- 一個數據塊在DataNode上以文件形式存儲在磁盤上,包括兩個文件,一個是數據本身,一個是元數據包括數據塊的長度,塊數據的校驗和,以及時間戳。
- DataNode啓動後向NameNode註冊,通過後,週期性(1小時)的向NameNode上報所有的塊信息。
- 心跳是每3秒一次,心跳返回結果帶有NameNode給該DataNode的命令如複製塊數據到另一臺機器,或刪除某個數據塊。如果超過10分鐘沒有收到某個DataNode的心跳,則認爲該節點不可用。